@rightkit/release 0.2.135 → 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.
Files changed (81) hide show
  1. package/README.md +62 -0
  2. package/addon-command.mjs +53 -34
  3. package/apple-profile-renew.mjs +30 -12
  4. package/artifact-storage.mjs +47 -10
  5. package/background-process.mjs +18 -0
  6. package/build-release.mjs +1 -1
  7. package/cache-command.mjs +4 -3
  8. package/cargo-contract.mjs +1 -1
  9. package/cargo-guard.mjs +4 -3
  10. package/cargo-registry-readback.mjs +51 -0
  11. package/cargo-target.mjs +3 -1
  12. package/cli/right-release.mjs +26 -0
  13. package/codesign-environment.mjs +26 -2
  14. package/codesign-shim.mjs +2 -1
  15. package/committed-source.mjs +3 -3
  16. package/development.mjs +7 -7
  17. package/direct-bootstrap.mjs +19 -9
  18. package/dmg-build.mjs +117 -0
  19. package/dmg-layout.mjs +123 -0
  20. package/github-release.mjs +74 -37
  21. package/heavy-command.mjs +3 -2
  22. package/install-app.mjs +358 -0
  23. package/inventory-check.mjs +980 -0
  24. package/macos-signing-session.mjs +3 -0
  25. package/managed-cargo.mjs +23 -19
  26. package/managed-host-secrets.mjs +30 -0
  27. package/managed-roots.mjs +39 -0
  28. package/model-promote.mjs +31 -37
  29. package/notary-auth.mjs +4 -0
  30. package/osascript-guard.mjs +24 -0
  31. package/ownership-approval-command.mjs +15 -4
  32. package/ownership-approval.mjs +114 -49
  33. package/ownership-availability.mjs +25 -0
  34. package/ownership-catalog-config.json +18 -0
  35. package/ownership-catalog.mjs +94 -0
  36. package/ownership-command.mjs +17 -10
  37. package/ownership-declaration.json +15 -0
  38. package/ownership-default-policy.json +1 -11
  39. package/ownership-evidence.mjs +10 -8
  40. package/ownership-git-inventory.mjs +29 -1
  41. package/ownership-helper-paths.json +2 -2
  42. package/ownership-hooks.mjs +107 -0
  43. package/ownership-internal-owners.json +44 -0
  44. package/ownership-key-authority.mjs +112 -7
  45. package/ownership-key-command.mjs +76 -19
  46. package/ownership-key-trust.mjs +97 -9
  47. package/ownership-lockfiles.mjs +311 -0
  48. package/ownership-report-command.mjs +12 -3
  49. package/ownership-report.mjs +20 -17
  50. package/ownership-repositories.json +19 -0
  51. package/ownership-scan-worker.mjs +96 -11
  52. package/ownership-source.mjs +39 -0
  53. package/ownership-staged.mjs +193 -0
  54. package/ownership-verdict.mjs +74 -0
  55. package/ownership.mjs +215 -107
  56. package/package.json +1 -1
  57. package/protected-credentials.mjs +111 -0
  58. package/prune-r2.mjs +4 -3
  59. package/pub-upload.mjs +3 -85
  60. package/publication-artifacts.mjs +108 -0
  61. package/publication-gate.mjs +28 -7
  62. package/publish-cargo.mjs +47 -16
  63. package/publish-swift.mjs +46 -17
  64. package/publish-update.mjs +61 -39
  65. package/r2-verified-upload.mjs +41 -0
  66. package/registry-parity.mjs +5 -3
  67. package/release-state.mjs +1 -0
  68. package/release-token.mjs +2 -0
  69. package/rightkit-drift.mjs +3 -2
  70. package/rightkit-ownership.json +2028 -139
  71. package/rightkit-signing-agent.mjs +5 -3
  72. package/rightkit-sync.mjs +6 -2
  73. package/rightkit-versions.json +213 -103
  74. package/runtime-artifact-manifest.mjs +20 -6
  75. package/sign-updater.mjs +5 -0
  76. package/signing-agent-client.mjs +2 -1
  77. package/signing-agent-install.mjs +3 -2
  78. package/standalone-clone-verify.mjs +3 -2
  79. package/tauri-bundle-marker.mjs +3 -2
  80. package/upload-large.mjs +36 -82
  81. package/verifier.mjs +5 -1
@@ -1,3 +1,4 @@
1
+ import { resolveManagedRoots } from './managed-roots.mjs';
1
2
  import * as fs from "node:fs";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
@@ -29,7 +30,7 @@ export function signingAgentPlist({ paths, node = process.execPath, home }) {
29
30
  }
