@rightkit/release 0.2.27 → 0.2.29
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 +1 -1
- package/legal-contract.mjs +20 -14
- package/legal-contract.test.mjs +21 -4
- package/package.json +1 -1
- package/pub-upload.mjs +100 -0
- package/right-suite-contract.test.mjs +530 -528
- package/rightkit-versions.json +36 -34
- package/standalone-clone-verify.mjs +1 -7
- package/upload-large.test.mjs +1 -0
package/cargo-contract.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export function assertPublishedRightKitCargoDependencies(dependencies, published
|
|
|
39
39
|
const allowed = configured instanceof Set ? configured : new Set(Array.isArray(configured) ? configured : [configured]);
|
|
40
40
|
const exactVersions = new Set([...allowed].map((version) => `=${version}`));
|
|
41
41
|
if (!CRATES_IO_SOURCES.has(dependency.source) || dependency.path || dependency.registry || !exactVersions.has(dependency.req)) {
|
|
42
|
-
throw new Error(`${label} ${dependency.name} must use
|
|
42
|
+
throw new Error(`${label} ${dependency.name} must use exact crates.io versions (${[...exactVersions].join(" or ")})`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
return checked;
|
package/legal-contract.mjs
CHANGED
|
@@ -135,20 +135,26 @@ export function assertLegalReleaseContract(root, legal, appName, platform) {
|
|
|
135
135
|
appendices.push({ ...appendix, absolutePath: realFile });
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
138
|
+
// The in-app gate is the SINGLE assent surface (Adrian, 2026-07-17). An
|
|
139
|
+
// installer/DMG can only PRESENT the text: it cannot record who agreed, cannot ask
|
|
140
|
+
// the individual-vs-enterprise basis, and passive updaters reach neither — so a
|
|
141
|
+
// license page is duplicate presentation that captures nothing. This gate
|
|
142
|
+
// previously REQUIRED bundle.licenseFile; it now requires its absence.
|
|
143
|
+
//
|
|
144
|
+
// Checked on EVERY platform, not just Windows: one bundle.licenseFile drives BOTH
|
|
145
|
+
// the NSIS license page and the macOS DMG SLA (branded-dmg.mjs derives its --eula
|
|
146
|
+
// from it), so a win-only check would let a mac build reintroduce the SLA.
|
|
147
|
+
if (typeof legal.tauriConfig !== "string") fail(appName, "the legal gate requires legal.tauriConfig");
|
|
148
|
+
const tauriPath = path.resolve(appRoot, legal.tauriConfig);
|
|
149
|
+
if (!within(appRoot, tauriPath) || !existsSync(tauriPath)) fail(appName, `missing Tauri config: ${legal.tauriConfig}`);
|
|
150
|
+
const tauri = readJson(tauriPath, appName, "Tauri config");
|
|
151
|
+
const configured = tauri.bundle?.licenseFile;
|
|
152
|
+
if (typeof configured === "string" && configured.trim()) {
|
|
153
|
+
fail(
|
|
154
|
+
appName,
|
|
155
|
+
"Tauri bundle.licenseFile must be absent: the in-app legal gate is the only assent surface, and an " +
|
|
156
|
+
"installer license page (NSIS) or DMG SLA duplicates it without recording acceptance",
|
|
157
|
+
);
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
return {
|
package/legal-contract.test.mjs
CHANGED
|
@@ -46,9 +46,11 @@ function fixture() {
|
|
|
46
46
|
})),
|
|
47
47
|
};
|
|
48
48
|
writeFileSync(path.join(legalDir, "legal-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
49
|
+
// No bundle.licenseFile: the in-app gate is the single assent surface, so a
|
|
50
|
+
// compliant app ships an installer with no license page (Adrian, 2026-07-17).
|
|
49
51
|
writeFileSync(
|
|
50
52
|
path.join(tauriDir, "tauri.conf.json"),
|
|
51
|
-
`${JSON.stringify({ bundle: {
|
|
53
|
+
`${JSON.stringify({ bundle: { publisher: "Damned Ventures LLC" } }, null, 2)}\n`,
|
|
52
54
|
);
|
|
53
55
|
return {
|
|
54
56
|
root,
|
|
@@ -98,10 +100,25 @@ test("rejects an explicitly unresolved release blocker even without a checkbox",
|
|
|
98
100
|
assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /release blocker/i);
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
test("
|
|
103
|
+
test("rejects a Windows installer license page: the in-app gate is the only assent surface", () => {
|
|
102
104
|
const { root, legal } = fixture();
|
|
103
|
-
writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/
|
|
104
|
-
assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /licenseFile
|
|
105
|
+
writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
|
|
106
|
+
assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /licenseFile must be absent/i);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("rejects a macOS DMG SLA too — one licenseFile drives both bundlers", () => {
|
|
110
|
+
// branded-dmg.mjs derives its --eula from bundle.licenseFile, so a Windows-only
|
|
111
|
+
// check would let a mac release quietly reintroduce the mount-time SLA.
|
|
112
|
+
const { root, legal } = fixture();
|
|
113
|
+
writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
|
|
114
|
+
assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "mac"), /licenseFile must be absent/i);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("accepts an app whose installer carries no license page", () => {
|
|
118
|
+
const { root, legal } = fixture();
|
|
119
|
+
for (const platform of ["win", "mac"]) {
|
|
120
|
+
assert.doesNotThrow(() => assertLegalReleaseContract(root, legal, "fixture", platform));
|
|
121
|
+
}
|
|
105
122
|
});
|
|
106
123
|
|
|
107
124
|
test("requires third-party build provenance", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.29",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: signed installers, updater artifacts, patch/update routing, hardening, R2 publish, and RightApps registration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/pub-upload.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { accessSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const ACCOUNT_ID = "03ae77ccd7a07bcbb2dcfde47fa7ba3a";
|
|
7
|
+
const BUCKETS = new Map([
|
|
8
|
+
["public", "rightapps-downloads"],
|
|
9
|
+
["private", "rightapps-updates"],
|
|
10
|
+
]);
|
|
11
|
+
// Wrangler always runs remotely through a package runner; which one exists is machine-specific
|
|
12
|
+
// (a node install without npm/npx is normal when pnpm is the pinned package manager).
|
|
13
|
+
const RUNNERS = [
|
|
14
|
+
{ cmd: "npx", prefix: [] },
|
|
15
|
+
{ cmd: "pnpm", prefix: ["dlx"] },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const [, , localFile, r2Key, bucketAlias = "public"] = process.argv;
|
|
19
|
+
if (!localFile || !r2Key) {
|
|
20
|
+
console.error("Usage: node upload-large.mjs <localFile> <r2Key> [public|private]");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const bucket = BUCKETS.get(bucketAlias);
|
|
25
|
+
if (!bucket) {
|
|
26
|
+
console.error(`upload-large: invalid bucket alias ${bucketAlias}; expected public|private`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
accessSync(localFile);
|
|
32
|
+
} catch {
|
|
33
|
+
console.error(`upload-large: missing local file: ${localFile}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fileSize = statSync(localFile).size;
|
|
38
|
+
const object = `${bucket}/${r2Key}`;
|
|
39
|
+
const wranglerArgs = ["wrangler@4", "r2", "object", "put", object, "--file", path.resolve(localFile), "--remote"];
|
|
40
|
+
const env = cleanCloudflareEnv(process.env);
|
|
41
|
+
|
|
42
|
+
console.log(`Uploading ${path.basename(localFile)} (${(fileSize / 1024 / 1024).toFixed(1)} MB) -> ${object}`);
|
|
43
|
+
console.log(`wrangler r2 object put ${object} --file ${path.resolve(localFile)} --remote`);
|
|
44
|
+
|
|
45
|
+
// Resolve the package runner before the dry-run exit: a release box without one cannot upload,
|
|
46
|
+
// and that must fail here rather than after a signed build has already been produced.
|
|
47
|
+
const runner = resolveRunner(env);
|
|
48
|
+
if (!runner) {
|
|
49
|
+
console.error(
|
|
50
|
+
"upload-large: no package runner found. Tried: " +
|
|
51
|
+
`${RUNNERS.map((entry) => [entry.cmd, ...entry.prefix].join(" ")).join(", ")}. ` +
|
|
52
|
+
"Install one, or set RIGHT_RELEASE_WRANGLER_RUNNER (e.g. \"pnpm dlx\").",
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(`runner: ${[runner.cmd, ...runner.prefix].join(" ")}`);
|
|
57
|
+
|
|
58
|
+
if (process.env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1") {
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = spawnSync(runner.cmd, [...runner.prefix, ...wranglerArgs], {
|
|
63
|
+
env,
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
shell: process.platform === "win32",
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (result.error) {
|
|
70
|
+
console.error(`upload-large: failed to start wrangler: ${result.error.message}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
process.exit(result.status ?? 1);
|
|
74
|
+
|
|
75
|
+
function resolveRunner(env) {
|
|
76
|
+
const override = process.env.RIGHT_RELEASE_WRANGLER_RUNNER?.trim();
|
|
77
|
+
if (override) {
|
|
78
|
+
const [cmd, ...prefix] = override.split(/\s+/);
|
|
79
|
+
return { cmd, prefix };
|
|
80
|
+
}
|
|
81
|
+
for (const candidate of RUNNERS) {
|
|
82
|
+
const probe = spawnSync(candidate.cmd, ["--version"], {
|
|
83
|
+
env,
|
|
84
|
+
stdio: "ignore",
|
|
85
|
+
shell: process.platform === "win32",
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
});
|
|
88
|
+
if (!probe.error && probe.status === 0) return candidate;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cleanCloudflareEnv(source) {
|
|
94
|
+
const env = { ...source, CLOUDFLARE_ACCOUNT_ID: source.CLOUDFLARE_ACCOUNT_ID || ACCOUNT_ID };
|
|
95
|
+
delete env.CLOUDFLARE_API_KEY;
|
|
96
|
+
delete env.CLOUDFLARE_EMAIL;
|
|
97
|
+
delete env.CF_API_KEY;
|
|
98
|
+
delete env.CF_EMAIL;
|
|
99
|
+
return env;
|
|
100
|
+
}
|