diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ee1c2e0..12cc0b3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,11 @@ jobs: publish-crates: name: publish to crates.io - needs: [validate, quality-recheck] + # Gate the first irreversible publish on the CLI binaries building + # successfully (RFC-0110 / Codex #519 P1): if any platform build fails we + # must NOT publish to a registry, or the release is partial (crates live but + # binaries missing). npm/pypi are gated on publish-crates. + needs: [validate, quality-recheck, build-cli-binaries] runs-on: ubuntu-latest timeout-minutes: 60 environment: crates-io @@ -145,28 +149,62 @@ jobs: publish-npm: name: publish to npm - needs: [validate, quality-recheck, publish-crates] + # RFC-0110: assemble the per-platform + launcher packages from the prebuilt + # binaries and publish them. needs build-cli-binaries for the artifacts. + needs: [validate, quality-recheck, publish-crates, build-cli-binaries] runs-on: ubuntu-latest timeout-minutes: 30 environment: npm permissions: id-token: write # for provenance + env: + VERSION: ${{ needs.validate.outputs.version }} steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: | - if [ -d "bindings/node" ]; then - cd bindings/node - npm ci - npm publish --access public --provenance - else - echo "no bindings/node yet — skipping" - fi + - name: Download CLI binaries + uses: actions/download-artifact@v7 + with: + pattern: cli-* + path: dl + - name: Reshape artifacts into a platform-keyed bin dir + run: | + mkdir -p dist-bin + for d in dl/cli-*; do + key="${d#dl/cli-}" + mkdir -p "dist-bin/$key" + cp "$d"/* "dist-bin/$key/" + done + ls -R dist-bin + - name: Assemble npm packages + run: node npm/scripts/build-npm.mjs --version "$VERSION" --bin-dir dist-bin --out dist-npm + - name: Publish packages (idempotent — platform packages first) env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -z "${NODE_AUTH_TOKEN:-}" ]; then + echo "::warning::NPM_TOKEN is not set — skipping npm publish. Configure it in repo Settings → Environments → npm." + exit 0 + fi + publish_one() { + dir="$1" + name=$(node -p "require('./$dir/package.json').name") + ver=$(node -p "require('./$dir/package.json').version") + if npm view "$name@$ver" version >/dev/null 2>&1; then + echo "$name@$ver already on npm; skipping" + return 0 + fi + ( cd "$dir" && npm publish --access public --provenance ) + } + # Platform packages must exist before the main package (whose + # optionalDependencies reference them). + for dir in dist-npm/mycelium-*; do + [ -d "$dir" ] && publish_one "$dir" + done + publish_one "dist-npm/mycelium" publish-pypi: name: publish to PyPI @@ -190,6 +228,56 @@ jobs: echo "no bindings/python yet — skipping" fi + build-cli-binaries: + # RFC-0110: cross-compile the `mycelium` CLI for each distributed platform, + # upload each as a workflow artifact (consumed by publish-npm in a follow-up + # and attached to the GitHub Release below). Native builds for 4 targets; + # `cross` handles the linux-arm64 C toolchain (tree-sitter grammars are C). + name: build CLI binary (${{ matrix.key }}) + needs: [validate, quality-recheck] + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - { key: darwin-arm64, os: macos-14, target: aarch64-apple-darwin, exe: mycelium, cross: false } + - { key: darwin-x64, os: macos-13, target: x86_64-apple-darwin, exe: mycelium, cross: false } + - { key: linux-x64, os: ubuntu-latest, target: x86_64-unknown-linux-gnu, exe: mycelium, cross: false } + - { key: linux-arm64, os: ubuntu-latest, target: aarch64-unknown-linux-gnu, exe: mycelium, cross: true } + - { key: win32-x64, os: windows-latest, target: x86_64-pc-windows-msvc, exe: mycelium.exe, cross: false } + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + key: release-${{ matrix.target }} + - name: Install cross (linux-arm64) + if: ${{ matrix.cross }} + uses: taiki-e/install-action@v2 + with: + tool: cross + - name: Build release binary + shell: bash + run: | + if [ "${{ matrix.cross }}" = "true" ]; then + cross build --release -p mycelium-rcig-cli --target ${{ matrix.target }} + else + cargo build --release -p mycelium-rcig-cli --target ${{ matrix.target }} + fi + - name: Stage binary under its platform key + shell: bash + run: | + mkdir -p "dist-bin/${{ matrix.key }}" + cp "target/${{ matrix.target }}/release/${{ matrix.exe }}" "dist-bin/${{ matrix.key }}/${{ matrix.exe }}" + - uses: actions/upload-artifact@v7 + with: + name: cli-${{ matrix.key }} + path: dist-bin/${{ matrix.key }}/${{ matrix.exe }} + if-no-files-found: error + finalize: name: merge to main, tag, GitHub Release # Charter §5.12: touching main requires founder authorization. @@ -198,7 +286,7 @@ jobs: # (scripts/release-ceremony.sh) or a manual workflow_dispatch serves as # the explicit human authorization step. if: github.event_name == 'workflow_dispatch' - needs: [validate, publish-crates, publish-npm, publish-pypi] + needs: [validate, publish-crates, publish-npm, publish-pypi, build-cli-binaries] runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -263,13 +351,38 @@ jobs: git tag -a "v$VERSION" -m "Release v$VERSION" git push origin "v$VERSION" - # Step D — Create the GitHub Release on the tag reachable from main. + # Step D — Download the per-platform CLI binaries and rename them with a + # platform suffix (the raw artifacts are all named `mycelium`/`.exe`, which + # would collide as release assets). RFC-0110. + - name: Download CLI binaries + uses: actions/download-artifact@v7 + with: + pattern: cli-* + path: dist-release + - name: Collect release assets + shell: bash + run: | + mkdir -p release-assets + for d in dist-release/cli-*; do + key="${d#dist-release/cli-}" + if [ -f "$d/mycelium.exe" ]; then + cp "$d/mycelium.exe" "release-assets/mycelium-$key.exe" + elif [ -f "$d/mycelium" ]; then + cp "$d/mycelium" "release-assets/mycelium-$key" + fi + done + ls -la release-assets + + # Step E — Create the GitHub Release on the tag reachable from main, + # attaching the per-platform binaries (a download path for users who do + # not use npm/bun/cargo). - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: v${{ env.VERSION }} generate_release_notes: true target_commitish: main + files: release-assets/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}