chore(changeset): track apply, archive and view fixes for 1.14.0 (#2018) #4712
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: 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' | |
| # The Nix build runs `openspec completion generate`, so a change to | |
| # the generator can break packaging without touching flake.nix. | |
| - 'src/commands/completion.ts' | |
| - 'src/core/completions/**' | |
| 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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 | |
| - 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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 | |
| - 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@3138316df39ed29be04236d7ffc686fa525866aa # v23 | |
| - name: Setup Nix cache | |
| uses: DeterminateSystems/magic-nix-cache-action@84c0677f58dcedf3b91f8223ce36a9ea5b3c84b7 # v15 | |
| with: | |
| # Dependabot runs cannot access the FlakeHub credentials available to | |
| # regular CI, so keep those runs on the GitHub Actions cache. | |
| use-flakehub: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && 'disabled' || 'no-preference' }} | |
| use-gha-cache: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && 'enabled' || 'no-preference' }} | |
| # 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: Test downstream overlay composition | |
| run: | | |
| # Interpolation belongs to Nix, not the shell. | |
| # shellcheck disable=SC2016 | |
| nix eval --impure --expr ' | |
| let | |
| flake = builtins.getFlake (toString ./.); | |
| system = builtins.currentSystem; | |
| pkgs = import flake.inputs.nixpkgs { | |
| inherit system; | |
| overlays = [ flake.overlays.default ]; | |
| }; | |
| composed = import flake.inputs.nixpkgs { | |
| inherit system; | |
| overlays = [ | |
| flake.overlays.default | |
| (_final: prev: { | |
| nodejs_22 = prev.nodejs_22.overrideAttrs (_: { | |
| pname = "openspec-test-nodejs"; | |
| }); | |
| }) | |
| ]; | |
| }; | |
| overridden = pkgs.openspec.overrideAttrs (_: { version = "0.0.0-test"; }); | |
| in | |
| assert pkgs.openspec.drvPath == flake.packages.${system}.default.drvPath; | |
| assert pkgs.openspec.drvPath == flake.packages.${system}.openspec.drvPath; | |
| # stdenv selects the dev output of multi-output native build inputs. | |
| assert builtins.any (input: input.drvPath == composed.nodejs_22.drvPath) | |
| composed.openspec.nativeBuildInputs; | |
| assert composed.openspec.drvPath != pkgs.openspec.drvPath; | |
| assert overridden.version == "0.0.0-test"; | |
| assert overridden.pnpmDeps.version == "0.0.0-test"; | |
| 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 | |
| for completion in \ | |
| "share/bash-completion/completions/openspec.bash" \ | |
| "share/fish/vendor_completions.d/openspec.fish" \ | |
| "share/zsh/site-functions/_openspec"; do | |
| if [ ! -s "result/$completion" ]; then | |
| echo "Error: completion script missing or empty: $completion" | |
| exit 1 | |
| fi | |
| done | |
| if [ "$(head -1 result/share/zsh/site-functions/_openspec)" != "#compdef openspec" ]; then | |
| echo "Error: zsh completion is not autoloadable (missing #compdef header)" | |
| 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" | |
| # Run-unique delimiter: the value is a list of PR-authored paths, so a | |
| # fixed "EOF" would let a crafted path close the block early and append | |
| # its own key=value outputs. | |
| delim="EOF_$(openssl rand -hex 16)" | |
| { | |
| echo "files<<$delim" | |
| echo "$changed_changesets" | |
| echo "$delim" | |
| } >> "$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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 | |
| - 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!" |