@rightkit/release 0.2.149 → 0.2.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,7 +75,9 @@ Swift (SwiftPM) apps are a first-class project type. `projectType: "swiftpm"` (c
75
75
 
76
76
  `updater.kind: "appinstaller"` (default remains the Tauri minisign flow) generates `msix.appInstaller.out` (https `uri`/`msixUri`); trust is the MSIX Authenticode signature, so no `.sig` is produced and `publish-update` records the sealed package digest.
77
77
 
78
- Never cancel an MSIX deployment from app or updater code. Cancelling `AddPackageAsync` while it is in `RegistrationChanged` leaves the package `Modified, NeedsRemediation` (0x80073CF8, AppX event 751); the app then cannot activate, so the OnLaunch feed check never runs and cannot self-heal. Measured recovery (SwiftRight SliceApp R5 W08, 2026-10-10): `Add-AppxPackage -AppInstallerFile <feed uri>` restores launch by deploying the feed's newer version, with user data intact; `-RegisterByFamilyName`, `Reset-AppxPackage` and relaunching do not. Recovery when the feed offers no newer version is unverified, so publish the next version to the feed before directing a user to that command.
78
+ Portable macOS targets (`macos-developer-id-notarized-portable-v1`) whose `notarize.file` is an archive of bare executables set `notarize.staple: false`: the archive is notarized (Accepted) and recorded, nothing is stapled. Portable Windows and macOS targets with no installer or updater block seal their archives from `targets.<platform>.artifacts`.
79
+
80
+ Never cancel an MSIX deployment from app or updater code. Cancelling `AddPackageAsync` while it is in `RegistrationChanged` leaves the package `Modified, NeedsRemediation` (0x80073CF8, AppX event 751); the app then cannot activate, so the OnLaunch feed check never runs and cannot self-heal. Measured recovery (SwiftRight SliceApp R5 W08 tuple 08, 2026-10-10, one run per method, user data intact in every recovering case): Settings › Apps › Advanced options › Repair recovers a feed install (with or without a newer version on the feed) and a path install with no feed; `Add-AppxPackage -Path <same-version .msix>` recovers; `Add-AppxPackage -AppInstallerFile <feed uri>` recovers with or without a newer version on the feed. `Reset-AppxPackage`, `-RegisterByFamilyName`, the platform's own `RegisterByPackageFullName` (runs on every launch attempt, reports success, package stays NeedsRemediation) and relaunching do not. Not measured: Repair with the install source gone.
79
81
 
80
82
  macOS Swift apps use `right-release swift-package mac-app` (Info.plist from config, resources, frameworks, entitlements on the bundle only; nested code signed inside-out before the app).
81
83
 
package/build-release.mjs CHANGED
@@ -477,6 +477,15 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
477
477
  addSource(resolveArtifactPath(artifact.file), "updater");
478
478
  if (target.updater?.kind !== "appinstaller") addSource(resolveArtifactPath(artifact.signature), "updater-signature");
479
479
  }
480
+ // Portable contracts ship bare archives (zip / tar.gz) with no installer or
481
+ // updater block: `target.artifacts` entries (paths or { file }) seal with the
482
+ // "portable" role.
483
+ for (const artifact of target.artifacts ?? []) {
484
+ const file = typeof artifact === "string" ? artifact : artifact?.file;
485
+ if (typeof file !== "string" || !file) fail("target.artifacts entries must be paths or { file }");
486
+ addSource(resolveArtifactPath(file), "portable");
487
+ }
488
+ if (sources.size === 0) fail(`nothing to seal: declare installer.artifacts, updater.artifacts or artifacts for the ${platform} target`);
480
489
  for (const file of sources.keys()) if (!existsSync(file)) fail(`cannot seal missing artifact: ${file}`);
481
490
  const temp = `${sealedDir}.tmp-${process.pid}`;
482
491
  if (existsSync(temp)) rmSync(temp, { recursive: true, force: true });
