@rightkit/release 0.2.29 → 0.2.31

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.
@@ -23,6 +23,10 @@ const outputPath = path.resolve(cwd, output);
23
23
  if (!dryRun && !existsSync(appPath)) fail(`signed app not found: ${appPath}`);
24
24
 
25
25
  const env = { ...process.env };
26
+ // bsdtar otherwise serializes macOS extended attributes as AppleDouble `._*`
27
+ // entries. Tauri's Rust extractor writes those entries as real bundle files,
28
+ // invalidating the Developer ID seal and causing syspolicyd to trash the app.
29
+ env.COPYFILE_DISABLE = "1";
26
30
  if (!dryRun && !env.TAURI_SIGNING_PRIVATE_KEY) {
27
31
  try {
28
32
  env.TAURI_SIGNING_PRIVATE_KEY = execFileSync(
@@ -38,11 +42,20 @@ env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD ??= "";
38
42
 
39
43
  rmSync(outputPath, { force: true });
40
44
  rmSync(`${outputPath}.sig`, { force: true });
41
- run("tar", ["-czf", outputPath, "-C", path.dirname(appPath), path.basename(appPath)]);
45
+ // Tauri 2.10 strips the first path component while extracting. Omitting the explicit
46
+ // App.app/ directory entry prevents it from unpacking that entry onto its existing temp dir.
47
+ run("tar", [
48
+ "-czf",
49
+ outputPath,
50
+ "-C",
51
+ path.dirname(appPath),
52
+ path.join(path.basename(appPath), "Contents"),
53
+ ]);
42
54
  run("pnpm", ["exec", "tauri", "signer", "sign", outputPath]);
43
55
 
44
56
  function run(command, commandArgs) {
45
- console.log(`${dryRun ? "[dry-run] " : ""}${command} ${commandArgs.join(" ")}`);
57
+ const envPrefix = command === "tar" ? "COPYFILE_DISABLE=1 " : "";
58
+ console.log(`${dryRun ? "[dry-run] " : ""}${envPrefix}${command} ${commandArgs.join(" ")}`);
46
59
  if (dryRun) return;
47
60
  const result = spawnSync(command, commandArgs, { cwd, env, stdio: "inherit" });
48
61
  if (result.status !== 0) process.exit(result.status ?? 1);
@@ -8,6 +8,7 @@ const helper = fileURLToPath(new URL("./create-mac-updater.mjs", import.meta.url
8
8
  test("documents the final signed-app to signed-updater sequence in dry-run mode", () => {
9
9
  const result = spawnSync(process.execPath, [helper, "--app", "bundle/Test.app", "--output", "bundle/Test.app.tar.gz", "--dry-run"], { encoding: "utf8" });
10
10
  assert.equal(result.status, 0, result.stderr);
11
- assert.match(result.stdout, /tar .*Test\.app\.tar\.gz.*Test\.app/);
11
+ assert.match(result.stdout, /COPYFILE_DISABLE=1 tar .*Test\.app\.tar\.gz.*Test\.app\/Contents/);
12
+ assert.doesNotMatch(result.stdout, / Test\.app$/m);
12
13
  assert.match(result.stdout, /tauri signer sign .*Test\.app\.tar\.gz/);
13
14
  });
@@ -135,15 +135,8 @@ export function assertLegalReleaseContract(root, legal, appName, platform) {
135
135
  appendices.push({ ...appendix, absolutePath: realFile });
136
136
  }
137
137
 
138
- // The in-app gate is the SINGLE assent surface (Adrian, 2026-07-17). An
139
- // installer/DMG can only PRESENT the text: it cannot record who agreed, cannot ask
140
- // the individual-vs-enterprise basis, and passive updaters reach neither — so a
141
- // license page is duplicate presentation that captures nothing. This gate
142
- // previously REQUIRED bundle.licenseFile; it now requires its absence.
143
- //
144
- // Checked on EVERY platform, not just Windows: one bundle.licenseFile drives BOTH
145
- // the NSIS license page and the macOS DMG SLA (branded-dmg.mjs derives its --eula
146
- // from it), so a win-only check would let a mac build reintroduce the SLA.
138
+ // The in-app gate is the single assent surface. An installer or DMG can only
139
+ // present text; it cannot record who agreed or the individual/enterprise basis.
147
140
  if (typeof legal.tauriConfig !== "string") fail(appName, "the legal gate requires legal.tauriConfig");
148
141
  const tauriPath = path.resolve(appRoot, legal.tauriConfig);
149
142
  if (!within(appRoot, tauriPath) || !existsSync(tauriPath)) fail(appName, `missing Tauri config: ${legal.tauriConfig}`);
@@ -46,8 +46,6 @@ function fixture() {
46
46
  })),
47
47
  };
48
48
  writeFileSync(path.join(legalDir, "legal-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
49
- // No bundle.licenseFile: the in-app gate is the single assent surface, so a
50
- // compliant app ships an installer with no license page (Adrian, 2026-07-17).
51
49
  writeFileSync(
52
50
  path.join(tauriDir, "tauri.conf.json"),
53
51
  `${JSON.stringify({ bundle: { publisher: "Damned Ventures LLC" } }, null, 2)}\n`,
@@ -107,8 +105,6 @@ test("rejects a Windows installer license page: the in-app gate is the only asse
107
105
  });
108
106
 
109
107
  test("rejects a macOS DMG SLA too — one licenseFile drives both bundlers", () => {
110
- // branded-dmg.mjs derives its --eula from bundle.licenseFile, so a Windows-only
111
- // check would let a mac release quietly reintroduce the mount-time SLA.
112
108
  const { root, legal } = fixture();
113
109
  writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
114
110
  assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "mac"), /licenseFile must be absent/i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "Portable Right Suite release CLI/SDK: signed installers, updater artifacts, patch/update routing, hardening, R2 publish, and RightApps registration.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -193,10 +193,10 @@ test("requires the durable release credential before the first patch upload", ()
193
193
  ...process.env,
194
194
  APPDATA: path.join(path.dirname(config), "empty-appdata"),
195
195
  RIGHT_RELEASE_TIER: "patch",
196
+ RIGHTAPPS_RELEASE_TOKEN_FILE: path.join(path.dirname(config), "missing-release-token"),
196
197
  RIGHTAPPS_ADMIN_TOKEN: "expired-admin-session",
197
198
  };
198
199
  delete env.RIGHTAPPS_RELEASE_TOKEN;
199
- delete env.RIGHTAPPS_RELEASE_TOKEN_FILE;
200
200
  const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win"], {
201
201
  encoding: "utf8",
202
202
  env,
package/release-token.mjs CHANGED
@@ -23,7 +23,11 @@ export async function loadReleaseToken({ env = process.env, platform = process.p
23
23
  if (direct) return direct;
24
24
 
25
25
  if (env.RIGHTAPPS_RELEASE_TOKEN_FILE) {
26
- return readTokenFile(path.resolve(env.RIGHTAPPS_RELEASE_TOKEN_FILE));
26
+ try {
27
+ return await readTokenFile(path.resolve(env.RIGHTAPPS_RELEASE_TOKEN_FILE));
28
+ } catch (error) {
29
+ throw new Error(`RIGHTAPPS_RELEASE_TOKEN is unavailable (${error.message})`);
30
+ }
27
31
  }
28
32
 
29
33
  if (platform === "win32") {
package/release.mjs CHANGED
@@ -16,7 +16,11 @@ const SIGN_UPDATER = path.resolve(TOOL_ROOT, "sign-updater.mjs");
16
16
  const RIGHTKIT_VERSIONS = JSON.parse(readFileSync(path.resolve(TOOL_ROOT, "rightkit-versions.json"), "utf8"));
17
17
  const VERSION = RIGHTKIT_VERSIONS.stagedNpm?.["@rightkit/release"] ?? RIGHTKIT_VERSIONS.npm["@rightkit/release"];
18
18
  const TIERS = new Set(["patch", "update"]);
19
- const RIGHTKIT_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.npm, RIGHTKIT_VERSIONS.stagedNpm);
19
+ const RIGHTKIT_ALLOWED = buildAllowedVersions(
20
+ RIGHTKIT_VERSIONS.npm,
21
+ RIGHTKIT_VERSIONS.stagedNpm,
22
+ RIGHTKIT_VERSIONS.legacyNpm,
23
+ );
20
24
  const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo);
21
25
  const FORBIDDEN_RIGHTKIT_SPEC = /^(?:git|file|link|workspace):|github|github\.com/i;
22
26
 
@@ -249,11 +253,15 @@ async function validateRightKitPackageContract(root, appName) {
249
253
  }
250
254
  }
251
255
 
252
- function buildAllowedVersions(published = {}, staged = {}) {
256
+ function buildAllowedVersions(published = {}, staged = {}, legacy = {}) {
253
257
  const allowed = new Map();
254
- for (const [name, version] of [...Object.entries(published), ...Object.entries(staged)]) {
258
+ for (const [name, value] of [
259
+ ...Object.entries(published),
260
+ ...Object.entries(staged),
261
+ ...Object.entries(legacy),
262
+ ]) {
255
263
  const versions = allowed.get(name) ?? new Set();
256
- versions.add(version);
264
+ for (const version of Array.isArray(value) ? value : [value]) versions.add(version);
257
265
  allowed.set(name, versions);
258
266
  }
259
267
  return allowed;