@rightkit/release 0.2.32 → 0.2.34
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 +22 -16
- package/package.json +1 -1
- package/release-state.mjs +33 -0
- package/release-state.test.mjs +16 -0
- package/right-suite-contract.test.mjs +1 -1
- package/rightkit-versions.json +1 -1
package/build-release.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { spawn, spawnSync } from "node:child_process";
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
|
-
import { cacheFingerprint, releaseEnvironment, runBuildStateMachine } from "./release-state.mjs";
|
|
23
|
+
import { cacheFingerprint, commandOutputPortable, releaseEnvironment, resolveReleaseLayout, runBuildStateMachine } from "./release-state.mjs";
|
|
24
24
|
|
|
25
25
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
26
26
|
const WORKER = path.join(TOOL_ROOT, "release.mjs");
|
|
@@ -44,7 +44,7 @@ const invocationRoot = path.dirname(path.resolve(configName));
|
|
|
44
44
|
const repoRoot = git(invocationRoot, ["rev-parse", "--show-toplevel"]);
|
|
45
45
|
const relativeConfig = path.relative(repoRoot, path.resolve(configName)).replaceAll("\\", "/");
|
|
46
46
|
if (relativeConfig.startsWith("../")) fail("release config must live inside the Git repository");
|
|
47
|
-
const vaultRoot =
|
|
47
|
+
const vaultRoot = resolveReleaseLayout({ repoRoot, configPath: path.resolve(configName) }).vaultRoot;
|
|
48
48
|
mkdirSync(path.join(vaultRoot, "locks"), { recursive: true });
|
|
49
49
|
const lock = acquireLock(path.join(vaultRoot, "locks", `${platform}.lock.json`));
|
|
50
50
|
let child = null;
|
|
@@ -71,6 +71,8 @@ try {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const worktreeConfigPath = path.join(worktree, relativeConfig);
|
|
74
|
+
const appRoot = path.dirname(worktreeConfigPath);
|
|
75
|
+
const layout = resolveReleaseLayout({ repoRoot, configPath: path.resolve(configName) });
|
|
74
76
|
const config = (await import(`${pathToFileURL(worktreeConfigPath).href}?commit=${commit}`)).default;
|
|
75
77
|
if (!config?.app || !config?.version) fail("release config must expose app and version");
|
|
76
78
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(config.app) || !/^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(config.version)) {
|
|
@@ -78,7 +80,7 @@ try {
|
|
|
78
80
|
}
|
|
79
81
|
const target = config.targets?.[platform];
|
|
80
82
|
if (!target?.package) fail(`${config.app} has no ${platform} package command`);
|
|
81
|
-
const requiredInputs = inputPaths(
|
|
83
|
+
const requiredInputs = inputPaths(appRoot, worktreeConfigPath, target);
|
|
82
84
|
const toolVersions = collectToolVersions(config.packageManager ?? "pnpm");
|
|
83
85
|
const inputHashes = hashInputs(requiredInputs);
|
|
84
86
|
const cargoLock = requiredInputs.find((file) => /Cargo\.lock$/i.test(file));
|
|
@@ -92,6 +94,8 @@ try {
|
|
|
92
94
|
const env = {
|
|
93
95
|
...process.env,
|
|
94
96
|
...releaseEnvironment({ root: vaultRoot, platform, cacheKey, kind: "release" }),
|
|
97
|
+
RIGHT_RELEASE_REPO_ROOT: layout.repoRoot,
|
|
98
|
+
RIGHT_RELEASE_APP_ROOT: layout.appRoot,
|
|
95
99
|
};
|
|
96
100
|
if (!commandExists("sccache")) delete env.RUSTC_WRAPPER;
|
|
97
101
|
const releaseId = `${config.app}-${config.version}-${shortCommit}`;
|
|
@@ -123,7 +127,7 @@ try {
|
|
|
123
127
|
assertDiskSpace(repoRoot, target.preflight?.minFreeGb ?? 20);
|
|
124
128
|
assertExecutables(["git", config.packageManager ?? "pnpm", "cargo", "rustc", ...(target.preflight?.executables ?? [])]);
|
|
125
129
|
for (const name of target.preflight?.env ?? []) if (!process.env[name]) fail(`missing required environment variable: ${name}`);
|
|
126
|
-
for (const command of target.preflight?.commands ?? []) runChecked(command.cmd, command.args ?? [], path.resolve(
|
|
130
|
+
for (const command of target.preflight?.commands ?? []) runChecked(command.cmd, command.args ?? [], path.resolve(appRoot, command.cwd ?? "."), env);
|
|
127
131
|
mkdirSync(buildRoot, { recursive: true });
|
|
128
132
|
mkdirSync(stateRoot, { recursive: true });
|
|
129
133
|
writeJson(path.join(buildRoot, "release-inputs.lock.json"), lockPayload);
|
|
@@ -132,16 +136,16 @@ try {
|
|
|
132
136
|
},
|
|
133
137
|
prepare: async () => {
|
|
134
138
|
const cacheTarget = env.CARGO_TARGET_DIR;
|
|
135
|
-
const targetLink = path.join(
|
|
139
|
+
const targetLink = path.join(appRoot, "src-tauri", "target");
|
|
136
140
|
mkdirSync(cacheTarget, { recursive: true });
|
|
137
141
|
if (!existsSync(targetLink)) symlinkSync(cacheTarget, targetLink, process.platform === "win32" ? "junction" : "dir");
|
|
138
142
|
else if (realpathSync(targetLink) !== realpathSync(cacheTarget)) {
|
|
139
143
|
fail(`worktree target exists and is not the release cache link: ${targetLink}`);
|
|
140
144
|
}
|
|
141
|
-
await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"],
|
|
145
|
+
await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"], appRoot, env, cacheTarget);
|
|
142
146
|
},
|
|
143
147
|
build: async () => {
|
|
144
|
-
await runProgress(process.execPath, [WORKER, "--config", worktreeConfigPath, "--platform", platform, "--no-upload"],
|
|
148
|
+
await runProgress(process.execPath, [WORKER, "--config", worktreeConfigPath, "--platform", platform, "--no-upload"], appRoot, env, env.CARGO_TARGET_DIR);
|
|
145
149
|
checkpoint(stateRoot, "build_complete");
|
|
146
150
|
checkpoint(stateRoot, "signed");
|
|
147
151
|
checkpoint(stateRoot, "hardened");
|
|
@@ -158,14 +162,14 @@ try {
|
|
|
158
162
|
lock.release();
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
function inputPaths(
|
|
165
|
+
function inputPaths(appRoot, configPath, target) {
|
|
162
166
|
const candidates = [
|
|
163
167
|
configPath,
|
|
164
|
-
path.join(
|
|
165
|
-
path.join(
|
|
166
|
-
path.join(
|
|
167
|
-
path.join(
|
|
168
|
-
...(target.preflight?.files ?? []).map((file) => path.resolve(
|
|
168
|
+
path.join(appRoot, "package.json"),
|
|
169
|
+
path.join(appRoot, "pnpm-lock.yaml"),
|
|
170
|
+
path.join(appRoot, "src-tauri", "Cargo.toml"),
|
|
171
|
+
path.join(appRoot, "src-tauri", "Cargo.lock"),
|
|
172
|
+
...(target.preflight?.files ?? []).map((file) => path.resolve(appRoot, expandEnv(file))),
|
|
169
173
|
];
|
|
170
174
|
const required = [...new Set(candidates)];
|
|
171
175
|
for (const file of required) if (!existsSync(file)) fail(`missing required release input: ${file}`);
|
|
@@ -338,9 +342,11 @@ function commandExists(name) {
|
|
|
338
342
|
}
|
|
339
343
|
|
|
340
344
|
function commandOutput(cmd, runArgs) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
345
|
+
try {
|
|
346
|
+
return commandOutputPortable(cmd, runArgs);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
fail(`required command failed: ${error.message}`);
|
|
349
|
+
}
|
|
344
350
|
}
|
|
345
351
|
|
|
346
352
|
function git(cwd, runArgs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
@@ -11,6 +12,38 @@ export function cacheFingerprint({ cargoLockSha256, rustc, target, features = []
|
|
|
11
12
|
});
|
|
12
13
|
return createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
export function resolveReleaseLayout({ repoRoot, configPath }) {
|
|
17
|
+
const resolvedRepoRoot = path.resolve(repoRoot);
|
|
18
|
+
const appRoot = path.dirname(path.resolve(configPath));
|
|
19
|
+
return {
|
|
20
|
+
repoRoot: resolvedRepoRoot,
|
|
21
|
+
appRoot,
|
|
22
|
+
vaultRoot: path.join(resolvedRepoRoot, ".right-release"),
|
|
23
|
+
targetLink: path.join(appRoot, "src-tauri", "target"),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function commandOutputPortable(cmd, args, { cwd, env = process.env } = {}) {
|
|
28
|
+
const windows = process.platform === "win32";
|
|
29
|
+
if (windows && ![cmd, ...args].every((value) => /^[A-Za-z0-9._:/@+=-]+$/.test(value))) {
|
|
30
|
+
throw new Error("unsafe Windows command token");
|
|
31
|
+
}
|
|
32
|
+
const executable = windows ? (env.ComSpec || env.COMSPEC || "cmd.exe") : cmd;
|
|
33
|
+
const executableArgs = windows ? ["/d", "/s", "/c", [cmd, ...args].join(" ")] : args;
|
|
34
|
+
const result = spawnSync(executable, executableArgs, {
|
|
35
|
+
cwd,
|
|
36
|
+
env,
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
windowsHide: true,
|
|
39
|
+
});
|
|
40
|
+
if (result.status !== 0) {
|
|
41
|
+
const detail = result.error?.message || result.stderr?.trim() || `exit ${result.status}`;
|
|
42
|
+
throw new Error(`${cmd} ${args.join(" ")} failed: ${detail}`);
|
|
43
|
+
}
|
|
44
|
+
return result.stdout.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
export function releaseEnvironment({ root, platform, cacheKey, kind = "release" }) {
|
|
15
48
|
if (kind !== "release" && kind !== "test") throw new Error(`invalid target kind: ${kind}`);
|
|
16
49
|
const targetKind = kind === "release" ? "cargo-target" : "test-target";
|
package/release-state.test.mjs
CHANGED
|
@@ -7,12 +7,28 @@ import test from "node:test";
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
cacheFingerprint,
|
|
10
|
+
commandOutputPortable,
|
|
11
|
+
resolveReleaseLayout,
|
|
10
12
|
releaseEnvironment,
|
|
11
13
|
runBuildStateMachine,
|
|
12
14
|
runUploadStateMachine,
|
|
13
15
|
verifySealedRelease,
|
|
14
16
|
} from "./release-state.mjs";
|
|
15
17
|
|
|
18
|
+
test("portable command capture resolves Windows command shims", { skip: process.platform !== "win32" }, () => {
|
|
19
|
+
assert.match(commandOutputPortable("pnpm", ["--version"]), /^11\.12\.0$/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("nested app configs keep the vault at repo root and build from the app root", () => {
|
|
23
|
+
const layout = resolveReleaseLayout({
|
|
24
|
+
repoRoot: "D:/suite/heardright",
|
|
25
|
+
configPath: "D:/suite/heardright/tauri-app-next/right-release.config.mjs",
|
|
26
|
+
});
|
|
27
|
+
assert.equal(layout.vaultRoot, path.resolve("D:/suite/heardright/.right-release"));
|
|
28
|
+
assert.equal(layout.appRoot, path.resolve("D:/suite/heardright/tauri-app-next"));
|
|
29
|
+
assert.equal(layout.targetLink, path.resolve("D:/suite/heardright/tauri-app-next/src-tauri/target"));
|
|
30
|
+
});
|
|
31
|
+
|
|
16
32
|
function sha256(value) {
|
|
17
33
|
return createHash("sha256").update(value).digest("hex");
|
|
18
34
|
}
|
|
@@ -317,7 +317,7 @@ test("Cargo version contract rejects staged versions that mismatch canonical man
|
|
|
317
317
|
test("RightKit exposes one current version manifest", () => {
|
|
318
318
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
319
319
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
320
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
320
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.34");
|
|
321
321
|
assert.equal(versions.npm["@rightkit/legal"], "0.2.0");
|
|
322
322
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
323
323
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
package/rightkit-versions.json
CHANGED