diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e63ff709..3301faf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,9 @@ jobs: type-firewall: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' @@ -49,20 +49,20 @@ jobs: matrix: node: [22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run unit + integration tests run: docker compose -f docker-compose.test.yml run --rm test-node${{ matrix.node }} test-bun: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run Bun integration tests run: docker compose -f docker-compose.test.yml run --rm test-bun test-deno: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run Deno integration tests run: docker compose -f docker-compose.test.yml run --rm test-deno diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 32027f74..156c54c8 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -16,7 +16,7 @@ jobs: name: Check broken links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Lychee link check uses: lycheeverse/lychee-action@v2 diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index dbf09d72..d0da9bbb 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -12,10 +12,10 @@ jobs: preflight: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node 22 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "22" cache: npm @@ -59,7 +59,7 @@ jobs: echo "pre=$PRE" >> "$GITHUB_OUTPUT" - name: PR comment (release preview) - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const body = [ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2eb0a28c..c68999e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,12 +21,12 @@ jobs: is_prerelease: ${{ steps.meta.outputs.is_prerelease }} npm_dist_tag: ${{ steps.meta.outputs.npm_dist_tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.ref_name }} - name: Setup Node 22 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "22" cache: npm @@ -135,12 +135,12 @@ jobs: package_name: ${{ steps.pkg.outputs.name }} version: ${{ steps.pkg.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ needs.verify.outputs.tag }} - name: Setup Node 22 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "22" cache: npm @@ -158,7 +158,23 @@ jobs: echo "name=$(node -p "require('./package.json').name")" >> "$GITHUB_OUTPUT" echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" + - name: Skip if npm version already exists + id: npm_exists + shell: bash + run: | + set -euo pipefail + PACKAGE="${{ steps.pkg.outputs.name }}" + VERSION="${{ steps.pkg.outputs.version }}" + + if npm view "${PACKAGE}@${VERSION}" version >/dev/null 2>&1; then + echo "::warning::npm version ${PACKAGE}@${VERSION} already exists; skipping immutable publish." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - name: Publish npm (retry x3) + if: ${{ steps.npm_exists.outputs.skip != 'true' }} uses: ./.github/actions/retry with: attempts: "3" @@ -175,12 +191,12 @@ jobs: environment: jsr continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ needs.verify.outputs.tag }} - name: Setup Node 22 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "22" cache: npm @@ -188,7 +204,46 @@ jobs: - name: Install run: npm ci + - name: Read JSR metadata + id: jsr_pkg + shell: bash + run: | + set -euo pipefail + NAME=$(node -p "require('./jsr.json').name") + VERSION=$(node -p "require('./jsr.json').version") + NPM_NAME=$(node - <<'NODE' + const name = require('./jsr.json').name; + if (!name.startsWith('@')) { + throw new Error(`Unsupported unscoped JSR package name: ${name}`); + } + const [scope, pkg] = name.slice(1).split('/'); + if (!scope || !pkg) { + throw new Error(`Unsupported JSR package name: ${name}`); + } + process.stdout.write(`@jsr/${scope}__${pkg}`); + NODE + ) + echo "name=$NAME" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "npm_name=$NPM_NAME" >> "$GITHUB_OUTPUT" + + - name: Skip if JSR version already exists + id: jsr_exists + shell: bash + run: | + set -euo pipefail + PACKAGE="${{ steps.jsr_pkg.outputs.npm_name }}" + VERSION="${{ steps.jsr_pkg.outputs.version }}" + + if npm view "${PACKAGE}@${VERSION}" version --registry=https://npm.jsr.io >/dev/null 2>&1; then + echo "::warning::JSR version ${{ steps.jsr_pkg.outputs.name }}@${VERSION} already exists; skipping immutable publish." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - name: Publish JSR (retry x3) + if: ${{ steps.jsr_exists.outputs.skip != 'true' }} uses: ./.github/actions/retry with: attempts: "3" @@ -220,13 +275,24 @@ jobs: EOF - name: Create / update release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.verify.outputs.tag }} - prerelease: ${{ needs.verify.outputs.is_prerelease == 'true' }} - generate_release_notes: true - append_body: true - body_path: RELEASE_SUMMARY.md + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.verify.outputs.tag }} + IS_PRERELEASE: ${{ needs.verify.outputs.is_prerelease }} + shell: bash + run: | + set -euo pipefail + + if gh release view "$TAG" >/dev/null 2>&1; then + echo "::warning::GitHub Release $TAG already exists; skipping immutable release update." + else + SUMMARY=$(cat RELEASE_SUMMARY.md) + args=(release create "$TAG" --generate-notes --notes "$SUMMARY") + if [[ "$IS_PRERELEASE" == "true" ]]; then + args+=(--prerelease) + fi + gh "${args[@]}" + fi - name: Fail if both registries failed if: ${{ needs.publish_npm.result != 'success' && needs.publish_jsr.result != 'success' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ae6ff950..2a841c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- **GitHub Actions runtime refresh** — Workflow actions now use Node-24-capable majors (`actions/checkout@v6`, `actions/setup-node@v6`, `actions/github-script@v8`), while the repo jobs themselves continue to run on Node 22. The release workflow now treats GitHub Releases and registry versions as immutable: if a tagged version already exists, it emits a warning and skips the repeated publish/update instead of mutating the existing release or retrying a republish. + ## [14.1.0] — 2026-03-14 ### Added diff --git a/ROADMAP.md b/ROADMAP.md index fa5bf34c..3e6618ca 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,7 +1,7 @@ # ROADMAP — @git-stunts/git-warp -> **Current version:** v14.0.0 -> **Last reconciled:** 2026-03-14 (main during v14.1.0 release prep; 26 active standalone items remain after trust/serve hardening, type-surface cleanup, large-graph traversal work, test-infra extraction, the constructor-default lint cleanup, checkpoint content-anchor batching, tree-construction determinism fuzzing, CI gate dedupe, the explicit type-only export manifest split, the merged Markdown code-sample lint gate, the pre-push gate regression harness, the missing-content blob error hardening, and the issue-45 content metadata API. The GitHub issue queue is now empty, so remaining tracked work resumes at B88, with a new benchmark slice queued for native-vs-WASM roaring evaluation.) +> **Current version:** v14.1.0 +> **Last reconciled:** 2026-03-15 (main after the v14.1.0 release; 26 active standalone items remain after trust/serve hardening, type-surface cleanup, large-graph traversal work, test-infra extraction, the constructor-default lint cleanup, checkpoint content-anchor batching, tree-construction determinism fuzzing, CI gate dedupe, the explicit type-only export manifest split, the merged Markdown code-sample lint gate, the pre-push gate regression harness, the missing-content blob error hardening, and the issue-45 content metadata API. The GitHub issue queue is now empty, so remaining tracked work resumes at B88, with a new benchmark slice queued for native-vs-WASM roaring evaluation.) > **Completed milestones:** [docs/ROADMAP/COMPLETED.md](docs/ROADMAP/COMPLETED.md) ---