diff --git a/README.md b/README.md index 5147fbf..c664362 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`. For local development, 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`. 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)). ## Roadmap diff --git a/packages/codra-npm-cli/README.md b/packages/codra-npm-cli/README.md index 08c3cff..4115828 100644 --- a/packages/codra-npm-cli/README.md +++ b/packages/codra-npm-cli/README.md @@ -2,18 +2,18 @@ npm package wrapper for the [Codra](https://github.com/talocode/codra) Rust CLI (`codra-cli` crate). Installs a global `codra` command that forwards to the native binary for your platform. -## Installation (coming soon) +**Status: not published to npm yet.** -This package is **not published to npm yet**. When it is: +## Installation (coming soon) ```bash npm install -g @codra/cli codra --help ``` -## Local development +Until the package is published, use [local development](#local-development) below. -From the monorepo root (or this package directory): +## Local development ```bash cd packages/codra-npm-cli @@ -23,7 +23,43 @@ 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/-/`. +`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). + +## Current limitation + +- 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)). + +This package does **not** ship multi-platform binaries today. + +## Publishing checklist + +When ready to publish (maintainers only): + +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` + +Do not publish until multi-platform release binaries are available for your intended audience, unless you are intentionally shipping a single-platform preview. + +## Multi-platform release plan + +Future release workflow should build and bundle: + +| 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` | + +Automation (GitHub Actions or similar) is not implemented yet. ## Supported commands @@ -39,8 +75,6 @@ Invalid tasks exit non-zero. With `--jsonl`, failures emit `codra.run.failed`. ## GitHub context (optional) -When running in GitHub Actions or with fixtures: - | Variable | Purpose | |----------|---------| | `GITHUB_ACTIONS` | Detect Actions runtime | diff --git a/packages/codra-npm-cli/bin/codra.js b/packages/codra-npm-cli/bin/codra.js index 1185661..fa4f3c7 100755 --- a/packages/codra-npm-cli/bin/codra.js +++ b/packages/codra-npm-cli/bin/codra.js @@ -5,37 +5,78 @@ const { spawnSync } = require('child_process'); const fs = require('fs'); const path = require('path'); -const SUPPORTED_PLATFORMS = new Set([ - 'linux-arm64', +const PLANNED_PLATFORMS = [ 'linux-x64', - 'darwin-arm64', + 'linux-arm64', 'darwin-x64', + 'darwin-arm64', 'win32-x64', -]); +]; function platformArchKey() { - const platform = process.platform; - const arch = process.arch; - return `${platform}-${arch}`; + return `${process.platform}-${process.arch}`; +} + +function binaryFileName(platformKey) { + return platformKey && platformKey.startsWith('win32') ? 'codra.exe' : 'codra'; } -function binaryFileName() { - return process.platform === '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`, + ); + + if (installed.length > 0) { + process.stderr.write('Binaries included in this package:\n'); + for (const p of installed) { + process.stderr.write(` - ${p}\n`); + } + } 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 name = binaryFileName(); - const nativePath = path.join(__dirname, 'native', key, name); + const nativePath = nativeBinaryPath(key); if (!fs.existsSync(nativePath)) { - const supported = [...SUPPORTED_PLATFORMS].sort().join(', '); - process.stderr.write( - `codra: native binary not found for ${key}.\n` + - `Expected: ${nativePath}\n` + - `Supported platform keys (when built): ${supported}\n` + - `Build the package for this machine: npm run build (from packages/codra-npm-cli)\n`, - ); + reportMissingBinary(key); process.exit(1); } diff --git a/packages/codra-npm-cli/package.json b/packages/codra-npm-cli/package.json index 36fa6bb..ea51bf3 100644 --- a/packages/codra-npm-cli/package.json +++ b/packages/codra-npm-cli/package.json @@ -20,7 +20,8 @@ "scripts": { "build": "node scripts/build.js", "test": "node scripts/test.js", - "pack:dry": "npm pack --dry-run" + "prepack": "node scripts/build.js", + "pack:dry": "node scripts/pack-dry-run.js" }, "publishConfig": { "access": "public" diff --git a/packages/codra-npm-cli/scripts/pack-dry-run.js b/packages/codra-npm-cli/scripts/pack-dry-run.js new file mode 100644 index 0000000..7d368ef --- /dev/null +++ b/packages/codra-npm-cli/scripts/pack-dry-run.js @@ -0,0 +1,58 @@ +'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\//]; + +function main() { + const output = execSync('npm pack --dry-run 2>&1', { + cwd: packageRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + shell: true, + }); + + const lines = output.split('\n'); + const tarballLines = lines.filter((line) => line.startsWith('npm notice ')); + const fileLines = tarballLines + .map((line) => line.replace(/^npm notice /, '').trim()) + .filter((line) => /^[\d.]+[kMG]?B\s+/.test(line)) + .map((line) => line.replace(/^[\d.]+[kMG]?B\s+/, '')); + + const errors = []; + + for (const file of fileLines) { + if (forbidden.some((re) => re.test(file))) { + errors.push(`forbidden path in pack: ${file}`); + } + } + + const required = ['package.json', 'README.md', 'bin/codra.js']; + for (const file of required) { + if (!fileLines.includes(file)) { + errors.push(`missing required file: ${file}`); + } + } + + const nativeBins = fileLines.filter((f) => f.startsWith('bin/native/')); + if (nativeBins.length === 0) { + errors.push('no bin/native/-/ binary in pack (run npm run build first)'); + } + + if (errors.length > 0) { + console.error('[pack-dry-run] failed:'); + for (const err of errors) { + console.error(` - ${err}`); + } + process.exit(1); + } + + console.log('[pack-dry-run] ok'); + console.log(`[pack-dry-run] native binaries: ${nativeBins.join(', ')}`); + process.stdout.write(output); +} + +main(); \ No newline at end of file