Skip to content

swift: wire SPM mirror publish (Phase A)#1448

Merged
kixelated merged 6 commits into
mainfrom
claude/swift-publish-phase-a
May 23, 2026
Merged

swift: wire SPM mirror publish (Phase A)#1448
kixelated merged 6 commits into
mainfrom
claude/swift-publish-phase-a

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

Summary

Phase A of getting Swift Package Manager publishing working against the new moq-dev/swift mirror. Three structural bugs in the existing pipeline plus the publish.sh stub gets a real implementation.

What was broken

  1. MoqFFI.xcframework.zip was never uploaded to a GitHub release. .release-plz.toml has git_release_enable = false, so release-plz creates the tag but no release. release-swift.yml built the xcframework but only kept it as a workflow artifact. Result: the mirror's Package.swift binaryTarget URL would have 404'd for every SPM consumer.
  2. Mirror tag format was SPM-incompatible. The recipe tagged with moq-ffi-v${VERSION}. SPM only recognizes bare semver (X.Y.Z or vX.Y.Z) — anything prefixed is invisible.
  3. References pointed at a placeholder repo name (moq-dev/moq-swift) instead of the actual mirror (moq-dev/swift).

What this PR does

  • New release job in release-swift.yml that runs .github/scripts/release.sh create after the package job and attaches MoqFFI.xcframework.zip + the staged Swift package tarball to the moq-ffi-v* release. Same pattern libmoq.yml and moq-relay.yml already use.
  • publish.sh rewritten with the hardened recipe:
    • Bare-semver tag on the mirror (e.g. 0.2.11) while the monorepo keeps moq-ffi-v0.2.11.
    • Default git identity (GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL overridable).
    • --dry-run flag that uses an anonymous clone, stages the diff, and skips commit/push. Lets us exercise the pipeline before flipping PUBLISH_SPM.
    • Token filtered out of clone stdout/stderr.
    • Idempotency check: skips if the mirror tag already exists.
    • Explicit push to refs/heads/main so the very first publish lands under the expected branch name regardless of runner config.
    • Pushes only the new tag, not --tags.
  • package.sh now stages LICENSE-MIT, LICENSE-APACHE, and a minimal consumer-facing README.md into the mirror tarball, so the published repo is properly licensed and a visitor sees an orientation page.
  • Package.swift and swift/README.md comments / docs updated to reference moq-dev/swift.

Safe to merge as-is

