Skip to content

fix(release): default to embedded SwiftPM packaging and restore portable local/PR packages#406

Merged
NathanWalker merged 2 commits into
mainfrom
fix/spm-stuff
Jul 21, 2026
Merged

fix(release): default to embedded SwiftPM packaging and restore portable local/PR packages#406
NathanWalker merged 2 commits into
mainfrom
fix/spm-stuff

Conversation

@edusperoni

@edusperoni edusperoni commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

After #405, packages built outside the release pipeline are unusable:

  • Local builds stamped an XCLocalSwiftPackageReference with an absolute path into the developer's checkout (dist/local-spm) — the tgz only works on that machine, at that path, and can't be shared with teammates or an app's CI.
  • PR-workflow artifacts pinned ios-spm at a PR version whose tag is never created — the npm-package artifact could never resolve, and the xcframework zips weren't uploaded either.
  • The README still documented the pre-SPM flow (including npm run build, which doesn't exist), and stamp-template-local-spm.mjs cited SPM_DISTRIBUTION_PLAN.md, which was never committed.
  • npm_release.yml hand-built JSON matrices in bash, looped checksum args, curl | grep'd manifests, and copy-pasted the deps-install block 4×.

Change

Embedded packaging is now the default; the slim deploy shape is an explicit opt-in used only by the release workflow.

  • build_npm_ios.sh / build_npm_vision.sh (and the build_all_* wrappers) take --spm-mode <embedded|remote>:
    • embedded (default): the xcframework zips from build_spm_artifacts.sh are embedded at framework/internal/local-spm with a committed Package.swift template (spm-templates/), referenced by relative path. The tgz is fully self-contained and portable — zipped because npm strips symlinks (the Catalyst slices contain them); SwiftPM extracts local zip binary targets itself.
    • remote: the deploy shape pinning ios-spm at the package version; the release workflow builds with npm run build-ios -- --spm-mode=remote. The CI env heuristic and NS_SPM_LOCAL are removed.
  • PR workflow: artifacts are usable again (ns platform add ios --framework-path=<tgz> works on the downloaded artifact); the SwiftPM artifact zips are uploaded as well.
  • Release workflow cleanup (job graph, gating, and permissions unchanged):
    • version/tag/matrix resolution → scripts/resolve-release.mjs (matrix built with JSON.stringify, tag-vs-package.json assertion preserved; the dispatch input still flows through env to avoid shell interpolation of untrusted input)
    • checksum collection + channel gating → generate-spm-manifest.mjs --checksums-dir --channel (non-next channels still hard-require the visionOS checksums)
    • the stale-manifest assertion → generate-spm-probe.mjs --assert-release-manifest
    • the 4× deps-install block → .github/actions/setup-build-env composite + scripts/install-ci-deps.sh (PR jobs move from Node 20 → 22, matching release builds)
  • All touched scripts take real CLI argumentsutil.parseArgs (strict) in Node, while/case parsers in bash; both --flag value and --flag=value forms, --help everywhere, hard failure on unknown args. No env-flag behavior switching, no heredoc-generated files (the local Package.swift is a committed template).
  • README: distribution section rewritten, new Local Development & Linking section.

Verification

  • ./build_spm_artifacts.sh ios && ./build_npm_ios.sh → extracted tgz has relativePath = "internal/local-spm", both zips + manifest inside, zero absolute paths, __NS_RUNTIME_VERSION__ gone.
  • Renamed a copy of the packaged template (__PROJECT_NAME__TestApp) and ran xcodebuild -resolvePackageDependencies: resolves NativeScriptSDK @ local from the embedded package.
  • --spm-mode=remote → template pins ios-spm exactVersion 9.0.3, no binaries in the tgz. Space/= flag forms, invalid mode, and unknown args all behave.
  • resolve-release.mjs reproduces the old bash logic for all three trigger shapes (dispatch version, tag push incl. mismatch failure, main push → next + iOS-only matrix).
  • generate-spm-manifest.mjs --checksums-dir shapes next manifests iOS-only and fails latest without visionOS checksums; generate-spm-probe.mjs --assert-release-manifest exits non-zero on a 404/stale manifest.
  • All workflow/action YAML parses; bash -n clean on every touched shell script.

Not in scope (flagged separately): the git tag -f/push -f on ios-spm release tags in spm-update.

Summary by CodeRabbit

  • New Features
    • Added embedded vs. remote Swift Package Manager packaging modes for iOS and visionOS.
    • Added local SwiftPM templates that bundle generated XCFramework zip artifacts for embedded mode.
    • Added --spm-mode command-line support (with help) and improved argument forwarding in build scripts.
  • Documentation
    • Updated distribution packaging docs, artifact paths, and local linking instructions for both SwiftPM modes.
  • Bug Fixes
    • Strengthened release/version validation and improved stale-manifest checking during SwiftPM probe creation.
  • Chores
    • Standardized CI build/test setup via a shared reusable setup step.

…ble local/PR packages

The slim SPM-based packages from #405 were unusable outside the release
pipeline: local builds stamped an absolute path into the checkout
(machine-locked tgz) and PR artifacts pinned an ios-spm tag that is never
created. Embedded packaging is now the default everywhere; the slim shape
is an explicit opt-in used only by the release workflow.

- build_npm_ios/vision gain --spm-mode <embedded|remote> (default
  embedded): the xcframework zips from build_spm_artifacts.sh are embedded
  at framework/internal/local-spm with a committed Package.swift template
  (spm-templates/), referenced by relative path — the tgz is
  self-contained and portable (zips because npm strips symlinks; SwiftPM
  extracts local zip binary targets). remote keeps the deploy shape
  pinning ios-spm; the release workflow builds with
  `npm run build-ios -- --spm-mode=remote`. The CI env-var heuristic and
  NS_SPM_LOCAL are gone.
- pull_request.yml artifacts are usable again (embedded default) and the
  SwiftPM artifact zips are uploaded too.
- release workflow cleanup: version/tag/matrix resolution moves to
  scripts/resolve-release.mjs; the checksum-arg loop and curl|grep
  manifest assertion fold into generate-spm-manifest.mjs
  (--checksums-dir, --channel) and generate-spm-probe.mjs
  (--assert-release-manifest); the 4x copy-pasted deps-install block
  becomes the setup-build-env composite action + scripts/install-ci-deps.sh.
- all touched scripts take real CLI arguments (util.parseArgs / while-case
  parsers, both --flag value and --flag=value, --help) instead of env
  flags and hand-rolled argv loops; no heredoc-generated files.
- README documents both modes and local linking via --framework-path
  (and the dangling SPM_DISTRIBUTION_PLAN.md reference is removed).
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3848f79f-b150-483f-afbf-e0c3306dc4b8

📥 Commits

Reviewing files that changed from the base of the PR and between cfd159b and 8379bad.

📒 Files selected for processing (5)
  • .github/workflows/npm_release.yml
  • README.md
  • build_npm_ios.sh
  • build_npm_vision.sh
  • build_utils.sh
🚧 Files skipped from review as they are similar to previous changes (4)
  • README.md
  • build_npm_vision.sh
  • build_npm_ios.sh
  • .github/workflows/npm_release.yml

📝 Walkthrough

Walkthrough

The PR centralizes CI setup, adds release metadata resolution, introduces embedded and remote SwiftPM packaging modes for iOS and visionOS, adds local SwiftPM manifests, and consolidates release manifest generation and verification.

Changes

Release and packaging flow

Layer / File(s) Summary
Shared CI bootstrap
.github/actions/setup-build-env/action.yml, scripts/install-ci-deps.sh, .github/workflows/*
CI jobs use a shared composite action for Node, Python, npm, CMake, and optional test-tool setup, while build jobs publish SwiftPM artifacts.
Release identity resolution
scripts/resolve-release.mjs, scripts/get-next-version.js, scripts/get-npm-tag.js, .github/workflows/npm_release.yml
CLI-enabled scripts resolve the version, npm tag, and iOS/visionOS build matrix for releases.
Mode-based SwiftPM packaging
build_*.sh, build_utils.sh, scripts/stamp-template-*.mjs, spm-templates/*, README.md
Builds accept embedded or remote SwiftPM modes, package local XCFramework artifacts when embedded, and document distribution and local-linking flows.
Manifest generation and verification
scripts/generate-spm-manifest.mjs, scripts/generate-spm-probe.mjs, .github/workflows/npm_release.yml
Manifest generation scans checksum directories and applies channel strictness; probe generation verifies the released ios-spm version pin.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

I’m a rabbit with packages tucked in my den,
SwiftPM hops through embedded paths again.
CI tools gather, releases take flight,
Checksums and probes keep manifests right.
Remote or local—the build wheels spin bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: defaulting PR/local builds to embedded SwiftPM packaging and restoring portable packages.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
scripts/install-ci-deps.sh (3)

33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer npm ci for reliable CI builds.

Using npm ci instead of npm install in CI environments guarantees a clean, reproducible installation strictly matching the package-lock.json and prevents accidental lockfile modifications during the build.

♻️ Proposed refactor
-npm install
+npm ci
🤖 Prompt for 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.

In `@scripts/install-ci-deps.sh` at line 33, Replace the npm install command in
the CI dependency installation step with npm ci so installs remain clean and
strictly follow package-lock.json without modifying it.

46-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Disable Homebrew auto-update to speed up CI.

Running brew install in a CI environment can trigger a lengthy auto-update process. Prepending HOMEBREW_NO_AUTO_UPDATE=1 prevents this delay.

♻️ Proposed refactor
 if [ "$TEST_TOOLS" = "1" ]; then
-  brew install chargepoint/xcparse/xcparse
+  HOMEBREW_NO_AUTO_UPDATE=1 brew install chargepoint/xcparse/xcparse
   npm install -g `@edusperoni/junit-cli-report-viewer` verify-junit-xml
 fi
🤖 Prompt for 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.

In `@scripts/install-ci-deps.sh` around lines 46 - 49, Update the Homebrew
installation command in the TEST_TOOLS block to set HOMEBREW_NO_AUTO_UPDATE=1
for that invocation, while leaving the npm installation unchanged.

36-39: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Disable Homebrew auto-update to speed up CI.

Running brew install in a CI environment can trigger a lengthy auto-update process. Prepending HOMEBREW_NO_AUTO_UPDATE=1 prevents this delay.

♻️ Proposed refactor
 # Ensure CMake is available without conflicting with pinned Homebrew formula
 if ! command -v cmake >/dev/null; then
-  brew list cmake || brew install cmake
+  brew list cmake || HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake
 fi
🤖 Prompt for 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.

In `@scripts/install-ci-deps.sh` around lines 36 - 39, Update the brew install
fallback in the CMake availability check to set HOMEBREW_NO_AUTO_UPDATE=1 for
the install command, while preserving the existing brew list fallback and
command-not-found condition.
build_npm_ios.sh (1)

5-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Near-duplicate usage()/arg-parsing block with build_npm_vision.sh.

Lines 5-59 here are essentially identical to build_npm_vision.sh lines 5-58 (only wording/tool names differ). Consider extracting the shared --spm-mode parsing into a small sourced helper (e.g. scripts/parse-spm-mode.sh) to avoid maintaining two copies that can drift.

♻️ Suggested shared helper sketch
# scripts/parse-spm-mode.sh (sourced)
parse_spm_mode_args() {
  SPM_MODE="embedded"
  while [ $# -gt 0 ]; do
    case "$1" in
      --spm-mode) SPM_MODE="$2"; shift 2 ;;
      --spm-mode=*) SPM_MODE="${1#*=}"; shift ;;
      -h|--help) usage; exit 0 ;;
      *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
    esac
  done
  case "$SPM_MODE" in
    embedded|remote) ;;
    *) echo "Invalid --spm-mode '$SPM_MODE'" >&2; usage >&2; exit 1 ;;
  esac
}
🤖 Prompt for 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.

In `@build_npm_ios.sh` around lines 5 - 59, Extract the duplicated --spm-mode
argument parsing and validation from the usage/argument-handling blocks in
build_npm_ios.sh and build_npm_vision.sh into a shared sourced helper. Keep each
script’s tool-specific usage output, ensure the helper preserves defaulting to
embedded, both option syntaxes, missing-value handling, help behavior,
unknown-argument errors, and embedded/remote validation, then invoke the helper
from both scripts.
🤖 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/npm_release.yml:
- Around line 107-111: Update the “Build (${{ matrix.target }})” step to pass
the matrix script through an environment variable, following the existing
INPUT_VERSION pattern, and invoke npm using that variable instead of
interpolating matrix.script directly in the shell command. Preserve the remote
--spm-mode argument.

In `@README.md`:
- Around line 270-273: Update the shell command fenced block in README.md to
include the shell language identifier on its opening fence, preserving the
existing commands and formatting.

In `@scripts/stamp-template-version.mjs`:
- Around line 18-19: Remove the duplicate let declaration for values and
positionals in the stamp-template-version.mjs initialization near the try block,
ensuring each variable is declared only once while preserving their existing use
in remote-mode stamping.

---

Nitpick comments:
In `@build_npm_ios.sh`:
- Around line 5-59: Extract the duplicated --spm-mode argument parsing and
validation from the usage/argument-handling blocks in build_npm_ios.sh and
build_npm_vision.sh into a shared sourced helper. Keep each script’s
tool-specific usage output, ensure the helper preserves defaulting to embedded,
both option syntaxes, missing-value handling, help behavior, unknown-argument
errors, and embedded/remote validation, then invoke the helper from both
scripts.

In `@scripts/install-ci-deps.sh`:
- Line 33: Replace the npm install command in the CI dependency installation
step with npm ci so installs remain clean and strictly follow package-lock.json
without modifying it.
- Around line 46-49: Update the Homebrew installation command in the TEST_TOOLS
block to set HOMEBREW_NO_AUTO_UPDATE=1 for that invocation, while leaving the
npm installation unchanged.
- Around line 36-39: Update the brew install fallback in the CMake availability
check to set HOMEBREW_NO_AUTO_UPDATE=1 for the install command, while preserving
the existing brew list fallback and command-not-found condition.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: adb7dd29-2ec0-458f-8790-83e6a4717a2e

📥 Commits

Reviewing files that changed from the base of the PR and between 746d4da and cfd159b.

📒 Files selected for processing (18)
  • .github/actions/setup-build-env/action.yml
  • .github/workflows/npm_release.yml
  • .github/workflows/pull_request.yml
  • README.md
  • build_all_ios.sh
  • build_all_vision.sh
  • build_npm_ios.sh
  • build_npm_vision.sh
  • scripts/generate-spm-manifest.mjs
  • scripts/generate-spm-probe.mjs
  • scripts/get-next-version.js
  • scripts/get-npm-tag.js
  • scripts/install-ci-deps.sh
  • scripts/resolve-release.mjs
  • scripts/stamp-template-local-spm.mjs
  • scripts/stamp-template-version.mjs
  • spm-templates/local-spm-ios/Package.swift
  • spm-templates/local-spm-visionos/Package.swift

Comment thread .github/workflows/npm_release.yml Outdated
Comment thread README.md Outdated
Comment thread scripts/stamp-template-version.mjs
…pm-mode parser

Address CodeRabbit review on #406:
- pass matrix.script to the build step through an env var (consistent with
  INPUT_VERSION; silences zizmor's template-injection warning — the values
  are hard-coded in resolve-release.mjs, so this is hardening, not a fix)
- add the bash language tag to the README linking snippet (MD040)
- extract the duplicated --spm-mode parsing from build_npm_ios.sh /
  build_npm_vision.sh into parse_spm_mode_args in build_utils.sh (already
  sourced by both; usage() stays per-script)
@NathanWalker
NathanWalker merged commit 2098243 into main Jul 21, 2026
9 checks passed
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