@rightkit/release 0.2.121 → 0.2.122
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 +6 -2
- package/cli/right-release.mjs +12 -0
- package/committed-source.mjs +200 -0
- package/ownership-approval-command.mjs +39 -0
- package/ownership-approval.mjs +370 -0
- package/ownership-command.mjs +50 -14
- package/ownership-default-policy.json +21 -0
- package/ownership-evidence.mjs +224 -0
- package/ownership-git-inventory.mjs +195 -0
- package/ownership-helper-paths.json +11 -0
- package/ownership-key-authority.mjs +85 -0
- package/ownership-key-command.mjs +530 -0
- package/ownership-key-log.mjs +185 -0
- package/ownership-key-trust.mjs +326 -0
- package/ownership-report-command.mjs +87 -0
- package/ownership-report.mjs +144 -0
- package/ownership-scan-worker.mjs +15 -0
- package/ownership.mjs +504 -197
- package/package.json +4 -1
- package/preflight.mjs +19 -16
- package/publication-gate.mjs +2 -1
- package/publish-cargo.mjs +5 -0
- package/release.mjs +1 -1
- package/rightkit-drift.mjs +112 -0
- package/rightkit-ownership.json +131 -0
- package/rightkit-sync.mjs +150 -0
- package/rightkit-versions.json +109 -26
- package/upload-release.mjs +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.122",
|
|
4
4
|
"description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"url": "git+https://github.com/bogusyogi/claude.git",
|
|
27
27
|
"directory": "tools/rightkit/packages/release"
|
|
28
28
|
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"smol-toml": "1.7.0"
|
|
31
|
+
},
|
|
29
32
|
"scripts": {
|
|
30
33
|
"test": "node --test --test-concurrency=1 --test-force-exit *.test.mjs",
|
|
31
34
|
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
package/preflight.mjs
CHANGED
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
24
23
|
import { spawnSync } from "./background-process.mjs";
|
|
25
24
|
import { brokerManagedHost } from "./cargo-contract.mjs";
|
|
26
|
-
import {
|
|
25
|
+
import { createOwnershipEvidence } from "./ownership-evidence.mjs";
|
|
27
26
|
|
|
28
27
|
export const PREFLIGHT_OK = "ok";
|
|
29
28
|
export const PREFLIGHT_WARN = "warn";
|
|
@@ -179,6 +178,7 @@ export function collectPreflight({
|
|
|
179
178
|
versionProbe = commandVersion,
|
|
180
179
|
isBrokerManagedHost = brokerManagedHost,
|
|
181
180
|
ownership = true,
|
|
181
|
+
ownershipEvidenceFactory = createOwnershipEvidence,
|
|
182
182
|
} = {}) {
|
|
183
183
|
const checks = [];
|
|
184
184
|
const windows = platform === "win" || platform === "win32";
|
|
@@ -293,30 +293,33 @@ export function collectPreflight({
|
|
|
293
293
|
if (repoRoot && ownership) {
|
|
294
294
|
// An app living inside the RightKit workspace repository is scoped to its own directory.
|
|
295
295
|
const workspace = existsSync(path.join(repoRoot, "tools", "rightkit", "packages", "release"));
|
|
296
|
-
checks.push(ownershipCheck(workspace && appRoot ? appRoot : repoRoot));
|
|
296
|
+
checks.push(ownershipCheck(repoRoot, { scanRoot: workspace && appRoot ? appRoot : repoRoot, evidenceFactory: ownershipEvidenceFactory }));
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
return checks;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
/** Release-blocking: the app keeps no unexcepted copy of a RightKit-owned capability. */
|
|
303
|
-
export function ownershipCheck(repoRoot,
|
|
304
|
-
let
|
|
305
|
-
try {
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
303
|
+
export function ownershipCheck(repoRoot, options = {}) {
|
|
304
|
+
let checked;
|
|
305
|
+
try { checked = (options.evidenceFactory ?? createOwnershipEvidence)(repoRoot, options.scanRoot ?? repoRoot); }
|
|
306
|
+
catch (error) { return fail("ownership", `ownership evidence failed: ${error.message}`, "restore protected RightKit ownership trust and catalog"); }
|
|
307
|
+
const { result, evidence, floor } = checked;
|
|
308
|
+
if (checked.enrollmentPending) {
|
|
309
|
+
const diagnostics = [...(result.problems ?? []), ...(result.violations ?? []).map((entry) => `${entry.fingerprint} at ${entry.file}${entry.line ? `:${entry.line}` : ""}`)];
|
|
310
|
+
return { ...warn("ownership", `${checked.warning}${diagnostics.length ? `; ${diagnostics.length} diagnostic finding/problem(s) retained` : ""}`, null), evidence, diagnostics, result, floor };
|
|
309
311
|
}
|
|
310
|
-
|
|
311
|
-
if (result.ok) {
|
|
312
|
+
if (checked.ok) {
|
|
312
313
|
const excepted = result.covered.length;
|
|
313
|
-
return ok("ownership", excepted ? `no
|
|
314
|
+
return { ...ok("ownership", excepted ? `no unapproved copies (${excepted} signed findings)` : "no copies of RightKit-owned capabilities"), evidence };
|
|
314
315
|
}
|
|
315
316
|
const first = result.violations[0];
|
|
316
|
-
const detail =
|
|
317
|
-
?
|
|
318
|
-
:
|
|
319
|
-
|
|
317
|
+
const detail = !floor.ok
|
|
318
|
+
? floor.problems.join("; ")
|
|
319
|
+
: first
|
|
320
|
+
? `${result.violations.length} own cop${result.violations.length === 1 ? "y" : "ies"} of RightKit capabilities, e.g. ${first.fingerprint} at ${first.file}${first.line ? `:${first.line}` : ""}`
|
|
321
|
+
: result.problems[0] ?? result.coverage[0]?.reason ?? "ownership scan incomplete";
|
|
322
|
+
return { ...fail("ownership", detail, "run: right-release ownership (move to RightKit, or request a signed, dated exception)"), evidence };
|
|
320
323
|
}
|
|
321
324
|
|
|
322
325
|
export function formatPreflight(checks) {
|
package/publication-gate.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { spawnSync } from "./background-process.mjs";
|
|
11
|
+
import { recordPublished } from "./rightkit-sync.mjs";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
|
|
13
14
|
export const GATE_SCHEMA = 1;
|
|
@@ -158,7 +159,7 @@ export function publishGatedTarball({ name, version, tarball, evidencePath }, de
|
|
|
158
159
|
for (;;) {
|
|
159
160
|
const view = run("npm", ["view", `${name}@${version}`, "dist.shasum", "--prefer-online", "--registry", "https://registry.npmjs.org/"], { encoding: "utf8", windowsHide: true });
|
|
160
161
|
const registry = (view.stdout ?? "").trim();
|
|
161
|
-
if (registry === sha1) { process.stdout.write(`[right-release] registry bytes verified for ${name}@${version} (shasum ${sha1})\n`); return; }
|
|
162
|
+
if (registry === sha1) { process.stdout.write(`[right-release] registry bytes verified for ${name}@${version} (shasum ${sha1})\n`); (deps.recordPublished ?? recordPublished)({ registry: "npm", name, version }); return; }
|
|
162
163
|
if (registry && registry !== sha1) throw new Error(`registry shasum ${registry} != gated tarball ${sha1} for ${name}@${version}`);
|
|
163
164
|
if (Date.now() > deadline) throw new Error(`${name}@${version} not visible on the registry after publish; verify later with npm view`);
|
|
164
165
|
(deps.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)))(15000);
|
package/publish-cargo.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { spawnSync } from "./background-process.mjs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { enforcePublicationGate, defaultEvidencePath } from "./publication-gate.mjs";
|
|
7
|
+
import { recordPublished } from "./rightkit-sync.mjs";
|
|
7
8
|
|
|
8
9
|
const FORBIDDEN_PACKAGE_PATH = /(^|\/)(?:\.env(?:\.[^/]*)?|target(?:\/.*)?|node_modules(?:\/.*)?|[^/]+\.(?:p8|p12|pem|key))$/i;
|
|
9
10
|
const SECRET_PATTERNS = [
|
|
@@ -139,6 +140,10 @@ function main() {
|
|
|
139
140
|
const options = parseCargoPublishArgs(process.argv.slice(2));
|
|
140
141
|
runCargoPublish(options);
|
|
141
142
|
process.stdout.write(`[right-release] ${options.dryRun ? "validated" : "published"} ${options.crate}\n`);
|
|
143
|
+
if (!options.dryRun) {
|
|
144
|
+
const version = fs.readFileSync(path.join(options.workspaceRoot, "crates", options.crate, "Cargo.toml"), "utf8").match(/^version\s*=\s*"([^"]+)"/m)?.[1];
|
|
145
|
+
if (version) recordPublished({ registry: "cargo", name: options.crate, version });
|
|
146
|
+
}
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
|
package/release.mjs
CHANGED
|
@@ -41,7 +41,7 @@ const RIGHTKIT_ALLOWED = buildAllowedVersions(
|
|
|
41
41
|
RIGHTKIT_VERSIONS.stagedNpm,
|
|
42
42
|
RIGHTKIT_VERSIONS.legacyNpm,
|
|
43
43
|
);
|
|
44
|
-
const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo);
|
|
44
|
+
const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo, RIGHTKIT_VERSIONS.legacyCargo);
|
|
45
45
|
const FORBIDDEN_RIGHTKIT_SPEC = /^(?:git|file|link|workspace):|github|github\.com/i;
|
|
46
46
|
const WINDOWS_NSIS_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-nsis-v1";
|
|
47
47
|
const WINDOWS_PORTABLE_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-portable-v1";
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// RightKit adoption drift: which @rightkit/* and rightkit-* pins in an app are behind the latest
|
|
3
|
+
// published versions, and (with --apply) move them forward and refresh the lockfiles.
|
|
4
|
+
// Self-contained (node: built-ins only) so the SessionStart hook can carry a byte copy of it.
|
|
5
|
+
// node rightkit-drift.mjs [--apply [--all]] [app-root] (--all also adopts breaking bumps)
|
|
6
|
+
import { spawnSync } from "./background-process.mjs";
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const DEPENDENCY_SECTIONS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
13
|
+
const CARGO_PIN = /^(\s*)(rightkit-[A-Za-z0-9_-]+)(\s*=\s*(?:\{[^}\n]*?\bversion\s*=\s*)?")([=^~]?)(\d+\.\d+\.\d+)(")/;
|
|
14
|
+
|
|
15
|
+
export function loadRightkitVersions() {
|
|
16
|
+
for (const name of ["rightkit-versions.json", "ownership-session-versions.json"]) {
|
|
17
|
+
const file = path.join(here, name);
|
|
18
|
+
if (existsSync(file)) return JSON.parse(readFileSync(file, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
throw new Error("rightkit versions manifest not found next to rightkit-drift.mjs");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const numeric = (value) => String(value).split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
24
|
+
const newer = (candidate, current) => { const a = numeric(candidate); const b = numeric(current); for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] > b[i]; return false; };
|
|
25
|
+
/** Semver-breaking for 0.x crates/packages is a minor bump; from 1.0 it is a major bump. */
|
|
26
|
+
export const breakingBump = (from, to) => { const a = numeric(from); const b = numeric(to); return a[0] === 0 && b[0] === 0 ? a[1] !== b[1] : a[0] !== b[0]; };
|
|
27
|
+
// Only versions the registry is known to serve: a staged version may not be published yet.
|
|
28
|
+
const latestFor = (versions, ecosystem, name) => versions.registry
|
|
29
|
+
? versions.registry[ecosystem]?.[name]
|
|
30
|
+
: ecosystem === "npm" ? versions.stagedNpm?.[name] ?? versions.npm?.[name] : versions.stagedCargo?.[name] ?? versions.cargo?.[name];
|
|
31
|
+
|
|
32
|
+
/** Tracked package.json and Cargo.toml files, excluding RightKit's own source tree and vendored trees. */
|
|
33
|
+
export function dependencyManifests(appRoot) {
|
|
34
|
+
const listed = spawnSync("git", ["-C", appRoot, "ls-files", "-z", "--", "package.json", "*/package.json", "Cargo.toml", "*/Cargo.toml"], { encoding: "utf8", windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
35
|
+
const files = listed.status === 0 ? listed.stdout.split("\0").filter(Boolean) : ["package.json", "Cargo.toml"].filter((file) => existsSync(path.join(appRoot, file)));
|
|
36
|
+
return files.filter((file) => !/(^|\/)(node_modules|vendor|target|dist)\//.test(file) && !file.startsWith("tools/rightkit/"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Pins behind the latest published RightKit version. Workspace, path, git and range-less specs are skipped. */
|
|
40
|
+
export function rightkitDrift(appRoot, versions = loadRightkitVersions()) {
|
|
41
|
+
const drift = [];
|
|
42
|
+
for (const file of dependencyManifests(appRoot)) {
|
|
43
|
+
const text = readFileSync(path.join(appRoot, file), "utf8");
|
|
44
|
+
if (file.endsWith("package.json")) {
|
|
45
|
+
let manifest; try { manifest = JSON.parse(text); } catch { continue; }
|
|
46
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
47
|
+
for (const [name, spec] of Object.entries(manifest[section] ?? {})) {
|
|
48
|
+
const match = name.startsWith("@rightkit/") && typeof spec === "string" ? spec.match(/^([\^~=]?)(\d+\.\d+\.\d+)$/) : null;
|
|
49
|
+
const latest = match && latestFor(versions, "npm", name);
|
|
50
|
+
if (latest && newer(latest, match[2])) drift.push({ ecosystem: "npm", name, file, current: match[2], latest, breaking: breakingBump(match[2], latest) });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const line of text.split(/\r?\n/)) {
|
|
56
|
+
if (/\bpath\s*=/.test(line)) continue;
|
|
57
|
+
const match = line.match(CARGO_PIN);
|
|
58
|
+
const latest = match && latestFor(versions, "cargo", match[2]);
|
|
59
|
+
if (latest && newer(latest, match[5])) drift.push({ ecosystem: "cargo", name: match[2], file, current: match[5], latest, breaking: breakingBump(match[5], latest) });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return drift;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function run(command, args, cwd) {
|
|
66
|
+
const result = spawnSync(command, args, { cwd, stdio: "inherit", windowsHide: true, shell: process.platform === "win32" });
|
|
67
|
+
if (result.status !== 0) throw new Error(`${command} ${args.join(" ")} failed in ${cwd} (exit ${result.status ?? result.error?.message})`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Move drifted pins to the latest version (keeping their = ^ ~ prefix), then refresh lockfiles.
|
|
71
|
+
* Breaking bumps need code changes, so they are adopted only with includeBreaking. */
|
|
72
|
+
export function applyRightkitAdoption(appRoot, versions = loadRightkitVersions(), { refreshLocks = true, includeBreaking = false } = {}) {
|
|
73
|
+
const drift = rightkitDrift(appRoot, versions).filter((item) => includeBreaking || !item.breaking);
|
|
74
|
+
const byFile = Map.groupBy(drift, (item) => item.file);
|
|
75
|
+
for (const [file, items] of byFile) {
|
|
76
|
+
const full = path.join(appRoot, file); let text = readFileSync(full, "utf8");
|
|
77
|
+
if (file.endsWith("package.json")) {
|
|
78
|
+
const manifest = JSON.parse(text);
|
|
79
|
+
for (const item of items) for (const section of DEPENDENCY_SECTIONS) {
|
|
80
|
+
const spec = manifest[section]?.[item.name];
|
|
81
|
+
if (typeof spec === "string") manifest[section][item.name] = spec.replace(item.current, item.latest);
|
|
82
|
+
}
|
|
83
|
+
text = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
84
|
+
} else {
|
|
85
|
+
const latest = new Map(items.map((item) => [item.name, item.latest]));
|
|
86
|
+
text = text.split(/(?<=\n)/).map((line) => line.replace(CARGO_PIN, (all, indent, name, mid, prefix, version, close) => /\bpath\s*=/.test(line) || !latest.has(name) ? all : `${indent}${name}${mid}${prefix}${latest.get(name)}${close}`)).join("");
|
|
87
|
+
}
|
|
88
|
+
writeFileSync(full, text);
|
|
89
|
+
}
|
|
90
|
+
if (refreshLocks) {
|
|
91
|
+
for (const file of byFile.keys()) {
|
|
92
|
+
const dir = path.join(appRoot, path.dirname(file));
|
|
93
|
+
if (file.endsWith("package.json")) run("pnpm", ["install", "--lockfile-only", "--ignore-scripts"], dir);
|
|
94
|
+
else run("rightkit", ["cargo", "metadata", "--format-version", "1", "--manifest-path", path.join(appRoot, file)], dir);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return drift;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function formatDrift(drift) {
|
|
101
|
+
if (!drift.length) return "RightKit pins are current.";
|
|
102
|
+
return [`RightKit updates available (${drift.length}):`, ...drift.map((d) => `- ${d.name} ${d.current} -> ${d.latest}${d.breaking ? " (breaking)" : ""} (${d.file})`)].join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
106
|
+
const args = process.argv.slice(2); const apply = args.includes("--apply");
|
|
107
|
+
const root = path.resolve(args.find((arg) => !arg.startsWith("--")) ?? process.cwd());
|
|
108
|
+
try {
|
|
109
|
+
const drift = apply ? applyRightkitAdoption(root, undefined, { includeBreaking: args.includes("--all") }) : rightkitDrift(root);
|
|
110
|
+
process.stdout.write(`${apply && drift.length ? `Adopted:\n${formatDrift(drift).split("\n").slice(1).join("\n")}` : formatDrift(drift)}\n`);
|
|
111
|
+
} catch (error) { process.stderr.write(`rightkit-drift: ${error.message}\n`); process.exitCode = 1; }
|
|
112
|
+
}
|
package/rightkit-ownership.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
+
"version": "1",
|
|
3
4
|
"capabilities": [
|
|
4
5
|
{
|
|
5
6
|
"package": "rightkit-browser",
|
|
@@ -47,6 +48,93 @@
|
|
|
47
48
|
"type": "dependency",
|
|
48
49
|
"ecosystem": "npm",
|
|
49
50
|
"name": "playwright-core"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "browser.npm.playwright",
|
|
54
|
+
"type": "dependency",
|
|
55
|
+
"ecosystem": "npm",
|
|
56
|
+
"name": "playwright"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "browser.npm.playwright-test",
|
|
60
|
+
"type": "dependency",
|
|
61
|
+
"ecosystem": "npm",
|
|
62
|
+
"name": "@playwright/test"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "browser.npm.selenium-webdriver",
|
|
66
|
+
"type": "dependency",
|
|
67
|
+
"ecosystem": "npm",
|
|
68
|
+
"name": "selenium-webdriver"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "browser.cargo.webdriver",
|
|
72
|
+
"type": "dependency",
|
|
73
|
+
"ecosystem": "cargo",
|
|
74
|
+
"name": "webdriver"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "browser.source.chrome-discovery",
|
|
78
|
+
"type": "source",
|
|
79
|
+
"pattern": "Google[\\\\/]+Chrome[\\\\/]+Application[\\\\/]+chrome\\.exe|Google Chrome\\.app/Contents/MacOS/Google Chrome"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"package": "rightkit-control",
|
|
85
|
+
"capability": "desktop-control",
|
|
86
|
+
"summary": "Background native input, accessibility reads and WebDriver bridge for driving desktop apps without stealing focus",
|
|
87
|
+
"fingerprints": [
|
|
88
|
+
{
|
|
89
|
+
"id": "control.cargo.enigo",
|
|
90
|
+
"type": "dependency",
|
|
91
|
+
"ecosystem": "cargo",
|
|
92
|
+
"name": "enigo"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "control.cargo.rdev",
|
|
96
|
+
"type": "dependency",
|
|
97
|
+
"ecosystem": "cargo",
|
|
98
|
+
"name": "rdev"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "control.cargo.uiautomation",
|
|
102
|
+
"type": "dependency",
|
|
103
|
+
"ecosystem": "cargo",
|
|
104
|
+
"name": "uiautomation"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "control.cargo.accessibility-sys",
|
|
108
|
+
"type": "dependency",
|
|
109
|
+
"ecosystem": "cargo",
|
|
110
|
+
"name": "accessibility-sys"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "control.npm.nut-js",
|
|
114
|
+
"type": "dependency",
|
|
115
|
+
"ecosystem": "npm",
|
|
116
|
+
"name": "@nut-tree/nut-js"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "control.npm.robotjs",
|
|
120
|
+
"type": "dependency",
|
|
121
|
+
"ecosystem": "npm",
|
|
122
|
+
"name": "robotjs"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "control.api.cgevent-post",
|
|
126
|
+
"type": "source",
|
|
127
|
+
"pattern": "\\bCGEventPost(ToPid)?\\s*\\("
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "control.api.ax-element",
|
|
131
|
+
"type": "source",
|
|
132
|
+
"pattern": "\\bAXUIElementCreate(Application|SystemWide)\\s*\\("
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "control.api.sendinput",
|
|
136
|
+
"type": "source",
|
|
137
|
+
"pattern": "\\bSendInput\\s*\\("
|
|
50
138
|
}
|
|
51
139
|
]
|
|
52
140
|
},
|
|
@@ -227,6 +315,37 @@
|
|
|
227
315
|
}
|
|
228
316
|
]
|
|
229
317
|
},
|
|
318
|
+
{
|
|
319
|
+
"package": "rightkit-qa",
|
|
320
|
+
"capability": "desktop-qa",
|
|
321
|
+
"summary": "Desktop app QA engine: launch, drive, assert and capture installed apps (with rightkit-control)",
|
|
322
|
+
"fingerprints": [
|
|
323
|
+
{
|
|
324
|
+
"id": "qa.npm.webdriverio",
|
|
325
|
+
"type": "dependency",
|
|
326
|
+
"ecosystem": "npm",
|
|
327
|
+
"name": "webdriverio"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "qa.npm.wdio-cli",
|
|
331
|
+
"type": "dependency",
|
|
332
|
+
"ecosystem": "npm",
|
|
333
|
+
"name": "@wdio/cli"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"id": "qa.npm.axe-playwright",
|
|
337
|
+
"type": "dependency",
|
|
338
|
+
"ecosystem": "npm",
|
|
339
|
+
"name": "@axe-core/playwright"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"id": "qa.cargo.tauri-driver",
|
|
343
|
+
"type": "dependency",
|
|
344
|
+
"ecosystem": "cargo",
|
|
345
|
+
"name": "tauri-driver"
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
230
349
|
{
|
|
231
350
|
"package": "rightkit-sandbox",
|
|
232
351
|
"capability": "os-sandbox",
|
|
@@ -358,6 +477,18 @@
|
|
|
358
477
|
"name": "chokidar"
|
|
359
478
|
}
|
|
360
479
|
]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"package": "RightKitSwift",
|
|
483
|
+
"capability": "os-credential-store",
|
|
484
|
+
"summary": "Native Apple scoped credential storage and sync migration",
|
|
485
|
+
"fingerprints": [
|
|
486
|
+
{
|
|
487
|
+
"id": "secrets.swift.scoped-migration",
|
|
488
|
+
"type": "source",
|
|
489
|
+
"pattern": "\\bfunc\\s+copySyncedToDeviceOnly\\s*\\("
|
|
490
|
+
}
|
|
491
|
+
]
|
|
361
492
|
}
|
|
362
493
|
]
|
|
363
494
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Release feed: set rightkit-versions.json staged maps from what crates.io and npm actually serve.
|
|
3
|
+
// Every publishable crate under crates/ and package under packages/ is looked up; a newer
|
|
4
|
+
// published version becomes the staged (latest) version and the one it replaces stays allowed
|
|
5
|
+
// in legacyCargo / legacyNpm, so apps pinned to it keep validating until they adopt.
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const rightkitRoot = path.resolve(packageRoot, "..", "..");
|
|
12
|
+
const VERSIONS_FILE = path.join(packageRoot, "rightkit-versions.json");
|
|
13
|
+
const USER_AGENT = "rightkit-sync/1 (https://github.com/bogusyogi/claude)";
|
|
14
|
+
|
|
15
|
+
const field = (toml, key) => toml.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"))?.[1] ?? null;
|
|
16
|
+
|
|
17
|
+
/** Publishable RightKit crates and npm packages, from the source tree. */
|
|
18
|
+
export function rightkitPublishables(root = rightkitRoot) {
|
|
19
|
+
const crates = []; const packages = [];
|
|
20
|
+
for (const dir of readdirSync(path.join(root, "crates")).sort()) {
|
|
21
|
+
const manifest = path.join(root, "crates", dir, "Cargo.toml");
|
|
22
|
+
if (!existsSync(manifest)) continue;
|
|
23
|
+
const toml = readFileSync(manifest, "utf8");
|
|
24
|
+
const [packageSection] = toml.split(/^\[(?!package\])/m);
|
|
25
|
+
if (/^publish\s*=\s*false/m.test(packageSection)) continue;
|
|
26
|
+
const name = field(packageSection, "name");
|
|
27
|
+
if (name?.startsWith("rightkit-")) crates.push(name);
|
|
28
|
+
}
|
|
29
|
+
for (const dir of readdirSync(path.join(root, "packages")).sort()) {
|
|
30
|
+
const manifest = path.join(root, "packages", dir, "package.json");
|
|
31
|
+
if (!existsSync(manifest)) continue;
|
|
32
|
+
const pkg = JSON.parse(readFileSync(manifest, "utf8"));
|
|
33
|
+
if (!pkg.private && pkg.name?.startsWith("@rightkit/")) packages.push(pkg.name);
|
|
34
|
+
}
|
|
35
|
+
return { crates, packages };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function compareVersions(left, right) {
|
|
39
|
+
const parse = (value) => String(value).split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
40
|
+
const a = parse(left); const b = parse(right);
|
|
41
|
+
for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] - b[i];
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function fetchJson(fetchImpl, url) {
|
|
46
|
+
const response = await fetchImpl(url, { headers: { "user-agent": USER_AGENT, accept: "application/json" }, signal: AbortSignal.timeout(15_000) });
|
|
47
|
+
if (response.status === 404) return null;
|
|
48
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
49
|
+
return response.json();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Latest stable published version per registry, or null when never published. */
|
|
53
|
+
export async function registryLatest({ crates, packages }, { fetchImpl = fetch, concurrency = 6 } = {}) {
|
|
54
|
+
const jobs = [
|
|
55
|
+
...crates.map((name) => ({ registry: "cargo", name, url: `https://crates.io/api/v1/crates/${name}`, pick: (body) => body?.crate?.max_stable_version ?? null })),
|
|
56
|
+
...packages.map((name) => ({ registry: "npm", name, url: `https://registry.npmjs.org/${name.replace("/", "%2f")}`, pick: (body) => body?.["dist-tags"]?.latest ?? null })),
|
|
57
|
+
];
|
|
58
|
+
const results = []; let cursor = 0;
|
|
59
|
+
const worker = async () => {
|
|
60
|
+
while (cursor < jobs.length) {
|
|
61
|
+
const job = jobs[cursor]; cursor += 1;
|
|
62
|
+
results.push({ registry: job.registry, name: job.name, version: job.pick(await fetchJson(fetchImpl, job.url)) });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));
|
|
66
|
+
return results.sort((a, b) => `${a.registry}:${a.name}`.localeCompare(`${b.registry}:${b.name}`));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Pure update: returns the new versions document and the list of changes. */
|
|
70
|
+
export function applyRegistryLatest(versions, latest) {
|
|
71
|
+
const next = structuredClone(versions); const changes = []; let verified = 0;
|
|
72
|
+
for (const { registry, name, version } of latest) {
|
|
73
|
+
if (!version) continue;
|
|
74
|
+
// What the registry serves: the only versions adoption recommends (staged may still be unpublished).
|
|
75
|
+
next.registry ??= { cargo: {}, npm: {} }; next.registry[registry] ??= {};
|
|
76
|
+
if (next.registry[registry][name] !== version) { next.registry[registry][name] = version; verified += 1; }
|
|
77
|
+
const stagedKey = registry === "cargo" ? "stagedCargo" : "stagedNpm";
|
|
78
|
+
const legacyKey = registry === "cargo" ? "legacyCargo" : "legacyNpm";
|
|
79
|
+
const current = next[stagedKey]?.[name] ?? next[registry]?.[name] ?? null;
|
|
80
|
+
if (current && compareVersions(version, current) <= 0) continue;
|
|
81
|
+
next[stagedKey] ??= {};
|
|
82
|
+
const previous = next[stagedKey][name];
|
|
83
|
+
if (previous && previous !== next[registry]?.[name]) {
|
|
84
|
+
next[legacyKey] ??= {};
|
|
85
|
+
const legacy = new Set(next[legacyKey][name] ?? []); legacy.add(previous);
|
|
86
|
+
next[legacyKey][name] = [...legacy].sort(compareVersions);
|
|
87
|
+
}
|
|
88
|
+
next[stagedKey][name] = version;
|
|
89
|
+
next[stagedKey] = Object.fromEntries(Object.entries(next[stagedKey]).sort(([a], [b]) => a.localeCompare(b)));
|
|
90
|
+
changes.push({ registry, name, from: current, to: version });
|
|
91
|
+
}
|
|
92
|
+
for (const key of ["cargo", "npm"]) if (next.registry?.[key]) next.registry[key] = Object.fromEntries(Object.entries(next.registry[key]).sort(([a], [b]) => a.localeCompare(b)));
|
|
93
|
+
return { versions: next, changes, verified };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function syncRightkitVersions({ check = false, fetchImpl = fetch, file = VERSIONS_FILE, root = rightkitRoot, log = (line) => process.stdout.write(`${line}\n`) } = {}) {
|
|
97
|
+
const versions = JSON.parse(readFileSync(file, "utf8"));
|
|
98
|
+
const latest = await registryLatest(rightkitPublishables(root), { fetchImpl });
|
|
99
|
+
const { versions: next, changes, verified } = applyRegistryLatest(versions, latest);
|
|
100
|
+
for (const change of changes) log(`${change.registry} ${change.name}: ${change.from ?? "unlisted"} -> ${change.to}`);
|
|
101
|
+
const unpublished = latest.filter((item) => !item.version).map((item) => `${item.registry}:${item.name}`);
|
|
102
|
+
if (unpublished.length) log(`never published: ${unpublished.join(", ")}`);
|
|
103
|
+
if (!changes.length) log("rightkit-versions.json matches the registries");
|
|
104
|
+
if (check) return { changes, unpublished, copies: [], written: false };
|
|
105
|
+
if (changes.length || verified) writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
106
|
+
const copies = syncConsumerCopies(root);
|
|
107
|
+
for (const copy of copies) log(`refreshed ${copy}`);
|
|
108
|
+
return { changes, unpublished, copies, written: changes.length > 0 };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Record one just-published version (the registry index can lag the upload) and refresh the copies.
|
|
112
|
+
* Called by right-release after a verified cargo or npm publish; never fails the publish itself. */
|
|
113
|
+
export function recordPublished({ registry, name, version }, { file = VERSIONS_FILE, root = rightkitRoot, log = (line) => process.stdout.write(`${line}\n`) } = {}) {
|
|
114
|
+
try {
|
|
115
|
+
const { versions: next, changes, verified } = applyRegistryLatest(JSON.parse(readFileSync(file, "utf8")), [{ registry, name, version }]);
|
|
116
|
+
if (changes.length || verified) writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
117
|
+
const copies = syncConsumerCopies(root);
|
|
118
|
+
log(`[right-release] release feed: ${name} ${version} ${changes.length ? "staged" : "already current"}${copies.length ? `; refreshed ${copies.length} copies` : ""}; commit rightkit-versions.json and its copies`);
|
|
119
|
+
} catch (error) { log(`[right-release] release feed not updated (${error.message}); run node rightkit-sync.mjs`); }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The broker client and the session hook carry byte copies of the catalog and versions; keep them exact. */
|
|
123
|
+
export function syncConsumerCopies(root = rightkitRoot) {
|
|
124
|
+
const release = path.join(root, "packages", "release");
|
|
125
|
+
const targets = [
|
|
126
|
+
["rightkit-ownership.json", path.join(root, "packages", "build-control-clients", "src", "ownership-catalog.json")],
|
|
127
|
+
["rightkit-versions.json", path.join(root, "packages", "build-control-clients", "src", "rightkit-versions.json")],
|
|
128
|
+
["rightkit-ownership.json", path.join(root, "packages", "build-control-policy", "src", "ownership-session-catalog.json")],
|
|
129
|
+
["rightkit-ownership.json", path.join(root, "packages", "build-control-policy", "src", "ownership-fingerprints.json")],
|
|
130
|
+
["rightkit-versions.json", path.join(root, "packages", "build-control-policy", "src", "ownership-session-versions.json")],
|
|
131
|
+
["rightkit-drift.mjs", path.join(root, "packages", "build-control-policy", "src", "rightkit-drift.mjs")],
|
|
132
|
+
["background-process.mjs", path.join(root, "packages", "build-control-policy", "src", "background-process.mjs")],
|
|
133
|
+
];
|
|
134
|
+
const updated = [];
|
|
135
|
+
for (const [source, target] of targets) {
|
|
136
|
+
const bytes = readFileSync(path.join(release, source));
|
|
137
|
+
if (source.endsWith(".json")) JSON.parse(bytes.toString("utf8"));
|
|
138
|
+
if (existsSync(target) && readFileSync(target).equals(bytes)) continue;
|
|
139
|
+
writeFileSync(target, bytes);
|
|
140
|
+
updated.push(path.relative(root, target).split(path.sep).join("/"));
|
|
141
|
+
}
|
|
142
|
+
return updated;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
146
|
+
const check = process.argv.includes("--check");
|
|
147
|
+
syncRightkitVersions({ check })
|
|
148
|
+
.then(({ changes }) => { if (check && changes.length) process.exitCode = 1; })
|
|
149
|
+
.catch((error) => { process.stderr.write(`rightkit-sync: ${error.message}\n`); process.exitCode = 1; });
|
|
150
|
+
}
|