@@ -33,7 +33,12 @@ function fileEvidence(file) {
33
33
 
34
34
  // DMG/PKG receive their own ticket. For a separately submitted app ZIP,
35
35
  // target.notarize.app (CLI --app) identifies the bundle to staple before packaging.
36
- export function notarizeMacosArtifact({ archive, receiptPath, authArgs, profile, app, run = spawnSync, env = process.env, ...agentOptions }) {
36
+ // `staple: false` is for archives of bare Mach-O executables (portable CLIs):
37
+ // the notary ticket is recorded online and nothing can carry a staple, so the
38
+ // receipt records `staplingSkipped` instead of stapling evidence.
39
+ export const STAPLING_SKIPPED_PORTABLE = "portable-executables";
40
+
41
+ export function notarizeMacosArtifact({ archive, receiptPath, authArgs, profile, app, staple = true, run = spawnSync, env = process.env, ...agentOptions }) {
37
42
  archive = path.resolve(archive);
38
43
  const stapleTargets = /\.(dmg|pkg)$/i.test(archive) ? [archive] : [];
39
44
  if (app) {
@@ -41,7 +46,11 @@ export function notarizeMacosArtifact({ archive, receiptPath, authArgs, profile,
41
46
  if (!/\.app$/i.test(bundle)) throw new Error(`notarize.app must name an app bundle: ${bundle}`);
42
47
  stapleTargets.push(bundle);
43
48
  }
44
- if (!stapleTargets.length) throw new Error("notarization archives require notarize.app (or --app) for stapling their app bundle");
49
+ if (staple === false) {
50
+ if (stapleTargets.length) throw new Error("notarize.staple: false applies only to archives of bare executables (no .dmg/.pkg archive and no notarize.app)");
51
+ } else if (!stapleTargets.length) {
52
+ throw new Error("notarization archives require notarize.app (or --app) for stapling their app bundle, or notarize.staple: false for bare executables");
53
+ }
45
54
  let result, stapling;
46
55
  if (detectMacSigningSession({ env, run }).guiRunner) {
47
56
  // SSH ignores API credential environment/argv entirely. Only profile name
@@ -64,6 +73,7 @@ export function notarizeMacosArtifact({ archive, receiptPath, authArgs, profile,
64
73
  archive: { file: archive, ...fileEvidence(archive) },
65
74
  notarization: { status: result.status, id: result.id ?? result.submissionId ?? null },
66
75
  stapling,
76
+ ...(staple === false ? { staplingSkipped: STAPLING_SKIPPED_PORTABLE } : {}),
67
77
  };
68
78
  mkdirSync(path.dirname(path.resolve(receiptPath)), { recursive: true });
69
79
  writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
@@ -99,6 +109,12 @@ export function validateNotarizationReceipt({ receiptPath, archive, run = spawnS
99
109
  || path.resolve(receipt.archive?.file ?? "") !== archive || receipt.archive.sha256 !== fileEvidence(archive).sha256
100
110
  || receipt.notarization?.status !== "Accepted") throw new Error(`invalid macOS notarization receipt evidence: ${receiptPath}`);
101
111
  const stapling = receipt.stapling;
112
+ if (receipt.staplingSkipped === STAPLING_SKIPPED_PORTABLE) {
113
+ if (/\.(dmg|pkg|app)$/i.test(archive) || !Array.isArray(stapling) || stapling.length) {
114
+ throw new Error(`invalid macOS notarization receipt: stapling may be skipped only for archives of bare executables: ${receiptPath}`);
115
+ }
116
+ return receipt;
117
+ }
102
118
  if (!Array.isArray(stapling) || !stapling.length || stapling.some((item) => item.validated !== true || !/\.(dmg|pkg|app)$/i.test(item.file ?? ""))
103
119
  || (/\.(dmg|pkg|app)$/i.test(archive) && !stapling.some((item) => path.resolve(item.file) === archive))) {
104
120
  throw new Error(`missing macOS stapling evidence: ${receiptPath}; rerun notarization before sealing`);
@@ -5,14 +5,15 @@ import { notarizeMacosArtifact } from "./macos-notarization.mjs";
5
5
 
6
6
  const args = process.argv.slice(2);
7
7
  const dryRun = args.includes("--dry-run");
8
+ const noStaple = args.includes("--no-staple");
8
9
  const receiptIndex = args.indexOf("--receipt");
9
10
  const profileIndex = args.indexOf("--profile");
10
11
  const appIndex = args.indexOf("--app");
11
12
  const app = appIndex >= 0 ? args[appIndex + 1] : undefined;
12
13
  const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
13
14
  const profile = profileIndex >= 0 ? args[profileIndex + 1] : undefined;
14
- const archiveArgs = args.filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && arg !== "--profile" && arg !== "--app" && (appIndex < 0 || index !== appIndex + 1) && (receiptIndex < 0 || index !== receiptIndex + 1) && (profileIndex < 0 || index !== profileIndex + 1));
15
- if (archiveArgs.length !== 1) fail("usage: node notarize-macos.mjs [--dry-run] --receipt <path> [--profile <name>] [--app <bundle>] <archive>");
15
+ const archiveArgs = args.filter((arg, index) => arg !== "--dry-run" && arg !== "--no-staple" && arg !== "--receipt" && arg !== "--profile" && arg !== "--app" && (appIndex < 0 || index !== appIndex + 1) && (receiptIndex < 0 || index !== receiptIndex + 1) && (profileIndex < 0 || index !== profileIndex + 1));
16
+ if (archiveArgs.length !== 1) fail("usage: node notarize-macos.mjs [--dry-run] [--no-staple] --receipt <path> [--profile <name>] [--app <bundle>] <archive>");
16
17
  if (!receiptPath || receiptPath.startsWith("--")) fail("--receipt requires a path");
17
18
  if (appIndex >= 0 && (!app || app.startsWith("--"))) fail("--app requires an app bundle path");
18
19
  const archive = path.resolve(archiveArgs[0]);
@@ -23,7 +24,7 @@ if (dryRun) {
23
24
  if (process.platform !== "darwin") fail("macOS notarization must run on macOS");
24
25
  if (!existsSync(archive)) fail(`missing archive: ${archive}`);
25
26
 
26
- try { notarizeMacosArtifact({ archive, receiptPath, profile, app }); }
27
+ try { notarizeMacosArtifact({ archive, receiptPath, profile, app, staple: noStaple ? false : true }); }
27
28
  catch (error) {
28
29
  if (error.report) console.error(JSON.stringify(error.report));
29
30
  fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.149",
3
+ "version": "0.2.151",
4
4
  "description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
package/publish-swift.mjs CHANGED
@@ -16,7 +16,10 @@ import { scanCrateForSecrets } from "./publish-cargo.mjs";
16
16
  import { enforcePublicationGate, defaultEvidencePath } from "./publication-gate.mjs";
17
17
 
18
18
  export const DEFAULT_SWIFT_GIT_URL = "https://github.com/bogusyogi/rightkit-swift.git";
19
- const MIRROR_EXCLUDES = new Set([".build", ".swiftpm", ".git", "Package.resolved", ".DS_Store"]);
19
+ // The mirror must reproduce the verified git subtree exactly (its tree id is
20
+ // compared), so only untracked build state is excluded. Tracked Xcode scheme
21
+ // metadata under .swiftpm ships with the package; SwiftPM consumers ignore it.
22
+ const MIRROR_EXCLUDES = new Set([".build", ".git", "Package.resolved", ".DS_Store"]);
20
23
 
21
24
  export function parseSwiftPublishArgs(argv, env = process.env) {
22
25
  const allowed = new Set(["--repo", "--dry-run", "--allow-dirty", "--evidence", "--workspace"]);
@@ -138,7 +141,10 @@ export function runSwiftPublish(options, execute = spawnSync) {
138
141
  const prefix = git(["rev-parse", "--show-prefix"], packageDir, execute).replace(/\/$/, "");
139
142
  const sourceCommit = gate.evidence?.commit ?? "HEAD";
140
143
  const tree = git(["rev-parse", prefix ? `${sourceCommit}:${prefix}` : `${sourceCommit}^{tree}`], packageDir, execute);
141
- const archive = execute("git", ["archive", "--format=tar", "--mtime=1970-01-01T00:00:00Z", tree], { cwd: packageDir, windowsHide: true, maxBuffer: 128 * 1024 * 1024 });
144
+ // `git archive` filters by the current subdirectory, so archiving the package
145
+ // subtree from inside the package yields an empty tar; run it at the toplevel.
146
+ const toplevel = git(["rev-parse", "--show-toplevel"], packageDir, execute) || packageDir;
147
+ const archive = execute("git", ["archive", "--format=tar", "--mtime=1970-01-01T00:00:00Z", tree], { cwd: toplevel, windowsHide: true, maxBuffer: 128 * 1024 * 1024 });
142
148
  if (archive.error || archive.status !== 0 || !archive.stdout?.length) throw new Error("Swift source archive creation failed");
143
149
  const bytes = Buffer.from(archive.stdout);
144
150
  if (gate.ok || !options.dryRun) verifySwiftSourceArchive(bytes, gate.evidence);
package/release.mjs CHANGED
@@ -160,6 +160,9 @@ if (opts.platform === "mac") {
160
160
  if (!target.sign?.prePackageFiles?.length) fail(`${config.app ?? "app"} mac must declare at least one sign.prePackageFiles`);
161
161
  if (!target.notarize?.file) fail(`${config.app ?? "app"} mac must declare notarize.file`);
162
162
  if (target.notarize.appBeforePackage && !target.notarize.app) fail(`${config.app ?? "app"} mac notarize.appBeforePackage requires notarize.app`);
163
+ if (target.notarize.staple === false && (target.notarize.app || /\.(dmg|pkg)$/i.test(target.notarize.file))) {
164
+ fail(`${config.app ?? "app"} mac notarize.staple: false applies only to an archive of bare executables (no notarize.app, not a .dmg/.pkg)`);
165
+ }
163
166
  const swiftDmgError = swiftMacDmgConfigError({ target, projectType: resolveProjectType({ appRoot: root, config, target }), appName: config.app ?? "app" });
164
167
  if (swiftDmgError) fail(swiftDmgError);
165
168
  }
@@ -820,9 +823,13 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
820
823
  const allowedRegeneratePermissions = new Set(["contents", "pull-requests"]);
821
824
  const devMacSpan = workflow === "ci.yml" ? jobSpan("dev-mac-sign") : null;
822
825
  const devMacSource = devMacSpan ? source.slice(devMacSpan.start, devMacSpan.end) : "";
823
- const allowedDevMacSecrets = new Set(["APPLE_CERTIFICATE_BASE64", "APPLE_CERTIFICATE_PASSWORD", "APPLE_KEYCHAIN_PASSWORD"]);
826
+ // `mac: "notarize"` additionally allows the notary API key secrets; the
827
+ // job gate (push to the default branch, release environment) is the same.
828
+ const allowedDevMacSecrets = manifest?.devArtifact?.mac === "notarize"
829
+ ? allowedMacosSecrets
830
+ : new Set(["APPLE_CERTIFICATE_BASE64", "APPLE_CERTIFICATE_PASSWORD", "APPLE_KEYCHAIN_PASSWORD"]);
824
831
  const protectedDevMacSigning = workflow === "ci.yml"
825
- && manifest?.devArtifact?.mac === "sign"
832
+ && (manifest?.devArtifact?.mac === "sign" || manifest?.devArtifact?.mac === "notarize")
826
833
  && /dev-mac-sign:\n(?: #[^\n]*\n)* needs: \[ci\]\n if: \$\{\{ github\.event_name == 'push' && github\.ref_name == github\.event\.repository\.default_branch \}\}\n environment: /.test(devMacSource);
827
834
  const releaseChain = workflow === "release-candidate.yml" ? manifest?.candidate?.releaseChain : undefined;
828
835
  const allowedReleaseSecrets = new Set(releaseChain?.publishSecrets ?? []);
@@ -927,6 +934,7 @@ async function notarizeMacos(archive, root) {
927
934
  if (opts.dryRun) args.push("--dry-run");
928
935
  args.push("--receipt", receipt);
929
936
  if (target.notarize.profile) args.push("--profile", target.notarize.profile);
937
+ if (target.notarize.staple === false) args.push("--no-staple");
930
938
  // With appBeforePackage the app already carries its own stapled ticket inside the packaged DMG.
931
939
  if (target.notarize.app && !target.notarize.appBeforePackage) args.push("--app", resolveReleaseArtifactPath(root, target.notarize.app));
932
940
  args.push(archive);
@@ -13,27 +13,27 @@
13
13
  "@rightkit/updates": "0.2.3"
14
14
  },
15
15
  "stagedNpm": {
16
- "@rightkit/app-shell": "0.3.0",
16
+ "@rightkit/app-shell": "0.4.0",
17
17
  "@rightkit/ax": "0.2.4",
18
- "@rightkit/doc-render": "0.1.4",
19
- "@rightkit/git": "0.2.36",
18
+ "@rightkit/doc-render": "0.1.5",
19
+ "@rightkit/git": "0.2.37",
20
20
  "@rightkit/hooks": "0.1.2",
21
21
  "@rightkit/i18n": "0.1.0",
22
22
  "@rightkit/legal": "0.3.1",
23
23
  "@rightkit/legal-ui": "0.2.0",
24
24
  "@rightkit/license": "0.1.8",
25
25
  "@rightkit/logs": "0.1.4",
26
- "@rightkit/media-ui": "0.3.0",
27
- "@rightkit/onboarding": "0.3.0",
28
- "@rightkit/pdf-ui": "0.1.2",
26
+ "@rightkit/media-ui": "0.3.1",
27
+ "@rightkit/onboarding": "0.3.1",
28
+ "@rightkit/pdf-ui": "0.1.3",
29
29
  "@rightkit/platform-ui": "0.2.0",
30
- "@rightkit/qa": "0.4.0",
30
+ "@rightkit/qa": "0.4.1",
31
31
  "@rightkit/qa-chrome": "0.1.3",
32
- "@rightkit/release": "0.2.149",
33
- "@rightkit/settings-ui": "0.3.0",
32
+ "@rightkit/release": "0.2.151",
33
+ "@rightkit/settings-ui": "0.3.1",
34
34
  "@rightkit/shell": "0.1.0",
35
35
  "@rightkit/tauri": "0.2.0",
36
- "@rightkit/theme": "0.2.1",
36
+ "@rightkit/theme": "0.2.2",
37
37
  "@rightkit/updates": "0.2.5"
38
38
  },
39
39
  "legacyNpm": {
@@ -77,7 +77,8 @@
77
77
  "0.2.32",
78
78
  "0.2.33",
79
79
  "0.2.34",
80
- "0.2.35"
80
+ "0.2.35",
81
+ "0.2.36"
81
82
  ],
82
83
  "@rightkit/legal-ui": [
83
84
  "0.1.0",
@@ -184,7 +185,9 @@
184
185
  "0.2.145",
185
186
  "0.2.146",
186
187
  "0.2.147",
187
- "0.2.148"
188
+ "0.2.148",
189
+ "0.2.149",
190
+ "0.2.150"
188
191
  ],
189
192
  "@rightkit/qa": [
190
193
  "0.1.0",
@@ -195,12 +198,14 @@
195
198
  "0.2.4",
196
199
  "0.3.0",
197
200
  "0.3.1",
198
- "0.3.2"
201
+ "0.3.2",
202
+ "0.4.0"
199
203
  ],
200
204
  "@rightkit/app-shell": [
201
205
  "0.1.0",
202
206
  "0.2.0",
203
- "0.2.1"
207
+ "0.2.1",
208
+ "0.3.0"
204
209
  ],
205
210
  "@rightkit/media-ui": [
206
211
  "0.1.0",
@@ -210,11 +215,13 @@
210
215
  "0.2.3",
211
216
  "0.2.4",
212
217
  "0.2.5",
213
- "0.2.6"
218
+ "0.2.6",
219
+ "0.3.0"
214
220
  ],
215
221
  "@rightkit/theme": [
216
222
  "0.1.0",
217
- "0.2.0"
223
+ "0.2.0",
224
+ "0.2.1"
218
225
  ],
219
226
  "@rightkit/settings-ui": [
220
227
  "0.1.0",
@@ -224,20 +231,23 @@
224
231
  "0.2.4",
225
232
  "0.2.5",
226
233
  "0.2.6",
227
- "0.2.7"
234
+ "0.2.7",
235
+ "0.3.0"
228
236
  ],
229
237
  "@rightkit/onboarding": [
230
238
  "0.1.0",
231
239
  "0.2.0",
232
240
  "0.2.1",
233
241
  "0.2.2",
234
- "0.2.3"
242
+ "0.2.3",
243
+ "0.3.0"
235
244
  ],
236
245
  "@rightkit/doc-render": [
237
246
  "0.1.0",
238
247
  "0.1.1",
239
248
  "0.1.2",
240
- "0.1.3"
249
+ "0.1.3",
250
+ "0.1.4"
241
251
  ],
242
252
  "@rightkit/platform-ui": [
243
253
  "0.1.1",
@@ -245,7 +255,8 @@
245
255
  ],
246
256
  "@rightkit/pdf-ui": [
247
257
  "0.1.0",
248
- "0.1.1"
258
+ "0.1.1",
259
+ "0.1.2"
249
260
  ],
250
261
  "@rightkit/qa-chrome": [
251
262
  "0.1.0",
@@ -326,13 +337,13 @@
326
337
  "swift": {
327
338
  "rightkit-swift": {
328
339
  "url": "https://github.com/bogusyogi/rightkit-swift.git",
329
- "version": "0.1.1"
340
+ "version": "0.4.0"
330
341
  }
331
342
  },
332
343
  "stagedSwift": {
333
344
  "rightkit-swift": {
334
345
  "url": "https://github.com/bogusyogi/rightkit-swift.git",
335
- "version": "0.3.1"
346
+ "version": "0.4.0"
336
347
  }
337
348
  },
338
349
  "registry": {
@@ -380,27 +391,27 @@
380
391
  "rightkit-watch": "0.3.1"
381
392
  },
382
393
  "npm": {
383
- "@rightkit/app-shell": "0.3.0",
394
+ "@rightkit/app-shell": "0.4.0",
384
395
  "@rightkit/ax": "0.2.4",
385
- "@rightkit/doc-render": "0.1.4",
386
- "@rightkit/git": "0.2.36",
396
+ "@rightkit/doc-render": "0.1.5",
397
+ "@rightkit/git": "0.2.37",
387
398
  "@rightkit/hooks": "0.1.2",
388
399
  "@rightkit/i18n": "0.1.0",
389
400
  "@rightkit/legal": "0.3.1",
390
401
  "@rightkit/legal-ui": "0.2.0",
391
402
  "@rightkit/license": "0.1.8",
392
403
  "@rightkit/logs": "0.1.4",
393
- "@rightkit/media-ui": "0.3.0",
394
- "@rightkit/onboarding": "0.3.0",
395
- "@rightkit/pdf-ui": "0.1.2",
404
+ "@rightkit/media-ui": "0.3.1",
405
+ "@rightkit/onboarding": "0.3.1",
406
+ "@rightkit/pdf-ui": "0.1.3",
396
407
  "@rightkit/platform-ui": "0.2.0",
397
- "@rightkit/qa": "0.4.0",
408
+ "@rightkit/qa": "0.4.1",
398
409
  "@rightkit/qa-chrome": "0.1.3",
399
- "@rightkit/release": "0.2.148",
400
- "@rightkit/settings-ui": "0.3.0",
410
+ "@rightkit/release": "0.2.150",
411
+ "@rightkit/settings-ui": "0.3.1",
401
412
  "@rightkit/shell": "0.1.0",
402
413
  "@rightkit/tauri": "0.2.0",
403
- "@rightkit/theme": "0.2.1",
414
+ "@rightkit/theme": "0.2.2",
404
415
  "@rightkit/updates": "0.2.5"
405
416
  }
406
417
  },