@rightkit/release 0.2.19 → 0.2.21
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/cargo-contract.mjs +159 -0
- package/cli/right-release.mjs +9 -0
- package/model-promote.mjs +262 -0
- package/model-promote.test.mjs +240 -0
- package/package.json +3 -2
- package/publish-update.mjs +7 -12
- package/release.mjs +2 -0
- package/release.test.mjs +64 -2
- package/right-suite-contract.test.mjs +315 -22
- package/rightapps-register.mjs +31 -0
- package/rightapps-register.test.mjs +28 -0
- package/rightkit-versions.json +7 -4
- package/runtime-artifact-manifest.mjs +247 -0
- package/runtime-artifact-manifest.test.mjs +128 -0
- package/standalone-clone-verify.mjs +131 -0
- package/standalone-clone-verify.test.mjs +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
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": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "node --test *.test.mjs",
|
|
19
|
-
"doctor:all": "node --test right-suite-contract.test.mjs"
|
|
19
|
+
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
20
|
+
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
20
21
|
},
|
|
21
22
|
"publishConfig": {
|
|
22
23
|
"registry": "https://registry.npmjs.org/",
|
package/publish-update.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { pruneReleaseObjects } from "./prune-r2.mjs";
|
|
8
8
|
import { loadReleaseToken } from "./release-token.mjs";
|
|
9
|
+
import { registerRightAppsRelease } from "./rightapps-register.mjs";
|
|
9
10
|
|
|
10
11
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const UPLOAD = path.join(TOOL_ROOT, "upload-large.mjs");
|
|
@@ -272,18 +273,12 @@ function patchInstallerKey(artifact) {
|
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
async function register(route, body) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"x-store-slug": process.env.RIGHTAPPS_STORE_SLUG || "rightapps",
|
|
282
|
-
},
|
|
283
|
-
body: JSON.stringify(body),
|
|
284
|
-
});
|
|
285
|
-
if (!response.ok) fail(`registration failed: ${response.status} ${await response.text()}`);
|
|
286
|
-
console.log(await response.text());
|
|
276
|
+
try {
|
|
277
|
+
const response = await registerRightAppsRelease(route, body, { token: releaseToken });
|
|
278
|
+
if (response !== null) console.log(typeof response === "string" ? response : JSON.stringify(response));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
fail(error.message);
|
|
281
|
+
}
|
|
287
282
|
}
|
|
288
283
|
|
|
289
284
|
function fail(message) {
|
package/release.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import { validateRightKitCargoContract } from "./cargo-contract.mjs";
|
|
7
8
|
|
|
8
9
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
|
|
@@ -62,6 +63,7 @@ if (config.schema !== 1) fail(`unsupported config schema: ${config.schema ?? "<m
|
|
|
62
63
|
const root = path.dirname(configPath);
|
|
63
64
|
const workdir = path.resolve(root, config.workdir ?? ".");
|
|
64
65
|
await validateRightKitPackageContract(root, config.app);
|
|
66
|
+
validateRightKitCargoContract(root, RIGHTKIT_VERSIONS.cargo, config.app ?? path.basename(root));
|
|
65
67
|
const target = config.targets?.[opts.platform];
|
|
66
68
|
if (!target) fail(`${config.app ?? "app"} has no ${opts.platform} release target`);
|
|
67
69
|
if (target.signed !== true) {
|
package/release.test.mjs
CHANGED
|
@@ -9,8 +9,10 @@ import test from "node:test";
|
|
|
9
9
|
const release = fileURLToPath(new URL("./release.mjs", import.meta.url));
|
|
10
10
|
const versions = JSON.parse(readFileSync(new URL("./rightkit-versions.json", import.meta.url), "utf8"));
|
|
11
11
|
|
|
12
|
-
function fixture({ signed = true, publish = false, packageJson } = {}) {
|
|
13
|
-
const
|
|
12
|
+
function fixture({ signed = true, publish = false, packageJson, cargoManifest, cargoConfig, repoCargoConfig, siblingCargoManifest } = {}) {
|
|
13
|
+
const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), "right-release-test-"));
|
|
14
|
+
const dir = repoCargoConfig ? path.join(fixtureRoot, "apps", "fixture") : fixtureRoot;
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
14
16
|
const config = path.join(dir, "right-release.config.mjs");
|
|
15
17
|
writeFileSync(
|
|
16
18
|
path.join(dir, "package.json"),
|
|
@@ -49,6 +51,26 @@ function fixture({ signed = true, publish = false, packageJson } = {}) {
|
|
|
49
51
|
},
|
|
50
52
|
})};\n`,
|
|
51
53
|
);
|
|
54
|
+
if (cargoManifest) {
|
|
55
|
+
writeFileSync(path.join(dir, "Cargo.toml"), `${cargoManifest}\n[lib]\npath = "fixture.rs"\n`, "utf8");
|
|
56
|
+
writeFileSync(path.join(dir, "fixture.rs"), "", "utf8");
|
|
57
|
+
}
|
|
58
|
+
if (cargoConfig) {
|
|
59
|
+
const cargoDir = path.join(dir, ".cargo");
|
|
60
|
+
mkdirSync(cargoDir, { recursive: true });
|
|
61
|
+
writeFileSync(path.join(cargoDir, "config.toml"), cargoConfig, "utf8");
|
|
62
|
+
}
|
|
63
|
+
if (repoCargoConfig) {
|
|
64
|
+
mkdirSync(path.join(fixtureRoot, ".git"), { recursive: true });
|
|
65
|
+
const cargoDir = path.join(fixtureRoot, ".cargo");
|
|
66
|
+
mkdirSync(cargoDir, { recursive: true });
|
|
67
|
+
writeFileSync(path.join(cargoDir, "config.toml"), repoCargoConfig, "utf8");
|
|
68
|
+
}
|
|
69
|
+
if (siblingCargoManifest) {
|
|
70
|
+
const sibling = path.join(fixtureRoot, "docs", "archived-workspace");
|
|
71
|
+
mkdirSync(sibling, { recursive: true });
|
|
72
|
+
writeFileSync(path.join(sibling, "Cargo.toml"), siblingCargoManifest, "utf8");
|
|
73
|
+
}
|
|
52
74
|
return config;
|
|
53
75
|
}
|
|
54
76
|
|
|
@@ -171,6 +193,46 @@ test("rejects hosted workflow files before release work starts", () => {
|
|
|
171
193
|
assert.match(result.stderr, /hosted workflow.*forbidden/i);
|
|
172
194
|
});
|
|
173
195
|
|
|
196
|
+
test("doctor accepts an exact crates.io RightKit pin with benign Cargo config", () => {
|
|
197
|
+
const config = fixture({
|
|
198
|
+
cargoManifest: `[package]\nname = "fixture"\nversion = "0.0.0"\nedition = "2021"\n[dependencies]\nrightkit-license = "=0.1.2"`,
|
|
199
|
+
cargoConfig: `[net]\nretry = 2\n[registries.private]\nindex = "https://example.test/index"`,
|
|
200
|
+
});
|
|
201
|
+
const result = run(config, "--doctor");
|
|
202
|
+
assert.equal(result.status, 0, result.stderr);
|
|
203
|
+
assert.match(result.stdout, /right-release/);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("doctor scopes Cargo inspection to the configured app inside a monorepo", () => {
|
|
207
|
+
const config = fixture({
|
|
208
|
+
cargoManifest: `[package]\nname = "fixture"\nversion = "0.0.0"\nedition = "2021"\n[dependencies]\nrightkit-license = "=0.1.2"`,
|
|
209
|
+
repoCargoConfig: `[net]\nretry = 2`,
|
|
210
|
+
siblingCargoManifest: `[package]\nname = "archived-copy"\nversion = "0.0.0"\n[dependencies]\nrightkit-license = { path = "../../rightkit-license" }`,
|
|
211
|
+
});
|
|
212
|
+
const result = run(config, "--doctor");
|
|
213
|
+
assert.equal(result.status, 0, result.stderr);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("doctor rejects a repo-local RightKit Cargo override", () => {
|
|
217
|
+
const config = fixture({
|
|
218
|
+
cargoManifest: `[package]\nname = "fixture"\nversion = "0.0.0"\nedition = "2021"\n[dependencies]\nrightkit-license = "=0.1.1"`,
|
|
219
|
+
cargoConfig: `[patch.crates-io]\nrightkit-license = { path = "../rightkit-license" }`,
|
|
220
|
+
});
|
|
221
|
+
const result = run(config, "--doctor");
|
|
222
|
+
assert.notEqual(result.status, 0);
|
|
223
|
+
assert.match(result.stderr, /forbidden RightKit Cargo override/i);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("doctor rejects a RightKit override at an ancestor repository root", () => {
|
|
227
|
+
const config = fixture({
|
|
228
|
+
cargoManifest: `[package]\nname = "fixture"\nversion = "0.0.0"\nedition = "2021"\n[dependencies]\nrightkit-license = "=0.1.1"`,
|
|
229
|
+
repoCargoConfig: `[patch.crates-io]\nrightkit-license = { git = "https://example.test/rightkit-license.git" }`,
|
|
230
|
+
});
|
|
231
|
+
const result = run(config, "--doctor");
|
|
232
|
+
assert.notEqual(result.status, 0);
|
|
233
|
+
assert.match(result.stderr, /forbidden RightKit Cargo override/i);
|
|
234
|
+
});
|
|
235
|
+
|
|
174
236
|
test("publish runs the signed package step before the updater publication step", () => {
|
|
175
237
|
const result = run(fixture({ publish: true }), "--tier=update", "--upload");
|
|
176
238
|
assert.equal(result.status, 0, result.stderr);
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
4
6
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import test from "node:test";
|
|
7
|
+
import test, { after } from "node:test";
|
|
8
|
+
import {
|
|
9
|
+
assertNoRightKitCargoOverrides,
|
|
10
|
+
assertPublishedRightKitCargoDependencies,
|
|
11
|
+
validateRightKitCargoContract,
|
|
12
|
+
} from "./cargo-contract.mjs";
|
|
6
13
|
|
|
7
14
|
const workspace = path.resolve(new URL("../../../..", import.meta.url).pathname.replace(/^\/(\w:)/, "$1"));
|
|
8
15
|
const versionsPath = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/(\w:)/, "$1")), "rightkit-versions.json");
|
|
9
16
|
const versions = JSON.parse(readFileSync(versionsPath, "utf8"));
|
|
17
|
+
const isolatedCargoHome = mkdtempSync(path.join(tmpdir(), "rightkit-cargo-home-"));
|
|
18
|
+
after(() => rmSync(isolatedCargoHome, { recursive: true, force: true }));
|
|
10
19
|
const pubkey = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDI5Mzk1RjlGRjQ2NjI2MUQKUldRZEptYjBuMTg1S1VSUXlBdFM4WmtzaHArYko0U2hRMDVlSDJmSExVZG82Q0hoQ2srUlhqanAK";
|
|
11
20
|
const apps = [
|
|
12
21
|
{ key: "viewright", root: "viewright", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/build-mac-notarized.sh"] },
|
|
@@ -16,19 +25,301 @@ const apps = [
|
|
|
16
25
|
{ key: "coderight", root: "coderight/apps/coderight-tauri", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/mac-dmg.mjs"] },
|
|
17
26
|
];
|
|
18
27
|
|
|
28
|
+
function resolveCargoVersionContract(versionManifest, canonicalVersions) {
|
|
29
|
+
const consumer = new Map(Object.entries(versionManifest.cargo ?? {}));
|
|
30
|
+
const staged = new Map(Object.entries(versionManifest.stagedCargo ?? {}));
|
|
31
|
+
const canonical = new Map();
|
|
32
|
+
|
|
33
|
+
for (const [crate, version] of staged) {
|
|
34
|
+
if (!canonicalVersions.has(crate)) throw new Error(`unknown staged Cargo crate: ${crate}`);
|
|
35
|
+
const actual = canonicalVersions.get(crate);
|
|
36
|
+
if (actual !== version) {
|
|
37
|
+
throw new Error(`${crate} canonical manifest is ${actual}, expected staged version ${version}`);
|
|
38
|
+
}
|
|
39
|
+
canonical.set(crate, version);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [crate, version] of consumer) {
|
|
43
|
+
if (!canonicalVersions.has(crate)) throw new Error(`unknown published Cargo crate: ${crate}`);
|
|
44
|
+
const expected = staged.get(crate) ?? version;
|
|
45
|
+
const actual = canonicalVersions.get(crate);
|
|
46
|
+
if (actual !== expected) {
|
|
47
|
+
const source = staged.has(crate) ? "staged" : "published";
|
|
48
|
+
throw new Error(`${crate} canonical manifest is ${actual}, expected ${source} version ${expected}`);
|
|
49
|
+
}
|
|
50
|
+
canonical.set(crate, expected);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { canonical, consumer };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let currentCargoVersionContract;
|
|
57
|
+
function getCurrentCargoVersionContract() {
|
|
58
|
+
currentCargoVersionContract ??= resolveCargoVersionContract(versions, readCanonicalCrateVersions());
|
|
59
|
+
return currentCargoVersionContract;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cargoContractCache = new Map();
|
|
63
|
+
function readCargoManifestContract(manifestPath, label) {
|
|
64
|
+
const cacheKey = path.resolve(manifestPath).toLowerCase();
|
|
65
|
+
if (cargoContractCache.has(cacheKey)) return cargoContractCache.get(cacheKey);
|
|
66
|
+
const result = spawnSync(
|
|
67
|
+
"cargo",
|
|
68
|
+
["metadata", "--no-deps", "--offline", "--format-version", "1", "--manifest-path", manifestPath],
|
|
69
|
+
{
|
|
70
|
+
cwd: path.dirname(manifestPath),
|
|
71
|
+
encoding: "utf8",
|
|
72
|
+
env: { ...process.env, CARGO_HOME: isolatedCargoHome },
|
|
73
|
+
windowsHide: true,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
assert.equal(
|
|
77
|
+
result.status,
|
|
78
|
+
0,
|
|
79
|
+
`${label} Cargo metadata rejected manifest; RightKit dependencies must use exact crates.io versions: ${String(result.stderr ?? "").trim()}`,
|
|
80
|
+
);
|
|
81
|
+
const metadata = JSON.parse(result.stdout);
|
|
82
|
+
const expectedPath = path.resolve(manifestPath).toLowerCase();
|
|
83
|
+
const pkg = metadata.packages.find(({ manifest_path: candidate }) => path.resolve(candidate).toLowerCase() === expectedPath);
|
|
84
|
+
if (!pkg && path.resolve(metadata.workspace_root, "Cargo.toml").toLowerCase() === expectedPath) {
|
|
85
|
+
const contract = { dependencies: [], workspaceRoot: path.resolve(metadata.workspace_root) };
|
|
86
|
+
cargoContractCache.set(cacheKey, contract);
|
|
87
|
+
return contract;
|
|
88
|
+
}
|
|
89
|
+
assert.ok(pkg, `${label} was not returned by Cargo metadata`);
|
|
90
|
+
const contract = { dependencies: pkg.dependencies, workspaceRoot: path.resolve(metadata.workspace_root) };
|
|
91
|
+
cargoContractCache.set(cacheKey, contract);
|
|
92
|
+
return contract;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function assertCargoFixture(manifest, published, label) {
|
|
96
|
+
const fixtureRoot = mkdtempSync(path.join(tmpdir(), "rightkit-cargo-contract-"));
|
|
97
|
+
const manifestPath = path.join(fixtureRoot, "Cargo.toml");
|
|
98
|
+
const packageMetadata = /^\s*\[\s*package\s*\]/m.test(manifest)
|
|
99
|
+
? ""
|
|
100
|
+
: '\n[package]\nname = "rightkit-contract-fixture"\nversion = "0.0.0"\nedition = "2021"\n';
|
|
101
|
+
writeFileSync(manifestPath, `${manifest}${packageMetadata}\n[lib]\npath = "fixture.rs"\n`, "utf8");
|
|
102
|
+
writeFileSync(path.join(fixtureRoot, "fixture.rs"), "", "utf8");
|
|
103
|
+
try {
|
|
104
|
+
return assertPublishedRightKitCargoDependencies(
|
|
105
|
+
readCargoManifestContract(manifestPath, label).dependencies,
|
|
106
|
+
published,
|
|
107
|
+
label,
|
|
108
|
+
);
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function assertCargoOverrideFixture({ manifest, workspaceManifest, localConfig, ancestorConfig, outsideConfig }) {
|
|
115
|
+
const fixtureRoot = mkdtempSync(path.join(tmpdir(), "rightkit-cargo-override-"));
|
|
116
|
+
const repoRoot = path.join(fixtureRoot, "repo");
|
|
117
|
+
const appRoot = path.join(repoRoot, "app");
|
|
118
|
+
mkdirSync(appRoot, { recursive: true });
|
|
119
|
+
const manifestPath = path.join(appRoot, "Cargo.toml");
|
|
120
|
+
writeFileSync(manifestPath, manifest, "utf8");
|
|
121
|
+
if (workspaceManifest) writeFileSync(path.join(repoRoot, "Cargo.toml"), workspaceManifest, "utf8");
|
|
122
|
+
for (const [root, filename, source] of [
|
|
123
|
+
[appRoot, "config", localConfig],
|
|
124
|
+
[repoRoot, "config.toml", ancestorConfig],
|
|
125
|
+
[fixtureRoot, "config.toml", outsideConfig],
|
|
126
|
+
]) {
|
|
127
|
+
if (!source) continue;
|
|
128
|
+
const cargoDir = path.join(root, ".cargo");
|
|
129
|
+
mkdirSync(cargoDir, { recursive: true });
|
|
130
|
+
writeFileSync(path.join(cargoDir, filename), source, "utf8");
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
assertNoRightKitCargoOverrides(manifestPath, repoRoot, "fixture/Cargo.toml");
|
|
134
|
+
if (workspaceManifest) {
|
|
135
|
+
assertNoRightKitCargoOverrides(path.join(repoRoot, "Cargo.toml"), repoRoot, "fixture/workspace Cargo.toml");
|
|
136
|
+
}
|
|
137
|
+
return 0;
|
|
138
|
+
} finally {
|
|
139
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
test("Cargo override contract accepts exact registry pins without repo-local overrides", () => {
|
|
144
|
+
assert.equal(assertCargoOverrideFixture({
|
|
145
|
+
manifest: `[package]\nname = "fixture"\nversion = "0.0.0"\n[dependencies]\nrightkit-license = "=0.1.1"`,
|
|
146
|
+
localConfig: `[net]\nretry = 2\n[registries.private]\nindex = "https://example.test/index"`,
|
|
147
|
+
outsideConfig: `[source.crates-io]\nreplace-with = "user-global-vendor"`,
|
|
148
|
+
}), 0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("Cargo override contract rejects RightKit patches and replacements in repo-local Cargo config", () => {
|
|
152
|
+
for (const config of [
|
|
153
|
+
{ localConfig: `[patch.crates-io]\nrightkit-license = { path = "../rightkit-license" }` },
|
|
154
|
+
{ localConfig: `patch . "https://github.com/rust-lang/crates.io-index" . "rightkit-logs" = { git = "https://example.test/rightkit-logs.git" }` },
|
|
155
|
+
{ localConfig: `[ replace ]\n"rightkit-license:0.1.1" = { path = "../rightkit-license" }` },
|
|
156
|
+
{ ancestorConfig: `[patch.private-source]\nadapter = { package = "rightkit-license", registry = "private" }` },
|
|
157
|
+
]) {
|
|
158
|
+
assert.throws(
|
|
159
|
+
() => assertCargoOverrideFixture({
|
|
160
|
+
manifest: `[package]\nname = "fixture"\nversion = "0.0.0"\n[dependencies]\nrightkit-license = "=0.1.1"`,
|
|
161
|
+
...config,
|
|
162
|
+
}),
|
|
163
|
+
/forbidden RightKit Cargo override/,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("Cargo override contract rejects path patches, Git patches, and replace entries", () => {
|
|
169
|
+
for (const manifest of [
|
|
170
|
+
`patch . "crates-io" . "rightkit-license" = { path = "../rightkit-license" } # dotted path patch`,
|
|
171
|
+
`[ patch . "https://github.com/rust-lang/crates.io-index" ]\n"rightkit-logs" = { git = "https://example.test/rightkit-logs.git" }`,
|
|
172
|
+
`[ replace ]\n"rightkit-license:0.1.1" = { path = "../rightkit-license" }`,
|
|
173
|
+
]) {
|
|
174
|
+
assert.throws(
|
|
175
|
+
() => assertCargoOverrideFixture({ manifest }),
|
|
176
|
+
/forbidden RightKit Cargo override/,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("Cargo override contract rejects workspace-root patches inherited by a member", () => {
|
|
182
|
+
assert.throws(
|
|
183
|
+
() => assertCargoOverrideFixture({
|
|
184
|
+
manifest: `[package]\nname = "fixture"\nversion = "0.0.0"`,
|
|
185
|
+
workspaceManifest: `[workspace]\nmembers = ["app"]\n[patch.crates-io]\nrightkit-license = { path = "patched/rightkit-license" }`,
|
|
186
|
+
}),
|
|
187
|
+
/forbidden RightKit Cargo override/,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("Cargo override contract rejects local and repo-ancestor crates.io source replacement", () => {
|
|
192
|
+
for (const fixture of [
|
|
193
|
+
{
|
|
194
|
+
localConfig: `source . crates-io . replace-with = "vendored-sources"\n[source.vendored-sources]\ndirectory = "vendor"`,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
ancestorConfig: `[source."crates-io"]\nreplace-with = "rightkit-git"\n[source.rightkit-git]\ngit = "https://example.test/rightkit-index.git"`,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
ancestorConfig: `[source.crates-io]\nreplace-with = "private-registry"\n[source.private-registry]\nregistry = "https://example.test/index"`,
|
|
201
|
+
},
|
|
202
|
+
]) {
|
|
203
|
+
assert.throws(
|
|
204
|
+
() => assertCargoOverrideFixture({
|
|
205
|
+
manifest: `[package]\nname = "fixture"\nversion = "0.0.0"\n[dependencies]\nrightkit-license = "=0.1.1"`,
|
|
206
|
+
...fixture,
|
|
207
|
+
}),
|
|
208
|
+
/forbidden crates\.io source replacement/,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("direct Cargo dependency contract accepts only published exact RightKit pins", () => {
|
|
214
|
+
const published = new Map([
|
|
215
|
+
["rightkit-license", "0.1.2"],
|
|
216
|
+
["rightkit-logs", "0.1.0"],
|
|
217
|
+
["rightkit-tauri", "0.1.0"],
|
|
218
|
+
]);
|
|
219
|
+
const manifest = `
|
|
220
|
+
[ package ]
|
|
221
|
+
name = "rightkit-tauri"
|
|
222
|
+
# rightkit-tauri = "=0.1.0"
|
|
223
|
+
|
|
224
|
+
[ dependencies ]
|
|
225
|
+
"rightkit-license" = "=0.1.2" # published exact pin
|
|
226
|
+
"rightkit-tauri" = "=0.1.0"
|
|
227
|
+
|
|
228
|
+
[ target . 'cfg(windows)' . dependencies . "rightkit-logs" ]
|
|
229
|
+
version = "=0.1.0"
|
|
230
|
+
|
|
231
|
+
[ target . 'cfg(unix)' . dev-dependencies ]
|
|
232
|
+
license-adapter = { package = "rightkit-license", version = "=0.1.2" }
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
assert.equal(assertCargoFixture(manifest, published, "fixture/Cargo.toml"), 4);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("direct Cargo dependency contract rejects staged-only RightKit crates", () => {
|
|
239
|
+
const { consumer } = resolveCargoVersionContract(
|
|
240
|
+
{
|
|
241
|
+
cargo: { "rightkit-license": "0.1.2" },
|
|
242
|
+
stagedCargo: { "rightkit-tauri": "0.1.0" },
|
|
243
|
+
},
|
|
244
|
+
new Map([
|
|
245
|
+
["rightkit-license", "0.1.2"],
|
|
246
|
+
["rightkit-tauri", "0.1.0"],
|
|
247
|
+
]),
|
|
248
|
+
);
|
|
249
|
+
assert.equal(consumer.has("rightkit-tauri"), false);
|
|
250
|
+
assert.throws(
|
|
251
|
+
() => assertCargoFixture(
|
|
252
|
+
`dependencies . "rightkit-tauri" = "=0.1.0" # staged only`,
|
|
253
|
+
consumer,
|
|
254
|
+
"fixture/Cargo.toml",
|
|
255
|
+
),
|
|
256
|
+
/fixture\/Cargo\.toml rightkit-tauri is not a published RightKit crate/,
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("direct Cargo dependency contract still rejects path, Git, and custom registry sources", () => {
|
|
261
|
+
const published = new Map([
|
|
262
|
+
["rightkit-license", "0.1.1"],
|
|
263
|
+
["rightkit-logs", "0.1.0"],
|
|
264
|
+
]);
|
|
265
|
+
for (const manifest of [
|
|
266
|
+
`dependencies . rightkit-license = { path = "../rightkit-license" }`,
|
|
267
|
+
`target . 'cfg(windows)' . dependencies . "rightkit-logs" = { git = "https://example.test/rightkit.git" }`,
|
|
268
|
+
`dependencies . rightkit-license = { version = "=0.1.1", registry = "private" }`,
|
|
269
|
+
]) {
|
|
270
|
+
assert.throws(
|
|
271
|
+
() => assertCargoFixture(manifest, published, "fixture/Cargo.toml"),
|
|
272
|
+
/must use exact crates\.io versions|must use the exact crates\.io version/,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("Cargo version contract keeps staged versions out of consumer pins", () => {
|
|
278
|
+
const resolved = resolveCargoVersionContract(
|
|
279
|
+
{
|
|
280
|
+
cargo: { "rightkit-license": "0.1.1" },
|
|
281
|
+
stagedCargo: { "rightkit-license": "0.1.2" },
|
|
282
|
+
},
|
|
283
|
+
new Map([["rightkit-license", "0.1.2"]]),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
assert.equal(resolved.consumer.get("rightkit-license"), "0.1.1");
|
|
287
|
+
assert.equal(resolved.canonical.get("rightkit-license"), "0.1.2");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("Cargo version contract rejects unknown staged crates", () => {
|
|
291
|
+
assert.throws(
|
|
292
|
+
() => resolveCargoVersionContract(
|
|
293
|
+
{ cargo: {}, stagedCargo: { "rightkit-unknown": "0.1.0" } },
|
|
294
|
+
new Map(),
|
|
295
|
+
),
|
|
296
|
+
/unknown staged Cargo crate: rightkit-unknown/,
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("Cargo version contract rejects staged versions that mismatch canonical manifests", () => {
|
|
301
|
+
assert.throws(
|
|
302
|
+
() => resolveCargoVersionContract(
|
|
303
|
+
{ cargo: { "rightkit-license": "0.1.1" }, stagedCargo: { "rightkit-license": "0.1.3" } },
|
|
304
|
+
new Map([["rightkit-license", "0.1.2"]]),
|
|
305
|
+
),
|
|
306
|
+
/rightkit-license canonical manifest is 0\.1\.2, expected staged version 0\.1\.3/,
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
19
310
|
test("RightKit exposes one current version manifest", () => {
|
|
20
311
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
21
312
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
22
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
313
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.21");
|
|
23
314
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
24
315
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
|
316
|
+
assert.equal(versions.npm["@rightkit/tauri"], "0.1.0");
|
|
25
317
|
assert.equal(versions.npm["@rightkit/updates"], "0.2.3");
|
|
26
|
-
assert.equal(versions.cargo["rightkit-license"], "0.1.
|
|
318
|
+
assert.equal(versions.cargo["rightkit-license"], "0.1.2");
|
|
27
319
|
assert.equal(versions.cargo["rightkit-logs"], "0.1.0");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
320
|
+
assert.equal(versions.cargo["rightkit-tauri"], "0.1.0");
|
|
321
|
+
assert.deepEqual(versions.stagedCargo, {});
|
|
322
|
+
getCurrentCargoVersionContract();
|
|
32
323
|
});
|
|
33
324
|
|
|
34
325
|
test("license v2 public vector is identical at every portable consumer boundary", () => {
|
|
@@ -119,18 +410,8 @@ for (const app of apps) {
|
|
|
119
410
|
]);
|
|
120
411
|
assert.equal(tauri.bundle.createUpdaterArtifacts, true);
|
|
121
412
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for (const [crate, version] of Object.entries(versions.cargo)) {
|
|
125
|
-
const dependency = manifest.match(new RegExp(`^${crate}\\s*=\\s*(.+)$`, "m"));
|
|
126
|
-
if (!dependency) continue;
|
|
127
|
-
assert.equal(
|
|
128
|
-
dependency[1].trim(),
|
|
129
|
-
`"=${version}"`,
|
|
130
|
-
`${path.relative(workspace, manifestPath)} ${crate} must use the exact crates.io version`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
413
|
+
const { consumer } = getCurrentCargoVersionContract();
|
|
414
|
+
validateRightKitCargoContract(root, consumer, app.key);
|
|
134
415
|
|
|
135
416
|
for (const localCopy of [
|
|
136
417
|
"src-tauri/vendor/rightkit-license",
|
|
@@ -173,7 +454,7 @@ function findFiles(root, filename) {
|
|
|
173
454
|
const found = [];
|
|
174
455
|
const visit = (dir) => {
|
|
175
456
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
176
|
-
if (entry.isDirectory() && [".git", ".claude", ".worktrees", "node_modules", "target", "vendor"].includes(entry.name)) continue;
|
|
457
|
+
if (entry.isDirectory() && [".cache", ".git", ".claude", ".worktrees", "node_modules", "target", "vendor"].includes(entry.name)) continue;
|
|
177
458
|
const full = path.join(dir, entry.name);
|
|
178
459
|
if (entry.isDirectory()) visit(full);
|
|
179
460
|
else if (entry.isFile() && entry.name === filename) found.push(full);
|
|
@@ -183,7 +464,19 @@ function findFiles(root, filename) {
|
|
|
183
464
|
return found;
|
|
184
465
|
}
|
|
185
466
|
|
|
186
|
-
|
|
467
|
+
function readCanonicalCrateVersions() {
|
|
468
|
+
const crateRoot = path.join(workspace, "tools/rightkit/crates");
|
|
469
|
+
return new Map(
|
|
470
|
+
readdirSync(crateRoot, { withFileTypes: true })
|
|
471
|
+
.filter((entry) => entry.isDirectory() && existsSync(path.join(crateRoot, entry.name, "Cargo.toml")))
|
|
472
|
+
.map((entry) => {
|
|
473
|
+
const manifest = readFileSync(path.join(crateRoot, entry.name, "Cargo.toml"), "utf8");
|
|
474
|
+
const version = manifest.match(/^version\s*=\s*"([^"]+)"$/m)?.[1];
|
|
475
|
+
assert.ok(version, `${entry.name} canonical manifest must declare a package version`);
|
|
476
|
+
return [entry.name, version];
|
|
477
|
+
}),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
187
480
|
|
|
188
481
|
test("Right Suite has no hosted workflow files", () => {
|
|
189
482
|
for (const root of ["viewright", "scraperight", "heardright", "mailright", "coderight", "genright", "voiceright", "tools/rightkit"]) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const DEFAULT_API_BASE = "https://api.spoares.com";
|
|
2
|
+
const REGISTRATION_ROUTES = new Set([
|
|
3
|
+
"/v1/admin/apps/patches",
|
|
4
|
+
"/v1/admin/apps/releases",
|
|
5
|
+
"/v1/admin/apps/runtime-artifacts",
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
export async function registerRightAppsRelease(route, body, {
|
|
9
|
+
token,
|
|
10
|
+
env = process.env,
|
|
11
|
+
fetchImpl = fetch,
|
|
12
|
+
} = {}) {
|
|
13
|
+
if (!token?.trim()) throw new Error("RIGHTAPPS_RELEASE_TOKEN release token is required for RightApps registration");
|
|
14
|
+
if (!REGISTRATION_ROUTES.has(route)) {
|
|
15
|
+
throw new Error("RightApps registration route is not approved");
|
|
16
|
+
}
|
|
17
|
+
const apiBase = (env.RIGHTAPPS_API_URL || DEFAULT_API_BASE).replace(/\/$/, "");
|
|
18
|
+
const response = await fetchImpl(`${apiBase}${route}`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
authorization: `Bearer ${token.trim()}`,
|
|
22
|
+
"content-type": "application/json",
|
|
23
|
+
"x-store-slug": env.RIGHTAPPS_STORE_SLUG || "rightapps",
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(body),
|
|
26
|
+
});
|
|
27
|
+
const text = await response.text();
|
|
28
|
+
if (!response.ok) throw new Error(`RightApps registration failed: ${response.status} ${text}`);
|
|
29
|
+
if (!text) return null;
|
|
30
|
+
try { return JSON.parse(text); } catch { return text; }
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { registerRightAppsRelease } from "./rightapps-register.mjs";
|
|
5
|
+
|
|
6
|
+
test("registers through the shared durable release-token boundary", async () => {
|
|
7
|
+
const calls = [];
|
|
8
|
+
const response = await registerRightAppsRelease("/v1/admin/apps/runtime-artifacts", { schema: 1 }, {
|
|
9
|
+
token: "durable-token",
|
|
10
|
+
env: { RIGHTAPPS_API_URL: "https://rightapps.example", RIGHTAPPS_STORE_SLUG: "rightapps" },
|
|
11
|
+
fetchImpl: async (url, init) => {
|
|
12
|
+
calls.push({ url, init });
|
|
13
|
+
return { ok: true, text: async () => '{"id":"registered"}' };
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
assert.deepEqual(response, { id: "registered" });
|
|
17
|
+
assert.equal(calls[0].url, "https://rightapps.example/v1/admin/apps/runtime-artifacts");
|
|
18
|
+
assert.equal(calls[0].init.headers.authorization, "Bearer durable-token");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("rejects absent durable token and non-admin registration paths before network", async () => {
|
|
22
|
+
let called = false;
|
|
23
|
+
const fetchImpl = async () => { called = true; };
|
|
24
|
+
await assert.rejects(registerRightAppsRelease("/v1/admin/apps/runtime-artifacts", {}, { token: "", fetchImpl }), /release token/i);
|
|
25
|
+
await assert.rejects(registerRightAppsRelease("https://evil.invalid", {}, { token: "token", fetchImpl }), /route/i);
|
|
26
|
+
await assert.rejects(registerRightAppsRelease("/v1/admin/apps/../../evil", {}, { token: "token", fetchImpl }), /route/i);
|
|
27
|
+
assert.equal(called, false);
|
|
28
|
+
});
|
package/rightkit-versions.json
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
"npm": {
|
|
5
5
|
"@rightkit/license": "0.1.5",
|
|
6
6
|
"@rightkit/logs": "0.1.3",
|
|
7
|
-
"@rightkit/release": "0.2.
|
|
7
|
+
"@rightkit/release": "0.2.21",
|
|
8
|
+
"@rightkit/tauri": "0.1.0",
|
|
8
9
|
"@rightkit/updates": "0.2.3"
|
|
9
10
|
},
|
|
10
11
|
"cargo": {
|
|
11
|
-
"rightkit-license": "0.1.
|
|
12
|
-
"rightkit-logs": "0.1.0"
|
|
13
|
-
|
|
12
|
+
"rightkit-license": "0.1.2",
|
|
13
|
+
"rightkit-logs": "0.1.0",
|
|
14
|
+
"rightkit-tauri": "0.1.0"
|
|
15
|
+
},
|
|
16
|
+
"stagedCargo": {}
|
|
14
17
|
}
|