diff --git a/.github/workflows/codra-cli-release.yml b/.github/workflows/codra-cli-release.yml index d1153ea..aae1ff1 100644 --- a/.github/workflows/codra-cli-release.yml +++ b/.github/workflows/codra-cli-release.yml @@ -7,35 +7,77 @@ on: description: Publish @codra/cli to npm (requires NPM_TOKEN secret) type: boolean default: false + include_darwin_x64: + description: Include Intel macOS darwin-x64 build (macos-13; can queue for a long time) + type: boolean + default: false + allow_partial_binaries: + description: Package only available artifacts. Dry runs (publish=false) allow partial by default; publish=true requires full set unless this is true + type: boolean + default: false jobs: + resolve-matrix: + name: Resolve release matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + expected_platforms: ${{ steps.set-matrix.outputs.expected_platforms }} + allow_partial: ${{ steps.set-matrix.outputs.allow_partial }} + expect_all_pack: ${{ steps.set-matrix.outputs.expect_all_pack }} + steps: + - name: Compute matrix and packaging policy + id: set-matrix + env: + INCLUDE_DARWIN_X64: ${{ inputs.include_darwin_x64 }} + PUBLISH: ${{ inputs.publish }} + ALLOW_PARTIAL_BINARIES: ${{ inputs.allow_partial_binaries }} + run: | + python3 <<'PY' + import json + import os + + include_darwin_x64 = os.environ.get("INCLUDE_DARWIN_X64", "false").lower() == "true" + publish = os.environ.get("PUBLISH", "false").lower() == "true" + allow_partial_input = os.environ.get("ALLOW_PARTIAL_BINARIES", "false").lower() == "true" + + 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-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"}, + ] + if include_darwin_x64: + matrix_include.append( + { + "target": "darwin-x64", + "os": "macos-13", + "artifact": "codra-darwin-x64", + "bin_name": "codra", + } + ) + + platforms = [entry["target"] for entry in matrix_include] + # publish=false dry runs may package partial binaries; publish=true requires full set unless opted in + allow_partial = (not publish) or allow_partial_input + expect_all_pack = publish and (not allow_partial_input) + + github_output = os.environ["GITHUB_OUTPUT"] + with open(github_output, "a", encoding="utf-8") as handle: + handle.write(f"matrix={json.dumps({'include': matrix_include})}\n") + handle.write(f"expected_platforms={','.join(platforms)}\n") + handle.write(f"allow_partial={'1' if allow_partial else '0'}\n") + handle.write(f"expect_all_pack={'1' if expect_all_pack else '0'}\n") + PY + build-binaries: name: build ${{ matrix.target }} + needs: resolve-matrix runs-on: ${{ matrix.os }} + timeout-minutes: 45 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 + matrix: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }} steps: - name: Check out repository @@ -67,7 +109,7 @@ jobs: package-npm: name: Package @codra/cli npm tarball - needs: build-binaries + needs: [resolve-matrix, build-binaries] runs-on: ubuntu-latest steps: @@ -91,6 +133,7 @@ jobs: env: CODRA_USE_ARTIFACTS: '1' CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + CODRA_ALLOW_PARTIAL_BINARIES: ${{ needs.resolve-matrix.outputs.allow_partial }} run: npm run build:from-artifacts - name: Test npm wrapper @@ -100,7 +143,7 @@ jobs: - name: Validate npm pack contents working-directory: packages/codra-npm-cli env: - CODRA_EXPECT_ALL_PLATFORMS: '1' + CODRA_EXPECT_PLATFORMS: ${{ needs.resolve-matrix.outputs.expect_all_pack == '1' && needs.resolve-matrix.outputs.expected_platforms || '' }} run: npm run pack:dry - name: Build npm tarball (no publish) @@ -108,6 +151,7 @@ jobs: env: CODRA_USE_ARTIFACTS: '1' CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + CODRA_ALLOW_PARTIAL_BINARIES: ${{ needs.resolve-matrix.outputs.allow_partial }} run: npm pack - name: Upload npm tarball artifact @@ -124,9 +168,14 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} CODRA_USE_ARTIFACTS: '1' CODRA_ARTIFACTS_DIR: ${{ github.workspace }}/packages/codra-npm-cli/artifacts + CODRA_ALLOW_PARTIAL_BINARIES: ${{ inputs.allow_partial_binaries == true && '1' || '' }} + CODRA_EXPECT_PLATFORMS: ${{ inputs.allow_partial_binaries != true && needs.resolve-matrix.outputs.expected_platforms || '' }} run: | if [ -z "$NODE_AUTH_TOKEN" ]; then echo "NPM_TOKEN secret is required when publish=true" exit 1 fi + if [ "${{ inputs.allow_partial_binaries }}" != "true" ]; then + CODRA_ALLOW_PARTIAL_BINARIES=0 node scripts/build-platform-binaries.js + fi npm publish --access public \ No newline at end of file diff --git a/README.md b/README.md index d49db40..5e82dbc 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`. 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)). +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 platform binaries before publish. Intel macOS (`darwin-x64`) is optional in dry runs because `macos-13` runners can queue for a long time; npm publish stays guarded and off by default (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 d4600fc..da97acf 100644 --- a/packages/codra-npm-cli/README.md +++ b/packages/codra-npm-cli/README.md @@ -60,16 +60,19 @@ 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). +- Set `CODRA_ALLOW_PARTIAL_BINARIES=1` to package only available artifacts (local testing or CI dry runs). ### 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. +- **Always builds:** linux-x64, linux-arm64, darwin-arm64, win32-x64. +- **Optional:** darwin-x64 (Intel macOS) when `include_darwin_x64: true`. Off by default because `macos-13` runner availability can be slow and block the workflow. +- Job `package-npm` runs after the selected matrix finishes (it does not wait for darwin-x64 when that input is false). +- **Dry run (`publish: false`):** may package partial binaries (`CODRA_ALLOW_PARTIAL_BINARIES=1`) so tarball verification is not blocked by one scarce runner. +- **Real publish (`publish: true`):** requires every platform in the selected matrix unless `allow_partial_binaries: true` is set explicitly. `NPM_TOKEN` secret is required; the publish step is skipped when `publish: false`. +- Recommended dry-run dispatch: `publish=false`, `include_darwin_x64=false`. ## Local vs release packaging @@ -86,11 +89,11 @@ When ready to publish (maintainers only): 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. +4. `CODRA_EXPECT_PLATFORMS=linux-x64,linux-arm64,darwin-arm64,win32-x64 npm run pack:dry` (add `darwin-x64` when Intel macOS is included). +5. Verify tarball lists every required `bin/native//` binary. 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. +Do not publish until all required target binaries are included unless intentionally shipping a preview with `allow_partial_binaries: true`. ## Supported commands diff --git a/packages/codra-npm-cli/scripts/pack-dry-run.js b/packages/codra-npm-cli/scripts/pack-dry-run.js index 5ae4617..c3beee2 100644 --- a/packages/codra-npm-cli/scripts/pack-dry-run.js +++ b/packages/codra-npm-cli/scripts/pack-dry-run.js @@ -13,8 +13,26 @@ const EXPECTED_NATIVE = SUPPORTED_PLATFORM_KEYS.map((key) => { return `bin/native/${key}/${name}`; }); +function expectedNativePaths() { + if (process.env.CODRA_EXPECT_PLATFORMS) { + const keys = process.env.CODRA_EXPECT_PLATFORMS.split(',') + .map((key) => key.trim()) + .filter(Boolean); + return keys.map((key) => { + const name = key.startsWith('win32') ? 'codra.exe' : 'codra'; + return `bin/native/${key}/${name}`; + }); + } + + if (process.env.CODRA_EXPECT_ALL_PLATFORMS === '1') { + return EXPECTED_NATIVE; + } + + return []; +} + function main() { - const expectAll = process.env.CODRA_EXPECT_ALL_PLATFORMS === '1'; + const expectedNative = expectedNativePaths(); const output = execSync('npm pack --dry-run 2>&1', { cwd: packageRoot, @@ -23,7 +41,9 @@ function main() { shell: true, env: { ...process.env, - CODRA_USE_ARTIFACTS: process.env.CODRA_USE_ARTIFACTS || (expectAll ? '1' : process.env.CODRA_USE_ARTIFACTS), + CODRA_USE_ARTIFACTS: + process.env.CODRA_USE_ARTIFACTS || + (expectedNative.length > 0 ? '1' : process.env.CODRA_USE_ARTIFACTS), }, }); @@ -54,11 +74,9 @@ 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}`); - } + for (const expected of expectedNative) { + if (!fileLines.includes(expected)) { + errors.push(`missing release binary: ${expected}`); } }