The publish job is still gated on vars.PUBLISH_SPM == 'true'. Merging this PR does not push anything to the mirror. It does start attaching the xcframework to the GH release on the next moq-ffi-v* tag, which is necessary regardless of whether SPM publishing is enabled (it's the same artifact a manual xcframework consumer would download).

Phase B (next, separate PR)

After the first post-merge moq-ffi-v* tag attaches the asset to a release, run ./swift/scripts/publish.sh --dry-run against the downloaded tarball to verify the recipe works end-to-end. Once green:

  1. Provision a fine-grained PAT or GitHub App with contents: write on moq-dev/swift.
  2. Add SWIFT_MIRROR_TOKEN secret and PUBLISH_SPM=true variable in moq-dev/moq settings.
  3. Cut a patch tag and watch the publish job push the mirror.

Test plan

  • bash -n syntax check on both modified scripts
  • YAML parse check on release-swift.yml
  • On next moq-ffi-v* tag: verify MoqFFI.xcframework.zip lands as a release asset and the URL resolves
  • Dry-run publish.sh --dry-run against the downloaded tarball, verify it clones the mirror, stages the right tree, and prints a sensible diff
  • After Phase B prep, cut a tag with PUBLISH_SPM=true and verify a tiny consumer Package.swift can swift package resolve against moq-dev/swift

🤖 Generated with Claude Code

Make moq-ffi-v* tags produce a fully consumable Swift Package Manager
release. Three structural fixes plus the publish.sh stub gets wired up:

1. Attach MoqFFI.xcframework.zip to the GitHub release. release-plz has
   `git_release_enable = false`, so nothing was creating the GH release
   or uploading the xcframework. Added a `release` job in
   release-swift.yml that runs `.github/scripts/release.sh create` once
   the package job finishes. Without this, the mirror's Package.swift
   binaryTarget URL would 404 for every SPM consumer.

2. Tag the mirror with bare semver. The original recipe tagged with
   `moq-ffi-v${VERSION}`, which SPM does not recognize as a version.
   publish.sh now tags `${VERSION}` (e.g. 0.2.11) on the mirror while
   the source monorepo keeps its prefixed tag.

3. Point everything at moq-dev/swift (the newly-created mirror), not
   the placeholder moq-dev/moq-swift name the stub was using.

publish.sh additionally:
- Sets a default git identity (overridable via GIT_AUTHOR_NAME/EMAIL).
- Adds --dry-run so the pipeline can be exercised before flipping
  PUBLISH_SPM=true. Dry-run uses an anonymous clone, stages the diff,
  prints the file list, and exits before any push.
- Filters SWIFT_MIRROR_TOKEN out of git clone stdout/stderr.
- Skips cleanly if the mirror tag already exists (re-run safety).
- Pushes to refs/heads/main explicitly so the very first publish lands
  the branch under the expected name regardless of runner default.
- Pushes only the new tag rather than --tags.

package.sh now stages LICENSE-MIT, LICENSE-APACHE, and a minimal
consumer-facing README.md into the mirror tarball so the published
package is properly licensed and a visitor to moq-dev/swift sees an
orientation page rather than a bare manifest.

The `publish` workflow job is still gated on `vars.PUBLISH_SPM`, so
this PR is safe to land without flipping the variable. Once it merges,
the next moq-ffi-v* tag will start attaching the xcframework to the
GH release. After that, a dry-run via `./swift/scripts/publish.sh
--dry-run` against a downloaded artifact confirms the recipe works
end-to-end, and then PUBLISH_SPM + SWIFT_MIRROR_TOKEN can be enabled.

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

coderabbitai Bot commented May 23, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d78262a6-42b3-4cc4-bce9-09308b3dc61b

📥 Commits

Reviewing files that changed from the base of the PR and between 2328089 and 5e45418.

📒 Files selected for processing (1)
  • swift/scripts/publish.sh

Walkthrough

This pull request adds a release-and-publish workflow for Swift SPM artifacts (new release job, runtime moq-bot token for publishing, and updated publish job), enriches Swift package staging with licenses and a README, implements swift/scripts/publish.sh to mirror staged packages into moq-dev/moq-swift under bare-semver tags (with dry-run support and idempotent tagging), updates Swift README publishing docs, and expands site documentation with new Python, Kotlin, and Swift library pages plus a Libraries navigation grouping.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: wiring SPM mirror publishing in Phase A. It directly reflects the core change (enabling Swift Package Manager publishing to the mirror).
Description check ✅ Passed The description comprehensively explains what was broken, what the PR fixes, structural changes made, safety considerations, and next steps. It is clearly related to the changeset across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/swift-publish-phase-a
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/swift-publish-phase-a

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-swift.yml:
- Around line 162-171: Pin mutable Actions to specific SHAs and harden the
checkout step in the release job: replace uses: actions/checkout@v6 with a
pinned SHA (e.g., actions/checkout@<commit-sha>) and uses:
actions/download-artifact@v8 with its pinned SHA, and add persist-credentials:
false to the actions/checkout step in the release job (which runs with contents:
write) to avoid exposing the token; update any related step IDs or names (e.g.,
"Find previous tag" / prev_tag, "Download package") accordingly so the job
continues to reference the same steps.

In `@swift/README.md`:
- Line 65: Replace the incorrect workflow job name "publish-spm" in the README
sentence with the actual workflow identifier and/or display name used in the
repo (use "publish" and/or "Publish to Swift Package mirror") so the runbook
references the real job; update the text that currently reads "The `publish-spm`
job runs `publish.sh`..." to instead reference `publish` (or the human-friendly
"Publish to Swift Package mirror") to avoid confusion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c905fa4-c0b0-4dc8-a339-e195e7233b2d

📥 Commits

Reviewing files that changed from the base of the PR and between de7cc60 and 18ca561.

📒 Files selected for processing (5)
  • .github/workflows/release-swift.yml
  • swift/Package.swift
  • swift/README.md
  • swift/scripts/package.sh
  • swift/scripts/publish.sh

Comment thread .github/workflows/release-swift.yml
Comment thread swift/README.md Outdated
kixelated and others added 3 commits May 22, 2026 19:40
- release-swift.yml: set persist-credentials: false on the release
  job's checkout. The job has contents: write, but no git operation
  here uses the stored credential (the gh CLI reads GH_TOKEN env
  directly), so the token has no business sitting in .git/config.
