diff --git a/.github/workflows/codra-cli-release.yml b/.github/workflows/codra-cli-release.yml new file mode 100644 index 0000000..d1153ea --- /dev/null +++ b/.github/workflows/codra-cli-release.yml @@ -0,0 +1,132 @@ +name: Codra CLI release + +on: + workflow_dispatch: + inputs: + publish: + description: Publish @codra/cli to npm (requires NPM_TOKEN secret) + type: boolean + default: false + +jobs: + build-binaries: + name: build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - target: linux-x64 + os: ubuntu-latest + artifact: codra-linux-x64 + bin_name: codra + - target: linux-arm64 + os: ubuntu-24.04-arm + artifact: codra-linux-arm64 + bin_name: codra + - target: darwin-x64 + os: macos-13 + artifact: codra-darwin-x64 + bin_name: codra + - target: darwin-arm64 + os: macos-14 + artifact: codra-darwin-arm64 + bin_name: codra + - target: win32-x64 + os: windows-latest + artifact: codra-win32-x64.exe + bin_name: codra.exe + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Build codra-cli release binary + run: cargo build -p codra-cli --release + + - name: Stage platform artifact (Unix) + if: runner.os != 'Windows' + run: | + install -m 755 target/release/${{ matrix.bin_name }} "${{ matrix.artifact }}" + + - name: Stage platform artifact (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Copy-Item target/release/${{ matrix.bin_name }} -Destination ${{ matrix.artifact }} + + - name: Upload platform artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }} + if-no-files-found: error + + package-npm: + name: Package @codra/cli npm tarball + needs: build-binaries + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Download release binaries + uses: actions/download-artifact@v4 + with: + path: packages/codra-npm-cli/artifacts + pattern: codra-* + merge-multiple: true + + - name: Package native binaries from artifacts + working-directory: packages/codra-npm-cli + env: + CODRA_USE_ARTIFACTS: '1' + CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + run: npm run build:from-artifacts + + - name: Test npm wrapper + working-directory: packages/codra-npm-cli + run: npm test + + - name: Validate npm pack contents + working-directory: packages/codra-npm-cli + env: + CODRA_EXPECT_ALL_PLATFORMS: '1' + run: npm run pack:dry + + - name: Build npm tarball (no publish) + working-directory: packages/codra-npm-cli + env: + CODRA_USE_ARTIFACTS: '1' + CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + run: npm pack + + - name: Upload npm tarball artifact + uses: actions/upload-artifact@v4 + with: + name: codra-cli-npm-tarball + path: packages/codra-npm-cli/codra-cli-*.tgz + if-no-files-found: error + + - name: Publish to npm (guarded) + if: inputs.publish == true + working-directory: packages/codra-npm-cli + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + CODRA_USE_ARTIFACTS: '1' + CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "NPM_TOKEN secret is required when publish=true" + exit 1 + fi + npm publish --access public \ No newline at end of file diff --git a/README.md b/README.md index c664362..d49db40 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ npm install -g @codra/cli # coming soon codra run --task summarize-context --jsonl ``` -The [`@codra/cli`](packages/codra-npm-cli/) package is a thin Node wrapper that spawns the native `codra` binary built from `codra-cli`. Local `npm run build` packages only the current platform binary; multi-platform npm releases need a release workflow (see [packages/codra-npm-cli/README.md](packages/codra-npm-cli/README.md)). +The [`@codra/cli`](packages/codra-npm-cli/) package is a thin Node wrapper that spawns the native `codra` binary built from `codra-cli`. Multi-platform npm distribution is in progress (linux/macOS/Windows targets); a manual [release workflow](.github/workflows/codra-cli-release.yml) packages all platform binaries before publish (see [packages/codra-npm-cli/README.md](packages/codra-npm-cli/README.md)). ## Roadmap diff --git a/packages/codra-npm-cli/.gitignore b/packages/codra-npm-cli/.gitignore index 245139d..efeeacc 100644 --- a/packages/codra-npm-cli/.gitignore +++ b/packages/codra-npm-cli/.gitignore @@ -1 +1,2 @@ -bin/native/ \ No newline at end of file +bin/native/ +artifacts/ \ No newline at end of file diff --git a/packages/codra-npm-cli/README.md b/packages/codra-npm-cli/README.md index 4115828..d4600fc 100644 --- a/packages/codra-npm-cli/README.md +++ b/packages/codra-npm-cli/README.md @@ -13,7 +13,19 @@ codra --help Until the package is published, use [local development](#local-development) below. -## Local development +## Supported platforms + +| Platform key | npm binary path | CI artifact name | +|--------------|-----------------|------------------| +| `linux-x64` | `bin/native/linux-x64/codra` | `codra-linux-x64` | +| `linux-arm64` | `bin/native/linux-arm64/codra` | `codra-linux-arm64` | +| `darwin-x64` | `bin/native/darwin-x64/codra` | `codra-darwin-x64` | +| `darwin-arm64` | `bin/native/darwin-arm64/codra` | `codra-darwin-arm64` | +| `win32-x64` | `bin/native/win32-x64/codra.exe` | `codra-win32-x64.exe` | + +Optional per-platform npm packages may be added later if tarball size becomes too large. For now, all targets ship in `@codra/cli`. + +## Local development (current host only) ```bash cd packages/codra-npm-cli @@ -23,43 +35,62 @@ node bin/codra.js run --task summarize-context --jsonl npm test ``` -`npm run build` runs `cargo build -p codra-cli --release` and copies the release binary into `bin/native/-/` for **the current machine only** (for example `linux-arm64` on this host). +`npm run build` runs `cargo build -p codra-cli --release` and copies the binary into `bin/native/-/` only. -## Current limitation +## Multi-platform release (artifacts) -- A local `npm run build` packages **only the current platform/arch** binary. -- `npm pack` / `npm publish` run `prepack`, which rebuilds and copies that same host binary into the tarball. -- End users on other platforms will see a clear error until release-built binaries for their OS/arch are included. -- Real public npm publishing needs release-built binaries for each supported target (see [Multi-platform release plan](#multi-platform-release-plan)). +Release maintainers build per-platform binaries in CI, then package them into one npm tarball. -This package does **not** ship multi-platform binaries today. +### Artifact naming -## Publishing checklist +Place prebuilt files in `packages/codra-npm-cli/artifacts/` (or set `CODRA_ARTIFACTS_DIR`): -When ready to publish (maintainers only): +``` +artifacts/codra-linux-x64 +artifacts/codra-linux-arm64 +artifacts/codra-darwin-x64 +artifacts/codra-darwin-arm64 +artifacts/codra-win32-x64.exe +``` -1. `npm login` -2. `npm run build` — release Rust binary for this host -3. `npm test` -4. `npm run pack:dry` — verify tarball contents (runs `prepack` + dry-run checks) -5. Confirm tarball includes `README.md`, `package.json`, `bin/codra.js`, and `bin/native/-/codra` only -6. `npm publish --access public` +Package into `bin/native/`: + +```bash +npm run build:from-artifacts +``` + +- Fails if any artifact is missing (default). +- Set `CODRA_ALLOW_PARTIAL_BINARIES=1` to package only available artifacts (local testing). + +### Manual GitHub Actions release + +Workflow: [`.github/workflows/codra-cli-release.yml`](../../.github/workflows/codra-cli-release.yml) + +- Trigger: **workflow_dispatch** only (not automatic on push). +- Builds matrix: linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64. +- Job `package-npm`: downloads artifacts, runs `build:from-artifacts`, `npm test`, `npm pack`, uploads tarball. +- **npm publish is disabled by default.** Set workflow input `publish: true` and configure `NPM_TOKEN` secret to publish. -Do not publish until multi-platform release binaries are available for your intended audience, unless you are intentionally shipping a single-platform preview. +## Local vs release packaging -## Multi-platform release plan +| Flow | Command | Result | +|------|---------|--------| +| Local dev | `npm run build` | Current host binary only | +| Release | `npm run build:from-artifacts` | All artifacts → `bin/native/*` | +| `npm pack` / `npm publish` | `prepack` | Uses artifacts if present, else host `build` | -Future release workflow should build and bundle: +## Publishing checklist -| Target | Binary path | -|--------|-------------| -| `linux-x64` | `bin/native/linux-x64/codra` | -| `linux-arm64` | `bin/native/linux-arm64/codra` | -| `darwin-x64` | `bin/native/darwin-x64/codra` | -| `darwin-arm64` | `bin/native/darwin-arm64/codra` | -| `win32-x64` | `bin/native/win32-x64/codra.exe` | +When ready to publish (maintainers only): -Automation (GitHub Actions or similar) is not implemented yet. +1. Run **Codra CLI release** workflow (or supply all artifacts locally). +2. `npm login` (only if publishing manually). +3. `npm test` +4. `CODRA_EXPECT_ALL_PLATFORMS=1 npm run pack:dry` +5. Verify tarball lists all five `bin/native//` binaries. +6. Publish via workflow with `publish: true` **or** `npm publish --access public` (guarded). + +Do not publish until all target binaries are included unless intentionally shipping a preview. ## Supported commands @@ -71,8 +102,6 @@ codra run --task explain-issue --jsonl codra run --task summarize-context --jsonl ``` -Invalid tasks exit non-zero. With `--jsonl`, failures emit `codra.run.failed`. - ## GitHub context (optional) | Variable | Purpose | @@ -87,7 +116,7 @@ Invalid tasks exit non-zero. With `--jsonl`, failures emit `codra.run.failed`. - No AI provider API calls in this CLI layer yet. - Does not print `GITHUB_TOKEN` or other secrets in output. -- Local-first CLI foundation; wraps the existing Rust binary unchanged. +- Wraps the existing Rust binary unchanged. ## License diff --git a/packages/codra-npm-cli/bin/codra-lib.js b/packages/codra-npm-cli/bin/codra-lib.js new file mode 100644 index 0000000..35daeaa --- /dev/null +++ b/packages/codra-npm-cli/bin/codra-lib.js @@ -0,0 +1,122 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const SUPPORTED_PLATFORM_KEYS = [ + 'linux-x64', + 'linux-arm64', + 'darwin-x64', + 'darwin-arm64', + 'win32-x64', +]; + +function platformArchKey(platform = process.platform, arch = process.arch) { + return `${platform}-${arch}`; +} + +function isSupportedPlatformKey(key) { + return SUPPORTED_PLATFORM_KEYS.includes(key); +} + +function binaryFileName(platformKey) { + return platformKey && platformKey.startsWith('win32') ? 'codra.exe' : 'codra'; +} + +function defaultNativeRoot() { + return path.join(__dirname, 'native'); +} + +function nativeBinaryPath(platformKey, nativeRoot = defaultNativeRoot()) { + const name = binaryFileName(platformKey); + return path.join(nativeRoot, platformKey, name); +} + +function listInstalledBinaryPaths(nativeRoot = defaultNativeRoot()) { + if (!fs.existsSync(nativeRoot)) { + return []; + } + + const installed = []; + for (const entry of fs.readdirSync(nativeRoot, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const key = entry.name; + const candidate = nativeBinaryPath(key, nativeRoot); + if (fs.existsSync(candidate)) { + installed.push(candidate); + } + } + return installed.sort(); +} + +function formatMissingBinaryMessage(requestedKey, nativeRoot = defaultNativeRoot()) { + const lines = []; + const expected = nativeBinaryPath(requestedKey, nativeRoot); + const installed = listInstalledBinaryPaths(nativeRoot); + + lines.push(`Codra CLI binary is not available for ${requestedKey} in this package.`); + lines.push(`Expected path: ${expected}`); + lines.push(`Supported platforms: ${SUPPORTED_PLATFORM_KEYS.join(', ')}`); + + if (installed.length > 0) { + lines.push('Binaries bundled in this package:'); + for (const p of installed) { + lines.push(` - ${p}`); + } + } else { + lines.push( + 'No native binaries are bundled. For local dev: npm run build (packages/codra-npm-cli).', + ); + } + + lines.push( + 'Multi-platform npm installs require release-built binaries for your OS/arch.', + ); + + return lines.join('\n') + '\n'; +} + +function formatUnsupportedPlatformMessage(requestedKey) { + const installed = listInstalledBinaryPaths(); + const lines = [ + `Codra CLI does not support platform ${requestedKey}.`, + `Supported platforms: ${SUPPORTED_PLATFORM_KEYS.join(', ')}`, + ]; + + if (installed.length > 0) { + lines.push('Binaries bundled in this package:'); + for (const p of installed) { + lines.push(` - ${p}`); + } + } + + return lines.join('\n') + '\n'; +} + +function resolveNativeBinary(nativeRoot = defaultNativeRoot()) { + const key = platformArchKey(); + + if (!isSupportedPlatformKey(key)) { + return { ok: false, reason: 'unsupported', key }; + } + + const nativePath = nativeBinaryPath(key, nativeRoot); + if (!fs.existsSync(nativePath)) { + return { ok: false, reason: 'missing', key }; + } + + return { ok: true, path: nativePath, key }; +} + +module.exports = { + SUPPORTED_PLATFORM_KEYS, + platformArchKey, + isSupportedPlatformKey, + binaryFileName, + nativeBinaryPath, + listInstalledBinaryPaths, + formatMissingBinaryMessage, + formatUnsupportedPlatformMessage, + resolveNativeBinary, + defaultNativeRoot, +}; \ No newline at end of file diff --git a/packages/codra-npm-cli/bin/codra.js b/packages/codra-npm-cli/bin/codra.js index fa4f3c7..3eb6dcc 100755 --- a/packages/codra-npm-cli/bin/codra.js +++ b/packages/codra-npm-cli/bin/codra.js @@ -2,92 +2,26 @@ 'use strict'; const { spawnSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); +const { + resolveNativeBinary, + formatMissingBinaryMessage, + formatUnsupportedPlatformMessage, +} = require('./codra-lib'); -const PLANNED_PLATFORMS = [ - 'linux-x64', - 'linux-arm64', - 'darwin-x64', - 'darwin-arm64', - 'win32-x64', -]; - -function platformArchKey() { - return `${process.platform}-${process.arch}`; -} - -function binaryFileName(platformKey) { - return platformKey && platformKey.startsWith('win32') ? 'codra.exe' : 'codra'; -} - -function nativeBinaryPath(platformKey) { - const name = binaryFileName(platformKey); - return path.join(__dirname, 'native', platformKey, name); -} - -function listInstalledBinaryPaths() { - const nativeRoot = path.join(__dirname, 'native'); - if (!fs.existsSync(nativeRoot)) { - return []; - } - - const installed = []; - for (const entry of fs.readdirSync(nativeRoot, { withFileTypes: true })) { - if (!entry.isDirectory()) continue; - const key = entry.name; - const candidate = nativeBinaryPath(key); - if (fs.existsSync(candidate)) { - installed.push(candidate); - } - } - return installed.sort(); -} - -function reportMissingBinary(requestedKey) { - const expected = nativeBinaryPath(requestedKey); - const installed = listInstalledBinaryPaths(); - - process.stderr.write( - `Codra CLI binary is not available for ${requestedKey} in this package.\n` + - `Expected path: ${expected}\n` + - `Planned release targets: ${PLANNED_PLATFORMS.join(', ')}\n`, - ); +function main() { + const resolved = resolveNativeBinary(); - if (installed.length > 0) { - process.stderr.write('Binaries included in this package:\n'); - for (const p of installed) { - process.stderr.write(` - ${p}\n`); + if (!resolved.ok) { + if (resolved.reason === 'unsupported') { + process.stderr.write(formatUnsupportedPlatformMessage(resolved.key)); + } else { + process.stderr.write(formatMissingBinaryMessage(resolved.key)); } - } else { - process.stderr.write( - 'No native binaries are bundled. Run: npm run build (from packages/codra-npm-cli)\n', - ); - } - - process.stderr.write( - 'Public npm installs require a release-built binary for your platform. ' + - 'This tarball currently ships only binaries built on the publishing machine.\n', - ); -} - -function resolveNativeBinary() { - const key = platformArchKey(); - const nativePath = nativeBinaryPath(key); - - if (!fs.existsSync(nativePath)) { - reportMissingBinary(key); process.exit(1); } - return nativePath; -} - -function main() { - const binary = resolveNativeBinary(); const args = process.argv.slice(2); - - const result = spawnSync(binary, args, { + const result = spawnSync(resolved.path, args, { stdio: 'inherit', env: process.env, }); diff --git a/packages/codra-npm-cli/package.json b/packages/codra-npm-cli/package.json index ea51bf3..7bae4e2 100644 --- a/packages/codra-npm-cli/package.json +++ b/packages/codra-npm-cli/package.json @@ -19,8 +19,9 @@ ], "scripts": { "build": "node scripts/build.js", + "build:from-artifacts": "node scripts/build-platform-binaries.js", "test": "node scripts/test.js", - "prepack": "node scripts/build.js", + "prepack": "node scripts/prepack.js", "pack:dry": "node scripts/pack-dry-run.js" }, "publishConfig": { diff --git a/packages/codra-npm-cli/scripts/build-platform-binaries.js b/packages/codra-npm-cli/scripts/build-platform-binaries.js new file mode 100644 index 0000000..015c240 --- /dev/null +++ b/packages/codra-npm-cli/scripts/build-platform-binaries.js @@ -0,0 +1,86 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const packageRoot = path.resolve(__dirname, '..'); + +const TARGETS = [ + { key: 'linux-x64', artifact: 'codra-linux-x64', destName: 'codra' }, + { key: 'linux-arm64', artifact: 'codra-linux-arm64', destName: 'codra' }, + { key: 'darwin-x64', artifact: 'codra-darwin-x64', destName: 'codra' }, + { key: 'darwin-arm64', artifact: 'codra-darwin-arm64', destName: 'codra' }, + { key: 'win32-x64', artifact: 'codra-win32-x64.exe', destName: 'codra.exe' }, +]; + +function artifactsDir() { + return process.env.CODRA_ARTIFACTS_DIR + ? path.resolve(process.env.CODRA_ARTIFACTS_DIR) + : path.join(packageRoot, 'artifacts'); +} + +function allowPartial() { + return process.env.CODRA_ALLOW_PARTIAL_BINARIES === '1'; +} + +function main() { + const srcDir = artifactsDir(); + const partial = allowPartial(); + const missing = []; + const packaged = []; + + console.log(`[build:from-artifacts] artifacts dir: ${srcDir}`); + console.log(`[build:from-artifacts] partial packaging: ${partial ? 'yes' : 'no'}`); + + if (!fs.existsSync(srcDir)) { + console.error(`[build:from-artifacts] artifacts directory not found: ${srcDir}`); + process.exit(1); + } + + for (const target of TARGETS) { + const src = path.join(srcDir, target.artifact); + const destDir = path.join(packageRoot, 'bin', 'native', target.key); + const dest = path.join(destDir, target.destName); + + if (!fs.existsSync(src)) { + missing.push(target.artifact); + continue; + } + + fs.mkdirSync(destDir, { recursive: true }); + fs.copyFileSync(src, dest); + + if (!target.key.startsWith('win32')) { + fs.chmodSync(dest, 0o755); + } + + packaged.push(`${target.key} -> ${dest}`); + } + + for (const line of packaged) { + console.log(`[build:from-artifacts] ${line}`); + } + + if (missing.length > 0) { + console.error('[build:from-artifacts] missing artifacts:'); + for (const name of missing) { + console.error(` - ${path.join(srcDir, name)}`); + } + + if (!partial) { + console.error( + '[build:from-artifacts] set CODRA_ALLOW_PARTIAL_BINARIES=1 to package available targets only', + ); + process.exit(1); + } + } + + if (packaged.length === 0) { + console.error('[build:from-artifacts] no artifacts packaged'); + process.exit(1); + } + + console.log(`[build:from-artifacts] packaged ${packaged.length}/${TARGETS.length} targets`); +} + +main(); \ No newline at end of file diff --git a/packages/codra-npm-cli/scripts/pack-dry-run.js b/packages/codra-npm-cli/scripts/pack-dry-run.js index 7d368ef..5ae4617 100644 --- a/packages/codra-npm-cli/scripts/pack-dry-run.js +++ b/packages/codra-npm-cli/scripts/pack-dry-run.js @@ -1,18 +1,30 @@ 'use strict'; const { execSync } = require('child_process'); -const fs = require('fs'); const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); -const forbidden = [/^node_modules\//, /^target\//, /\.env$/, /^scripts\//]; +const { SUPPORTED_PLATFORM_KEYS } = require('../bin/codra-lib'); + +const forbidden = [/^node_modules\//, /^target\//, /^artifacts\//, /\.env$/, /^scripts\//]; + +const EXPECTED_NATIVE = SUPPORTED_PLATFORM_KEYS.map((key) => { + const name = key.startsWith('win32') ? 'codra.exe' : 'codra'; + return `bin/native/${key}/${name}`; +}); function main() { + const expectAll = process.env.CODRA_EXPECT_ALL_PLATFORMS === '1'; + const output = execSync('npm pack --dry-run 2>&1', { cwd: packageRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], shell: true, + env: { + ...process.env, + CODRA_USE_ARTIFACTS: process.env.CODRA_USE_ARTIFACTS || (expectAll ? '1' : process.env.CODRA_USE_ARTIFACTS), + }, }); const lines = output.split('\n'); @@ -42,6 +54,14 @@ function main() { errors.push('no bin/native/-/ binary in pack (run npm run build first)'); } + if (expectAll) { + for (const expected of EXPECTED_NATIVE) { + if (!fileLines.includes(expected)) { + errors.push(`missing release binary: ${expected}`); + } + } + } + if (errors.length > 0) { console.error('[pack-dry-run] failed:'); for (const err of errors) { @@ -51,7 +71,7 @@ function main() { } console.log('[pack-dry-run] ok'); - console.log(`[pack-dry-run] native binaries: ${nativeBins.join(', ')}`); + console.log(`[pack-dry-run] native binaries (${nativeBins.length}): ${nativeBins.join(', ')}`); process.stdout.write(output); } diff --git a/packages/codra-npm-cli/scripts/prepack.js b/packages/codra-npm-cli/scripts/prepack.js new file mode 100644 index 0000000..15917a3 --- /dev/null +++ b/packages/codra-npm-cli/scripts/prepack.js @@ -0,0 +1,45 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const packageRoot = path.resolve(__dirname, '..'); + +const TARGET_ARTIFACTS = [ + 'codra-linux-x64', + 'codra-linux-arm64', + 'codra-darwin-x64', + 'codra-darwin-arm64', + 'codra-win32-x64.exe', +]; + +function artifactsDir() { + return process.env.CODRA_ARTIFACTS_DIR + ? path.resolve(process.env.CODRA_ARTIFACTS_DIR) + : path.join(packageRoot, 'artifacts'); +} + +function shouldUseArtifacts() { + if (process.env.CODRA_USE_ARTIFACTS === '1') { + return true; + } + + const dir = artifactsDir(); + if (!fs.existsSync(dir)) { + return false; + } + + return TARGET_ARTIFACTS.some((name) => fs.existsSync(path.join(dir, name))); +} + +function main() { + const script = shouldUseArtifacts() + ? path.join(__dirname, 'build-platform-binaries.js') + : path.join(__dirname, 'build.js'); + + console.log(`[prepack] running ${path.basename(script)}`); + execSync(`node "${script}"`, { cwd: packageRoot, stdio: 'inherit' }); +} + +main(); \ No newline at end of file diff --git a/packages/codra-npm-cli/scripts/test.js b/packages/codra-npm-cli/scripts/test.js index 42733ac..a15bef1 100644 --- a/packages/codra-npm-cli/scripts/test.js +++ b/packages/codra-npm-cli/scripts/test.js @@ -1,19 +1,22 @@ 'use strict'; const { spawnSync } = require('child_process'); +const fs = require('fs'); +const os = require('os'); const path = require('path'); const packageRoot = path.resolve(__dirname, '..'); const wrapper = path.join(packageRoot, 'bin', 'codra.js'); +const lib = require('../bin/codra-lib'); -function run(args, { expectFail = false } = {}) { +function run(args, { expectFail = false, env = process.env } = {}) { const label = ['node', 'bin/codra.js', ...args].join(' '); console.log(`[test] ${label}`); const result = spawnSync(process.execPath, [wrapper, ...args], { cwd: packageRoot, encoding: 'utf8', - env: process.env, + env, }); const stdout = result.stdout || ''; @@ -46,7 +49,104 @@ function assertIncludes(haystack, needle, label) { } } +function testPlatformHelpers() { + const key = lib.platformArchKey(); + console.log(`[test] current platform key: ${key}`); + + if (!lib.isSupportedPlatformKey('linux-arm64')) { + console.error('[test] linux-arm64 should be supported'); + process.exit(1); + } + + if (lib.isSupportedPlatformKey('linux-ia32')) { + console.error('[test] linux-ia32 should not be supported'); + process.exit(1); + } + + const resolved = lib.resolveNativeBinary(); + if (!resolved.ok && resolved.reason === 'missing') { + console.error('[test] expected current host binary to exist after npm run build'); + process.exit(1); + } + + if (!resolved.ok) { + console.error(`[test] unexpected resolve failure: ${resolved.reason}`); + process.exit(1); + } + + const unsupported = lib.formatUnsupportedPlatformMessage('linux-ia32'); + assertIncludes(unsupported, 'does not support platform linux-ia32', 'unsupported message'); + + const missing = lib.formatMissingBinaryMessage(key); + assertIncludes(missing, 'not available', 'missing message'); +} + +function testArtifactPackaging() { + const tmpArtifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'codra-artifacts-')); + const hostBinary = lib.resolveNativeBinary(); + + if (!hostBinary.ok) { + console.error('[test] skip artifact packaging: host binary missing'); + return; + } + + try { + const targets = [ + 'codra-linux-x64', + 'codra-linux-arm64', + 'codra-darwin-x64', + 'codra-darwin-arm64', + 'codra-win32-x64.exe', + ]; + + for (const name of targets) { + fs.copyFileSync(hostBinary.path, path.join(tmpArtifacts, name)); + } + + const result = spawnSync( + process.execPath, + [path.join(__dirname, 'build-platform-binaries.js')], + { + cwd: packageRoot, + encoding: 'utf8', + env: { + ...process.env, + CODRA_ARTIFACTS_DIR: tmpArtifacts, + }, + }, + ); + + if (result.status !== 0) { + console.error('[test] build-platform-binaries failed'); + if (result.stderr) console.error(result.stderr); + process.exit(1); + } + + for (const key of lib.SUPPORTED_PLATFORM_KEYS) { + const dest = lib.nativeBinaryPath(key); + if (!fs.existsSync(dest)) { + console.error(`[test] missing packaged binary: ${dest}`); + process.exit(1); + } + } + + console.log('[test] artifact packaging layout ok'); + } finally { + fs.rmSync(tmpArtifacts, { recursive: true, force: true }); + const nativeRoot = path.join(packageRoot, 'bin', 'native'); + if (fs.existsSync(nativeRoot)) { + fs.rmSync(nativeRoot, { recursive: true, force: true }); + } + spawnSync(process.execPath, [path.join(__dirname, 'build.js')], { + cwd: packageRoot, + stdio: 'inherit', + }); + } +} + function main() { + testPlatformHelpers(); + const help = run(['--help']); assertIncludes(help.stdout + help.stderr, 'codra', 'help output'); @@ -65,6 +165,8 @@ function main() { process.exit(1); } + testArtifactPackaging(); + console.log('[test] all checks passed'); }