@rightkit/release 0.2.42 → 0.2.44
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/build-release.mjs +1 -1
- package/build-release.test.mjs +4 -3
- package/cache-command.test.mjs +2 -1
- package/cache-policy.mjs +5 -4
- package/cargo-contract.mjs +1 -1
- package/create-mac-updater.test.mjs +1 -1
- package/package.json +1 -1
- package/release-state.mjs +19 -1
- package/release-state.test.mjs +27 -2
- package/release.test.mjs +1 -1
- package/right-suite-contract.test.mjs +26 -4
- package/rightkit-versions.json +2 -2
- package/standalone-clone-verify.mjs +22 -1
package/build-release.mjs
CHANGED
|
@@ -89,7 +89,7 @@ try {
|
|
|
89
89
|
const cargoToml = requiredInputs.find((file) => /Cargo\.toml$/i.test(file));
|
|
90
90
|
const cacheIdentity = resolveSharedCacheIdentity({ cargoLockPath: cargoLock, cargoTomlPath: cargoToml, rustcVerbose: toolVersions.rustc, targetTriple: target.cargoTarget ?? target.targetTriple ?? target.rustTarget ?? toolVersions.rustHost, profile: target.profile ?? "release" });
|
|
91
91
|
const cacheKey = cacheIdentity.fingerprint;
|
|
92
|
-
const cacheMode = process.env.RIGHT_RELEASE_CACHE_MODE ?? (platform === "mac" ? "shared" : "legacy");
|
|
92
|
+
const cacheMode = process.env.RIGHT_RELEASE_CACHE_MODE ?? (platform === "mac" || platform === "win" ? "shared" : "legacy");
|
|
93
93
|
if (cacheMode !== "legacy" && cacheMode !== "shared") fail("RIGHT_RELEASE_CACHE_MODE must be legacy or shared");
|
|
94
94
|
const sharedCacheRoot = cacheMode === "shared" ? resolveSharedCacheRoot({ platform, env: process.env }) : undefined;
|
|
95
95
|
const sharedLayout = cacheMode === "shared"
|
package/build-release.test.mjs
CHANGED
|
@@ -2,12 +2,13 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
|
|
6
|
-
const source = readFileSync(path.join(path.dirname(
|
|
7
|
+
const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs"), "utf8");
|
|
7
8
|
|
|
8
|
-
test("macOS builds default to Cache V2 while explicit legacy mode remains available", () => {
|
|
9
|
+
test("macOS and Windows builds default to Cache V2 while explicit legacy mode remains available", () => {
|
|
9
10
|
assert.match(source, /RIGHT_RELEASE_CACHE_MODE/);
|
|
10
|
-
assert.match(source, /platform === "mac" \? "shared" : "legacy"/);
|
|
11
|
+
assert.match(source, /platform === "mac" \|\| platform === "win" \? "shared" : "legacy"/);
|
|
11
12
|
assert.match(source, /cacheMode !== "legacy" && cacheMode !== "shared"/);
|
|
12
13
|
assert.match(source, /resolveSharedCacheRoot/);
|
|
13
14
|
assert.match(source, /acquireSuiteBuildSlot/);
|
package/cache-command.test.mjs
CHANGED
|
@@ -4,8 +4,9 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import test from "node:test";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
|
-
const root = path.dirname(
|
|
9
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const cli = path.join(root, "cli", "right-release.mjs");
|
|
10
11
|
|
|
11
12
|
function run(args, cacheRoot, env = {}) {
|
package/cache-policy.mjs
CHANGED
|
@@ -35,13 +35,13 @@ export function resolveSharedCacheRoot({ platform = process.platform, env = proc
|
|
|
35
35
|
if (!isAbsoluteForPlatform(override, platform)) throw new Error("RIGHT_RELEASE_CACHE_ROOT must be an absolute path");
|
|
36
36
|
return resolveForPlatform(override, platform);
|
|
37
37
|
}
|
|
38
|
-
if (platform === "darwin" || platform === "mac") return path.join(home, "Library", "Caches", "RightSuite", "release");
|
|
38
|
+
if (platform === "darwin" || platform === "mac") return path.posix.join(home, "Library", "Caches", "RightSuite", "release");
|
|
39
39
|
if (platform === "win32" || platform === "win") {
|
|
40
40
|
const local = env.LOCALAPPDATA;
|
|
41
41
|
if (!local || !isAbsoluteForPlatform(local, platform)) throw new Error("LOCALAPPDATA must be an absolute path for the RightSuite cache");
|
|
42
42
|
return path.win32.resolve(local, "RightSuite", "Cache", "release");
|
|
43
43
|
}
|
|
44
|
-
return path.resolve(xdgCacheHome || env.XDG_CACHE_HOME || path.join(home, ".cache"), "rightsuite", "release");
|
|
44
|
+
return path.posix.resolve(xdgCacheHome || env.XDG_CACHE_HOME || path.posix.join(home, ".cache"), "rightsuite", "release");
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function resolveCacheLayout({ cacheRoot, platform, architecture, app, fingerprint, kind = "release" } = {}) {
|
|
@@ -417,7 +417,8 @@ function mkdirSafeDescendant(root, dir) {
|
|
|
417
417
|
else mkdirSync(current);
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
-
function
|
|
421
|
-
function
|
|
420
|
+
function isWindowsAbsolute(value) { return /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value); }
|
|
421
|
+
function isAbsoluteForPlatform(value) { return isWindowsAbsolute(value) || path.posix.isAbsolute(value); }
|
|
422
|
+
function resolveForPlatform(value) { return isWindowsAbsolute(value) ? path.win32.resolve(value) : path.posix.resolve(value); }
|
|
422
423
|
function readRustcVerbose() { const result = spawnSync("rustc", ["-vV"], { encoding: "utf8", windowsHide: true }); if (result.status !== 0) throw new Error(`rustc -vV failed: ${(result.stderr || result.error?.message || `exit ${result.status}`).trim()}`); return result.stdout.trim(); }
|
|
423
424
|
function cargoNativeFeatures(cargoTomlPath) { if (!cargoTomlPath || !existsSync(cargoTomlPath)) return []; return [...readFileSync(cargoTomlPath, "utf8").matchAll(/features\s*=\s*\[([^\]]+)\]/g)].flatMap((match) => match[1].match(/"([^"]+)"/g) ?? []).map((value) => value.slice(1, -1)).filter((value) => /sqlcipher|openssl/i.test(value)); }
|
package/cargo-contract.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
const SKIP_DIRECTORIES = new Set([".audit", ".cache", ".git", ".claude", ".worktrees", "node_modules", "target", "vendor"]);
|
|
6
|
+
const SKIP_DIRECTORIES = new Set([".audit", ".cache", ".git", ".claude", ".right-release", ".worktrees", "node_modules", "target", "vendor"]);
|
|
7
7
|
const CRATES_IO_SOURCES = new Set([
|
|
8
8
|
"registry+https://github.com/rust-lang/crates.io-index",
|
|
9
9
|
"registry+https://index.crates.io/",
|
|
@@ -8,7 +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, /COPYFILE_DISABLE=1 tar .*Test\.app\.tar\.gz.*Test\.app
|
|
11
|
+
assert.match(result.stdout, /COPYFILE_DISABLE=1 tar .*Test\.app\.tar\.gz.*Test\.app[\\/]Contents/);
|
|
12
12
|
assert.doesNotMatch(result.stdout, / Test\.app$/m);
|
|
13
13
|
assert.match(result.stdout, /tauri signer sign .*Test\.app\.tar\.gz/);
|
|
14
14
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
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": {
|
package/release-state.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, watch } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, watch } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { DEFAULT_SCCACHE_MAX_BYTES, resolveCacheLayout } from "./cache-policy.mjs";
|
|
6
6
|
|
|
@@ -118,6 +118,24 @@ export async function runBuildStateMachine({
|
|
|
118
118
|
const releaseId = `${app}-${version}-${commit.slice(0, 8)}`;
|
|
119
119
|
const platformDir = platform === "win" ? "windows" : platform === "mac" ? "mac" : platform;
|
|
120
120
|
const sealedDir = path.join(root, ".right-release", "sealed", releaseId, platformDir);
|
|
121
|
+
const sealedRoot = path.join(root, ".right-release", "sealed");
|
|
122
|
+
if (existsSync(sealedRoot)) {
|
|
123
|
+
for (const releaseEntry of readdirSync(sealedRoot, { withFileTypes: true })) {
|
|
124
|
+
if (!releaseEntry.isDirectory()) continue;
|
|
125
|
+
const releaseRoot = path.join(sealedRoot, releaseEntry.name);
|
|
126
|
+
for (const platformEntry of readdirSync(releaseRoot, { withFileTypes: true })) {
|
|
127
|
+
if (!platformEntry.isDirectory()) continue;
|
|
128
|
+
const manifestPath = path.join(releaseRoot, platformEntry.name, "release-manifest.json");
|
|
129
|
+
if (!existsSync(manifestPath)) continue;
|
|
130
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
131
|
+
if (manifest.app === app && manifest.version === version && manifest.commit !== commit) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`${app} version ${version} is already sealed from commit ${manifest.commit} (${manifest.releaseId}); bump the version before rebuilding`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
121
139
|
if (existsSync(path.join(sealedDir, "release-manifest.json"))) {
|
|
122
140
|
const sealed = verifySealedRelease(sealedDir);
|
|
123
141
|
if (sealed.manifest.app !== app || sealed.manifest.version !== version || sealed.manifest.commit !== commit) {
|
package/release-state.test.mjs
CHANGED
|
@@ -60,8 +60,8 @@ test("shared release environment is isolated from the app vault", () => {
|
|
|
60
60
|
cacheKey: "abcdef0123456789",
|
|
61
61
|
mode: "shared",
|
|
62
62
|
});
|
|
63
|
-
assert.match(env.CARGO_TARGET_DIR, /RightSuite
|
|
64
|
-
assert.match(env.CARGO_HOME, /RightSuite
|
|
63
|
+
assert.match(env.CARGO_TARGET_DIR, /RightSuite[\\/]release[\\/]targets[\\/]mac[\\/]fixture[\\/]abcdef0123456789$/);
|
|
64
|
+
assert.match(env.CARGO_HOME, /RightSuite[\\/]release[\\/]cargo-home$/);
|
|
65
65
|
assert.equal(env.RIGHT_RELEASE_CACHE_OWNER, "rightkit-v2");
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -150,6 +150,31 @@ test("missing runtime input fails before build preparation and cannot touch an e
|
|
|
150
150
|
assert.equal(readFileSync(fx.installer, "utf8"), "signed-installer");
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
test("a version already sealed from another commit cannot be rebuilt", async () => {
|
|
154
|
+
const fx = fixture();
|
|
155
|
+
let prepared = false;
|
|
156
|
+
await assert.rejects(
|
|
157
|
+
runBuildStateMachine({
|
|
158
|
+
root: fx.root,
|
|
159
|
+
app: "fixture",
|
|
160
|
+
version: fx.manifest.version,
|
|
161
|
+
commit: "1234567890abcdef",
|
|
162
|
+
platform: "win",
|
|
163
|
+
requiredInputs: [],
|
|
164
|
+
ops: {
|
|
165
|
+
preflight: async () => {},
|
|
166
|
+
prepare: async () => { prepared = true; },
|
|
167
|
+
build: async () => {},
|
|
168
|
+
sign: async () => {},
|
|
169
|
+
harden: async () => {},
|
|
170
|
+
seal: async () => {},
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
/version 1\.2\.3 is already sealed from commit abcdef1234567890/,
|
|
174
|
+
);
|
|
175
|
+
assert.equal(prepared, false);
|
|
176
|
+
});
|
|
177
|
+
|
|
153
178
|
test("an interrupted build leaves every previously sealed release untouched", async () => {
|
|
154
179
|
const fx = fixture();
|
|
155
180
|
await assert.rejects(
|
package/release.test.mjs
CHANGED
|
@@ -238,7 +238,7 @@ test("doctor rejects a selected target with missing or empty buildInputs.include
|
|
|
238
238
|
test("doctor passes a clean selected target", () => {
|
|
239
239
|
const result = runDoctor(fixture());
|
|
240
240
|
assert.equal(result.status, 0, result.stderr);
|
|
241
|
-
assert.match(result.stdout, /right-release 0\.2\.
|
|
241
|
+
assert.match(result.stdout, /right-release 0\.2\.43/);
|
|
242
242
|
});
|
|
243
243
|
|
|
244
244
|
test("doctor permits unrelated dirt", () => {
|
|
@@ -190,6 +190,28 @@ test("Cargo override contract accepts exact registry pins without repo-local ove
|
|
|
190
190
|
}), 0);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
test("Cargo contract ignores release caches and build outputs", () => {
|
|
194
|
+
const fixtureRoot = mkdtempSync(path.join(tmpdir(), "rightkit-cargo-scan-"));
|
|
195
|
+
const appRoot = path.join(fixtureRoot, "app");
|
|
196
|
+
mkdirSync(path.join(appRoot, ".right-release", "cache", "cargo-home", "registry", "cached"), { recursive: true });
|
|
197
|
+
writeFileSync(
|
|
198
|
+
path.join(appRoot, "Cargo.toml"),
|
|
199
|
+
'[package]\nname = "fixture"\nversion = "0.0.0"\nedition = "2021"\n[lib]\npath = "fixture.rs"\n',
|
|
200
|
+
"utf8",
|
|
201
|
+
);
|
|
202
|
+
writeFileSync(path.join(appRoot, "fixture.rs"), "", "utf8");
|
|
203
|
+
writeFileSync(
|
|
204
|
+
path.join(appRoot, ".right-release", "cache", "cargo-home", "registry", "cached", "Cargo.toml"),
|
|
205
|
+
"this is deliberately invalid cached TOML",
|
|
206
|
+
"utf8",
|
|
207
|
+
);
|
|
208
|
+
try {
|
|
209
|
+
assert.equal(validateRightKitCargoContract(appRoot, new Map(), "fixture"), 1);
|
|
210
|
+
} finally {
|
|
211
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
193
215
|
test("Cargo override contract rejects RightKit patches and replacements in repo-local Cargo config", () => {
|
|
194
216
|
for (const config of [
|
|
195
217
|
{ localConfig: `[patch.crates-io]\nrightkit-license = { path = "../rightkit-license" }` },
|
|
@@ -410,7 +432,7 @@ test("every app platform requires effective non-empty build inputs", () => {
|
|
|
410
432
|
test("RightKit exposes one current version manifest", () => {
|
|
411
433
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
412
434
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
413
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
435
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.44");
|
|
414
436
|
assert.equal(versions.npm["@rightkit/legal"], "0.2.0");
|
|
415
437
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
416
438
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
|
@@ -422,11 +444,11 @@ test("RightKit exposes one current version manifest", () => {
|
|
|
422
444
|
"@rightkit/license": "0.1.6",
|
|
423
445
|
});
|
|
424
446
|
assert.deepEqual(versions.legacyNpm, {
|
|
425
|
-
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41"],
|
|
447
|
+
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43"],
|
|
426
448
|
});
|
|
427
449
|
assert.ok(
|
|
428
|
-
new Set([versions.npm["@rightkit/release"], ...versions.legacyNpm["@rightkit/release"]]).has("0.2.
|
|
429
|
-
"the previously published @rightkit/release 0.2.
|
|
450
|
+
new Set([versions.npm["@rightkit/release"], ...versions.legacyNpm["@rightkit/release"]]).has("0.2.42"),
|
|
451
|
+
"the previously published @rightkit/release 0.2.42 must remain accepted during the 0.2.44 rollout",
|
|
430
452
|
);
|
|
431
453
|
const licensePackage = JSON.parse(readFileSync(
|
|
432
454
|
path.join(workspace, "tools/rightkit/packages/license/package.json"),
|
package/rightkit-versions.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"@rightkit/logs": "0.1.3",
|
|
8
8
|
"@rightkit/platform-ui": "0.1.0",
|
|
9
9
|
"@rightkit/qa": "0.1.0",
|
|
10
|
-
"@rightkit/release": "0.2.
|
|
10
|
+
"@rightkit/release": "0.2.44",
|
|
11
11
|
"@rightkit/tauri": "0.1.0",
|
|
12
12
|
"@rightkit/updates": "0.2.3"
|
|
13
13
|
},
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@rightkit/license": "0.1.6"
|
|
18
18
|
},
|
|
19
19
|
"legacyNpm": {
|
|
20
|
-
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41"]
|
|
20
|
+
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43"]
|
|
21
21
|
},
|
|
22
22
|
"cargo": {
|
|
23
23
|
"rightkit-license": "0.1.2",
|
|
@@ -32,8 +32,29 @@ function run(command, args, cwd) {
|
|
|
32
32
|
function runPnpm(args, cwd, command) {
|
|
33
33
|
if (process.platform !== "win32" || command !== "pnpm") return run(command, args, cwd);
|
|
34
34
|
for (const entry of String(process.env.PATH ?? "").split(path.delimiter)) {
|
|
35
|
-
const
|
|
35
|
+
const bin = entry.replace(/^"|"$/g, "");
|
|
36
|
+
const cli = path.join(bin, "node_modules", "pnpm", "bin", "pnpm.mjs");
|
|
36
37
|
if (existsSync(cli)) return run(process.execPath, [cli, ...args], cwd);
|
|
38
|
+
const shim = path.join(bin, "pnpm.cmd");
|
|
39
|
+
if (existsSync(shim)) {
|
|
40
|
+
if (!args.every((value) => /^[A-Za-z0-9._:@+=-]+$/.test(value))) throw new Error("unsafe pnpm argument");
|
|
41
|
+
const commandLine = `"${shim}" ${args.join(" ")}`;
|
|
42
|
+
const result = spawnSync(commandLine, {
|
|
43
|
+
cwd,
|
|
44
|
+
encoding: "utf8",
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
47
|
+
env: { ...process.env, CI: "1", GIT_TERMINAL_PROMPT: "0" },
|
|
48
|
+
shell: true,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
command: commandLine,
|
|
52
|
+
status: result.status,
|
|
53
|
+
stdout: String(result.stdout ?? "").trim().slice(-8000),
|
|
54
|
+
stderr: String(result.stderr ?? "").trim().slice(-8000),
|
|
55
|
+
error: result.error?.message,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
37
58
|
}
|
|
38
59
|
throw new Error("pnpm installation could not be resolved from PATH");
|
|
39
60
|
}
|