Skip to content

Commit c63cc5d

Browse files
authored
Merge pull request #143 from TrueNine/dev
Update CLI integration coverage and version sync for the SDK split
2 parents bd79c26 + 18b0047 commit c63cc5d

File tree

141 files changed

+13372
-1139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+13372
-1139
lines changed

.githooks/sync-versions.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ function createFixtureRepo(): string {
4040
version: initialVersion,
4141
private: true
4242
})
43+
writeJson(join(rootDir, 'cli-integration-test', 'package.json'), {
44+
name: '@truenine/memory-sync-cli-integration-test',
45+
version: initialVersion,
46+
private: true
47+
})
4348
writeJson(join(rootDir, 'cli', 'npm', 'darwin-arm64', 'package.json'), {
4449
name: '@truenine/memory-sync-cli-darwin-arm64',
4550
version: initialVersion
@@ -106,6 +111,7 @@ describe('sync-versions hook', () => {
106111
expect(result.versionSource).toBe('cli/npm/darwin-arm64/package.json')
107112
expect(JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
108113
expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
114+
expect(JSON.parse(readFileSync(join(rootDir, 'cli-integration-test', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
109115
expect(JSON.parse(readFileSync(join(rootDir, 'sdk', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
110116
expect(JSON.parse(readFileSync(join(rootDir, 'libraries', 'logger', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
111117
expect(readFileSync(join(rootDir, 'Cargo.toml'), 'utf-8')).toContain(`version = "${nextVersion}"`)
@@ -114,6 +120,7 @@ describe('sync-versions hook', () => {
114120
expect(stagedFiles).toEqual(new Set([
115121
'Cargo.toml',
116122
'cli-crate/Cargo.toml',
123+
'cli-integration-test/package.json',
117124
'cli/npm/darwin-arm64/package.json',
118125
'cli/package.json',
119126
'gui/src-tauri/tauri.conf.json',
@@ -142,6 +149,45 @@ describe('sync-versions hook', () => {
142149
expect(result.versionSource).toBe('sdk/package.json')
143150
expect(JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
144151
expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
152+
expect(JSON.parse(readFileSync(join(rootDir, 'cli-integration-test', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
153+
expect(JSON.parse(readFileSync(join(rootDir, 'sdk', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
154+
expect(JSON.parse(readFileSync(join(rootDir, 'libraries', 'logger', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
155+
expect(readFileSync(join(rootDir, 'Cargo.toml'), 'utf-8')).toContain(`version = "${nextVersion}"`)
156+
expect(readFileSync(join(rootDir, 'cli-crate', 'Cargo.toml'), 'utf-8')).toContain(`version = "${nextVersion}"`)
157+
expect(JSON.parse(readFileSync(join(rootDir, 'gui', 'src-tauri', 'tauri.conf.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
158+
expect(stagedFiles).toEqual(new Set([
159+
'Cargo.toml',
160+
'cli-crate/Cargo.toml',
161+
'cli-integration-test/package.json',
162+
'cli/npm/darwin-arm64/package.json',
163+
'cli/package.json',
164+
'gui/src-tauri/tauri.conf.json',
165+
'libraries/logger/package.json',
166+
'package.json',
167+
'sdk/package.json'
168+
]))
169+
})
170+
171+
it('accepts cli-integration-test/package.json as a staged version source and propagates it', () => {
172+
const rootDir = createFixtureRepo()
173+
tempDirs.push(rootDir)
174+
175+
const nextVersion = '2026.10324.10318'
176+
writeJson(join(rootDir, 'cli-integration-test', 'package.json'), {
177+
name: '@truenine/memory-sync-cli-integration-test',
178+
version: nextVersion,
179+
private: true
180+
})
181+
runGit(rootDir, ['add', 'cli-integration-test/package.json'])
182+
183+
const result = runSyncVersions({rootDir})
184+
const stagedFiles = new Set(runGit(rootDir, ['diff', '--cached', '--name-only']).split(/\r?\n/).filter(Boolean))
185+
186+
expect(result.targetVersion).toBe(nextVersion)
187+
expect(result.versionSource).toBe('cli-integration-test/package.json')
188+
expect(JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
189+
expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
190+
expect(JSON.parse(readFileSync(join(rootDir, 'cli-integration-test', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
145191
expect(JSON.parse(readFileSync(join(rootDir, 'sdk', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
146192
expect(JSON.parse(readFileSync(join(rootDir, 'libraries', 'logger', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
147193
expect(readFileSync(join(rootDir, 'Cargo.toml'), 'utf-8')).toContain(`version = "${nextVersion}"`)
@@ -150,6 +196,7 @@ describe('sync-versions hook', () => {
150196
expect(stagedFiles).toEqual(new Set([
151197
'Cargo.toml',
152198
'cli-crate/Cargo.toml',
199+
'cli-integration-test/package.json',
153200
'cli/npm/darwin-arm64/package.json',
154201
'cli/package.json',
155202
'gui/src-tauri/tauri.conf.json',

.github/actions/setup-rust/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ runs:
2626
uses: dtolnay/rust-toolchain@stable
2727
with:
2828
toolchain: ${{ inputs.rust-version }}
29+
components: rustfmt
2930
targets: ${{ inputs.targets }}
3031

3132
- name: Cache cargo

.github/actions/setup-tauri/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ runs:
5353
uses: dtolnay/rust-toolchain@stable
5454
with:
5555
toolchain: ${{ inputs.rust-version }}
56+
components: rustfmt
5657
targets: ${{ inputs.rust-targets }}
5758

5859
- name: Normalize Tauri signing key

.github/workflows/pull-request.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,27 @@ jobs:
138138
- name: Build
139139
run: pnpm run build
140140

141-
- name: Run all tests
142-
run: pnpm run test
141+
- name: Run workspace tests except container-backed CLI integration
142+
run: pnpm exec turbo run test --ui=stream --log-order=grouped --filter='!@truenine/memory-sync-cli-integration-test'
143+
144+
test-cli-integration:
145+
if: github.event.pull_request.draft == false
146+
runs-on: ubuntu-24.04
147+
timeout-minutes: 45
148+
steps:
149+
- uses: actions/checkout@v6
150+
151+
- uses: ./.github/actions/setup-node-pnpm
152+
153+
- uses: ./.github/actions/setup-rust
154+
with:
155+
cache-key: pr
156+
157+
- name: Verify Docker is available
158+
run: docker version
159+
160+
- name: Run CLI integration tests
161+
run: pnpm -C cli-integration-test run test
143162

144163
test-gui:
145164
if: github.event.pull_request.draft == false

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ members = [
1010
]
1111

1212
[workspace.package]
13-
version = "2026.10411.10132"
13+
version = "2026.10412.11551"
1414
edition = "2024"
1515
rust-version = "1.88"
1616
license = "AGPL-3.0-only"

cli-integration-test/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@truenine/memory-sync-cli-integration-test",
3+
"private": true,
4+
"type": "module",
5+
"version": "2026.10412.11551",
6+
"description": "Container-backed CLI integration tests for tnmsc",
7+
"scripts": {
8+
"test": "vitest run",
9+
"test:codex": "vitest run test/codex.integration.test.ts",
10+
"test:claude-code": "vitest run test/claude-code-cli.integration.test.ts",
11+
"test:integration": "pnpm run test",
12+
"check:type": "tsc --noEmit -p tsconfig.test.json"
13+
},
14+
"devDependencies": {
15+
"@types/node": "catalog:",
16+
"testcontainers": "catalog:",
17+
"typescript": "catalog:",
18+
"vitest": "catalog:"
19+
}
20+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import {spawnSync} from 'node:child_process'
2+
import {existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync} from 'node:fs'
3+
import {tmpdir} from 'node:os'
4+
import path from 'node:path'
5+
import {fileURLToPath} from 'node:url'
6+
7+
const REPO_ROOT = fileURLToPath(new URL('../../', import.meta.url))
8+
const CLI_DIR = path.join(REPO_ROOT, 'cli')
9+
const SCRIPT_RUNTIME_DIR = path.join(REPO_ROOT, 'libraries', 'script-runtime')
10+
const CLI_LINUX_PACKAGE_DIR = path.join(CLI_DIR, 'npm', 'linux-x64-gnu')
11+
const EXPECTED_LINUX_NODE_FILES = 4
12+
const MAX_BUFFER = 16 * 1024 * 1024
13+
14+
export interface CliIntegrationArtifacts {
15+
readonly tempDir: string
16+
readonly cliTarballPath: string
17+
readonly linuxTarballPath: string
18+
readonly scriptRuntimeTarballPath: string
19+
readonly latestPnpmVersion: string
20+
}
21+
22+
let cachedArtifacts: CliIntegrationArtifacts | undefined
23+
let cleanupRegistered = false
24+
25+
function registerArtifactCleanup(): void {
26+
if (cleanupRegistered) return
27+
28+
cleanupRegistered = true
29+
process.once('exit', () => {
30+
cleanupCliIntegrationArtifacts()
31+
})
32+
}
33+
34+
function runCommand(
35+
command: string,
36+
args: readonly string[],
37+
cwd: string = REPO_ROOT
38+
): string {
39+
const result = spawnSync(command, args, {
40+
cwd,
41+
encoding: 'utf8',
42+
maxBuffer: MAX_BUFFER
43+
})
44+
45+
if (result.error != null) throw result.error
46+
if (result.status === 0) return `${result.stdout ?? ''}${result.stderr ?? ''}`
47+
48+
throw new Error([
49+
`Command failed: ${command} ${args.join(' ')}`,
50+
`cwd: ${cwd}`,
51+
`${result.stdout ?? ''}${result.stderr ?? ''}`.trim() || 'No output captured.'
52+
].join('\n'))
53+
}
54+
55+
function resolveLatestPackageVersion(packageName: string): string {
56+
const raw = runCommand(
57+
'npm',
58+
['view', packageName, 'version'],
59+
tmpdir()
60+
).trim()
61+
const firstLine = raw
62+
.split(/\r?\n/u)
63+
.map(line => line.trim())
64+
.find(line => line.length > 0)
65+
66+
if (firstLine != null) return firstLine
67+
68+
throw new Error(`Failed to resolve the latest version for "${packageName}".`)
69+
}
70+
71+
function ensureDirectory(dirPath: string): void {
72+
mkdirSync(dirPath, {recursive: true})
73+
}
74+
75+
function findSingleTarball(dirPath: string): string {
76+
const tarballs = readdirSync(dirPath)
77+
.filter(fileName => fileName.endsWith('.tgz'))
78+
.sort()
79+
80+
if (tarballs.length !== 1) {
81+
throw new Error(
82+
`Expected exactly one tarball in "${dirPath}", found ${tarballs.length}.`
83+
)
84+
}
85+
86+
return path.join(dirPath, tarballs[0] ?? '')
87+
}
88+
89+
function packWorkspacePackage(packageDir: string, targetDir: string): string {
90+
ensureDirectory(targetDir)
91+
runCommand('pnpm', ['-C', packageDir, 'pack', '--pack-destination', targetDir])
92+
return findSingleTarball(targetDir)
93+
}
94+
95+
function ensureLinuxPlatformPackageReady(): void {
96+
const nodeFiles = existsSync(CLI_LINUX_PACKAGE_DIR)
97+
? readdirSync(CLI_LINUX_PACKAGE_DIR).filter(fileName => fileName.endsWith('.node'))
98+
: []
99+
100+
if (nodeFiles.length >= EXPECTED_LINUX_NODE_FILES) return
101+
102+
runCommand('pnpm', ['-C', CLI_DIR, 'run', 'build:napi:copy'])
103+
104+
const copiedNodeFiles = readdirSync(CLI_LINUX_PACKAGE_DIR)
105+
.filter(fileName => fileName.endsWith('.node'))
106+
107+
if (copiedNodeFiles.length < EXPECTED_LINUX_NODE_FILES) {
108+
throw new Error(
109+
`Expected ${EXPECTED_LINUX_NODE_FILES} Linux x64 NAPI artifacts in "${CLI_LINUX_PACKAGE_DIR}", found ${copiedNodeFiles.length}.`
110+
)
111+
}
112+
}
113+
114+
function assertSupportedHost(): void {
115+
if (process.platform === 'linux' && process.arch === 'x64') return
116+
117+
throw new Error(
118+
`cli-integration-test currently supports only linux-x64 hosts. Current host: ${process.platform}-${process.arch}.`
119+
)
120+
}
121+
122+
export function prepareCliIntegrationArtifacts(): CliIntegrationArtifacts {
123+
if (cachedArtifacts != null) return cachedArtifacts
124+
125+
assertSupportedHost()
126+
registerArtifactCleanup()
127+
runCommand('pnpm', ['-C', CLI_DIR, 'run', 'build'])
128+
ensureLinuxPlatformPackageReady()
129+
130+
const tempDir = mkdtempSync(path.join(tmpdir(), 'tnmsc-cli-integration-artifacts-'))
131+
const cliTarballPath = packWorkspacePackage(CLI_DIR, path.join(tempDir, 'cli'))
132+
const linuxTarballPath = packWorkspacePackage(
133+
CLI_LINUX_PACKAGE_DIR,
134+
path.join(tempDir, 'cli-linux-x64')
135+
)
136+
const scriptRuntimeTarballPath = packWorkspacePackage(
137+
SCRIPT_RUNTIME_DIR,
138+
path.join(tempDir, 'script-runtime')
139+
)
140+
const latestPnpmVersion = resolveLatestPackageVersion('pnpm')
141+
142+
cachedArtifacts = {
143+
tempDir,
144+
cliTarballPath,
145+
linuxTarballPath,
146+
scriptRuntimeTarballPath,
147+
latestPnpmVersion
148+
}
149+
150+
return cachedArtifacts
151+
}
152+
153+
export function cleanupCliIntegrationArtifacts(): void {
154+
if (cachedArtifacts == null) return
155+
156+
rmSync(cachedArtifacts.tempDir, {recursive: true, force: true})
157+
cachedArtifacts = void 0
158+
}

0 commit comments

Comments
 (0)