@rightkit/release 0.2.35 → 0.2.37
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/package.json +1 -1
- package/pub-upload.mjs +9 -23
- package/release-cli-contract.test.mjs +17 -0
- package/right-suite-contract.test.mjs +1 -1
- package/rightkit-versions.json +1 -1
- package/sign-windows.mjs +10 -5
- package/upload-large.mjs +9 -23
- package/upload-large.test.mjs +1 -17
- package/upload-release.mjs +3 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.37",
|
|
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
CHANGED
|
@@ -8,12 +8,7 @@ const BUCKETS = new Map([
|
|
|
8
8
|
["public", "rightapps-downloads"],
|
|
9
9
|
["private", "rightapps-updates"],
|
|
10
10
|
]);
|
|
11
|
-
|
|
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
|
-
];
|
|
11
|
+
const RUNNER = { cmd: "pnpm", prefix: ["dlx"] };
|
|
17
12
|
|
|
18
13
|
const [, , localFile, r2Key, bucketAlias = "public"] = process.argv;
|
|
19
14
|
if (!localFile || !r2Key) {
|
|
@@ -48,8 +43,7 @@ const runner = resolveRunner(env);
|
|
|
48
43
|
if (!runner) {
|
|
49
44
|
console.error(
|
|
50
45
|
"upload-large: no package runner found. Tried: " +
|
|
51
|
-
|
|
52
|
-
"Install one, or set RIGHT_RELEASE_WRANGLER_RUNNER (e.g. \"pnpm dlx\").",
|
|
46
|
+
"pnpm dlx. Install the pinned workspace package manager before release.",
|
|
53
47
|
);
|
|
54
48
|
process.exit(1);
|
|
55
49
|
}
|
|
@@ -73,21 +67,13 @@ if (result.error) {
|
|
|
73
67
|
process.exit(result.status ?? 1);
|
|
74
68
|
|
|
75
69
|
function resolveRunner(env) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
|
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;
|
|
91
77
|
}
|
|
92
78
|
|
|
93
79
|
function cleanCloudflareEnv(source) {
|
|
@@ -10,6 +10,7 @@ const root = path.dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const cli = path.join(root, "cli", "right-release.mjs");
|
|
11
11
|
const build = path.join(root, "build-release.mjs");
|
|
12
12
|
const upload = path.join(root, "upload-release.mjs");
|
|
13
|
+
const signWindows = path.join(root, "sign-windows.mjs");
|
|
13
14
|
|
|
14
15
|
test("CLI routes build and release only to the build state machine", () => {
|
|
15
16
|
const source = readFileSync(cli, "utf8");
|
|
@@ -41,3 +42,19 @@ test("upload requires an explicit patch or update tier before reading a release"
|
|
|
41
42
|
assert.notEqual(result.status, 0);
|
|
42
43
|
assert.match(result.stderr, /tier is required.*patch\|update/i);
|
|
43
44
|
});
|
|
45
|
+
|
|
46
|
+
test("Windows upload trust verification uses signtool instead of PowerShell modules", () => {
|
|
47
|
+
const uploadSource = readFileSync(upload, "utf8");
|
|
48
|
+
const signingSource = readFileSync(signWindows, "utf8");
|
|
49
|
+
assert.match(uploadSource, /SIGN_WINDOWS/);
|
|
50
|
+
assert.match(uploadSource, /--verify-only/);
|
|
51
|
+
assert.doesNotMatch(uploadSource, /Get-AuthenticodeSignature/);
|
|
52
|
+
assert.match(signingSource, /verifyOnly/);
|
|
53
|
+
assert.match(signingSource, /\["verify", "\/pa", "\/v", file\]/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("upload uses only the pinned pnpm runner", () => {
|
|
57
|
+
const source = readFileSync(upload, "utf8");
|
|
58
|
+
assert.match(source, /spawnSync\("pnpm", \["dlx", "wrangler@4"/);
|
|
59
|
+
assert.doesNotMatch(source, /\bnpx\b/);
|
|
60
|
+
});
|
|
@@ -317,7 +317,7 @@ test("Cargo version contract rejects staged versions that mismatch canonical man
|
|
|
317
317
|
test("RightKit exposes one current version manifest", () => {
|
|
318
318
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
319
319
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
320
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
320
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.37");
|
|
321
321
|
assert.equal(versions.npm["@rightkit/legal"], "0.2.0");
|
|
322
322
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
323
323
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
package/rightkit-versions.json
CHANGED
package/sign-windows.mjs
CHANGED
|
@@ -18,17 +18,24 @@ const userEnvCache = new Map();
|
|
|
18
18
|
|
|
19
19
|
const args = process.argv.slice(2);
|
|
20
20
|
const dryRun = args.includes("--dry-run");
|
|
21
|
-
const
|
|
21
|
+
const verifyOnly = args.includes("--verify-only");
|
|
22
|
+
const files = args.filter((arg) => arg !== "--dry-run" && arg !== "--verify-only").map((arg) => path.resolve(arg));
|
|
22
23
|
|
|
23
|
-
if (!files.length) fail("usage: node sign-windows.mjs [--dry-run] <file>...");
|
|
24
|
+
if (!files.length) fail("usage: node sign-windows.mjs [--dry-run|--verify-only] <file>...");
|
|
24
25
|
if (dryRun) {
|
|
25
|
-
console.log(`dry-run: Azure Artifact Signing ${files.join(", ")}`);
|
|
26
|
+
console.log(`dry-run: ${verifyOnly ? "Authenticode verification" : "Azure Artifact Signing"} ${files.join(", ")}`);
|
|
26
27
|
process.exit(0);
|
|
27
28
|
}
|
|
28
29
|
if (process.platform !== "win32") fail("Windows signing must run on Windows");
|
|
29
30
|
for (const file of files) if (!existsSync(file)) fail(`missing file: ${file}`);
|
|
30
31
|
|
|
32
|
+
const childEnv = signingEnv();
|
|
31
33
|
const signtool = findFirst([env("AZURE_SIGNTOOL_PATH"), env("SIGNTOOL_PATH"), ...signtoolCandidates()]);
|
|
34
|
+
if (!signtool) fail("signtool.exe not found; set AZURE_SIGNTOOL_PATH or install Windows SDK Build Tools");
|
|
35
|
+
if (verifyOnly) {
|
|
36
|
+
for (const file of files) run(signtool, ["verify", "/pa", "/v", file]);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
32
39
|
const dlib = findFirst([
|
|
33
40
|
env("AZURE_CODESIGN_DLIB_PATH"),
|
|
34
41
|
env("AZURE_ARTIFACT_SIGNING_DLIB_PATH"),
|
|
@@ -36,12 +43,10 @@ const dlib = findFirst([
|
|
|
36
43
|
"C:\\Program Files\\Microsoft Azure Artifact Signing Client Tools\\x64\\Azure.CodeSigning.Dlib.dll",
|
|
37
44
|
"C:\\Program Files (x86)\\Microsoft\\ArtifactSigningClientTools\\bin\\x64\\Azure.CodeSigning.Dlib.dll",
|
|
38
45
|
]);
|
|
39
|
-
if (!signtool) fail("signtool.exe not found; set AZURE_SIGNTOOL_PATH or install Windows SDK Build Tools");
|
|
40
46
|
if (!dlib) fail("Azure.CodeSigning.Dlib.dll not found; set AZURE_CODESIGN_DLIB_PATH or install Azure Artifact Signing Client Tools");
|
|
41
47
|
|
|
42
48
|
const tempDir = mkdtempSync(path.join(os.tmpdir(), "right-sign-"));
|
|
43
49
|
const metadata = metadataPath(tempDir);
|
|
44
|
-
const childEnv = signingEnv();
|
|
45
50
|
try {
|
|
46
51
|
for (const file of files) {
|
|
47
52
|
run(signtool, ["sign", "/v", "/fd", "SHA256", "/tr", "http://timestamp.acs.microsoft.com", "/td", "SHA256", "/dlib", dlib, "/dmdf", metadata, file]);
|
package/upload-large.mjs
CHANGED
|
@@ -8,12 +8,7 @@ const BUCKETS = new Map([
|
|
|
8
8
|
["public", "rightapps-downloads"],
|
|
9
9
|
["private", "rightapps-updates"],
|
|
10
10
|
]);
|
|
11
|
-
|
|
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
|
-
];
|
|
11
|
+
const RUNNER = { cmd: "pnpm", prefix: ["dlx"] };
|
|
17
12
|
|
|
18
13
|
const [, , localFile, r2Key, bucketAlias = "public"] = process.argv;
|
|
19
14
|
if (!localFile || !r2Key) {
|
|
@@ -48,8 +43,7 @@ const runner = resolveRunner(env);
|
|
|
48
43
|
if (!runner) {
|
|
49
44
|
console.error(
|
|
50
45
|
"upload-large: no package runner found. Tried: " +
|
|
51
|
-
|
|
52
|
-
"Install one, or set RIGHT_RELEASE_WRANGLER_RUNNER (e.g. \"pnpm dlx\").",
|
|
46
|
+
"pnpm dlx. Install the pinned workspace package manager before release.",
|
|
53
47
|
);
|
|
54
48
|
process.exit(1);
|
|
55
49
|
}
|
|
@@ -73,21 +67,13 @@ if (result.error) {
|
|
|
73
67
|
process.exit(result.status ?? 1);
|
|
74
68
|
|
|
75
69
|
function resolveRunner(env) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
|
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;
|
|
91
77
|
}
|
|
92
78
|
|
|
93
79
|
function cleanCloudflareEnv(source) {
|
package/upload-large.test.mjs
CHANGED
|
@@ -46,10 +46,7 @@ test("refuses unknown bucket aliases", () => {
|
|
|
46
46
|
test("resolves a real package runner for wrangler on this machine", () => {
|
|
47
47
|
const result = run("public");
|
|
48
48
|
assert.equal(result.status, 0, result.stderr);
|
|
49
|
-
|
|
50
|
-
// manager; the uploader must find a runner that actually exists rather than assume npx.
|
|
51
|
-
// Assuming npx once made every R2 upload a silent no-op while the publish reported success.
|
|
52
|
-
assert.match(result.stdout, /^runner: (npx|pnpm dlx)$/m);
|
|
49
|
+
assert.match(result.stdout, /^runner: pnpm dlx$/m);
|
|
53
50
|
});
|
|
54
51
|
|
|
55
52
|
test("fails loudly when no package runner exists instead of skipping the upload", () => {
|
|
@@ -71,16 +68,3 @@ test("fails loudly when no package runner exists instead of skipping the upload"
|
|
|
71
68
|
assert.notEqual(result.status, 0, "a box with no runner must not report a successful upload");
|
|
72
69
|
assert.match(result.stderr, /no package runner found/);
|
|
73
70
|
});
|
|
74
|
-
|
|
75
|
-
test("honours an explicit runner override", () => {
|
|
76
|
-
const result = spawnSync(
|
|
77
|
-
process.execPath,
|
|
78
|
-
[uploader, fixtureFile(), "fixture/installers/windows/current/Artifact.exe", "public"],
|
|
79
|
-
{
|
|
80
|
-
encoding: "utf8",
|
|
81
|
-
env: { ...process.env, RIGHT_RELEASE_UPLOAD_DRY_RUN: "1", RIGHT_RELEASE_WRANGLER_RUNNER: "pnpm dlx" },
|
|
82
|
-
},
|
|
83
|
-
);
|
|
84
|
-
assert.equal(result.status, 0, result.stderr);
|
|
85
|
-
assert.match(result.stdout, /^runner: pnpm dlx$/m);
|
|
86
|
-
});
|
package/upload-release.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { runUploadStateMachine, verifySealedRelease } from "./release-state.mjs"
|
|
|
9
9
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const PUBLISH_UPDATE = path.join(TOOL_ROOT, "publish-update.mjs");
|
|
11
11
|
const HARDENING = path.join(TOOL_ROOT, "hardeningscan.mjs");
|
|
12
|
+
const SIGN_WINDOWS = path.join(TOOL_ROOT, "sign-windows.mjs");
|
|
12
13
|
const UPLOAD = path.join(TOOL_ROOT, "upload-large.mjs");
|
|
13
14
|
const PUBLIC_BASE = (process.env.RIGHTAPPS_PUBLIC_DOWNLOAD_BASE || "https://pub-6c73208d46c245a9b4881d5e02f6b618.r2.dev").replace(/\/$/, "");
|
|
14
15
|
const API_BASE = (process.env.RIGHTAPPS_API_URL || "https://api.spoares.com").replace(/\/$/, "");
|
|
@@ -73,9 +74,7 @@ function verifyPlatformTrust(release) {
|
|
|
73
74
|
if (platform === "win") {
|
|
74
75
|
for (const artifact of artifacts) {
|
|
75
76
|
const file = path.join(release.sealedDir, artifact.name);
|
|
76
|
-
|
|
77
|
-
const status = commandOutput("powershell", ["-NoProfile", "-Command", `(Get-AuthenticodeSignature -LiteralPath '${escaped}').Status`]);
|
|
78
|
-
if (status.trim() !== "Valid") throw new Error(`Authenticode verification failed for ${artifact.name}: ${status}`);
|
|
77
|
+
runChecked(process.execPath, [SIGN_WINDOWS, "--verify-only", file], repoRoot);
|
|
79
78
|
}
|
|
80
79
|
} else {
|
|
81
80
|
for (const artifact of artifacts.filter((file) => /\.dmg$/i.test(file.name))) {
|
|
@@ -183,7 +182,7 @@ function bucketName(bucket) {
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
function wrangler(runArgs) {
|
|
186
|
-
return spawnSync("
|
|
185
|
+
return spawnSync("pnpm", ["dlx", "wrangler@4", ...runArgs], { cwd: repoRoot, env: process.env, encoding: "utf8", windowsHide: true, shell: process.platform === "win32" });
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
function hashFile(file) {
|