@@ -27,7 +27,7 @@ permissions:
2727# slim npm package only carries the Xcode project template + metadata generator.
2828#
2929# Flow: setup → build (ios, visionos) → test → github-release (assets) →
30- # spm-update (stamp + tag ios-spm) → publish (npm) → verify-spm
30+ # spm-update (generate manifest + tag ios-spm) → publish (npm) → verify-spm
3131#
3232# The release half (github-release, spm-update, publish, verify-spm) is gated on
3333# repo variable ENABLE_SPM_RELEASE. Until it is 'true' (and the ios-spm App
@@ -350,17 +350,24 @@ jobs:
350350 pattern : spm-artifacts-*
351351 path : spm-artifacts
352352 merge-multiple : true
353- - name : Stamp Package.swift
353+ - name : Generate Package.swift
354354 run : |
355+ # The generator emits the WHOLE manifest, shaped by the checksums it
356+ # is given: "next" builds produce only checksums-ios.env, so the next
357+ # manifest omits the visionOS product/targets whose Release assets
358+ # don't exist (SwiftPM eagerly downloads every binaryTarget in a
359+ # resolved manifest — an unshipped asset 404s for every consumer).
360+ # Real releases pass --strict so a full release can never silently
361+ # ship an iOS-only manifest.
355362 STRICT=""
356- if [ "$NPM_TAG" = "latest " ]; then STRICT="--strict"; fi
363+ if [ "$NPM_TAG" ! = "next " ]; then STRICT="--strict"; fi
357364 CHECKSUM_ARGS=""
358365 for f in spm-artifacts/checksums-*.env; do
359366 [ -f "$f" ] && CHECKSUM_ARGS="$CHECKSUM_ARGS --checksums $f"
360367 done
361368 echo "Using checksum files:$CHECKSUM_ARGS"
362369 if [ -z "$CHECKSUM_ARGS" ]; then echo "No checksum files found" >&2; exit 1; fi
363- node ios/scripts/stamp -spm-release .mjs \
370+ node ios/scripts/generate -spm-manifest .mjs \
364371 --package ios-spm/Package.swift \
365372 --version "$NPM_VERSION" \
366373 $CHECKSUM_ARGS $STRICT
@@ -448,13 +455,12 @@ jobs:
448455 # Post-publish smoke test: resolve ios-spm at the released tag and verify the
449456 # binary artifact downloads + checksum-validate against the real Release.
450457 #
451- # Channel-aware, mirroring the build/publish matrix: `next` builds iOS only, so
452- # its Release has no visionos assets. `swift package resolve` eagerly downloads
453- # EVERY binaryTarget in a resolved package (not just the ones a referenced
454- # product needs), so resolving the full ios-spm manifest on a `next` version
455- # would try to fetch the visionos zips and 404. For `next` we therefore probe
456- # an iOS-only package; only real releases (v* tag / manual version) resolve the
457- # full manifest and thus also verify the visionos artifacts.
458+ # One probe for every channel, against the REAL released manifest. The
459+ # manifest's target set is channel-shaped by generate-spm-manifest.mjs
460+ # ("next" manifests only declare the iOS targets, because next Releases have
461+ # no visionos assets), so resolving ios-spm at the released tag exercises
462+ # exactly the artifact set a consumer's xcodebuild will download: iOS-only on
463+ # next, iOS + visionos on real releases (v* tag / manual version).
458464 verify-spm :
459465 name : Verify SPM resolution
460466 runs-on : macos-14
@@ -470,95 +476,41 @@ jobs:
470476 uses : step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
471477 with :
472478 egress-policy : audit
479+ - uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
480+ with :
481+ persist-credentials : false
473482 - uses : actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
474483 with :
475484 node-version : 22
476485 - name : Assert ios-spm manifest pins this release
477486 run : |
478- # Guard against the manifest pointing at a STALE release — e.g. a stamp
479- # that no-ops and leaves a prior version's nsVersion + artifact
480- # URLs/checksums in place. Those still resolve and checksum-pass (they're
481- # internally consistent), so `swift package resolve` below won't catch
482- # it; this explicit version assertion does.
487+ # Guard against the tag pointing at a STALE manifest — e.g. a
488+ # commit/tag step that silently failed and left a prior version's
489+ # nsVersion + artifact URLs/checksums in place. Those still resolve
490+ # and checksum-pass (they're internally consistent), so `swift
491+ # package resolve` below won't catch it; this explicit version
492+ # assertion does.
483493 url="https://raw.githubusercontent.com/NativeScript/ios-spm/${NPM_VERSION}/Package.swift"
484494 manifest="$(curl -fsSL "$url")"
485495 if ! printf '%s\n' "$manifest" | grep -q "let nsVersion = \"${NPM_VERSION}\""; then
486- echo "::error::ios-spm@${NPM_VERSION} does not pin nsVersion=\"${NPM_VERSION}\" (stale stamp ?). Found:" >&2
496+ echo "::error::ios-spm@${NPM_VERSION} does not pin nsVersion=\"${NPM_VERSION}\" (stale manifest ?). Found:" >&2
487497 printf '%s\n' "$manifest" | grep -n "nsVersion" >&2 || true
488498 exit 1
489499 fi
490500 echo "OK: ios-spm@${NPM_VERSION} pins nsVersion=\"${NPM_VERSION}\""
491- - name : Generate a probe package that depends on ios-spm (real releases)
492- # Real release (v* tag / manual version): the Release carries every asset,
493- # so probe the full ios-spm manifest. `swift package resolve` eagerly
494- # downloads all binaryTargets, exercising the iOS AND visionos artifacts.
495- if : ${{ needs.setup.outputs.npm_tag != 'next' }}
496- run : |
497- node -e '
498- const fs = require("fs");
499- const v = process.env.NPM_VERSION;
500- fs.mkdirSync("spmverify/Sources/Probe", { recursive: true });
501- fs.writeFileSync("spmverify/Package.swift",
502- "// swift-tools-version: 5.10\n" +
503- "import PackageDescription\n" +
504- "let package = Package(\n" +
505- " name: \"Probe\",\n" +
506- " platforms: [.iOS(.v13)],\n" +
507- " dependencies: [.package(url: \"https://github.com/NativeScript/ios-spm.git\", exact: \"" + v + "\")],\n" +
508- " targets: [.target(name: \"Probe\", dependencies: [.product(name: \"NativeScript\", package: \"ios-spm\")])]\n" +
509- ")\n");
510- fs.writeFileSync("spmverify/Sources/Probe/Probe.swift", "// probe\n");
511- '
512- - name : Generate an iOS-only probe package (next channel)
513- # The "next" Release has no visionos assets (build matrix is iOS-only for
514- # next), and `swift package resolve` would eagerly try to fetch every
515- # binaryTarget in the full ios-spm manifest -> 404 on the visionos zips.
516- # So probe an iOS-only package whose binaryTargets are the iOS artifacts
517- # from the stamped manifest (real Release URLs + real checksums): resolve
518- # still downloads and checksum-verifies exactly what a next consumer gets,
519- # and never touches visionos.
520- if : ${{ needs.setup.outputs.npm_tag == 'next' }}
521- run : |
522- url="https://raw.githubusercontent.com/NativeScript/ios-spm/${NPM_VERSION}/Package.swift"
523- curl -fsSL "$url" -o manifest.swift
524- node -e '
525- const fs = require("fs");
526- const v = process.env.NPM_VERSION;
527- const body = fs.readFileSync("manifest.swift", "utf8");
528- // Pull the checksum out of the binaryTarget whose url ends in the given
529- // artifact zip. The url is a Swift interpolation ("\(base)/<zip>"), so
530- // match up to the closing quote; anchoring on <zip>" keeps the iOS
531- // NativeScript slot from matching the visionos one (…visionos.xcframework.zip).
532- const ck = (artifact) => {
533- const re = new RegExp("url:\\s*\"[^\"]*" + artifact.replace(/\./g, "\\.") + "\"\\s*,\\s*checksum:\\s*\"([0-9a-f]{64})\"");
534- const m = body.match(re);
535- if (!m) { console.error("No iOS checksum for " + artifact + " in ios-spm@" + v); process.exit(1); }
536- return m[1];
537- };
538- const nsCk = ck("NativeScript.xcframework.zip");
539- const tkCk = ck("TKLiveSync.xcframework.zip");
540- fs.mkdirSync("spmverify/Sources/Probe", { recursive: true });
541- fs.writeFileSync("spmverify/Package.swift",
542- "// swift-tools-version: 5.10\n" +
543- "import PackageDescription\n" +
544- "let base = \"https://github.com/NativeScript/ios/releases/download/v" + v + "\"\n" +
545- "let package = Package(\n" +
546- " name: \"Probe\",\n" +
547- " platforms: [.iOS(.v13)],\n" +
548- " targets: [\n" +
549- " .binaryTarget(name: \"NativeScript\", url: \"\\(base)/NativeScript.xcframework.zip\", checksum: \"" + nsCk + "\"),\n" +
550- " .binaryTarget(name: \"TKLiveSync\", url: \"\\(base)/TKLiveSync.xcframework.zip\", checksum: \"" + tkCk + "\"),\n" +
551- " .target(name: \"Probe\", dependencies: [\"NativeScript\", \"TKLiveSync\"])\n" +
552- " ]\n" +
553- ")\n");
554- fs.writeFileSync("spmverify/Sources/Probe/Probe.swift", "// probe\n");
555- '
501+ - name : Generate a probe package that depends on ios-spm
502+ # The probe pins ios-spm at the exact released version. Resolving it
503+ # takes the same path a consumer's xcodebuild does, and the released
504+ # manifest only declares targets whose assets exist (channel-shaped by
505+ # generate-spm-manifest.mjs) — so this single probe is valid for every
506+ # channel.
507+ run : node scripts/generate-spm-probe.mjs --version "$NPM_VERSION" --dir spmverify
556508 - name : Resolve (downloads the xcframework zips and verifies their checksums)
557509 working-directory : spmverify
558510 run : |
559511 # Resolution fetches each binaryTarget's zip and verifies its SHA-256
560- # against the stamped checksum; a mismatch or a missing release asset
561- # fails the release here. For next this covers the iOS artifacts only;
562- # real releases resolve the full manifest and also cover visionos.
512+ # against the manifest checksum; a mismatch or a missing release asset
513+ # fails the release here. For next the manifest declares the iOS
514+ # artifacts only; real releases also declare ( and verify) visionos.
563515 swift package resolve
564516 echo "ios-spm@$NPM_VERSION resolved and checksum-verified."
0 commit comments