Skip to content

ci: add JSR publish workflow for @systeminit/swamp-lib#894

Merged
stack72 merged 2 commits intomainfrom
ci/publish-client-jsr
Mar 27, 2026
Merged

ci: add JSR publish workflow for @systeminit/swamp-lib#894
stack72 merged 2 commits intomainfrom
ci/publish-client-jsr

Conversation

@johnrwatson
Copy link
Copy Markdown
Contributor

Summary

Adds a GitHub Actions workflow that publishes the @systeminit/swamp-lib client package to JSR when files in packages/client/ change on merge to main.

  • Uses Deno's OIDC token auth (no secrets needed — JSR trusts GitHub Actions)
  • Auto-generates semver from date + commit count: 0.YYYYMMDD.N
  • Runs type check + tests before publishing
  • Also supports workflow_dispatch for manual publishing

Test plan

  • Workflow triggers on merge with packages/client/ changes
  • Version stamp produces valid semver
  • deno publish succeeds with OIDC auth

🤖 Generated with Claude Code

Publishes packages/client to JSR on merge to main when files in
packages/client/ change. Uses OIDC token auth (no secrets needed).
Version is auto-generated from date + commit count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI Security Review

Critical / High

None.

Medium

  1. .github/workflows/publish-client.yml:32denoland/setup-deno@v2 not pinned to SHA.
    Third-party actions should be pinned to a full commit SHA to prevent supply-chain attacks (tag rewriting, compromised maintainer account). Per the pinning policy, only actions/* (GitHub-owned) actions are acceptable with tag-only pins. Replace with the SHA for the current v2 release, e.g.:
    uses: denoland/setup-deno@<full-sha> # v2

Low

None.

Notes

  • Prompt injection: N/A — no LLM interaction in this workflow.
  • Expression injection: ${{ steps.version.outputs.version }} on line 47 is interpolated into a run: block, but the value is derived entirely from date and git rev-list --count, not from any attacker-controlled input. Safe.
  • Triggers: pull_request (not pull_request_target) with types: [closed] and an if: merged == true guard. Checkout is hardcoded to ref: main, so no PR-head code is checked out. workflow_dispatch has no inputs. Both safe.
  • Permissions: contents: read and id-token: write at workflow level. Since there is only one job, workflow-level scoping is equivalent to job-level. id-token: write is required for JSR's OIDC-based publish authentication — legitimate use.
  • Secrets: No secrets are referenced. Publishing relies entirely on OIDC.
  • Auto-merge: Not applicable — this workflow publishes a package after a PR is already merged.

Verdict

PASS — Clean workflow with one medium supply-chain hardening suggestion (SHA-pin denoland/setup-deno). No critical or high findings.

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Blocking Issues

  1. Shallow clone breaks version generation (.github/workflows/publish-client.yml:28-42): The actions/checkout@v4 step defaults to fetch-depth: 1 (shallow clone). The version generation command git rev-list --count HEAD -- packages/client requires full git history to produce a meaningful, monotonically increasing patch number. With a shallow clone, this will always return 0 or 1, making every publish stamp approximately 0.YYYYMMDD.1 regardless of actual commit history. Fix by adding fetch-depth: 0 to the checkout step:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        ref: main
        fetch-depth: 0

Suggestions

  1. Consider job-level permissions (line 17-18): Permissions are set at the workflow level. With a single job this is functionally equivalent to job-level, but scoping to the job is the more conventional pattern in this repo (see ci.yml) and makes it explicit which job needs id-token: write.

  2. Version monotonicity across days: The scheme 0.YYYYMMDD.N resets the patch component semantically each day (since new-day commits touching packages/client keep incrementing the total count, not a per-day count, this actually works). Worth a brief comment in the workflow explaining how the version is derived so future maintainers don't misread it.

…A, job-level perms

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI Security Review

Critical / High

None.

Medium

None.

Low

  1. .github/workflows/publish-client.yml:50-53 — The ${{ steps.version.outputs.version }} expression is interpolated directly into a deno eval script. While the value is currently safe (composed of date output and git rev-list --count, both strictly numeric), using an environment variable would be more robust against future refactors that might change how the version is computed. Not exploitable as-is.

Verdict

PASS — Clean workflow. Permissions are minimal and job-scoped, id-token: write is justified for JSR OIDC auth, triggers are safe (pull_request with merged gate + workflow_dispatch), checkout is pinned to main (not PR head), third-party actions are SHA-pinned, and no secrets are exposed. No security concerns blocking merge.

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Blocking Issues

None.

Suggestions

  1. Defense-in-depth for version stamping (.github/workflows/publish-client.yml:51-56): The deno eval step interpolates ${VERSION} via shell expansion into a JavaScript string literal. While the current version generation (date + git rev-list --count) guarantees VERSION contains only digits and dots, passing it through an environment variable would be more robust against future changes:

    - name: Stamp version
      working-directory: packages/client
      env:
        VERSION: ${{ steps.version.outputs.version }}
      run: |
        deno eval "
          const config = JSON.parse(await Deno.readTextFile('deno.json'));
          config.version = Deno.env.get('VERSION');
          await Deno.writeTextFile('deno.json', JSON.stringify(config, null, 2) + '\n');
        "
  2. Inconsistent setup-deno pinning: This workflow correctly pins denoland/setup-deno to a commit SHA (667a34c...), but the existing ci.yml and release.yml use tag-only (@v2). The new approach is better — consider aligning the other workflows to SHA pins as well (separate PR).

Overall: well-structured workflow with minimal permissions (job-level contents: read + id-token: write), proper SHA pinning, concurrency control, and the standard merged-PR guard. LGTM.

@stack72 stack72 merged commit 663cadd into main Mar 27, 2026
11 checks passed
@stack72 stack72 deleted the ci/publish-client-jsr branch March 27, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants