@rightkit/release 0.2.28 → 0.2.30

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.
@@ -38,7 +38,15 @@ env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD ??= "";
38
38
 
39
39
  rmSync(outputPath, { force: true });
40
40
  rmSync(`${outputPath}.sig`, { force: true });
41
- run("tar", ["-czf", outputPath, "-C", path.dirname(appPath), path.basename(appPath)]);
41
+ // Tauri 2.10 strips the first path component while extracting. Omitting the explicit
42
+ // App.app/ directory entry prevents it from unpacking that entry onto its existing temp dir.
43
+ run("tar", [
44
+ "-czf",
45
+ outputPath,
46
+ "-C",
47
+ path.dirname(appPath),
48
+ path.join(path.basename(appPath), "Contents"),
49
+ ]);
42
50
  run("pnpm", ["exec", "tauri", "signer", "sign", outputPath]);
43
51
 
44
52
  function run(command, commandArgs) {
@@ -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, /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,24 +135,19 @@ export function assertLegalReleaseContract(root, legal, appName, platform) {
135
135
  appendices.push({ ...appendix, absolutePath: realFile });
136
136
  }
137
137
 
138
- if (platform === "win") {
139
- if (typeof legal.tauriConfig !== "string") fail(appName, "Windows legal gate requires legal.tauriConfig");
140
- const tauriPath = path.resolve(appRoot, legal.tauriConfig);
141
- if (!within(appRoot, tauriPath) || !existsSync(tauriPath)) fail(appName, `missing Tauri config: ${legal.tauriConfig}`);
142
- const tauri = readJson(tauriPath, appName, "Tauri config");
143
- // The in-app gate is the SINGLE assent surface (Adrian, 2026-07-17). An
144
- // installer/DMG can only PRESENT the text: it cannot record who agreed, cannot
145
- // ask the individual-vs-enterprise basis, and passive updaters reach neither —
146
- // so a license page is duplicate presentation that captures nothing. This gate
147
- // previously REQUIRED bundle.licenseFile; it now requires its absence.
148
- const configured = tauri.bundle?.licenseFile;
149
- if (typeof configured === "string" && configured.trim()) {
150
- fail(
151
- appName,
152
- "Windows Tauri bundle.licenseFile must be absent: the in-app legal gate is the only assent surface, " +
153
- "and an installer license page duplicates it without recording acceptance",
154
- );
155
- }
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.
140
+ if (typeof legal.tauriConfig !== "string") fail(appName, "the legal gate requires legal.tauriConfig");
141
+ const tauriPath = path.resolve(appRoot, legal.tauriConfig);
142
+ if (!within(appRoot, tauriPath) || !existsSync(tauriPath)) fail(appName, `missing Tauri config: ${legal.tauriConfig}`);
143
+ const tauri = readJson(tauriPath, appName, "Tauri config");
144
+ const configured = tauri.bundle?.licenseFile;
145
+ if (typeof configured === "string" && configured.trim()) {
146
+ fail(
147
+ appName,
148
+ "Tauri bundle.licenseFile must be absent: the in-app legal gate is the only assent surface, and an " +
149
+ "installer license page (NSIS) or DMG SLA duplicates it without recording acceptance",
150
+ );
156
151
  }
157
152
 
158
153
  return {
@@ -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`,
@@ -100,12 +98,25 @@ test("rejects an explicitly unresolved release blocker even without a checkbox",
100
98
  assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /release blocker/i);
101
99
  });
102
100
 
103
- test("requires Windows installer clickthrough to use the exact EULA snapshot", () => {
101
+ test("rejects a Windows installer license page: the in-app gate is the only assent surface", () => {
104
102
  const { root, legal } = fixture();
105
103
  writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
106
104
  assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /licenseFile must be absent/i);
107
105
  });
108
106
 
107
+ test("rejects a macOS DMG SLA too — one licenseFile drives both bundlers", () => {
108
+ const { root, legal } = fixture();
109
+ writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
110
+ assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "mac"), /licenseFile must be absent/i);
111
+ });
112
+
113
+ test("accepts an app whose installer carries no license page", () => {
114
+ const { root, legal } = fixture();
115
+ for (const platform of ["win", "mac"]) {
116
+ assert.doesNotThrow(() => assertLegalReleaseContract(root, legal, "fixture", platform));
117
+ }
118
+ });
119
+
109
120
  test("requires third-party build provenance", () => {
110
121
  const { root, legal, manifest } = fixture();
111
122
  const notice = "Third-party software is included.\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
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;