Skip to content

Commit e652dd0

Browse files
committed
ci: spm version handling
[skip ci]
1 parent c453116 commit e652dd0

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

.github/workflows/npm_release.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,21 @@ jobs:
464464
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
465465
with:
466466
node-version: 22
467+
- name: Assert ios-spm manifest pins this release
468+
run: |
469+
# Guard against the manifest pointing at a STALE release — e.g. a stamp
470+
# that no-ops and leaves a prior version's nsVersion + artifact
471+
# URLs/checksums in place. Those still resolve and checksum-pass (they're
472+
# internally consistent), so `swift package resolve` below won't catch
473+
# it; this explicit version assertion does.
474+
url="https://raw.githubusercontent.com/NativeScript/ios-spm/${NPM_VERSION}/Package.swift"
475+
manifest="$(curl -fsSL "$url")"
476+
if ! printf '%s\n' "$manifest" | grep -q "let nsVersion = \"${NPM_VERSION}\""; then
477+
echo "::error::ios-spm@${NPM_VERSION} does not pin nsVersion=\"${NPM_VERSION}\" (stale stamp?). Found:" >&2
478+
printf '%s\n' "$manifest" | grep -n "nsVersion" >&2 || true
479+
exit 1
480+
fi
481+
echo "OK: ios-spm@${NPM_VERSION} pins nsVersion=\"${NPM_VERSION}\""
467482
- name: Generate a probe package that depends on ios-spm
468483
run: |
469484
node -e '

scripts/stamp-spm-release.mjs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,52 @@ for (const file of opts.checksums) {
6969

7070
let manifest = fs.readFileSync(opts.package, "utf8");
7171

72-
manifest = manifest.split("NS_SPM_VERSION").join(opts.version);
72+
// Stamping is IDEMPOTENT: each slot is rewritten whether it still holds the
73+
// NS_SPM_VERSION / NS_CHECKSUM_* token or an already-stamped concrete value.
74+
// This matters because the release flow pushes the stamped manifest back to
75+
// `main`, so by the second release there are no tokens left. A one-shot token
76+
// replace would then silently no-op, the "no changes to commit" tag would land
77+
// on the prior commit, and every new tag would resolve to the FIRST release's
78+
// artifacts (the 9.0.4-next.* incident). Anchoring on the structural shape
79+
// instead of the token lets us re-stamp correctly forever.
80+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7381

82+
// Each checksum env key ↔ the artifact zip its binaryTarget references (the
83+
// pairing baked into the manifest template). Lets us find the right checksum
84+
// slot by URL once the NS_CHECKSUM_* tokens are gone.
85+
const KEY_TO_ARTIFACT = {
86+
NS_CHECKSUM_NATIVESCRIPT_IOS: "NativeScript.xcframework.zip",
87+
NS_CHECKSUM_TKLIVESYNC_IOS: "TKLiveSync.xcframework.zip",
88+
NS_CHECKSUM_NATIVESCRIPT_VISIONOS: "NativeScript.visionos.xcframework.zip",
89+
NS_CHECKSUM_TKLIVESYNC_VISIONOS: "TKLiveSync.visionos.xcframework.zip",
90+
};
91+
92+
// Version: rewrite the value inside `let nsVersion = "..."`.
93+
const versionRe = /(let\s+nsVersion\s*=\s*")[^"]*(")/;
94+
if (!versionRe.test(manifest)) {
95+
console.error(`ERROR: could not find 'let nsVersion = "..."' in ${opts.package}`);
96+
process.exit(1);
97+
}
98+
manifest = manifest.replace(versionRe, `$1${opts.version}$2`);
99+
100+
// Checksums: for each provided checksum, rewrite the value in the binaryTarget
101+
// whose url points at the matching artifact zip.
74102
const applied = [];
75103
for (const [key, value] of Object.entries(checksums)) {
76-
if (manifest.includes(key)) {
77-
manifest = manifest.split(key).join(value);
78-
applied.push(key);
104+
const artifact = KEY_TO_ARTIFACT[key];
105+
if (!artifact) {
106+
console.error(`ERROR: no artifact mapping for checksum key ${key}; update KEY_TO_ARTIFACT.`);
107+
process.exit(1);
108+
}
109+
const re = new RegExp(
110+
`(url:\\s*"[^"]*${escapeRegExp(artifact)}",\\s*checksum:\\s*")[^"]*(")`
111+
);
112+
if (!re.test(manifest)) {
113+
console.error(`ERROR: no binaryTarget checksum slot for ${artifact} in ${opts.package}`);
114+
process.exit(1);
79115
}
116+
manifest = manifest.replace(re, `$1${value}$2`);
117+
applied.push(key);
80118
}
81119

82120
fs.writeFileSync(opts.package, manifest);
@@ -85,10 +123,13 @@ console.log(`Stamped ${opts.package}`);
85123
console.log(` version: ${opts.version}`);
86124
console.log(` checksums applied: ${applied.length ? applied.join(", ") : "(none)"}`);
87125

88-
const leftover = (manifest.match(/NS_SPM_VERSION|NS_CHECKSUM_[A-Z_]+/g) || []);
126+
// Safety net: no placeholder tokens may remain (they'd resolve to a broken URL
127+
// or a checksum mismatch). With idempotent stamping a leftover token signals a
128+
// real manifest/key drift (e.g. a missing platform's checksums), not a routine
129+
// 2nd-release no-op.
130+
const leftover = [...new Set(manifest.match(/NS_SPM_VERSION|NS_CHECKSUM_[A-Z_]+/g) || [])];
89131
if (leftover.length) {
90-
const unique = [...new Set(leftover)];
91-
const msg = `Unstamped tokens remain: ${unique.join(", ")}`;
132+
const msg = `Unstamped tokens remain: ${leftover.join(", ")}`;
92133
if (opts.strict) {
93134
console.error(`ERROR: ${msg}`);
94135
process.exit(1);

0 commit comments

Comments
 (0)