mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-09-14 20:16:53 +08:00
63666c8bb2
* ci: report the correct pnpmDeps hash when flake.nix is stale Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: scope the reported hash to the pnpmDeps block Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(flake): scope every hash rewrite to the pnpmDeps block alfred-openspec on #1817: the workflow read is scoped now, but the script it runs is not. update-flake.sh read CURRENT_HASH from the first hash assignment anywhere in flake.nix, and all three in-place rewrites matched every hash assignment. flake.nix holds one fixed-output derivation today, so that lands on the right line by luck; add a second and the script stamps the placeholder over both, reads back whichever mismatch Nix reported first, and writes pnpmDeps' hash into the other derivation. Scoping only the workflow left that path fragile, as the review says. The address range is declared once as PNPM_DEPS_BLOCK and used by the read and all three rewrites, so the scoping cannot drift between call sites. Also guards the read: an unmatched block previously left CURRENT_HASH empty, and the failure path would then restore hash = "". It now exits before touching the file. Verified against a three-derivation fixture with pnpmDeps in the middle, which catches both shapes of the bug: the scoped read returns the pnpmDeps hash while an unscoped read returns the first derivation's, the placeholder is written once rather than three times, and the neighbouring hashes survive the restore. That fixture is the new test, alongside a static check that no hash read or rewrite in the script is missing the range. Verified the static check fails when any one call site is unscoped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(flake): run the scoping fixture on its own volume The new test failed on windows-pwsh with 'sed: cannot rename ./sedKaAflu: Invalid cross-device link'. sed -i writes its temp file in the working directory and renames it over the target; on a GitHub Windows runner the repo is on D: and os.tmpdir() is on C:, so that rename crosses volumes. bash now runs with cwd set to the fixture directory and addresses the file by name, which keeps the temp file and its rename on one volume. The assertions are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
336 lines
11 KiB
YAML
336 lines
11 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
merge_group:
|
|
branches: [main]
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Detect which files changed to enable path-based filtering
|
|
changes:
|
|
name: Detect changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
nix: ${{ steps.filter.outputs.nix }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Check for Nix-related changes
|
|
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
|
|
id: filter
|
|
with:
|
|
filters: |
|
|
nix:
|
|
- 'flake.nix'
|
|
- 'flake.lock'
|
|
- 'package.json'
|
|
- 'pnpm-lock.yaml'
|
|
- 'pnpm-workspace.yaml'
|
|
- 'scripts/update-flake.sh'
|
|
- '.github/workflows/ci.yml'
|
|
|
|
test_matrix:
|
|
name: Test (${{ matrix.label }})
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 15
|
|
if: github.event_name == 'pull_request' || github.event_name == 'merge_group' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
shell: bash
|
|
label: linux-bash
|
|
vitest_workers: 4
|
|
- os: macos-latest
|
|
shell: bash
|
|
label: macos-bash
|
|
vitest_workers: 4
|
|
- os: windows-latest
|
|
shell: pwsh
|
|
label: windows-pwsh
|
|
vitest_workers: 2
|
|
|
|
defaults:
|
|
run:
|
|
shell: ${{ matrix.shell }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: '20.19.0'
|
|
cache: 'pnpm'
|
|
|
|
- name: Print environment diagnostics
|
|
run: |
|
|
node -p "JSON.stringify({ platform: process.platform, arch: process.arch, shell: process.env.SHELL || process.env.ComSpec || '' })"
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build project
|
|
run: pnpm run build
|
|
|
|
- name: Run tests
|
|
env:
|
|
VITEST_MAX_WORKERS: ${{ matrix.vitest_workers }}
|
|
run: pnpm test
|
|
|
|
- name: Upload test coverage
|
|
if: matrix.os == 'ubuntu-latest'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: coverage-report-${{ github.event_name }}
|
|
path: coverage/
|
|
retention-days: 7
|
|
|
|
test_pr_required:
|
|
name: Test
|
|
runs-on: ubuntu-latest
|
|
needs: [test_matrix]
|
|
if: always() && (github.event_name == 'pull_request' || github.event_name == 'merge_group')
|
|
steps:
|
|
- name: Verify matrix tests passed
|
|
run: |
|
|
if [[ "${{ needs.test_matrix.result }}" != "success" ]]; then
|
|
echo "Matrix test job failed"
|
|
exit 1
|
|
fi
|
|
echo "All matrix tests passed!"
|
|
|
|
lint:
|
|
name: Lint & Type Check
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: '20.19.0'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build project
|
|
run: pnpm run build
|
|
|
|
- name: Type check
|
|
run: pnpm exec tsc --noEmit
|
|
|
|
- name: Lint
|
|
run: pnpm lint
|
|
|
|
- name: Check for build artifacts
|
|
run: |
|
|
if [ ! -d "dist" ]; then
|
|
echo "Error: dist directory not found after build"
|
|
exit 1
|
|
fi
|
|
if [ ! -f "dist/cli/index.js" ]; then
|
|
echo "Error: CLI entry point not found"
|
|
exit 1
|
|
fi
|
|
|
|
nix-flake-validate:
|
|
name: Nix Flake Validation
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: changes
|
|
if: needs.changes.outputs.nix == 'true'
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Nix
|
|
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
|
|
|
|
- name: Setup Nix cache
|
|
uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # v14
|
|
|
|
# Run the update script before `nix build`, not after. The script recomputes
|
|
# the pnpmDeps hash from pnpm-lock.yaml and rewrites flake.nix in place, so a
|
|
# stale hash is reported here as the exact value to paste. Built first, the
|
|
# same staleness surfaces as pnpm's ERR_PNPM_NO_OFFLINE_TARBALL — which names
|
|
# a missing tarball, not the hash — and the script never runs to say otherwise.
|
|
# Every root lockfile change needs this value, and Dependabot cannot produce it.
|
|
- name: Verify pnpmDeps hash matches the lockfile
|
|
run: |
|
|
bash scripts/update-flake.sh
|
|
if git diff --quiet flake.nix; then
|
|
echo "✅ flake.nix pnpmDeps hash is up to date"
|
|
exit 0
|
|
fi
|
|
# Scoped to the pnpmDeps block: a bare first-match would report some other
|
|
# FOD's hash if one is ever added above it.
|
|
HASH=$(sed -n '/pnpmDeps = /,/};/p' flake.nix \
|
|
| sed -nE 's/.*hash = "(sha256-[^"]+)".*/\1/p' | head -1)
|
|
git diff flake.nix
|
|
echo "::error file=flake.nix::Stale pnpmDeps hash. Set pnpmDeps.hash to $HASH and push."
|
|
exit 1
|
|
|
|
- name: Restore flake.nix
|
|
if: always()
|
|
run: git checkout -- flake.nix || true
|
|
|
|
- name: Build with Nix
|
|
run: nix build
|
|
|
|
- name: Verify build output
|
|
run: |
|
|
if [ ! -e "result" ]; then
|
|
echo "Error: Nix build output 'result' symlink not found"
|
|
exit 1
|
|
fi
|
|
if [ ! -f "result/bin/openspec" ]; then
|
|
echo "Error: openspec binary not found in build output"
|
|
exit 1
|
|
fi
|
|
echo "✅ Build output verified"
|
|
|
|
- name: Test binary execution
|
|
run: |
|
|
VERSION=$(nix run . -- --version)
|
|
echo "OpenSpec version: $VERSION"
|
|
if [ -z "$VERSION" ]; then
|
|
echo "Error: Version command returned empty output"
|
|
exit 1
|
|
fi
|
|
echo "✅ Binary execution successful"
|
|
|
|
validate-changesets:
|
|
name: Validate Release Tracking
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Determine release tracking
|
|
id: changed-changesets
|
|
run: |
|
|
changed_changesets="$(git diff --name-only --diff-filter=ACMRT origin/main...HEAD -- '.changeset/*.md' ':!.changeset/README.md')"
|
|
if [[ -n "$changed_changesets" ]]; then
|
|
echo "has_changesets=true" >> "$GITHUB_OUTPUT"
|
|
{
|
|
echo "files<<EOF"
|
|
echo "$changed_changesets"
|
|
echo "EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_changesets=false" >> "$GITHUB_OUTPUT"
|
|
echo "This PR follows the normal release cadence; continuing with standard validation"
|
|
fi
|
|
|
|
- name: Setup pnpm
|
|
if: steps.changed-changesets.outputs.has_changesets == 'true'
|
|
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
|
|
- name: Setup Node.js
|
|
if: steps.changed-changesets.outputs.has_changesets == 'true'
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: '24'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
if: steps.changed-changesets.outputs.has_changesets == 'true'
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Validate release-tracked changesets
|
|
if: steps.changed-changesets.outputs.has_changesets == 'true'
|
|
env:
|
|
CHANGESET_FILES: ${{ steps.changed-changesets.outputs.files }}
|
|
run: |
|
|
echo "Validating changed changesets:"
|
|
printf '%s\n' "$CHANGESET_FILES"
|
|
pnpm exec changeset status --since=origin/main
|
|
|
|
required-checks-pr:
|
|
name: All checks passed
|
|
runs-on: ubuntu-latest
|
|
needs: [test_matrix, lint, nix-flake-validate]
|
|
if: always() && (github.event_name == 'pull_request' || github.event_name == 'merge_group')
|
|
steps:
|
|
- name: Verify all checks passed
|
|
run: |
|
|
if [[ "${{ needs.test_matrix.result }}" != "success" ]]; then
|
|
echo "Matrix test job failed"
|
|
exit 1
|
|
fi
|
|
if [[ "${{ needs.lint.result }}" != "success" ]]; then
|
|
echo "Lint job failed"
|
|
exit 1
|
|
fi
|
|
# Nix validation may be skipped if no Nix-related files changed
|
|
if [[ "${{ needs.nix-flake-validate.result }}" != "success" && "${{ needs.nix-flake-validate.result }}" != "skipped" ]]; then
|
|
echo "Nix flake validation job failed"
|
|
exit 1
|
|
fi
|
|
if [[ "${{ needs.nix-flake-validate.result }}" == "skipped" ]]; then
|
|
echo "Nix flake validation skipped (no Nix-related changes)"
|
|
fi
|
|
echo "All required checks passed!"
|
|
|
|
required-checks-main:
|
|
name: All checks passed
|
|
runs-on: ubuntu-latest
|
|
needs: [test_matrix, lint, nix-flake-validate]
|
|
if: always() && github.event_name == 'push'
|
|
steps:
|
|
- name: Verify all checks passed
|
|
run: |
|
|
if [[ "${{ needs.test_matrix.result }}" != "success" ]]; then
|
|
echo "Matrix test job failed"
|
|
exit 1
|
|
fi
|
|
if [[ "${{ needs.lint.result }}" != "success" ]]; then
|
|
echo "Lint job failed"
|
|
exit 1
|
|
fi
|
|
# Nix validation may be skipped if no Nix-related files changed
|
|
if [[ "${{ needs.nix-flake-validate.result }}" != "success" && "${{ needs.nix-flake-validate.result }}" != "skipped" ]]; then
|
|
echo "Nix flake validation job failed"
|
|
exit 1
|
|
fi
|
|
if [[ "${{ needs.nix-flake-validate.result }}" == "skipped" ]]; then
|
|
echo "Nix flake validation skipped (no Nix-related changes)"
|
|
fi
|
|
echo "All required checks passed!"
|