- swift/README.md: the job is named 'publish' / 'Publish to Swift
  Package mirror', not 'publish-spm'. Fix the runbook reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three changes carried in together since they're all part of the same
"get SPM publishing usable" stream:

1. Workflow: swap secrets.SWIFT_MIRROR_TOKEN for a moq-bot App token.
   actions/create-github-app-token@v3 mints a fresh contents:write
   token scoped to moq-dev/moq-swift on every run, expires in an hour,
   never at rest. Reuses APP_ID / APP_PRIVATE_KEY already in place for
   release-rs.yml. No new secret to provision. The publish step also
   sets GIT_AUTHOR_NAME/EMAIL to moq-bot so the mirror's commit log
   reflects who pushed.

2. Mirror rename: moq-dev/swift -> moq-dev/moq-swift. The new prefix
   leaves room for moq-dev/web-transport-swift later without colliding
   in the org namespace. Updated all references in Package.swift,
   README, scripts, and the workflow.

3. Docs: added a top-level "Libraries" nav dropdown grouping Rust,
   TypeScript, Python, Swift, and Kotlin. /rs/ and /js/ stay where
   they are (no URL breakage); /py/, /swift/, /kt/ are new sections,
   each with a landing page and a per-package doc covering install,
   quickstart for subscribe/publish, cancellation semantics, and links
   to source / package registry / Rust counterpart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With moq-bot wired up, there's no PAT to provision and no reason to
keep the publish step gated. Every moq-ffi-v* tag now publishes to the
mirror unconditionally. Dropped the `if: vars.PUBLISH_SPM == 'true'`
on the publish job and the matching steps in the README runbook.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-swift.yml:
- Around line 200-207: The GitHub App token step "Generate moq-bot token" (uses:
actions/create-github-app-token@v3, id: token) should be constrained to least
privilege by adding the permission input for repository contents; update that
step to include permission-contents: write so the generated token only has write
access to repository contents rather than inheriting the app installation’s full
permission set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f155c8b-355c-45e9-9d60-789221359218

📥 Commits

Reviewing files that changed from the base of the PR and between 493c9e2 and 866edc2.

📒 Files selected for processing (11)
  • .github/workflows/release-swift.yml
  • doc/.vitepress/config.ts
  • doc/kt/index.md
  • doc/kt/moq.md
  • doc/py/index.md
  • doc/py/moq-net.md
  • doc/swift/index.md
  • doc/swift/moq.md
  • swift/README.md
  • swift/scripts/package.sh
  • swift/scripts/publish.sh
✅ Files skipped from review due to trivial changes (7)
  • doc/kt/index.md
  • doc/py/index.md
  • doc/kt/moq.md
  • doc/py/moq-net.md
  • doc/swift/moq.md
  • doc/swift/index.md
  • swift/README.md

Comment thread .github/workflows/release-swift.yml
CodeRabbit pointed out that without an explicit permission-* input,
actions/create-github-app-token inherits the full App installation
scope. Add permission-contents: write so the minted token only has
the access publish.sh actually uses.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@swift/README.md`:
- Line 60: The README statement about dry-run auth is incorrect:
`swift/scripts/publish.sh` always performs an anonymous clone during dry-run so
exporting `SWIFT_MIRROR_TOKEN` has no effect; update the README text around the
BUILD_VERSION example to explicitly say dry-run uses an anonymous clone
unconditionally and that SWIFT_MIRROR_TOKEN is not used (or alternatively
document that to use authenticated clones you must run the non-dry-run flow),
referencing the `swift/scripts/publish.sh` behavior and the `SWIFT_MIRROR_TOKEN`
variable so readers know the current limitation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2770f3fc-9833-48d6-8e36-743a01776f78

📥 Commits

Reviewing files that changed from the base of the PR and between 866edc2 and 2328089.

📒 Files selected for processing (3)
  • .github/workflows/release-swift.yml
  • swift/README.md
  • swift/scripts/publish.sh

Comment thread swift/README.md
The README said dry-runs against a private mirror work if you export
SWIFT_MIRROR_TOKEN, but publish.sh hardcoded an anonymous clone for
the dry-run path, so that claim was false. Drop the dry-run/non-dry-run
branch and just use the token when it's set (anonymous when not).
The required-token check earlier in the script still gates the
publish path, so non-dry-run behavior is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated merged commit 357bd87 into main May 23, 2026
1 check passed
@kixelated kixelated deleted the claude/swift-publish-phase-a branch May 23, 2026 15:46
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.

1 participant