@rightkit/release 0.2.150 → 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.150",
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/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
  }
@@ -931,6 +934,7 @@ async function notarizeMacos(archive, root) {
931
934
  if (opts.dryRun) args.push("--dry-run");
932
935
  args.push("--receipt", receipt);
933
936
  if (target.notarize.profile) args.push("--profile", target.notarize.profile);
937
+ if (target.notarize.staple === false) args.push("--no-staple");
934
938
  // With appBeforePackage the app already carries its own stapled ticket inside the packaged DMG.
935
939
  if (target.notarize.app && !target.notarize.appBeforePackage) args.push("--app", resolveReleaseArtifactPath(root, target.notarize.app));
936
940
  args.push(archive);
@@ -29,7 +29,7 @@
29
29
  "@rightkit/platform-ui": "0.2.0",
30
30
  "@rightkit/qa": "0.4.1",
31
31
  "@rightkit/qa-chrome": "0.1.3",
32
- "@rightkit/release": "0.2.150",
32
+ "@rightkit/release": "0.2.151",
33
33
  "@rightkit/settings-ui": "0.3.1",
34
34
  "@rightkit/shell": "0.1.0",
35
35
  "@rightkit/tauri": "0.2.0",
@@ -186,7 +186,8 @@
186
186
  "0.2.146",
187
187
  "0.2.147",
188
188
  "0.2.148",
189
- "0.2.149"
189
+ "0.2.149",
190
+ "0.2.150"
190
191
  ],
191
192
  "@rightkit/qa": [
192
193
  "0.1.0",
@@ -390,9 +391,9 @@
390
391
  "rightkit-watch": "0.3.1"
391
392
  },
392
393
  "npm": {
393
- "@rightkit/app-shell": "0.3.0",
394
+ "@rightkit/app-shell": "0.4.0",
394
395
  "@rightkit/ax": "0.2.4",
395
- "@rightkit/doc-render": "0.1.4",
396
+ "@rightkit/doc-render": "0.1.5",
396
397
  "@rightkit/git": "0.2.37",
397
398
  "@rightkit/hooks": "0.1.2",
398
399
  "@rightkit/i18n": "0.1.0",
@@ -400,17 +401,17 @@
400
401
  "@rightkit/legal-ui": "0.2.0",
401
402
  "@rightkit/license": "0.1.8",
402
403
  "@rightkit/logs": "0.1.4",
403
- "@rightkit/media-ui": "0.3.0",
404
- "@rightkit/onboarding": "0.3.0",
405
- "@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",
406
407
  "@rightkit/platform-ui": "0.2.0",
407
- "@rightkit/qa": "0.4.0",
408
+ "@rightkit/qa": "0.4.1",
408
409
  "@rightkit/qa-chrome": "0.1.3",
409
- "@rightkit/release": "0.2.149",
410
- "@rightkit/settings-ui": "0.3.0",
410
+ "@rightkit/release": "0.2.150",
411
+ "@rightkit/settings-ui": "0.3.1",
411
412
  "@rightkit/shell": "0.1.0",
412
413
  "@rightkit/tauri": "0.2.0",
413
- "@rightkit/theme": "0.2.1",
414
+ "@rightkit/theme": "0.2.2",
414
415
  "@rightkit/updates": "0.2.5"
415
416
  }
416
417
  },