@rightkit/release 0.2.68 → 0.2.70
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/cli/right-release.mjs +0 -0
- package/package.json +13 -9
- package/release.mjs +47 -14
- package/rightkit-versions.json +4 -2
- package/addon-command.test.mjs +0 -54
- package/addon-contract.test.mjs +0 -48
- package/asr-artifact-adoption.test.mjs +0 -122
- package/build-invocation-contract.test.mjs +0 -124
- package/build-release.test.mjs +0 -242
- package/cache-command.test.mjs +0 -118
- package/cache-policy.test.mjs +0 -346
- package/cargo-guard.test.mjs +0 -195
- package/cargo-target.test.mjs +0 -82
- package/create-mac-updater.test.mjs +0 -14
- package/github-release.test.mjs +0 -103
- package/heavy-command.test.mjs +0 -221
- package/legal-contract.test.mjs +0 -151
- package/mirror-root-artifact.test.mjs +0 -58
- package/model-promote.test.mjs +0 -284
- package/notary-auth.test.mjs +0 -31
- package/nsis-payload.test.mjs +0 -139
- package/nsis-upgrade-contract.test.mjs +0 -57
- package/preflight.test.mjs +0 -123
- package/progress-control.test.mjs +0 -56
- package/prune-r2.test.mjs +0 -12
- package/publish-cargo.test.mjs +0 -43
- package/publish-swift.test.mjs +0 -44
- package/publish-update.test.mjs +0 -207
- package/qa-contract.test.mjs +0 -47
- package/registry-parity.test.mjs +0 -30
- package/release-cli-contract.test.mjs +0 -119
- package/release-invocation.test.mjs +0 -22
- package/release-state.test.mjs +0 -395
- package/release-token.test.mjs +0 -21
- package/release.test.mjs +0 -477
- package/right-suite-contract.test.mjs +0 -1007
- package/rightapps-register.test.mjs +0 -28
- package/runtime-artifact-manifest.test.mjs +0 -128
- package/sign-updater.test.mjs +0 -12
- package/source-gate.test.mjs +0 -25
- package/standalone-clone-evidence.json +0 -32
- package/standalone-clone-verify.test.mjs +0 -76
- package/suite-doctor.test.mjs +0 -19
- package/target-bridge.test.mjs +0 -269
- package/tauri-bundle-marker.test.mjs +0 -81
- package/upload-large.test.mjs +0 -70
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import { cancelAndReapManagedRequest, firstManagedRequestId, latestManagedRequestId, releaseProgressLimits, terminalResultFromOutput } from "./progress-control.mjs";
|
|
5
|
-
|
|
6
|
-
test("release progress stays bounded without killing a healthy long native build at 90 minutes", () => {
|
|
7
|
-
assert.deepEqual(releaseProgressLimits({}), { inactivityMs: 600_000, absoluteMs: 14_400_000 });
|
|
8
|
-
assert.deepEqual(releaseProgressLimits({ RIGHT_RELEASE_STALL_MS: "1000", RIGHT_RELEASE_ABSOLUTE_MS: "5000" }), { inactivityMs: 1_000, absoluteMs: 5_000 });
|
|
9
|
-
assert.throws(() => releaseProgressLimits({ RIGHT_RELEASE_STALL_MS: "1000", RIGHT_RELEASE_ABSOLUTE_MS: "1000" }), /must exceed/);
|
|
10
|
-
assert.throws(() => releaseProgressLimits({ RIGHT_RELEASE_STALL_MS: "nope" }), /positive finite/);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("declared inner liveness owner always exits before the outer watchdog", () => {
|
|
14
|
-
const ownerTimeoutMs = 45 * 60 * 1000;
|
|
15
|
-
assert.deepEqual(
|
|
16
|
-
releaseProgressLimits({}, { livenessOwnerTimeoutMs: ownerTimeoutMs }),
|
|
17
|
-
{ inactivityMs: 50 * 60 * 1000, absoluteMs: 4 * 60 * 60 * 1000 },
|
|
18
|
-
);
|
|
19
|
-
assert.deepEqual(
|
|
20
|
-
releaseProgressLimits({ RIGHT_RELEASE_STALL_MS: String(60 * 60 * 1000) }, { livenessOwnerTimeoutMs: ownerTimeoutMs }),
|
|
21
|
-
{ inactivityMs: 60 * 60 * 1000, absoluteMs: 4 * 60 * 60 * 1000 },
|
|
22
|
-
);
|
|
23
|
-
assert.throws(
|
|
24
|
-
() => releaseProgressLimits({ RIGHT_RELEASE_ABSOLUTE_MS: String(49 * 60 * 1000) }, { livenessOwnerTimeoutMs: ownerTimeoutMs }),
|
|
25
|
-
/must exceed effective release inactivity limit/,
|
|
26
|
-
);
|
|
27
|
-
assert.throws(() => releaseProgressLimits({}, { livenessOwnerTimeoutMs: 0 }), /positive finite/);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("latest managed request id follows streamed root Cargo requests", () => {
|
|
31
|
-
const first = "11111111-1111-4111-8111-111111111111";
|
|
32
|
-
const second = "22222222-2222-4222-8222-222222222222";
|
|
33
|
-
assert.equal(latestManagedRequestId(`rightkit: request ${first}\nrightkit managed-agent: request ${second}`), second);
|
|
34
|
-
assert.equal(firstManagedRequestId(`rightkit: request ${first}\nrightkit managed-agent: request ${second}`), first);
|
|
35
|
-
assert.equal(latestManagedRequestId("ordinary output", first), first);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("managed cancellation issues CANCEL, waits for terminal RESULT, & returns reap receipt", async () => {
|
|
39
|
-
const id = "33333333-3333-4333-8333-333333333333";
|
|
40
|
-
const calls = [];
|
|
41
|
-
const run = async (args) => {
|
|
42
|
-
calls.push(args);
|
|
43
|
-
if (args[0] === "cancel") return { code: 0, output: JSON.stringify({ type: "CANCEL_REQUESTED", id }) };
|
|
44
|
-
return { code: 130, output: `compiler tail\n${JSON.stringify({ type: "RESULT", id, status: "CANCELLED", exitCode: 130, receipt: { reap: { reaped: true } } })}\n` };
|
|
45
|
-
};
|
|
46
|
-
assert.deepEqual(await cancelAndReapManagedRequest(id, { run }), {
|
|
47
|
-
status: "CANCELLED", id, exitCode: 130, receipt: { reap: { reaped: true } }, cancelExitCode: 0, attachExitCode: 130,
|
|
48
|
-
});
|
|
49
|
-
assert.deepEqual(calls, [["cancel", id], ["attach", id]]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("missing terminal broker result fails closed", async () => {
|
|
53
|
-
const run = async () => ({ code: 125, output: "broker closed", timedOut: false });
|
|
54
|
-
await assert.rejects(cancelAndReapManagedRequest("44444444-4444-4444-8444-444444444444", { run }), /lacked terminal RESULT/);
|
|
55
|
-
assert.equal(terminalResultFromOutput("noise\n"), null);
|
|
56
|
-
});
|
package/prune-r2.test.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { isReleaseObject } from "./prune-r2.mjs";
|
|
4
|
-
|
|
5
|
-
test("pruning only targets release objects for the active OS", () => {
|
|
6
|
-
assert.equal(isReleaseObject("viewright", "mac", "viewright/installers/mac/ViewRight.dmg"), true);
|
|
7
|
-
assert.equal(isReleaseObject("viewright", "mac", "viewright/updates/mac/current/ViewRight.app.tar.gz"), true);
|
|
8
|
-
assert.equal(isReleaseObject("viewright", "mac", "viewright/installers/windows/ViewRight-Setup.exe"), false);
|
|
9
|
-
assert.equal(isReleaseObject("viewright", "windows", "viewright/installers/mac/ViewRight.dmg"), false);
|
|
10
|
-
assert.equal(isReleaseObject("viewright", "mac", "viewright/models/mac/model.bin"), false);
|
|
11
|
-
assert.equal(isReleaseObject("viewright", "mac", "other/installers/mac/Other.dmg"), false);
|
|
12
|
-
});
|
package/publish-cargo.test.mjs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
buildCargoPublishPlan,
|
|
5
|
-
packageFileViolations,
|
|
6
|
-
parseCargoPublishArgs,
|
|
7
|
-
} from "./publish-cargo.mjs";
|
|
8
|
-
|
|
9
|
-
test("cargo publish requires a safe explicit crate name", () => {
|
|
10
|
-
assert.throws(() => parseCargoPublishArgs([]), /--crate/);
|
|
11
|
-
assert.throws(() => parseCargoPublishArgs(["--crate", "../secret"]), /invalid crate/);
|
|
12
|
-
assert.throws(() => parseCargoPublishArgs(["--crate", "rightkit-license", "--allow-dirty"]), /only valid with --dry-run/);
|
|
13
|
-
assert.equal(parseCargoPublishArgs(["--crate", "rightkit-license"]).crate, "rightkit-license");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("cargo publish plan always runs local safety gates before upload", () => {
|
|
17
|
-
const plan = buildCargoPublishPlan({ crate: "rightkit-license", crateDir: "C:/rightkit/crates/rightkit-license", dryRun: false });
|
|
18
|
-
assert.deepEqual(plan.map((step) => step.label), [
|
|
19
|
-
"secret scan",
|
|
20
|
-
"format",
|
|
21
|
-
"tests",
|
|
22
|
-
"clippy",
|
|
23
|
-
"package contents",
|
|
24
|
-
"registry dry-run",
|
|
25
|
-
"registry publish",
|
|
26
|
-
]);
|
|
27
|
-
assert.deepEqual(plan.at(-1)?.args, ["publish", "-p", "rightkit-license"]);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("dry-run plan never contains a real registry upload", () => {
|
|
31
|
-
const plan = buildCargoPublishPlan({ crate: "rightkit-logs", crateDir: "C:/rightkit/crates/rightkit-logs", dryRun: true });
|
|
32
|
-
assert.equal(plan.at(-1)?.label, "registry dry-run");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("package inspection rejects secret and build-output paths", () => {
|
|
36
|
-
assert.deepEqual(packageFileViolations([
|
|
37
|
-
"Cargo.toml",
|
|
38
|
-
"src/lib.rs",
|
|
39
|
-
".env",
|
|
40
|
-
"keys/signing.p8",
|
|
41
|
-
"target/release/app",
|
|
42
|
-
]), [".env", "keys/signing.p8", "target/release/app"]);
|
|
43
|
-
});
|
package/publish-swift.test.mjs
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
import { parseSwiftPublishArgs, buildSwiftPublishPlan, readPackageIdentity } from "./publish-swift.mjs";
|
|
6
|
-
|
|
7
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const packageDir = path.resolve(here, "../../swift/RightKitSwift");
|
|
9
|
-
|
|
10
|
-
test("dry-run needs no url; publish requires one", () => {
|
|
11
|
-
assert.deepEqual(parseSwiftPublishArgs(["--dry-run"], {}), {
|
|
12
|
-
scope: "rightsuite", url: "", dryRun: true, allowDirty: false,
|
|
13
|
-
});
|
|
14
|
-
assert.throws(() => parseSwiftPublishArgs([], {}), /needs --url/);
|
|
15
|
-
assert.equal(parseSwiftPublishArgs(["--url", "https://reg.local"], {}).url, "https://reg.local");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("env supplies scope and url; --allow-dirty is dry-run only", () => {
|
|
19
|
-
const parsed = parseSwiftPublishArgs([], { RIGHTKIT_SWIFT_REGISTRY_URL: "https://e", RIGHTKIT_SWIFT_REGISTRY_SCOPE: "acme" });
|
|
20
|
-
assert.deepEqual(parsed, { scope: "acme", url: "https://e", dryRun: false, allowDirty: false });
|
|
21
|
-
assert.throws(() => parseSwiftPublishArgs(["--allow-dirty"], { RIGHTKIT_SWIFT_REGISTRY_URL: "https://e" }), /only valid with --dry-run/);
|
|
22
|
-
assert.throws(() => parseSwiftPublishArgs(["--bogus"], {}), /unexpected argument/);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("reads the real package identity from VERSION + Package.swift", () => {
|
|
26
|
-
const { name, version } = readPackageIdentity(packageDir);
|
|
27
|
-
assert.equal(name, "RightKitSwift");
|
|
28
|
-
assert.match(version, /^\d+\.\d+\.\d+/);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("dry-run plan stops before publish; real plan ends with publish", () => {
|
|
32
|
-
const base = { packageDir, scope: "rightsuite", name: "RightKitSwift", version: "0.1.0", url: "https://reg.local" };
|
|
33
|
-
const dry = buildSwiftPublishPlan({ ...base, dryRun: true });
|
|
34
|
-
assert.equal(dry[0].internal, "secret-scan");
|
|
35
|
-
assert.ok(dry.some((s) => s.label === "tests" && s.command === "swift"));
|
|
36
|
-
assert.ok(!dry.some((s) => s.label === "registry publish"), "dry-run must not publish");
|
|
37
|
-
|
|
38
|
-
const real = buildSwiftPublishPlan({ ...base, dryRun: false });
|
|
39
|
-
const publish = real.at(-1);
|
|
40
|
-
assert.equal(publish.label, "registry publish");
|
|
41
|
-
assert.deepEqual(publish.args, [
|
|
42
|
-
"package-registry", "publish", "rightsuite.RightKitSwift", "0.1.0", "--url", "https://reg.local", "--package-path", packageDir,
|
|
43
|
-
]);
|
|
44
|
-
});
|
package/publish-update.test.mjs
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import test from "node:test";
|
|
8
|
-
|
|
9
|
-
const publisher = fileURLToPath(new URL("./publish-update.mjs", import.meta.url));
|
|
10
|
-
|
|
11
|
-
function fixture({ materialize = false } = {}) {
|
|
12
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "right-publish-test-"));
|
|
13
|
-
const config = path.join(dir, "right-release.config.mjs");
|
|
14
|
-
writeFileSync(config, `export default ${JSON.stringify({
|
|
15
|
-
schema: 1,
|
|
16
|
-
app: "fixture",
|
|
17
|
-
version: "1.2.3",
|
|
18
|
-
targets: {
|
|
19
|
-
win: {
|
|
20
|
-
installer: { artifacts: [{ file: "Fixture-Setup.exe", key: "fixture/installers/windows/current/Fixture-Setup.exe" }] },
|
|
21
|
-
updater: {
|
|
22
|
-
artifacts: [
|
|
23
|
-
{ file: "Fixture.exe", signature: "Fixture.exe.sig", platform: "windows-x86_64", key: "fixture/updates/windows/current/Fixture.exe" },
|
|
24
|
-
{ file: "Fixture.exe", signature: "Fixture.exe.sig", platform: "windows-arm64", key: "fixture/updates/windows/current/Fixture.exe" },
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
})};\n`);
|
|
30
|
-
if (materialize) {
|
|
31
|
-
writeFileSync(path.join(dir, "Fixture-Setup.exe"), "signed installer");
|
|
32
|
-
writeFileSync(path.join(dir, "Fixture.exe"), "signed updater");
|
|
33
|
-
writeFileSync(path.join(dir, "Fixture.exe.sig"), "updater signature");
|
|
34
|
-
}
|
|
35
|
-
return config;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function versionedFixture() {
|
|
39
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "right-publish-test-"));
|
|
40
|
-
const config = path.join(dir, "right-release.config.mjs");
|
|
41
|
-
writeFileSync(config, `export default ${JSON.stringify({
|
|
42
|
-
schema: 1,
|
|
43
|
-
app: "fixture",
|
|
44
|
-
version: "1.2.3",
|
|
45
|
-
targets: {
|
|
46
|
-
win: {
|
|
47
|
-
installer: { artifacts: [{ file: "Fixture-Setup.exe", key: "fixture/installers/windows/1.2.3/Fixture-Setup.exe" }] },
|
|
48
|
-
updater: {
|
|
49
|
-
artifacts: [
|
|
50
|
-
{ file: "Fixture.exe", signature: "Fixture.exe.sig", platform: "windows-x86_64", key: "fixture/1.2.3/windows/Fixture.exe" },
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
})};\n`);
|
|
56
|
-
return config;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function installerOnlyFixture() {
|
|
60
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "right-publish-test-"));
|
|
61
|
-
const config = path.join(dir, "right-release.config.mjs");
|
|
62
|
-
writeFileSync(config, `export default ${JSON.stringify({
|
|
63
|
-
schema: 1,
|
|
64
|
-
app: "fixture",
|
|
65
|
-
version: "1.2.3",
|
|
66
|
-
targets: {
|
|
67
|
-
win: {
|
|
68
|
-
installer: { artifacts: [{ file: "Fixture-Setup.exe", key: "fixture/installers/windows/current/Fixture-Setup.exe" }] },
|
|
69
|
-
updater: { artifacts: [] },
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
})};\n`);
|
|
73
|
-
return config;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function run(tier) {
|
|
77
|
-
const config = fixture();
|
|
78
|
-
return spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win", "--dry-run"], {
|
|
79
|
-
encoding: "utf8",
|
|
80
|
-
env: { ...process.env, RIGHT_RELEASE_TIER: tier },
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function registrationBody(result) {
|
|
85
|
-
return result.stdout
|
|
86
|
-
.split(/\r?\n/)
|
|
87
|
-
.filter((line) => line.startsWith('{'))
|
|
88
|
-
.map((line) => JSON.parse(line))
|
|
89
|
-
.find((body) => body.artifactManifest);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
test("patch registers a free updater manifest backed by public installer-path objects", () => {
|
|
93
|
-
const result = run("patch");
|
|
94
|
-
assert.equal(result.status, 0, result.stderr);
|
|
95
|
-
const patchPayloadUpload = result.stdout.indexOf("UPLOAD public/fixture/installers/windows/current/Fixture.exe");
|
|
96
|
-
const installerUpload = result.stdout.indexOf("UPLOAD public/fixture/installers/windows/current/Fixture-Setup.exe");
|
|
97
|
-
const registration = result.stdout.indexOf("/v1/admin/apps/patches");
|
|
98
|
-
assert.ok(patchPayloadUpload >= 0, "patch updater payload must upload to the public installer path");
|
|
99
|
-
assert.ok(installerUpload >= 0, "patch download installer must upload to the public installer path");
|
|
100
|
-
assert.ok(patchPayloadUpload < installerUpload, "patch should upload app-update payloads before replacing the public download installer");
|
|
101
|
-
assert.ok(installerUpload < registration, "patch should register only after uploads complete");
|
|
102
|
-
assert.doesNotMatch(result.stdout, /UPLOAD public\/fixture\/updates\//);
|
|
103
|
-
assert.doesNotMatch(result.stdout, /UPLOAD private\//);
|
|
104
|
-
assert.match(result.stdout, /pub-6c73208d46c245a9b4881d5e02f6b618\.r2\.dev\/fixture\/installers\/windows\/current\/Fixture\.exe/);
|
|
105
|
-
assert.match(result.stdout, /POST .*\/v1\/admin\/apps\/patches/);
|
|
106
|
-
assert.match(result.stdout, /"tier":"patch"/);
|
|
107
|
-
assert.match(result.stdout, /"platform":"windows"/);
|
|
108
|
-
assert.doesNotMatch(result.stdout, /UPLOAD .*\.sig/);
|
|
109
|
-
assert.equal((result.stdout.match(/UPLOAD /g) ?? []).length, 2, "patch may upload only one installer and one updater object per OS");
|
|
110
|
-
assert.match(result.stdout, /PRUNE rightapps-downloads\/fixture\/windows/);
|
|
111
|
-
assert.match(result.stdout, /PRUNE rightapps-updates\/fixture\/windows keep=<none>/);
|
|
112
|
-
const body = registrationBody(result);
|
|
113
|
-
assert.deepEqual(
|
|
114
|
-
{
|
|
115
|
-
schema: body.artifactManifest.schema,
|
|
116
|
-
appKey: body.artifactManifest.appKey,
|
|
117
|
-
appVersion: body.artifactManifest.appVersion,
|
|
118
|
-
tier: body.artifactManifest.tier,
|
|
119
|
-
platform: body.artifactManifest.platform,
|
|
120
|
-
},
|
|
121
|
-
{ schema: 1, appKey: 'fixture', appVersion: '1.2.3', tier: 'patch', platform: 'windows' },
|
|
122
|
-
);
|
|
123
|
-
assert.match(body.artifactManifest.pipelineVersion, /^\d+\.\d+\.\d+$/);
|
|
124
|
-
assert.deepEqual(body.artifactManifest.artifacts.map((artifact) => [artifact.kind, artifact.r2Key]), [
|
|
125
|
-
['installer', 'fixture/installers/windows/current/Fixture-Setup.exe'],
|
|
126
|
-
['updater', 'fixture/installers/windows/current/Fixture.exe'],
|
|
127
|
-
]);
|
|
128
|
-
assert.match(body.artifactManifest.artifacts[1].updaterSignature, /^<signature:/);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("routes feature updates to the Pro-gated release endpoint", () => {
|
|
132
|
-
const result = run("update");
|
|
133
|
-
assert.equal(result.status, 0, result.stderr);
|
|
134
|
-
assert.match(result.stdout, /POST .*\/v1\/admin\/apps\/releases/);
|
|
135
|
-
assert.match(result.stdout, /"tier":"update"/);
|
|
136
|
-
assert.match(result.stdout, /"platform":"windows"/);
|
|
137
|
-
assert.match(result.stdout, /UPLOAD private\/fixture\/updates\/windows\/current\/Fixture\.exe/);
|
|
138
|
-
assert.doesNotMatch(result.stdout, /UPLOAD .*\.sig/);
|
|
139
|
-
assert.equal((result.stdout.match(/UPLOAD /g) ?? []).length, 1, "feature update may upload only one updater object per OS");
|
|
140
|
-
assert.match(result.stdout, /PRUNE rightapps-downloads\/fixture\/windows/);
|
|
141
|
-
assert.match(result.stdout, /PRUNE rightapps-updates\/fixture\/windows/);
|
|
142
|
-
assert.doesNotMatch(result.stdout, /UPLOAD .*Fixture-Setup\.exe/);
|
|
143
|
-
const body = registrationBody(result);
|
|
144
|
-
assert.equal(body.artifactManifest.tier, 'update');
|
|
145
|
-
assert.deepEqual(body.artifactManifest.artifacts.map((artifact) => [artifact.kind, artifact.r2Key]), [
|
|
146
|
-
['updater', 'fixture/updates/windows/current/Fixture.exe'],
|
|
147
|
-
]);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("allows an installer-only patch without registering an updater manifest", () => {
|
|
151
|
-
const config = installerOnlyFixture();
|
|
152
|
-
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win", "--dry-run"], {
|
|
153
|
-
encoding: "utf8",
|
|
154
|
-
env: { ...process.env, RIGHT_RELEASE_TIER: "patch" },
|
|
155
|
-
});
|
|
156
|
-
assert.equal(result.status, 0, result.stderr);
|
|
157
|
-
assert.match(result.stdout, /UPLOAD public\/fixture\/installers\/windows\/current\/Fixture-Setup\.exe/);
|
|
158
|
-
assert.match(result.stdout, /NO patch updater artifacts/);
|
|
159
|
-
assert.doesNotMatch(result.stdout, /\/v1\/admin\/apps\/patches/);
|
|
160
|
-
assert.doesNotMatch(result.stdout, /UPLOAD private\//);
|
|
161
|
-
assert.match(result.stdout, /PRUNE rightapps-downloads\/fixture\/windows/);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("refuses to publish without a valid tier", () => {
|
|
165
|
-
const result = run("");
|
|
166
|
-
assert.notEqual(result.status, 0);
|
|
167
|
-
assert.match(result.stderr, /RIGHT_RELEASE_TIER.*patch\|update/);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("refuses versioned R2 object keys that would accumulate old releases", () => {
|
|
171
|
-
const config = versionedFixture();
|
|
172
|
-
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win", "--dry-run"], {
|
|
173
|
-
encoding: "utf8",
|
|
174
|
-
env: { ...process.env, RIGHT_RELEASE_TIER: "patch" },
|
|
175
|
-
});
|
|
176
|
-
assert.notEqual(result.status, 0);
|
|
177
|
-
assert.match(result.stderr, /current R2 object/);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("refuses versioned updater object keys for gated feature updates", () => {
|
|
181
|
-
const config = versionedFixture();
|
|
182
|
-
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win", "--dry-run"], {
|
|
183
|
-
encoding: "utf8",
|
|
184
|
-
env: { ...process.env, RIGHT_RELEASE_TIER: "update" },
|
|
185
|
-
});
|
|
186
|
-
assert.notEqual(result.status, 0);
|
|
187
|
-
assert.match(result.stderr, /current R2 object/);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("requires the durable release credential before the first patch upload", () => {
|
|
191
|
-
const config = fixture({ materialize: true });
|
|
192
|
-
const env = {
|
|
193
|
-
...process.env,
|
|
194
|
-
APPDATA: path.join(path.dirname(config), "empty-appdata"),
|
|
195
|
-
RIGHT_RELEASE_TIER: "patch",
|
|
196
|
-
RIGHTAPPS_RELEASE_TOKEN_FILE: path.join(path.dirname(config), "missing-release-token"),
|
|
197
|
-
RIGHTAPPS_ADMIN_TOKEN: "expired-admin-session",
|
|
198
|
-
};
|
|
199
|
-
delete env.RIGHTAPPS_RELEASE_TOKEN;
|
|
200
|
-
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win"], {
|
|
201
|
-
encoding: "utf8",
|
|
202
|
-
env,
|
|
203
|
-
});
|
|
204
|
-
assert.notEqual(result.status, 0);
|
|
205
|
-
assert.match(result.stderr, /RIGHTAPPS_RELEASE_TOKEN/);
|
|
206
|
-
assert.doesNotMatch(result.stdout, /UPLOAD /, "credential preflight must run before R2 mutation");
|
|
207
|
-
});
|
package/qa-contract.test.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import { validateQaBackdoorContract } from "./qa-contract.mjs";
|
|
7
|
-
|
|
8
|
-
function fixture({ optional = true, releaseScript = "right-release release --tier patch" } = {}) {
|
|
9
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "right-release-qa-"));
|
|
10
|
-
mkdirSync(path.join(root, "src-tauri", "src"), { recursive: true });
|
|
11
|
-
writeFileSync(path.join(root, "right-qa.config.mjs"), "export default {};\n");
|
|
12
|
-
writeFileSync(path.join(root, "src-tauri", "Cargo.toml"), `
|
|
13
|
-
[features]
|
|
14
|
-
qa-native = ["dep:tauri-plugin-wdio-webdriver"]
|
|
15
|
-
[dependencies]
|
|
16
|
-
tauri-plugin-wdio-webdriver = { version = "1.2.0", optional = ${optional} }
|
|
17
|
-
`);
|
|
18
|
-
writeFileSync(path.join(root, "src-tauri", "src", "lib.rs"), `
|
|
19
|
-
#[cfg(all(not(debug_assertions), feature = "qa-native"))]
|
|
20
|
-
compile_error!("qa-native must never be enabled in release builds");
|
|
21
|
-
#[cfg(all(debug_assertions, feature = "qa-native"))]
|
|
22
|
-
fn qa_enabled() -> bool { std::env::var("RIGHTKIT_QA_NATIVE").as_deref() == Ok("1") }
|
|
23
|
-
#[cfg(all(debug_assertions, feature = "qa-native"))]
|
|
24
|
-
fn add_plugin() { tauri_plugin_wdio_webdriver::init(); }
|
|
25
|
-
`);
|
|
26
|
-
return { root, scripts: { "qa:native": "tauri dev --features qa-native", "release:patch:win": releaseScript } };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test("accepts a debug-only, runtime-gated native QA surface", () => {
|
|
30
|
-
const value = fixture();
|
|
31
|
-
assert.deepEqual(validateQaBackdoorContract(value.root, value.scripts), []);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("rejects a non-optional WebDriver plugin", () => {
|
|
35
|
-
const value = fixture({ optional: false });
|
|
36
|
-
assert.match(validateQaBackdoorContract(value.root, value.scripts).join("\n"), /must be optional/);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("rejects release scripts that enable native QA", () => {
|
|
40
|
-
const value = fixture({ releaseScript: "tauri build --features qa-native" });
|
|
41
|
-
assert.match(validateQaBackdoorContract(value.root, value.scripts).join("\n"), /release:patch:win/);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("does not impose native QA on an app before its config lands", () => {
|
|
45
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "right-release-no-qa-"));
|
|
46
|
-
assert.deepEqual(validateQaBackdoorContract(root, {}), []);
|
|
47
|
-
});
|
package/registry-parity.test.mjs
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { comparePackedTrees, verifyRegistryParity } from './registry-parity.mjs';
|
|
4
|
-
|
|
5
|
-
test('registry parity rejects same-version packed-byte drift', () => {
|
|
6
|
-
assert.deepEqual(comparePackedTrees([['build-release.mjs', 'new']], [['build-release.mjs', 'old']]), {
|
|
7
|
-
equal: false,
|
|
8
|
-
differences: ['build-release.mjs'],
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('registry parity accepts identical packed trees', () => {
|
|
13
|
-
assert.deepEqual(comparePackedTrees([['a.mjs', 'one'], ['package.json', 'two']], [['a.mjs', 'one'], ['package.json', 'two']]), {
|
|
14
|
-
equal: true,
|
|
15
|
-
differences: [],
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('registry parity distinguishes an unpublished version from registry failure', async () => {
|
|
20
|
-
const notFound = async () => ({ status: 404, ok: false });
|
|
21
|
-
assert.deepEqual(await verifyRegistryParity({ allowUnpublished: true, fetchImpl: notFound }), {
|
|
22
|
-
status: 'UNPUBLISHED',
|
|
23
|
-
name: '@rightkit/release',
|
|
24
|
-
version: '0.2.68',
|
|
25
|
-
});
|
|
26
|
-
await assert.rejects(
|
|
27
|
-
verifyRegistryParity({ fetchImpl: notFound }),
|
|
28
|
-
/registry metadata failed: HTTP 404/,
|
|
29
|
-
);
|
|
30
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import vm from "node:vm";
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
|
|
10
|
-
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const cli = path.join(root, "cli", "right-release.mjs");
|
|
12
|
-
const build = path.join(root, "build-release.mjs");
|
|
13
|
-
const upload = path.join(root, "upload-release.mjs");
|
|
14
|
-
const signWindows = path.join(root, "sign-windows.mjs");
|
|
15
|
-
|
|
16
|
-
test("CLI routes build and release only to the build state machine", () => {
|
|
17
|
-
const source = readFileSync(cli, "utf8");
|
|
18
|
-
assert.match(source, /\["build",\s*"build-release\.mjs"\]/);
|
|
19
|
-
assert.match(source, /\["release",\s*"build-release\.mjs"\]/);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("CLI routes upload and legacy publish only to the upload state machine", () => {
|
|
23
|
-
const source = readFileSync(cli, "utf8");
|
|
24
|
-
assert.match(source, /\["upload",\s*"upload-release\.mjs"\]/);
|
|
25
|
-
assert.match(source, /\["publish",\s*"upload-release\.mjs"\]/);
|
|
26
|
-
assert.doesNotMatch(source, /first === "publish"[\s\S]{0,500}run\("release\.mjs"/);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("upload dry-run cannot write a verified marker", () => {
|
|
30
|
-
const source = readFileSync(upload, "utf8");
|
|
31
|
-
assert.match(source, /if \(!dryRun\)\s+writeJson\(verifiedMarker,/);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("build is tier-neutral and rejects a tier before touching a repository", () => {
|
|
35
|
-
const result = spawnSync(process.execPath, [build, "--platform", "win", "--tier", "patch"], {
|
|
36
|
-
cwd: mkdtempSync(path.join(os.tmpdir(), "right-build-cli-")),
|
|
37
|
-
encoding: "utf8",
|
|
38
|
-
});
|
|
39
|
-
assert.notEqual(result.status, 0);
|
|
40
|
-
assert.match(result.stderr, /build is tier-neutral/i);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("build enforces primary-checkout and build-input cleanliness guards", () => {
|
|
44
|
-
const source = readFileSync(build, "utf8");
|
|
45
|
-
assert.match(source, /assertPrimaryReleaseCheckout\(repoRoot\)/);
|
|
46
|
-
assert.match(source, /dirtyBuildInputs\(repoRoot,/);
|
|
47
|
-
assert.match(source, /dirty files can change the packaged app/i);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("upload enforces the same primary-checkout vault", () => {
|
|
51
|
-
const source = readFileSync(upload, "utf8");
|
|
52
|
-
assert.match(source, /assertPrimaryReleaseCheckout\(repoRoot\)/);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("upload requires an explicit patch or update tier before reading a release", () => {
|
|
56
|
-
const result = spawnSync(process.execPath, [upload, "--platform", "win", "--release", "fixture-1.0.0-deadbeef"], {
|
|
57
|
-
cwd: mkdtempSync(path.join(os.tmpdir(), "right-upload-cli-")),
|
|
58
|
-
encoding: "utf8",
|
|
59
|
-
});
|
|
60
|
-
assert.notEqual(result.status, 0);
|
|
61
|
-
assert.match(result.stderr, /tier is required.*patch\|update/i);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("GitHub upload delegates platform trust verification to the shared release lane", () => {
|
|
65
|
-
const uploadSource = readFileSync(upload, "utf8");
|
|
66
|
-
const githubSource = readFileSync(path.join(root, "github-release.mjs"), "utf8");
|
|
67
|
-
const signingSource = readFileSync(signWindows, "utf8");
|
|
68
|
-
assert.match(uploadSource, /publishGitHubRelease/);
|
|
69
|
-
assert.match(githubSource, /sign-windows\.mjs/);
|
|
70
|
-
assert.match(githubSource, /--verify-only/);
|
|
71
|
-
assert.doesNotMatch(uploadSource, /CLOUDFLARE_API_TOKEN|wrangler|R2/i);
|
|
72
|
-
assert.match(signingSource, /verifyOnly/);
|
|
73
|
-
assert.match(signingSource, /\["verify", "\/pa", "\/v", file\]/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("Windows signer verification accepts CRLF subjects and retains identity checks", () => {
|
|
77
|
-
const source = readFileSync(signWindows, "utf8");
|
|
78
|
-
const functionSource = source.slice(source.indexOf("function assertVerification"), source.indexOf("\nfunction fileEvidence"));
|
|
79
|
-
const assertVerification = vm.runInNewContext(`(${functionSource})`, {
|
|
80
|
-
fail(message) { throw new Error(message); },
|
|
81
|
-
});
|
|
82
|
-
const valid = "Successfully verified\r\n\tIssued to: Damned Ventures LLC \r\nTimestamp: present\r\n";
|
|
83
|
-
assert.equal(JSON.stringify(assertVerification("fixture.exe", valid)), JSON.stringify({
|
|
84
|
-
authenticode: "Valid",
|
|
85
|
-
subject: "CN=Damned Ventures LLC",
|
|
86
|
-
timestampPresent: true,
|
|
87
|
-
}));
|
|
88
|
-
assert.throws(
|
|
89
|
-
() => assertVerification("fixture.exe", valid.replace("Damned Ventures LLC", "Other Company")),
|
|
90
|
-
/subject must be Damned Ventures LLC/,
|
|
91
|
-
);
|
|
92
|
-
assert.throws(
|
|
93
|
-
() => assertVerification("fixture.exe", valid.replace("Damned Ventures LLC", "damned ventures llc")),
|
|
94
|
-
/subject must be Damned Ventures LLC/,
|
|
95
|
-
);
|
|
96
|
-
assert.throws(
|
|
97
|
-
() => assertVerification("fixture.exe", valid.replace("Timestamp: present", "")),
|
|
98
|
-
/timestamp is missing/,
|
|
99
|
-
);
|
|
100
|
-
assert.throws(
|
|
101
|
-
() => assertVerification("fixture.exe", valid.replace("Successfully verified", "Verification failed")),
|
|
102
|
-
/did not report Valid/,
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("upload uses GitHub CLI and never invokes the R2 runner", () => {
|
|
107
|
-
const source = readFileSync(upload, "utf8");
|
|
108
|
-
const githubSource = readFileSync(path.join(root, "github-release.mjs"), "utf8");
|
|
109
|
-
assert.match(githubSource, /run\("gh", \["release", "upload"/);
|
|
110
|
-
assert.doesNotMatch(source, /wrangler|CLOUDFLARE_API_TOKEN|RIGHTAPPS_API_URL/i);
|
|
111
|
-
assert.doesNotMatch(source, /\bnpx\b/);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("upload accepts only GitHub Releases as configured product distribution", () => {
|
|
115
|
-
const source = readFileSync(upload, "utf8");
|
|
116
|
-
assert.match(source, /distribution\.provider/);
|
|
117
|
-
assert.match(source, /github-releases/);
|
|
118
|
-
assert.match(source, /releaseConfig\?\.distribution\?\.repository/);
|
|
119
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
import { resolvePrimaryWorktree } from "./release-invocation.mjs";
|
|
5
|
-
|
|
6
|
-
test("resolves a submodule primary worktree from core.worktree", () => {
|
|
7
|
-
assert.equal(resolvePrimaryWorktree({
|
|
8
|
-
repoRoot: path.resolve("/suite/membrane"),
|
|
9
|
-
worktrees: "worktree /suite/.git/modules/membrane\nHEAD abc\n",
|
|
10
|
-
gitDir: "/suite/.git/modules/membrane",
|
|
11
|
-
coreWorktree: "../../../membrane",
|
|
12
|
-
}), path.resolve("/suite/membrane"));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("preserves an ordinary primary worktree", () => {
|
|
16
|
-
assert.equal(resolvePrimaryWorktree({
|
|
17
|
-
repoRoot: "/suite/app",
|
|
18
|
-
worktrees: "worktree /suite/app\nHEAD abc\n",
|
|
19
|
-
gitDir: "/suite/app/.git",
|
|
20
|
-
coreWorktree: "",
|
|
21
|
-
}), "/suite/app");
|
|
22
|
-
});
|