build(deps): bump @actions/core from 3.0.0 to 3.0.1 in the actions-toolkit group #60
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E | |
| # Invokes the top-level composite against the test-fixtures/ projects on | |
| # ubuntu/macos/windows. Asserts: | |
| # - produced binary runs and prints the expected version | |
| # - checksum sidecar matches the binary | |
| # - archive round-trips (when compress != none) | |
| # - step summary contains a row for the target | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| paths: | |
| # `packages/**` covers packages/build/action.yml + every source, test, and | |
| # dist/ under packages/. The action.yml entries below are the top-level | |
| # composite and the two sub-actions whose directories are not under packages/. | |
| - 'packages/**' | |
| - 'scripts/**' | |
| - 'action.yml' | |
| - 'matrix/action.yml' | |
| - 'windows-metadata/action.yml' | |
| - 'test-fixtures/**' | |
| - '.github/workflows/e2e.yml' | |
| - '.github/scripts/**' | |
| - 'package.json' | |
| - 'yarn.lock' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: e2e-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| tiny-cjs: | |
| name: tiny-cjs / ${{ matrix.os }} / ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: node22-linux-x64 | |
| archive: tar.gz | |
| ext: tar.gz | |
| - os: macos-latest | |
| target: node22-macos-arm64 | |
| archive: zip | |
| ext: zip | |
| - os: windows-latest | |
| target: node22-win-x64 | |
| archive: zip | |
| ext: zip | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Run pkg-action | |
| id: build | |
| uses: ./ | |
| with: | |
| config: test-fixtures/tiny-app-cjs/package.json | |
| targets: ${{ matrix.target }} | |
| compress: ${{ matrix.archive }} | |
| checksum: sha256 | |
| filename: '{name}-{version}-{os}-{arch}' | |
| - name: Show outputs | |
| shell: bash | |
| run: | | |
| echo "binaries=${{ steps.build.outputs.binaries }}" | |
| echo "artifacts=${{ steps.build.outputs.artifacts }}" | |
| echo "checksums=${{ steps.build.outputs.checksums }}" | |
| echo "version=${{ steps.build.outputs.version }}" | |
| - name: Assert at least one artifact produced | |
| shell: bash | |
| run: | | |
| count=$(echo '${{ steps.build.outputs.artifacts }}' | jq 'length') | |
| if [ "$count" -lt 1 ]; then | |
| echo "::error::no artifacts produced"; exit 1 | |
| fi | |
| - name: Assert checksum sidecar matches the archived artifact | |
| shell: bash | |
| run: | | |
| first_artifact=$(echo '${{ steps.build.outputs.artifacts }}' | jq -r '.[0]') | |
| sidecar="${first_artifact}.sha256" | |
| if [ ! -f "$sidecar" ]; then | |
| echo "::error::missing checksum sidecar at $sidecar"; exit 1 | |
| fi | |
| # The sidecar is <digest> <basename>\n — verify the digest parses as 64 hex chars. | |
| awk 'NR==1 { if (length($1) != 64) exit 1 }' "$sidecar" | |
| echo "sidecar OK: $(cat "$sidecar")" | |
| codegen-drift: | |
| name: Verify action.yml codegen drift | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .node-version | |
| cache: yarn | |
| - run: yarn install --non-interactive | |
| - run: yarn gen | |
| - name: Verify committed action.yml matches source | |
| run: | | |
| git diff --exit-code -- action.yml packages/build/action.yml docs/inputs.md || { | |
| echo "::error::action.yml or docs/inputs.md is out of sync. Run 'yarn gen' locally and commit." | |
| exit 1 | |
| } | |
| # ────────────────────────────────────────────────────────────────────── | |
| # M2: matrix sub-action demo. | |
| # | |
| # Stage 1 ("plan") runs the matrix sub-action and emits a JSON array of | |
| # {target, runner} tuples. Stage 2 ("fan-out") consumes that array via | |
| # strategy.matrix and invokes the top-level composite on each runner. This | |
| # is the canonical downstream usage for pkg-action/matrix and doubles as a | |
| # smoke test that the JSON shape is strategy.matrix-compatible. | |
| matrix-plan: | |
| name: Matrix plan | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.plan.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Expand targets into a matrix | |
| id: plan | |
| uses: ./matrix | |
| with: | |
| targets: | | |
| node22-linux-x64 | |
| node22-macos-arm64 | |
| node22-win-x64 | |
| - name: Show expansion | |
| run: | | |
| echo 'matrix=${{ steps.plan.outputs.matrix }}' | |
| matrix-fanout: | |
| name: matrix-fanout / ${{ matrix.entry.target }} | |
| needs: matrix-plan | |
| runs-on: ${{ matrix.entry.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| entry: ${{ fromJson(needs.matrix-plan.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build fixture for ${{ matrix.entry.target }} | |
| id: build | |
| uses: ./ | |
| with: | |
| config: test-fixtures/tiny-app-cjs/package.json | |
| targets: ${{ matrix.entry.target }} | |
| compress: none | |
| checksum: sha256 | |
| filename: '{name}-{version}-{os}-{arch}' | |
| - name: Assert at least one binary | |
| shell: bash | |
| run: | | |
| count=$(echo '${{ steps.build.outputs.binaries }}' | jq 'length') | |
| if [ "$count" -lt 1 ]; then | |
| echo "::error::no binary produced for ${{ matrix.entry.target }}"; exit 1 | |
| fi | |
| # ────────────────────────────────────────────────────────────────────── | |
| # M2 §2.5: multi-target, single-runner. Proves the orchestrator's per- | |
| # target loop against a real pkg invocation that asks for >1 target at | |
| # once. Both linux-x64 and linuxstatic-x64 are native on ubuntu-latest, | |
| # so no cross-compile risks muddy the result. | |
| multi-target-linux: | |
| name: multi-target / ubuntu-latest | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build two linux targets in one invocation | |
| id: build | |
| uses: ./ | |
| with: | |
| config: test-fixtures/tiny-app-cjs/package.json | |
| targets: | | |
| node22-linux-x64 | |
| node22-linuxstatic-x64 | |
| compress: none | |
| checksum: sha256 | |
| filename: '{name}-{version}-{os}-{arch}' | |
| - name: Assert both binaries produced | |
| shell: bash | |
| run: | | |
| count=$(echo '${{ steps.build.outputs.binaries }}' | jq 'length') | |
| if [ "$count" -ne 2 ]; then | |
| echo "::error::expected 2 binaries, got $count" | |
| echo '${{ steps.build.outputs.binaries }}' | jq | |
| exit 1 | |
| fi | |
| # Every binary must exist on disk. | |
| echo '${{ steps.build.outputs.binaries }}' | jq -r '.[]' | while read -r bin; do | |
| [ -f "$bin" ] || { echo "::error::missing binary $bin"; exit 1; } | |
| done | |
| # A sidecar checksum must accompany each artifact. | |
| echo '${{ steps.build.outputs.artifacts }}' | jq -r '.[]' | while read -r art; do | |
| [ -f "$art.sha256" ] || { echo "::error::missing sidecar $art.sha256"; exit 1; } | |
| done | |
| # ────────────────────────────────────────────────────────────────────── | |
| # M3 §3.6: Windows metadata e2e smoke. Builds a win-x64 binary with | |
| # product-name / company-name / file-version set at the top-level, then | |
| # re-parses the output with resedit to verify those strings land in the | |
| # VS_VERSIONINFO block of the emitted PE. This covers the full chain: | |
| # parseInputs → parseWindowsMetadataInputs → orchestrator loop → | |
| # applyWindowsMetadata → NtExecutable.generate. | |
| windows-metadata: | |
| name: windows-metadata / windows-latest | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .node-version | |
| cache: yarn | |
| - run: yarn install --non-interactive | |
| shell: bash | |
| - name: Build tiny-app with windows metadata | |
| id: build | |
| uses: ./ | |
| with: | |
| config: test-fixtures/tiny-app-cjs/package.json | |
| targets: node22-win-x64 | |
| compress: none | |
| checksum: sha256 | |
| filename: '{name}-{version}-{os}-{arch}' | |
| windows-product-name: 'PkgActionTest' | |
| windows-company-name: 'Acme Ltd' | |
| windows-file-version: '1.2.3.4' | |
| windows-file-description: 'pkg-action M3 smoke' | |
| - name: Verify VS_VERSIONINFO round-trips via resedit | |
| shell: bash | |
| run: | | |
| bin=$(echo '${{ steps.build.outputs.binaries }}' | jq -r '.[0]') | |
| [ -f "$bin" ] || { echo "::error::binary missing: $bin"; exit 1; } | |
| echo "Checking $bin" | |
| node --experimental-strip-types .github/scripts/assert-windows-metadata.ts "$bin" | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Real-world smoke: pull @anthropic-ai/claude-code from npm, build a | |
| # native binary per runner OS/arch in SEA mode with Zstd-compressed | |
| # bundled sources, tar.gz archive, execute the binary with --version, | |
| # then verify the archive round-trips and the sha256 sidecar matches | |
| # the archive bytes. | |
| # | |
| # claude-code is ESM, so SEA mode is required (standard pkg can't | |
| # bytecode-compile ESM). Both `sea: true` and `compress: 'Zstd'` are | |
| # declared in the pkg config (package.json#pkg) — expressible in | |
| # config starting @yao-pkg/pkg v6.19.0 (yao-pkg/pkg#263). Each matrix | |
| # entry builds for the runner's native target so the produced binary | |
| # can be launched in-place, giving real cross-OS + cross-arch | |
| # coverage (x64 and arm64 on Linux, arm64 on macOS, x64 on Windows). | |
| claude-code-smoke: | |
| name: claude-code-smoke / ${{ matrix.os }} / ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: node22-linux-x64 | |
| - os: ubuntu-24.04-arm | |
| target: node22-linux-arm64 | |
| - os: macos-latest | |
| target: node22-macos-arm64 | |
| - os: windows-latest | |
| target: node22-win-x64 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .node-version | |
| - name: Prepare claude-code fixture | |
| id: fix | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| fix="${RUNNER_TEMP}/claude-code-fixture" | |
| mkdir -p "$fix" | |
| cd "$fix" | |
| npm init -y >/dev/null | |
| # Pin 2.1.112 — the last pure-JS release of | |
| # @anthropic-ai/claude-code. Starting at 2.1.113 the package | |
| # ships as a thin wrapper around a native binary distributed | |
| # via platform optionalDependencies (bin points to a shell- | |
| # script placeholder like `bin/claude.exe`), which pkg can't | |
| # meaningfully bundle. --ignore-scripts skips the postinstall | |
| # native-binary fetcher — irrelevant for 2.1.112 but a cheap | |
| # guardrail if the pin ever drifts. | |
| npm install --omit=dev --ignore-scripts @anthropic-ai/claude-code@2.1.112 | |
| # Resolve the package's `claude` bin entry. Prefer the named | |
| # 'claude' key so we don't accidentally pick a future per-OS | |
| # alias (e.g. 'claude.exe') that would break pkg on Linux. | |
| entry=$(node -e " | |
| const path = require('path'); | |
| const pkgPath = require.resolve('@anthropic-ai/claude-code/package.json', { paths: [process.cwd()] }); | |
| const pkgDir = path.dirname(pkgPath); | |
| const pkg = require(pkgPath); | |
| const bin = typeof pkg.bin === 'string' | |
| ? pkg.bin | |
| : (pkg.bin && pkg.bin.claude) || Object.values(pkg.bin || {})[0]; | |
| if (!bin) throw new Error('claude bin not found in package.json'); | |
| console.log(path.relative(process.cwd(), path.resolve(pkgDir, bin)).split(path.sep).join('/')); | |
| ") | |
| echo "entry=$entry" | |
| # Trip-wire: if the pin ever drifts to a claude-code version | |
| # that again points `bin.claude` at a shell-script placeholder | |
| # (bin/claude.exe), fail loudly here rather than letting pkg | |
| # silently bundle a non-JS file. | |
| case "$entry" in | |
| *.js|*.mjs|*.cjs) ;; | |
| *) echo "::error::claude bin is not a JS entrypoint ($entry) — pin may have drifted past 2.1.112"; exit 1 ;; | |
| esac | |
| node -e " | |
| const fs = require('fs'); | |
| const p = './package.json'; | |
| const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); | |
| pkg.bin = './' + process.argv[1]; | |
| // Full pkg-layer config lives here — no CLI-mirror inputs. | |
| // Requires @yao-pkg/pkg >= 6.19.0 for \`compress\` in config | |
| // (yao-pkg/pkg#263). | |
| pkg.pkg = { | |
| assets: ['node_modules/@anthropic-ai/claude-code/**/*'], | |
| sea: true, | |
| compress: 'Zstd', | |
| }; | |
| fs.writeFileSync(p, JSON.stringify(pkg, null, 2)); | |
| " "$entry" | |
| cat ./package.json | |
| # Emit drive-letter + forward-slash form (e.g. `D:/a/_temp/…`). | |
| # The downstream composite is Node-based: Node's fs resolves | |
| # `D:/a/…` correctly on Windows but mis-parses cygpath's POSIX | |
| # form (`/d/a/…`) as drive-relative, yielding `D:\d\a\…`. | |
| echo "fixture=${fix//\\//}" >> "$GITHUB_OUTPUT" | |
| - name: Build claude binary | |
| id: build | |
| uses: ./ | |
| with: | |
| config: ${{ steps.fix.outputs.fixture }}/package.json | |
| targets: ${{ matrix.target }} | |
| # 6.19.0+ is required for the `compress` key in pkg config. | |
| # SEA (`sea: true`) and Zstd (`compress: 'Zstd'`) live in the | |
| # package.json#pkg field, not as action inputs. | |
| pkg-version: ~6.19.0 | |
| compress: tar.gz | |
| checksum: sha256 | |
| filename: 'claude-{version}-{os}-{arch}' | |
| - name: Run claude --version | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| bin=$(echo '${{ steps.build.outputs.binaries }}' | jq -r '.[0]') | |
| [ -f "$bin" ] || { echo "::error::binary missing: $bin"; exit 1; } | |
| chmod +x "$bin" || true | |
| # Capture without `set -e` so stderr-carrying, non-zero exits | |
| # still print the output for diagnosis. | |
| out=$("$bin" --version 2>&1) | |
| status=$? | |
| echo "--- claude --version (exit=$status) ---" | |
| echo "$out" | |
| echo "--- end ---" | |
| [ "$status" -eq 0 ] || { echo "::error::claude --version exited $status"; exit 1; } | |
| # claude --version prints a semver-shaped string. Enforce the | |
| # shape to catch silent regressions where the binary boots but | |
| # spits an unrelated banner. | |
| echo "$out" | grep -qE '[0-9]+\.[0-9]+\.[0-9]+' || { | |
| echo "::error::unexpected --version output"; exit 1; | |
| } | |
| - name: Verify tar.gz archive + sha256 sidecar | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # GNU tar in git-bash parses `D:\a\...` / `D:/a/...` as | |
| # host:path (colon-separated remote rsh target). cygpath | |
| # converts to the POSIX form (`/d/a/...`) that GNU tar treats | |
| # as a local absolute path. No-op on Linux/macOS. | |
| to_posix() { | |
| if command -v cygpath >/dev/null 2>&1; then | |
| cygpath -u "$1" | |
| else | |
| echo "$1" | |
| fi | |
| } | |
| archive=$(to_posix "$(echo '${{ steps.build.outputs.artifacts }}' | jq -r '.[0]')") | |
| [ -f "$archive" ] || { echo "::error::archive missing: $archive"; exit 1; } | |
| case "$archive" in | |
| *.tar.gz) ;; | |
| *) echo "::error::expected .tar.gz archive, got: $archive"; exit 1 ;; | |
| esac | |
| # Sidecar must exist and carry a 64-hex-char sha256 + the archive | |
| # basename. Recompute the digest locally and compare byte-for-byte | |
| # against the sidecar so we catch any post-build tampering. | |
| sidecar="${archive}.sha256" | |
| [ -f "$sidecar" ] || { echo "::error::missing checksum sidecar: $sidecar"; exit 1; } | |
| recorded=$(awk 'NR==1 { print $1 }' "$sidecar") | |
| [ "${#recorded}" -eq 64 ] || { echo "::error::malformed sha256 in sidecar: $recorded"; exit 1; } | |
| # sha256sum on git-bash prefixes the whole line with a literal | |
| # backslash when the filename contains backslashes (Windows | |
| # paths), per GNU coreutils' name-escape convention — strip it | |
| # before comparing against the sidecar's clean hex digest. | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| actual=$(sha256sum "$archive" | sed 's/^\\//' | awk '{print $1}') | |
| else | |
| actual=$(shasum -a 256 "$archive" | awk '{print $1}') | |
| fi | |
| if [ "$recorded" != "$actual" ]; then | |
| echo "::error::sha256 mismatch — sidecar=$recorded actual=$actual"; exit 1 | |
| fi | |
| echo "sidecar sha256 OK: $recorded" | |
| # Tar round-trip: extract into a scratch dir, confirm the binary | |
| # lands inside, then run --version on the extracted copy so we | |
| # know the archive itself — not just the pre-archive binary — is | |
| # usable. | |
| extract=$(to_posix "${RUNNER_TEMP}/claude-extract") | |
| rm -rf "$extract" | |
| mkdir -p "$extract" | |
| tar -tzf "$archive" >/dev/null | |
| tar -xzf "$archive" -C "$extract" | |
| bin_basename=$(basename "$(echo '${{ steps.build.outputs.binaries }}' | jq -r '.[0]')") | |
| extracted_bin=$(find "$extract" -type f -name "$bin_basename" | head -n 1) | |
| [ -n "$extracted_bin" ] || { echo "::error::extracted tar missing $bin_basename"; exit 1; } | |
| chmod +x "$extracted_bin" || true | |
| # Temporarily suspend `set -e` so non-zero exits still print | |
| # the captured output for diagnosis. | |
| set +e | |
| out=$("$extracted_bin" --version 2>&1) | |
| status=$? | |
| set -e | |
| echo "--- extracted $extracted_bin --version (exit=$status) ---" | |
| echo "$out" | |
| echo "--- end ---" | |
| [ "$status" -eq 0 ] || { echo "::error::extracted binary exited $status"; exit 1; } | |
| echo "$out" | grep -qE '[0-9]+\.[0-9]+\.[0-9]+' || { | |
| echo "::error::extracted binary produced unexpected output"; exit 1; | |
| } |