30
31
  export function installSigningAgent({ home = os.userInfo().homedir, username = os.userInfo().username,
31
32
  uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs, node = process.execPath,
32
- managedRoot = "/Volumes/D/.rightkit-managed", cargoTargetDirs = [], health = requestSigningAgentSync,
33
+ managedRoot = resolveManagedRoots({ home }).dataRoot, cargoTargetDirs = [], health = requestSigningAgentSync,
33
34
  now = Date.now, sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } = {}) {
34
35
  if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
35
36
  requireMacGuiSession({ run, uid });
@@ -46,7 +47,7 @@ export function installSigningAgent({ home = os.userInfo().homedir, username = o
46
47
  for (const root of cargoTargetDirs) {
47
48
  if (typeof root !== "string" || !path.isAbsolute(root) || ["/", home, "/Volumes", "/Volumes/D"].includes(path.resolve(root))) throw new SigningAgentError("MAC_SIGNING_AGENT_PATH_DENIED", "Cargo target root must be a dedicated absolute directory");
48
49
  }
49
- for (const name of ["rightkit-signing-agent.mjs", "background-process.mjs", "codesign-argv.mjs"]) {
50
+ for (const name of ["rightkit-signing-agent.mjs", "background-process.mjs", "codesign-argv.mjs", "managed-roots.mjs"]) {
50
51
  const target = path.join(paths.runtime, name);
51
52
  if (io.existsSync(target)) privatePath(target, 0o600, { io, uid });
52
53
  io.writeFileSync(target, io.readFileSync(new URL(`./${name}`, import.meta.url)), { mode: 0o600 });
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, realpathSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "./background-process.mjs";
@@ -140,7 +140,8 @@ function parseArgs(argv) {
140
140
  return { apps, evidencePath };
141
141
  }
142
142
 
143
- if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
143
+ const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
144
+ if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
144
145
  try {
145
146
  const evidence = await verifyStandaloneClones(parseArgs(process.argv.slice(2)));
146
147
  for (const app of evidence.apps) process.stdout.write(`[standalone] ${app.key} ${app.revision.slice(0, 12)} install+doctor passed\n`);
@@ -10,7 +10,7 @@
10
10
  // a byte changing outside the marker all mean the assumption no longer holds.
11
11
 
12
12
  import { createHash } from "node:crypto";
13
- import { readFileSync, writeFileSync } from "node:fs";
13
+ import { readFileSync, writeFileSync, realpathSync } from "node:fs";
14
14
  import path from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
 
@@ -147,7 +147,8 @@ function sha256(bytes) {
147
147
  return createHash("sha256").update(bytes).digest("hex");
148
148
  }
149
149
 
150
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
150
+ const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
151
+ if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
151
152
  const argv = process.argv.slice(2);
152
153
  const bundleIndex = argv.indexOf("--bundle");
153
154
  const bundle = bundleIndex >= 0 ? argv[bundleIndex + 1] : "nsis";
package/upload-large.mjs CHANGED
@@ -1,86 +1,40 @@
1
1
  #!/usr/bin/env node
2
- import { accessSync, statSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { spawnSync } from "./background-process.mjs";
5
-
6
- const ACCOUNT_ID = "03ae77ccd7a07bcbb2dcfde47fa7ba3a";
7
- const BUCKETS = new Map([
8
- ["public", "rightapps-downloads"],
9
- ["private", "rightapps-updates"],
10
- ]);
11
- const RUNNER = { cmd: "pnpm", prefix: ["dlx"] };
12
-
13
- const [, , localFile, r2Key, bucketAlias = "public"] = process.argv;
14
- if (!localFile || !r2Key) {
15
- console.error("Usage: node upload-large.mjs <localFile> <r2Key> [public|private]");
16
- process.exit(1);
17
- }
18
-
19
- const bucket = BUCKETS.get(bucketAlias);
20
- if (!bucket) {
21
- console.error(`upload-large: invalid bucket alias ${bucketAlias}; expected public|private`);
22
- process.exit(1);
23
- }
24
-
25
- try {
26
- accessSync(localFile);
27
- } catch {
28
- console.error(`upload-large: missing local file: ${localFile}`);
29
- process.exit(1);
30
- }
31
-
32
- const fileSize = statSync(localFile).size;
33
- const object = `${bucket}/${r2Key}`;
34
- const wranglerArgs = ["wrangler@4", "r2", "object", "put", object, "--file", path.resolve(localFile), "--remote"];
35
- const env = cleanCloudflareEnv(process.env);
36
-
37
- console.log(`Uploading ${path.basename(localFile)} (${(fileSize / 1024 / 1024).toFixed(1)} MB) -> ${object}`);
38
- console.log(`wrangler r2 object put ${object} --file ${path.resolve(localFile)} --remote`);
39
-
40
- // Resolve the package runner before the dry-run exit: a release box without one cannot upload,
41
- // and that must fail here rather than after a signed build has already been produced.
42
- const runner = resolveRunner(env);
43
- if (!runner) {
44
- console.error(
45
- "upload-large: no package runner found. Tried: " +
46
- "pnpm dlx. Install the pinned workspace package manager before release.",
47
- );
48
- process.exit(1);
49
- }
50
- console.log(`runner: ${[runner.cmd, ...runner.prefix].join(" ")}`);
51
-
52
- if (process.env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1") {
53
- process.exit(0);
54
- }
55
-
56
- const result = spawnSync(runner.cmd, [...runner.prefix, ...wranglerArgs], {
57
- env,
58
- stdio: "inherit",
59
- shell: process.platform === "win32",
60
- windowsHide: true,
61
- });
62
-
63
- if (result.error) {
64
- console.error(`upload-large: failed to start wrangler: ${result.error.message}`);
65
- process.exit(1);
66
- }
67
- process.exit(result.status ?? 1);
68
-
69
- function resolveRunner(env) {
70
- const probe = spawnSync(RUNNER.cmd, ["--version"], {
71
- env,
72
- stdio: "ignore",
73
- shell: process.platform === "win32",
74
- windowsHide: true,
75
- });
76
- return !probe.error && probe.status === 0 ? RUNNER : null;
77
- }
78
-
79
- function cleanCloudflareEnv(source) {
80
- const env = { ...source, CLOUDFLARE_ACCOUNT_ID: source.CLOUDFLARE_ACCOUNT_ID || ACCOUNT_ID };
81
- delete env.CLOUDFLARE_API_KEY;
82
- delete env.CLOUDFLARE_EMAIL;
83
- delete env.CF_API_KEY;
84
- delete env.CF_EMAIL;
85
- return env;
6
+ import { verifySealedRelease } from "./release-state.mjs";
7
+ import { uploadVerifiedR2File } from "./r2-verified-upload.mjs";
8
+ import { resolveCloudflareApiToken } from "./protected-credentials.mjs";
9
+
10
+ export function runLargeUpload(argv, { run = spawnSync, env = process.env, credentialStore } = {}) {
11
+ const [localFile, r2Key, bucketAlias = "public", flag, evidencePath, ...extra] = argv;
12
+ if (!localFile || !r2Key) throw new Error("Usage: upload-large <file> <key> public|private --evidence <json> | --seal <sealed-dir>");
13
+ if (!["public", "private"].includes(bucketAlias)) throw new Error(`invalid bucket alias ${bucketAlias}; expected public|private`);
14
+ if (!["--evidence", "--seal"].includes(flag) || !evidencePath || extra.length) throw new Error("publication seal or hash evidence is required (--seal or --evidence)");
15
+ let evidence;
16
+ if (flag === "--seal") {
17
+ const sealed = verifySealedRelease(path.resolve(evidencePath));
18
+ if (path.dirname(path.resolve(localFile)) !== path.resolve(sealed.sealedDir)) throw new Error("artifact is outside sealed directory");
19
+ evidence = sealed.manifest.files.find((entry) => entry.name === path.basename(localFile));
20
+ } else {
21
+ const value = JSON.parse(readFileSync(evidencePath, "utf8"));
22
+ evidence = { sha256: value.artifactSha256, sizeBytes: value.sizeBytes };
23
+ }
24
+ // Validate before runner discovery or any remote operation, including dry runs.
25
+ uploadVerifiedR2File(localFile, evidence, r2Key, bucketAlias, { dryRun: true });
26
+ // The Cloudflare token is resolved only for a real upload; it is injected into the Wrangler child environment alone.
27
+ const dryRun = env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1";
28
+ const token = resolveCloudflareApiToken({ env, credentialStore, required: !dryRun });
29
+ const uploadEnv = dryRun ? env : { ...env, CLOUDFLARE_API_TOKEN: token };
30
+ const probe = run("pnpm", ["--version"], { env, stdio: "ignore", shell: process.platform === "win32", windowsHide: true });
31
+ if (probe.error || probe.status !== 0) throw new Error("no package runner found: pnpm dlx");
32
+ const object = `${bucketAlias === "public" ? "rightapps-downloads" : "rightapps-updates"}/${r2Key}`;
33
+ console.log(`wrangler r2 object put ${object} --remote (mandatory hash readback)`);
34
+ console.log("runner: pnpm dlx");
35
+ return uploadVerifiedR2File(localFile, evidence, r2Key, bucketAlias, { run, env: uploadEnv, dryRun });
36
+ }
37
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
38
+ try { runLargeUpload(process.argv.slice(2)); }
39
+ catch (error) { console.error(`upload-large: ${error.message}`); process.exitCode = 1; }
86
40
  }
package/verifier.mjs CHANGED
@@ -13,6 +13,9 @@
13
13
  * human nor a model can hand-edit its way to a grant.
14
14
  */
15
15
  import { createHash } from "node:crypto";
16
+ import { realpathSync } from "node:fs";
17
+ import path from "node:path";
18
+ import { fileURLToPath } from "node:url";
16
19
 
17
20
  export const GRANTS = Object.freeze(["BUILD_AUTHORIZED", "SIGNING_AUTHORIZED", "RELEASE_AUTHORIZED"]);
18
21
 
@@ -136,7 +139,8 @@ function decision(grant, granted, issues, input) {
136
139
  return body;
137
140
  }
138
141
 
139
- const invokedDirectly = process.argv[1] && process.argv[1].endsWith("verifier.mjs");
142
+ const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
143
+ const invokedDirectly = process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url));
140
144
  if (invokedDirectly) {
141
145
  const grant = process.argv[2];
142
146
  let input = {};