@rightkit/release 0.2.134 → 0.2.136
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 +62 -0
- package/addon-command.mjs +53 -34
- package/apple-profile-renew.mjs +30 -12
- package/artifact-storage.mjs +53 -12
- package/background-process.mjs +18 -0
- package/build-release.mjs +4 -3
- package/cache-command.mjs +4 -3
- package/cargo-contract.mjs +1 -1
- package/cargo-guard.mjs +4 -3
- package/cargo-registry-readback.mjs +51 -0
- package/cargo-target.mjs +3 -1
- package/cli/right-release.mjs +26 -0
- package/codesign-argv.mjs +64 -0
- package/codesign-environment.mjs +58 -0
- package/codesign-shim.mjs +51 -0
- package/committed-source.mjs +3 -3
- package/development.mjs +11 -9
- package/direct-bootstrap.mjs +19 -9
- package/dmg-build.mjs +117 -0
- package/dmg-layout.mjs +123 -0
- package/github-release.mjs +74 -37
- package/heavy-command.mjs +3 -2
- package/install-app.mjs +358 -0
- package/inventory-check.mjs +980 -0
- package/macos-notarization.mjs +24 -8
- package/macos-signing-session.mjs +50 -2
- package/managed-cargo.mjs +23 -19
- package/managed-host-secrets.mjs +30 -0
- package/managed-roots.mjs +39 -0
- package/model-promote.mjs +31 -37
- package/notarize-macos.mjs +5 -5
- package/notary-auth.mjs +4 -0
- package/osascript-guard.mjs +24 -0
- package/ownership-approval-command.mjs +15 -4
- package/ownership-approval.mjs +114 -49
- package/ownership-availability.mjs +25 -0
- package/ownership-catalog-config.json +18 -0
- package/ownership-catalog.mjs +94 -0
- package/ownership-command.mjs +17 -10
- package/ownership-declaration.json +15 -0
- package/ownership-default-policy.json +1 -11
- package/ownership-evidence.mjs +10 -8
- package/ownership-git-inventory.mjs +29 -1
- package/ownership-helper-paths.json +4 -4
- package/ownership-hooks.mjs +107 -0
- package/ownership-internal-owners.json +44 -0
- package/ownership-key-authority.mjs +112 -7
- package/ownership-key-command.mjs +76 -19
- package/ownership-key-trust.mjs +97 -9
- package/ownership-lockfiles.mjs +311 -0
- package/ownership-report-command.mjs +12 -3
- package/ownership-report.mjs +20 -17
- package/ownership-repositories.json +19 -0
- package/ownership-scan-worker.mjs +96 -11
- package/ownership-source.mjs +39 -0
- package/ownership-staged.mjs +193 -0
- package/ownership-verdict.mjs +74 -0
- package/ownership.mjs +215 -107
- package/package.json +1 -1
- package/protected-credentials.mjs +111 -0
- package/prune-r2.mjs +4 -3
- package/pub-upload.mjs +3 -85
- package/publication-artifacts.mjs +108 -0
- package/publication-gate.mjs +28 -7
- package/publish-cargo.mjs +47 -16
- package/publish-swift.mjs +46 -17
- package/publish-update.mjs +61 -39
- package/r2-verified-upload.mjs +41 -0
- package/registry-parity.mjs +5 -3
- package/release-state.mjs +1 -0
- package/release-token.mjs +2 -0
- package/release.mjs +2 -1
- package/rightkit-drift.mjs +3 -2
- package/rightkit-ownership.json +2028 -139
- package/rightkit-signing-agent.mjs +161 -19
- package/rightkit-sync.mjs +6 -2
- package/rightkit-versions.json +220 -106
- package/runtime-artifact-manifest.mjs +20 -6
- package/sign-updater.mjs +5 -0
- package/signing-agent-client.mjs +78 -22
- package/signing-agent-command.mjs +5 -4
- package/signing-agent-install.mjs +31 -4
- package/standalone-clone-verify.mjs +3 -2
- package/tauri-bundle-marker.mjs +3 -2
- package/upload-large.mjs +36 -82
- package/verifier.mjs +5 -1
package/cli/right-release.mjs
CHANGED
|
@@ -42,6 +42,7 @@ try {
|
|
|
42
42
|
if (githubBuildCommands.has(first)
|
|
43
43
|
|| first?.startsWith("-")
|
|
44
44
|
|| (first === "addon" && args[1] === "build")
|
|
45
|
+
|| (first === "dmg" && args[1] === "build")
|
|
45
46
|
|| (first === "publish" && ["cargo", "swift"].includes(args[1]))) {
|
|
46
47
|
assertReleaseExecutionAllowed({ command: `right-release ${args.join(" ")}`.trim() });
|
|
47
48
|
}
|
|
@@ -63,6 +64,8 @@ if (first === "--version" || first === "-v") {
|
|
|
63
64
|
} else {
|
|
64
65
|
run("release.mjs", ["--doctor", ...args.slice(1)]);
|
|
65
66
|
}
|
|
67
|
+
} else if (first === "hooks") {
|
|
68
|
+
run("ownership-hooks.mjs", args.slice(1));
|
|
66
69
|
} else if (first === "suite-doctor") {
|
|
67
70
|
runTest(
|
|
68
71
|
["--test", path.join(packageRoot, "right-suite-contract.test.mjs")],
|
|
@@ -70,6 +73,10 @@ if (first === "--version" || first === "-v") {
|
|
|
70
73
|
);
|
|
71
74
|
} else if (first === "cache") {
|
|
72
75
|
run("cache-command.mjs", args.slice(1));
|
|
76
|
+
} else if (first === "install-app") {
|
|
77
|
+
run("install-app.mjs", args.slice(1));
|
|
78
|
+
} else if (first === "dmg") {
|
|
79
|
+
run("dmg-build.mjs", args.slice(1));
|
|
73
80
|
} else if (first === "apple") {
|
|
74
81
|
run("apple-profile-renew.mjs", args.slice(1));
|
|
75
82
|
} else if (first === "publish") {
|
|
@@ -83,6 +90,12 @@ if (first === "--version" || first === "-v") {
|
|
|
83
90
|
} else {
|
|
84
91
|
run("upload-release.mjs", rest);
|
|
85
92
|
}
|
|
93
|
+
} else if (first === "inventory") {
|
|
94
|
+
if (args[1] !== "check") {
|
|
95
|
+
console.error("right-release inventory: expected check");
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
run("inventory-check.mjs", args.slice(2));
|
|
86
99
|
} else if (first === "model") {
|
|
87
100
|
const rest = args.slice(1);
|
|
88
101
|
if (rest[0] !== "promote") {
|
|
@@ -155,12 +168,20 @@ function printHelp() {
|
|
|
155
168
|
console.log(`right-release <command> [options]
|
|
156
169
|
|
|
157
170
|
Commands:
|
|
171
|
+
dmg build --app <.app> --out <dmg> --volname <name> [--background <png>]
|
|
172
|
+
[--icon-size N] [--app-position x,y] [--applications-position x,y] [--window w,h]
|
|
173
|
+
Build branded UDZO image without Finder
|
|
174
|
+
install-app --dmg <path.dmg> [--app-name Pulse.app] [--bundle-id <id>] [--dest /Applications]
|
|
175
|
+
[--relaunch|--no-relaunch] [--json]
|
|
176
|
+
Verify a signed DMG, quit, replace & relaunch hidden; no UI, no osascript (macOS)
|
|
158
177
|
build [--platform mac|win] [--skip-checks] [--resume-from <phase>]
|
|
159
178
|
Resume macOS at prepackage, sign, package, post-sign, or harden
|
|
160
179
|
dev --platform win|mac [--config <path>] [--dry-run] [--build-only]
|
|
161
180
|
Incremental unsigned build & optional local install
|
|
162
181
|
upload --release <id> --platform mac|win --tier patch|update
|
|
163
182
|
Upload one sealed release; never builds
|
|
183
|
+
publish-update --release <id> --platform mac|win --tier patch|update
|
|
184
|
+
Upload sealed bytes to R2 & register RightApps
|
|
164
185
|
release Compatibility alias for build
|
|
165
186
|
publish Compatibility alias for upload
|
|
166
187
|
publish npm --package @rightkit/<name> [--dry-run] Gate preflight for npm publish
|
|
@@ -169,6 +190,8 @@ Commands:
|
|
|
169
190
|
(npm-rightkit-publish) and verify registry bytes
|
|
170
191
|
publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
|
|
171
192
|
publish swift [--repo <git-url>] [--dry-run] Test, tag, resolve a fresh exact-pin consumer, and publish RightKitSwift to its git repo
|
|
193
|
+
inventory check [--json] [--warn-only] [--md <file>] [--no-cache]
|
|
194
|
+
Registry, account, dependency, app & hygiene drift (public endpoints)
|
|
172
195
|
model promote --authority <app-key> --config <file> [--dry-run]
|
|
173
196
|
Sign and promote one runtime/model artifact
|
|
174
197
|
addon doctor|build|upload|adopt <options> Manage a signed headless add-on
|
|
@@ -179,10 +202,13 @@ Commands:
|
|
|
179
202
|
doctor --all Verify all Right Suite app release contracts
|
|
180
203
|
suite-doctor Verify all local Right Suite repositories
|
|
181
204
|
ownership [--app <dir>] [--json] Fail on app copies of RightKit-owned capabilities
|
|
205
|
+
ownership --staged [--app <dir>] [--json] Pre-commit gate: scan the staged index for block-tier findings
|
|
206
|
+
hooks install [--app <dir>] Install the advisory pre-commit ownership gate (pinned @rightkit/release)
|
|
182
207
|
ownership enroll [--root] [--name <device>] [--root-fingerprint <sha256>] [--app <dir>]
|
|
183
208
|
Enroll one native key or create a signed-device request
|
|
184
209
|
ownership approve-key <fingerprint|request.json> Approve one pending device key from an active host
|
|
185
210
|
ownership revoke-key <fingerprint> Revoke one active device key
|
|
211
|
+
ownership trust-sync Advance this host's protected trust to the committed key log
|
|
186
212
|
ownership-approval --draft <input.json> [--out <draft.json>]
|
|
187
213
|
Draft an exception for native owner approval
|
|
188
214
|
ownership-approval --verify <signed.json> --context <scan.json>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
// Closed grammar shared by shim & agent. Never accept executable names, an
|
|
4
|
+
// environment, keychains, detached signatures, or file-producing display flags.
|
|
5
|
+
export function parseCodesignArgv(argv, cwd) {
|
|
6
|
+
const deny = () => { const error = new Error("Unsupported codesign argv"); error.code = "MAC_SIGNING_AGENT_ARGV_DENIED"; throw error; };
|
|
7
|
+
if (!Array.isArray(argv) || !argv.length || argv.length > 8192
|
|
8
|
+
|| argv.some((arg) => typeof arg !== "string" || !arg || arg.includes("\0") || arg.length > 32768)
|
|
9
|
+
|| typeof cwd !== "string" || !path.isAbsolute(cwd)) deny();
|
|
10
|
+
const targets = [], inputs = [], options = [];
|
|
11
|
+
let mode, verbose = false, operands = false;
|
|
12
|
+
const operation = (next) => { if (mode) deny(); mode = next; };
|
|
13
|
+
const value = (inline, i) => {
|
|
14
|
+
const result = inline ?? argv[i + 1];
|
|
15
|
+
if (!result || result.startsWith("--")) deny();
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const arg = argv[i];
|
|
20
|
+
if (operands || !arg.startsWith("-")) { targets.push(path.isAbsolute(arg) ? arg : `${cwd}/${arg}`); continue; }
|
|
21
|
+
if (arg === "--") { operands = true; continue; }
|
|
22
|
+
if (/^-[dv]+$/.test(arg)) {
|
|
23
|
+
if (arg.includes("d")) { if ((arg.match(/d/g) ?? []).length !== 1) deny(); operation("display"); }
|
|
24
|
+
if (arg.includes("v")) verbose = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const equal = arg.indexOf("=");
|
|
28
|
+
const flag = equal < 0 ? arg : arg.slice(0, equal);
|
|
29
|
+
const inline = equal < 0 ? undefined : arg.slice(equal + 1);
|
|
30
|
+
if (flag === "--display" && inline === undefined) { operation("display"); continue; }
|
|
31
|
+
if (flag === "--verify" && inline === undefined) { operation("verify"); continue; }
|
|
32
|
+
if (flag === "--verbose") { if (inline !== undefined && !/^\d{1,2}$/.test(inline)) deny(); verbose = true; continue; }
|
|
33
|
+
if (["-s", "--sign"].includes(flag) || /^-s.+/.test(arg)) {
|
|
34
|
+
const attached = flag === "-s" || flag === "--sign" ? inline : arg.slice(2);
|
|
35
|
+
const identity = value(attached, i);
|
|
36
|
+
if (identity.length > 512 || (identity.startsWith("-") && identity !== "-")) deny();
|
|
37
|
+
operation("sign"); if (attached === undefined) i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (["--force", "--deep", "--strict"].includes(flag) && inline === undefined) { options.push(flag); continue; }
|
|
41
|
+
if (flag === "--timestamp" && (inline === undefined || inline === "none")) { options.push(flag); continue; }
|
|
42
|
+
if (["--options", "--identifier", "--preserve-metadata", "--entitlements", "--requirements"].includes(flag) || arg === "-r-") {
|
|
43
|
+
const parameter = arg === "-r-" ? "-" : value(inline, i);
|
|
44
|
+
if (arg !== "-r-" && inline === undefined) i++;
|
|
45
|
+
if (flag === "--options" && !/^(runtime|hard|kill|host|expires|restrict|library)(,(runtime|hard|kill|host|expires|restrict|library))*$/.test(parameter)) deny();
|
|
46
|
+
if (flag === "--preserve-metadata" && !/^(identifier|entitlements|requirements|flags|runtime)(,(identifier|entitlements|requirements|flags|runtime))*$/.test(parameter)) deny();
|
|
47
|
+
if (flag === "--identifier" && (parameter.startsWith("-") || parameter.length > 512)) deny();
|
|
48
|
+
options.push(arg === "-r-" ? "--requirements" : flag);
|
|
49
|
+
if (flag === "--entitlements") inputs.push({ value: parameter, path: path.isAbsolute(parameter) ? parameter : `${cwd}/${parameter}` });
|
|
50
|
+
if ((flag === "--requirements" || arg === "-r-") && parameter !== "-") deny();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
deny();
|
|
54
|
+
}
|
|
55
|
+
mode ??= verbose ? "verify" : undefined;
|
|
56
|
+
if (!mode || !targets.length || targets.length > 4096) deny();
|
|
57
|
+
const permitted = mode === "sign"
|
|
58
|
+
? ["--force", "--options", "--timestamp", "--entitlements", "--deep", "--preserve-metadata", "--identifier"]
|
|
59
|
+
: mode === "verify" ? ["--strict", "--deep"] : ["--entitlements", "--requirements"];
|
|
60
|
+
if (options.some((flag) => !permitted.includes(flag))) deny();
|
|
61
|
+
if (mode === "display" && inputs.some((input) => ![":-", "-"].includes(input.value))) deny();
|
|
62
|
+
if (mode === "sign" && inputs.some((input) => [":-", "-"].includes(input.value))) deny();
|
|
63
|
+
return { mode, targets, entitlements: mode === "sign" ? inputs.map((input) => input.path) : [] };
|
|
64
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { detectMacSigningSession } from "./macos-signing-session.mjs";
|
|
6
|
+
import { ensurePrivateDirectory, privatePath } from "./rightkit-signing-agent.mjs";
|
|
7
|
+
import { resolveManagedRoot } from "./artifact-storage.mjs";
|
|
8
|
+
|
|
9
|
+
const quote = (text) => `'${String(text).replaceAll("'", "'\\''")}'`;
|
|
10
|
+
export function codesignShimScript({ route, node = process.execPath,
|
|
11
|
+
entrypoint = fileURLToPath(new URL("./codesign-shim.mjs", import.meta.url)), realCodesign = "/usr/bin/codesign" }) {
|
|
12
|
+
return `#!/bin/sh\nif [ -n "\${SSH_CONNECTION-}\${SSH_TTY-}\${SSH_CLIENT-}" ] || [ ${quote(route)} = agent ]; then
|
|
13
|
+
exec ${quote(node)} ${quote(entrypoint)} "$@"
|
|
14
|
+
fi
|
|
15
|
+
exec ${quote(realCodesign)} "$@"\n`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function macosCodesignEnvironment(env = process.env, { platform = process.platform, io = fs,
|
|
19
|
+
detect = detectMacSigningSession, node = process.execPath } = {}) {
|
|
20
|
+
if (platform !== "darwin") return env;
|
|
21
|
+
const route = detect({ env }).guiRunner ? "agent" : "direct";
|
|
22
|
+
const script = codesignShimScript({ route, node });
|
|
23
|
+
const automationScript = `#!/bin/sh\nexec ${quote(node)} ${quote(fileURLToPath(new URL("./osascript-guard.mjs", import.meta.url)))} "$@"\n`;
|
|
24
|
+
// Tauri's script invokes /usr/bin/osascript, bypassing PATH. Its env-bash
|
|
25
|
+
// shebang & app-side `bash bundle_dmg.sh` calls both traverse this wrapper.
|
|
26
|
+
const bashScript = `#!/bin/sh\nlayout_script=\nskip_layout=0\nfor arg in "$@"; do
|
|
27
|
+
case "$arg" in
|
|
28
|
+
*/bundle_dmg.sh|bundle_dmg.sh) layout_script="$arg" ;;
|
|
29
|
+
--skip-jenkins) skip_layout=1 ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
if [ -n "$layout_script" ] && [ "$skip_layout" = 0 ]; then
|
|
33
|
+
exec ${quote(node)} ${quote(fileURLToPath(new URL("./osascript-guard.mjs", import.meta.url)))} --caller "$layout_script"
|
|
34
|
+
fi
|
|
35
|
+
exec /bin/bash "$@"\n`;
|
|
36
|
+
// Immutable wrappers avoid races between GUI & SSH builds or package versions.
|
|
37
|
+
const identity = createHash("sha256").update(script).update(automationScript).update(bashScript).digest("hex").slice(0, 24);
|
|
38
|
+
const bin = path.join(resolveManagedRoot({ env }), "signing", "codesign-bin", identity);
|
|
39
|
+
ensurePrivateDirectory(bin, { io });
|
|
40
|
+
const file = path.join(bin, "codesign");
|
|
41
|
+
if (io.existsSync(file)) {
|
|
42
|
+
privatePath(file, 0o700, { io });
|
|
43
|
+
if (io.readFileSync(file, "utf8") !== script) throw new Error("Managed codesign shim content changed");
|
|
44
|
+
} else io.writeFileSync(file, script, { mode: 0o700, flag: "wx" });
|
|
45
|
+
const automationFile = path.join(bin, "osascript");
|
|
46
|
+
if (io.existsSync(automationFile)) {
|
|
47
|
+
privatePath(automationFile, 0o700, { io });
|
|
48
|
+
if (io.readFileSync(automationFile, "utf8") !== automationScript) throw new Error("Managed osascript guard content changed");
|
|
49
|
+
} else io.writeFileSync(automationFile, automationScript, { mode: 0o700, flag: "wx" });
|
|
50
|
+
const bashFile = path.join(bin, "bash");
|
|
51
|
+
if (io.existsSync(bashFile)) {
|
|
52
|
+
privatePath(bashFile, 0o700, { io });
|
|
53
|
+
if (io.readFileSync(bashFile, "utf8") !== bashScript) throw new Error("Managed bash guard content changed");
|
|
54
|
+
} else io.writeFileSync(bashFile, bashScript, { mode: 0o700, flag: "wx" });
|
|
55
|
+
// Keep every other shim (especially managed Cargo) in its original order.
|
|
56
|
+
return { ...env, CI: "true", TAURI_BUNDLER_DMG_IGNORE_CI: "false",
|
|
57
|
+
PATH: [bin, ...(env.PATH ?? "").split(path.delimiter).filter((entry) => entry !== bin)].join(path.delimiter) };
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "./background-process.mjs";
|
|
7
|
+
import { parseCodesignArgv } from "./codesign-argv.mjs";
|
|
8
|
+
import { detectMacSigningSession } from "./macos-signing-session.mjs";
|
|
9
|
+
import { requestSigningAgent } from "./signing-agent-client.mjs";
|
|
10
|
+
import { defaultSigningRoots, privatePath, signingAgentPaths, SigningAgentError } from "./rightkit-signing-agent.mjs";
|
|
11
|
+
|
|
12
|
+
export async function runCodesignShim(argv, { cwd = process.cwd(), env = process.env, home = os.userInfo().homedir,
|
|
13
|
+
io = fs, run = spawnSync, detect = detectMacSigningSession, request = requestSigningAgent } = {}) {
|
|
14
|
+
if (!detect({ env, run }).guiRunner) {
|
|
15
|
+
// Installed shell shim uses exec here; this branch also supports API callers.
|
|
16
|
+
const result = run("/usr/bin/codesign", argv, { cwd, env, stdio: "inherit", shell: false, windowsHide: true });
|
|
17
|
+
if (result.error) throw result.error;
|
|
18
|
+
return { status: result.status ?? 1, stdout: "", stderr: "" };
|
|
19
|
+
}
|
|
20
|
+
const paths = signingAgentPaths(home);
|
|
21
|
+
let roots = defaultSigningRoots(home, env.RIGHTKIT_MANAGED_ROOT);
|
|
22
|
+
if (io.existsSync(paths.config)) {
|
|
23
|
+
privatePath(paths.config, 0o600, { io });
|
|
24
|
+
roots = JSON.parse(io.readFileSync(paths.config, "utf8")).roots;
|
|
25
|
+
}
|
|
26
|
+
const { targets } = parseCodesignArgv(argv, cwd);
|
|
27
|
+
const report = await request({ schema: 1, type: "codesign-argv", argv, cwd, targets,
|
|
28
|
+
allowedRoots: roots.filter((root) => io.existsSync(root)), timeoutMs: 180000 }, { paths });
|
|
29
|
+
const result = report.results?.[0];
|
|
30
|
+
if (report.schema !== 1 || report.ok !== true || report.results?.length !== 1
|
|
31
|
+
|| !Number.isInteger(result?.status) || result.status < 0 || result.status > 255
|
|
32
|
+
|| typeof result.stdout !== "string" || typeof result.stderr !== "string") {
|
|
33
|
+
throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid codesign result");
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const realOrSelf = (file) => { try { return fs.realpathSync(file); } catch { return path.resolve(file); } };
|
|
39
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
40
|
+
try {
|
|
41
|
+
// Only the managed shell wrapper invokes this entry: session detection was
|
|
42
|
+
// already performed by right-release before selecting its wrapper directory.
|
|
43
|
+
const result = await runCodesignShim(process.argv.slice(2), { detect: () => ({ guiRunner: true }) });
|
|
44
|
+
process.stdout.write(result.stdout);
|
|
45
|
+
process.stderr.write(result.stderr);
|
|
46
|
+
process.exitCode = result.status;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
process.stderr.write(`rightkit codesign: [${error.code ?? "MAC_SIGNING_AGENT_JOB_FAILED"}] ${error.message}\n`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/committed-source.mjs
CHANGED
|
@@ -35,7 +35,7 @@ function gitEnv(env = process.env, platform = process.platform) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function runGit(root, args, { encoding = "buffer", maxBuffer = 4 * 1024 * 1024, timeout = COMMAND_TIMEOUT_MS, runner = spawnSync, env = gitEnv(), input } = {}) {
|
|
38
|
-
const result = runner(nativeGitPath(), ["-C", root, "--no-replace-objects", "-c", "core.fsmonitor=false", "-c", `core.hooksPath=${os.devNull}`, ...args], {
|
|
38
|
+
const result = runner(nativeGitPath(), ["-C", root, "--no-replace-objects", "-c", "core.fsmonitor=false", "-c", `core.hooksPath=${os.devNull}`, "-c", `safe.directory=${root}`, ...args], {
|
|
39
39
|
encoding, env, windowsHide: true, maxBuffer, timeout, ...(input ? { input } : {}),
|
|
40
40
|
});
|
|
41
41
|
if (result?.error || result?.status !== 0) throw new Error(`committed ownership: git ${args[0] ?? "command"} failed${result?.error?.code ? ` (${result.error.code})` : ""}`);
|
|
@@ -118,7 +118,7 @@ function acquire(root, { expectedCommit, expectedTree, requiredPaths = [], runne
|
|
|
118
118
|
const text = (args) => runGit(root, args, { encoding: "utf8", runner, env }).trim();
|
|
119
119
|
const repositoryRoot = path.resolve(text(["rev-parse", "--show-toplevel"]));
|
|
120
120
|
const pathPrefix = text(["rev-parse", "--show-prefix"]).replaceAll("\\", "/");
|
|
121
|
-
const originResult = runner(nativeGitPath(), ["-C", repositoryRoot, "--no-replace-objects", "-c", "core.fsmonitor=false", "-c", `core.hooksPath=${os.devNull}`, "config", "--get", "remote.origin.url"], {
|
|
121
|
+
const originResult = runner(nativeGitPath(), ["-C", repositoryRoot, "--no-replace-objects", "-c", "core.fsmonitor=false", "-c", `core.hooksPath=${os.devNull}`, "-c", `safe.directory=${repositoryRoot}`, "config", "--get", "remote.origin.url"], {
|
|
122
122
|
encoding: "utf8", env, windowsHide: true, maxBuffer: 4 * 1024 * 1024, timeout: COMMAND_TIMEOUT_MS,
|
|
123
123
|
});
|
|
124
124
|
const origin = originResult?.status === 0 ? String(originResult.stdout).trim() : null;
|
|
@@ -132,7 +132,7 @@ function acquire(root, { expectedCommit, expectedTree, requiredPaths = [], runne
|
|
|
132
132
|
const rows = parseTree(runGit(root, ["ls-tree", "-r", "-z", "--full-tree", tree], { encoding: "buffer", maxBuffer: MAX_TREE_BYTES, timeout: COMMAND_TIMEOUT_MS, runner, env }), objectFormat);
|
|
133
133
|
const wanted = new Set(requiredPaths.filter(validatePath));
|
|
134
134
|
const shouldRead = (row) => row.type === "blob" && row.mode !== "120000" && (
|
|
135
|
-
wanted.has(row.rel) || ["Cargo.toml", "package.json", "Package.swift", "ownership-exceptions.json"].includes(path.posix.basename(row.rel)) ||
|
|
135
|
+
wanted.has(row.rel) || ["Cargo.toml", "package.json", "Package.swift", "ownership-exceptions.json", "Cargo.lock", "pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock"].includes(path.posix.basename(row.rel)) ||
|
|
136
136
|
/\.(?:rs|ts|tsx|js|mjs|cjs|jsx|swift|m|mm|c|cc|cpp|h|py|go|kt|java|cs)$/i.test(row.rel)
|
|
137
137
|
);
|
|
138
138
|
const candidates = rows.filter(shouldRead);
|
package/development.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { discoverBrokerSocket, resolveManagedRoots } from './managed-roots.mjs';
|
|
1
2
|
import { spawnSync } from "./background-process.mjs";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
7
|
import { assertPrimaryReleaseCheckout } from "./release-invocation.mjs";
|
|
7
8
|
import { assertReleaseExecutionAllowed } from "./execution-policy.mjs";
|
|
8
9
|
import { acquireManagedRunLease, managedRuntimeClient, runCargoSync } from "./managed-cargo.mjs";
|
|
9
10
|
import { BUNDLE_MARKERS, bundleMarkerBuffer, readTauriBundleMarkers } from "./tauri-bundle-marker.mjs";
|
|
10
11
|
import { installPortableDevelopment } from "./portable-development.mjs";
|
|
11
12
|
import { signMacosDevelopmentBundle } from "./development-signing.mjs";
|
|
13
|
+
import { macosCodesignEnvironment } from "./codesign-environment.mjs";
|
|
12
14
|
|
|
13
15
|
export const DEVELOPMENT_PROFILE = "release-iterate";
|
|
14
16
|
const RELEASE_ONLY_APPLE_ENV = ["APPLE_ID", "APPLE_PASSWORD", "APPLE_API_KEY", "APPLE_API_ISSUER", "APPLE_API_KEY_PATH", "APPLE_CERTIFICATE", "APPLE_CERTIFICATE_PASSWORD"];
|
|
@@ -26,16 +28,14 @@ export function developmentEnvironment(env = process.env) {
|
|
|
26
28
|
delete result.CARGO_INCREMENTAL;
|
|
27
29
|
const client = managedRuntimeClient(result);
|
|
28
30
|
if (client) {
|
|
29
|
-
const
|
|
30
|
-
?? (process.platform === "win32" ? "D:\\.rightkit-managed" : process.platform === "darwin" ? "/Volumes/D/.rightkit-managed" : undefined);
|
|
31
|
-
const agentBin = managedRoot && path.join(managedRoot, "agent-bin");
|
|
31
|
+
const { agentBin } = resolveManagedRoots({ env: result });
|
|
32
32
|
if (agentBin && existsSync(agentBin)) {
|
|
33
33
|
const pathKey = Object.keys(result).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
34
34
|
result[pathKey] = [agentBin, result[pathKey]].filter(Boolean).join(path.delimiter);
|
|
35
35
|
}
|
|
36
36
|
// Same default as the agent-bin `rightkit` shim: children that run the managed client directly
|
|
37
37
|
// (resolveCommand("rightkit"), build scripts) must not lose the broker endpoint.
|
|
38
|
-
const socket =
|
|
38
|
+
const socket = discoverBrokerSocket(result);
|
|
39
39
|
if (!result.RIGHTKIT_BUILD_BROKER_SOCKET && socket && existsSync(socket)) result.RIGHTKIT_BUILD_BROKER_SOCKET = socket;
|
|
40
40
|
}
|
|
41
41
|
return result;
|
|
@@ -60,7 +60,8 @@ export function validateDevelopmentConfig(config, platform) {
|
|
|
60
60
|
return target;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function runDevelopment({ root, platform, config, buildOnly = false, dryRun = false, env = process.env, run = spawnSync
|
|
63
|
+
export function runDevelopment({ root, platform, config, buildOnly = false, dryRun = false, env = process.env, run = spawnSync,
|
|
64
|
+
codesignEnvironment = macosCodesignEnvironment }) {
|
|
64
65
|
if (!new Set(["win", "mac"]).has(platform)) throw new Error("--platform must be win or mac");
|
|
65
66
|
assertNativeDevelopmentHost(platform);
|
|
66
67
|
const target = validateDevelopmentConfig(config, platform);
|
|
@@ -68,7 +69,7 @@ export function runDevelopment({ root, platform, config, buildOnly = false, dryR
|
|
|
68
69
|
assertPrimaryReleaseCheckout(repoRoot, env);
|
|
69
70
|
if (dryRun) return { built: false, installed: false, plan: { build: target.build, install: buildOnly ? null : target.install ?? null, profile: DEVELOPMENT_PROFILE } };
|
|
70
71
|
assertReleaseExecutionAllowed({ cwd: repoRoot, environment: env, command: `right-release dev --platform ${platform}` });
|
|
71
|
-
const childEnv = developmentEnvironment(env);
|
|
72
|
+
const childEnv = codesignEnvironment(developmentEnvironment(env));
|
|
72
73
|
// One lease spans every managed request this run makes, so a broker cutover cannot land between them.
|
|
73
74
|
const lease = acquireManagedRunLease({ command: `right-release dev --platform ${platform}`, env });
|
|
74
75
|
try {
|
|
@@ -210,7 +211,7 @@ export function resolveCommand(command, args, { root, env = process.env } = {})
|
|
|
210
211
|
if (command === "rightkit") {
|
|
211
212
|
const client = managedRuntimeClient(env);
|
|
212
213
|
if (!client) throw new Error("installed RightKit runtime client is unavailable for development command");
|
|
213
|
-
const socket =
|
|
214
|
+
const socket = discoverBrokerSocket(env);
|
|
214
215
|
const childEnv = !env.RIGHTKIT_BUILD_BROKER_SOCKET && existsSync(socket) ? { ...env, RIGHTKIT_BUILD_BROKER_SOCKET: socket } : undefined;
|
|
215
216
|
return { command: process.execPath, args: [client, ...args], ...(childEnv ? { env: childEnv } : {}) };
|
|
216
217
|
}
|
|
@@ -280,6 +281,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
280
281
|
return result;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
|
|
284
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
285
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
284
286
|
main().catch((error) => { console.error(`right-release dev: ${error.code ? `[${error.code}] ` : ""}${error.message}`); process.exitCode = 1; });
|
|
285
287
|
}
|
package/direct-bootstrap.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { readVerifiedArtifact } from "./publication-artifacts.mjs";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
5
|
import { spawnSync } from "./background-process.mjs";
|
|
5
6
|
import { RIGHTRELEASE_MANIFEST_SIGNER, signReleaseManifestWithAzure } from "./sign-release-manifest.mjs";
|
|
@@ -539,7 +540,8 @@ export function planBootstrapPublication({ product, bootstrapVersion, scriptPath
|
|
|
539
540
|
if (stableKey !== `${product}/install.ps1`) throw new Error("bootstrap stable key must be product/install.ps1");
|
|
540
541
|
const script = statSync(scriptPath);
|
|
541
542
|
if (!script.isFile() || script.size < 1 || script.size > MAX_BOOTSTRAP_BYTES || scriptPath.split(/[\\/]/).at(-1) !== "install.ps1") throw new Error("bootstrap must be one small install.ps1 file");
|
|
542
|
-
const
|
|
543
|
+
const scriptBytes = readFileSync(scriptPath);
|
|
544
|
+
const validation = validatePowerShellBootstrap(scriptBytes.toString("utf8"), { product });
|
|
543
545
|
if (!validation.valid) throw new Error("bootstrap script violates generated RightRelease contract: " + validation.errors.join("; "));
|
|
544
546
|
const versionedKey = `${product}/versions/${bootstrapVersion}/install.ps1`;
|
|
545
547
|
return {
|
|
@@ -549,7 +551,7 @@ export function planBootstrapPublication({ product, bootstrapVersion, scriptPath
|
|
|
549
551
|
stableKey,
|
|
550
552
|
versionedKey,
|
|
551
553
|
scriptPath: resolve(scriptPath),
|
|
552
|
-
sha256:
|
|
554
|
+
sha256: createHash("sha256").update(scriptBytes).digest("hex"),
|
|
553
555
|
};
|
|
554
556
|
}
|
|
555
557
|
|
|
@@ -567,16 +569,24 @@ export function createWranglerR2Client({ commandRunner = spawnSync, wranglerRunn
|
|
|
567
569
|
|
|
568
570
|
export function publishBootstrapPlan(plan, r2, { verificationPath }) {
|
|
569
571
|
validateBootstrapPublicationPlan(plan);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
572
|
+
readVerifiedArtifact(plan.scriptPath, { sha256: plan.sha256 });
|
|
573
|
+
for (const [label, key] of [["immutable", plan.versionedKey], ["stable", plan.stableKey]]) {
|
|
574
|
+
try { r2.put(plan.bucket, key, plan.scriptPath); }
|
|
575
|
+
catch { throw new Error(`bootstrap upload failed; inspect remote key ${plan.bucket}/${key}`); }
|
|
576
|
+
// A successful readback must materialize fresh bytes, never reuse prior output.
|
|
577
|
+
rmSync(verificationPath, { force: true });
|
|
578
|
+
try {
|
|
579
|
+
r2.get(plan.bucket, key, verificationPath);
|
|
580
|
+
readVerifiedArtifact(verificationPath, { sha256: plan.sha256 });
|
|
581
|
+
} catch {
|
|
582
|
+
throw new Error(`${label} bootstrap remote verification failed; remove remote key ${plan.bucket}/${key}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
576
585
|
return { ...plan, verified: true };
|
|
577
586
|
}
|
|
578
587
|
|
|
579
588
|
export function validateBootstrapPublicationPlan(plan) {
|
|
589
|
+
if (!/^[a-f0-9]{64}$/.test(plan?.sha256 ?? "")) throw new Error("bootstrap publication hash evidence is required");
|
|
580
590
|
if (!plan || !/^[a-z][a-z0-9-]+$/.test(plan.product ?? "")) throw new Error("bootstrap publication product is invalid");
|
|
581
591
|
if (!VERSION.test(plan.bootstrapVersion ?? "")) throw new Error("bootstrap publication version is invalid");
|
|
582
592
|
if (plan.bucket !== "rightapps-downloads") throw new Error("bootstrap publication bucket is not branded public R2");
|
package/dmg-build.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawnSync } from "./background-process.mjs";
|
|
5
|
+
import { backgroundAlias, dmgDSStore } from "./dmg-layout.mjs";
|
|
6
|
+
import { assertReleaseExecutionAllowed } from "./execution-policy.mjs";
|
|
7
|
+
|
|
8
|
+
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
9
|
+
const pair = (value, label, positive = false) => {
|
|
10
|
+
const numbers = Array.isArray(value) ? value : String(value).split(",").map(Number);
|
|
11
|
+
if (numbers.length !== 2 || numbers.some((n) => !Number.isInteger(n) || n < (positive ? 1 : 0) || n > 32767)) {
|
|
12
|
+
throw fail("DMG_INVALID_OPTIONS", `${label} requires ${positive ? "positive" : "nonnegative"} x,y integers <=32767`);
|
|
13
|
+
}
|
|
14
|
+
return numbers;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildDmg({ app, out, volname, background, iconSize = 128, appPosition = [158, 231],
|
|
18
|
+
applicationsPosition = [492, 231], window = [660, 400], volumeIcon } = {},
|
|
19
|
+
{ platform = process.platform, run = spawnSync, makeAlias = backgroundAlias,
|
|
20
|
+
log = (event) => console.error(JSON.stringify(event)) } = {}) {
|
|
21
|
+
if (platform !== "darwin") throw fail("DMG_NATIVE_HOST_REQUIRED", "DMG creation requires macOS");
|
|
22
|
+
if (!app || !out || !volname) throw fail("DMG_INVALID_OPTIONS", "--app, --out & --volname are required");
|
|
23
|
+
app = path.resolve(app); out = path.resolve(out);
|
|
24
|
+
if (!app.endsWith(".app") || !statSync(app).isDirectory() || !existsSync(path.join(app, "Contents", "Info.plist"))) {
|
|
25
|
+
throw fail("DMG_INVALID_OPTIONS", `expected .app bundle with Contents/Info.plist: ${app}`);
|
|
26
|
+
}
|
|
27
|
+
if (!out.endsWith(".dmg") || out.startsWith(`${app}${path.sep}`)) throw fail("DMG_INVALID_OPTIONS", "--out must be a .dmg outside app bundle");
|
|
28
|
+
if (/[\0\r\n/:]/.test(volname) || Buffer.byteLength(volname) > 255) throw fail("DMG_INVALID_OPTIONS", "invalid volume name");
|
|
29
|
+
iconSize = Number(iconSize);
|
|
30
|
+
if (!Number.isInteger(iconSize) || iconSize < 16 || iconSize > 512) throw fail("DMG_INVALID_OPTIONS", "--icon-size must be 16..512");
|
|
31
|
+
appPosition = pair(appPosition, "--app-position"); applicationsPosition = pair(applicationsPosition, "--applications-position");
|
|
32
|
+
window = pair(window, "--window", true);
|
|
33
|
+
if (volumeIcon) {
|
|
34
|
+
volumeIcon = path.resolve(volumeIcon);
|
|
35
|
+
if (!existsSync(volumeIcon) || !statSync(volumeIcon).isFile() || !/\.icns$/i.test(volumeIcon)) throw fail("DMG_INVALID_OPTIONS", "--volume-icon must be an .icns file");
|
|
36
|
+
}
|
|
37
|
+
if (background) {
|
|
38
|
+
background = path.resolve(background);
|
|
39
|
+
// Existing Right Suite backgrounds include TIFF; accept PNG & TIFF without conversion.
|
|
40
|
+
if (!statSync(background).isFile() || !/\.(png|tiff?)$/i.test(background)) throw fail("DMG_INVALID_OPTIONS", "background must be PNG or TIFF");
|
|
41
|
+
if (/\.png$/i.test(background) && !readFileSync(background).subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))) {
|
|
42
|
+
throw fail("DMG_INVALID_OPTIONS", "invalid PNG signature");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (let cursor = out; cursor !== path.dirname(cursor); cursor = path.dirname(cursor)) {
|
|
46
|
+
if (existsSync(cursor) && lstatSync(cursor).isSymbolicLink()) throw fail("DMG_INVALID_OPTIONS", `output must not traverse symlink: ${cursor}`);
|
|
47
|
+
}
|
|
48
|
+
assertReleaseExecutionAllowed({ cwd: app, command: "right-release dmg build" });
|
|
49
|
+
mkdirSync(path.dirname(out), { recursive: true });
|
|
50
|
+
const work = mkdtempSync(path.join(path.dirname(out), ".rightkit-dmg-")), stage = path.join(work, "stage"), mount = path.join(work, "mount");
|
|
51
|
+
const rw = path.join(work, "writable.dmg"), compressed = path.join(work, "compressed.dmg");
|
|
52
|
+
let attached = false, attaching = false;
|
|
53
|
+
const command = (tool, args) => {
|
|
54
|
+
const result = run(tool, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], shell: false, timeout: 300000 });
|
|
55
|
+
if (result.error || result.status !== 0) throw fail("DMG_COMMAND_FAILED", `${tool} ${args[0]} failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
log({ event: "dmg-build-started", app, out, volname });
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(stage); mkdirSync(mount);
|
|
61
|
+
command("/usr/bin/ditto", [app, path.join(stage, path.basename(app))]);
|
|
62
|
+
symlinkSync("/Applications", path.join(stage, "Applications"));
|
|
63
|
+
const relative = background ? path.join(".background", path.basename(background)) : null;
|
|
64
|
+
if (relative) { mkdirSync(path.join(stage, ".background")); copyFileSync(background, path.join(stage, relative)); }
|
|
65
|
+
// Include layout headroom in hdiutil's source-folder size estimate. Alias
|
|
66
|
+
// CNIDs become available only after mounting, when this reservation is replaced.
|
|
67
|
+
writeFileSync(path.join(stage, ".DS_Store"), Buffer.alloc(64 * 1024));
|
|
68
|
+
command("/usr/bin/hdiutil", ["create", "-fs", "HFS+", "-format", "UDRW", "-volname", volname, "-srcfolder", stage, rw]);
|
|
69
|
+
attaching = true;
|
|
70
|
+
command("/usr/bin/hdiutil", ["attach", "-readwrite", "-nobrowse", "-noautoopen", "-mountpoint", mount, rw]);
|
|
71
|
+
attached = true; attaching = false;
|
|
72
|
+
if (volumeIcon) {
|
|
73
|
+
// Custom volume icon: .VolumeIcon.icns at the root plus the volume's kHasCustomIcon (0x0400) Finder flag.
|
|
74
|
+
copyFileSync(volumeIcon, path.join(mount, ".VolumeIcon.icns"));
|
|
75
|
+
const finderInfo = Buffer.alloc(32); finderInfo.writeUInt16BE(0x0400, 8);
|
|
76
|
+
command("/usr/bin/xattr", ["-wx", "com.apple.FinderInfo", finderInfo.toString("hex"), mount]);
|
|
77
|
+
}
|
|
78
|
+
const alias = relative ? makeAlias({ mount, volname, relative }) : undefined;
|
|
79
|
+
writeFileSync(path.join(mount, ".DS_Store"), dmgDSStore({ appName: path.basename(app), iconSize,
|
|
80
|
+
appPosition, applicationsPosition, window, alias }));
|
|
81
|
+
command("/usr/bin/hdiutil", ["detach", mount]); attached = false;
|
|
82
|
+
command("/usr/bin/hdiutil", ["convert", rw, "-format", "UDZO", "-imagekey", "zlib-level=9", "-o", compressed]);
|
|
83
|
+
command("/usr/bin/hdiutil", ["verify", compressed]);
|
|
84
|
+
renameSync(compressed, out);
|
|
85
|
+
log({ event: "dmg-build-completed", out, format: "UDZO" });
|
|
86
|
+
return { out, format: "UDZO" };
|
|
87
|
+
} catch (error) {
|
|
88
|
+
log({ event: "dmg-build-failed", out, code: error.code ?? "DMG_BUILD_FAILED", message: error.message });
|
|
89
|
+
throw error;
|
|
90
|
+
} finally {
|
|
91
|
+
if (attached || attaching) {
|
|
92
|
+
try { command("/usr/bin/hdiutil", ["detach", "-force", mount]); attached = false; attaching = false; }
|
|
93
|
+
catch (error) { log({ event: "dmg-cleanup-failed", mount, work, message: error.message }); }
|
|
94
|
+
}
|
|
95
|
+
// Never recurse through a mount that failed to detach.
|
|
96
|
+
if (!attached && !attaching) rmSync(work, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function parseDmgArguments(args) {
|
|
101
|
+
if (args[0] !== "build") throw fail("DMG_INVALID_OPTIONS", "expected dmg build");
|
|
102
|
+
const flags = new Map([["--app", "app"], ["--out", "out"], ["--volname", "volname"], ["--background", "background"],
|
|
103
|
+
["--icon-size", "iconSize"], ["--app-position", "appPosition"], ["--applications-position", "applicationsPosition"], ["--window", "window"], ["--volume-icon", "volumeIcon"]]);
|
|
104
|
+
const options = {};
|
|
105
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
106
|
+
const key = flags.get(args[i]);
|
|
107
|
+
if (!key || options[key] !== undefined || !args[i + 1] || args[i + 1].startsWith("--")) throw fail("DMG_INVALID_OPTIONS", `invalid option: ${args[i]}`);
|
|
108
|
+
options[key] = args[i + 1];
|
|
109
|
+
}
|
|
110
|
+
return options;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
114
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
115
|
+
try { buildDmg(parseDmgArguments(process.argv.slice(2))); }
|
|
116
|
+
catch (error) { console.error(`right-release dmg: [${error.code ?? "DMG_BUILD_FAILED"}] ${error.message}`); process.exitCode = 1; }
|
|
117
|
+
}
|