@rightkit/release 0.2.21 → 0.2.22
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/asr-artifact-adoption.mjs +68 -0
- package/asr-artifact-adoption.test.mjs +122 -0
- package/cargo-contract.mjs +26 -3
- package/cli/right-release.mjs +3 -0
- package/package.json +1 -1
- package/publish-swift.mjs +122 -0
- package/publish-swift.test.mjs +44 -0
- package/qa-contract.mjs +61 -0
- package/qa-contract.test.mjs +47 -0
- package/release.mjs +30 -0
- package/release.test.mjs +15 -0
- package/right-suite-contract.test.mjs +17 -4
- package/rightkit-versions.json +11 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { verifyRuntimeArtifactEnvelope } from "./runtime-artifact-manifest.mjs";
|
|
4
|
+
|
|
5
|
+
const ASR_AUTHORITY = "heardright";
|
|
6
|
+
const ASR_CONSUMER = "scraperight";
|
|
7
|
+
const ASR_KIND = "asr-model";
|
|
8
|
+
|
|
9
|
+
export function assertAsrArtifactAdoption({ authorityEnvelope, consumerEnvelope, publicKey, trustedKeyId }) {
|
|
10
|
+
const authority = verifyTrusted(authorityEnvelope, publicKey, trustedKeyId);
|
|
11
|
+
const consumer = verifyTrusted(consumerEnvelope, publicKey, trustedKeyId);
|
|
12
|
+
|
|
13
|
+
assertManifestRole(authority, ASR_AUTHORITY, "authority");
|
|
14
|
+
assertManifestRole(consumer, ASR_CONSUMER, "consumer");
|
|
15
|
+
for (const manifest of [authority, consumer]) {
|
|
16
|
+
if (manifest.artifactKind !== ASR_KIND) throw new Error("ASR adoption manifest must be an asr-model");
|
|
17
|
+
if (manifest.promotion.authorityAppKey !== ASR_AUTHORITY) throw new Error("ASR promotion authority must be heardright");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
assertSame(authority.target, consumer.target, "target");
|
|
21
|
+
assertSame(authority.object.filename, consumer.object.filename, "object.filename");
|
|
22
|
+
assertSame(authority.object.sha256, consumer.object.sha256, "object.sha256");
|
|
23
|
+
assertSame(authority.object.sizeBytes, consumer.object.sizeBytes, "object.sizeBytes");
|
|
24
|
+
for (const field of ["runtime", "model", "tokenizer", "preprocessing", "license", "provenance"]) {
|
|
25
|
+
assertSame(authority.versions[field], consumer.versions[field], `versions.${field}`);
|
|
26
|
+
}
|
|
27
|
+
assertSame(authority.provenance, consumer.provenance, "provenance");
|
|
28
|
+
assertSame(authority.promotion, consumer.promotion, "promotion");
|
|
29
|
+
return {
|
|
30
|
+
sha256: authority.object.sha256,
|
|
31
|
+
promotionId: authority.promotion.promotionId,
|
|
32
|
+
target: authority.target,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function assertAsrAdapterPair(authority, consumer) {
|
|
37
|
+
assertAdapter(authority, ASR_AUTHORITY, "authority");
|
|
38
|
+
assertAdapter(consumer, ASR_CONSUMER, "consumer");
|
|
39
|
+
for (const [field, expected] of [
|
|
40
|
+
["authorityAppKey", ASR_AUTHORITY],
|
|
41
|
+
["artifactKind", ASR_KIND],
|
|
42
|
+
["delivery", "private-r2"],
|
|
43
|
+
["entitlementTier", "pro"],
|
|
44
|
+
]) {
|
|
45
|
+
assertSame(authority[field], expected, `authority adapter ${field}`);
|
|
46
|
+
assertSame(consumer[field], expected, `consumer adapter ${field}`);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function verifyTrusted(envelope, publicKey, trustedKeyId) {
|
|
52
|
+
if (envelope?.signature?.keyId !== trustedKeyId) throw new Error("untrusted runtime artifact key id");
|
|
53
|
+
return verifyRuntimeArtifactEnvelope(envelope, publicKey);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function assertManifestRole(manifest, appKey, role) {
|
|
57
|
+
if (manifest.entitlement.appKey !== appKey) throw new Error(`${role} manifest must belong to ${appKey}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function assertAdapter(value, appKey, role) {
|
|
61
|
+
if (!value || value.schema !== 1 || value.appKey !== appKey || value.role !== role) {
|
|
62
|
+
throw new Error(`invalid ${role} ASR adapter`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assertSame(left, right, field) {
|
|
67
|
+
if (!isDeepStrictEqual(left, right)) throw new Error(`ASR drift: ${field}`);
|
|
68
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
|
|
5
|
+
import { assertAsrAdapterPair, assertAsrArtifactAdoption } from "./asr-artifact-adoption.mjs";
|
|
6
|
+
import { buildRuntimeArtifactManifest, signRuntimeArtifactManifest } from "./runtime-artifact-manifest.mjs";
|
|
7
|
+
|
|
8
|
+
const digest = "a".repeat(64);
|
|
9
|
+
const notice = "b".repeat(64);
|
|
10
|
+
const evidence = "c".repeat(64);
|
|
11
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
12
|
+
|
|
13
|
+
function envelope(appKey, overrides = {}) {
|
|
14
|
+
const manifest = buildRuntimeArtifactManifest({
|
|
15
|
+
artifactKind: "asr-model",
|
|
16
|
+
entitlement: { appKey, tier: "pro" },
|
|
17
|
+
distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
|
|
18
|
+
target: { os: "windows", arch: "x86_64" },
|
|
19
|
+
filename: "parakeet-unified-en-0.6b.tar.zst",
|
|
20
|
+
sha256: digest,
|
|
21
|
+
sizeBytes: 1234,
|
|
22
|
+
versions: {
|
|
23
|
+
runtime: "rightkit-asr@0.1.0",
|
|
24
|
+
model: "parakeet-unified-en-0.6b",
|
|
25
|
+
tokenizer: "sha256:tokenizer-v1",
|
|
26
|
+
preprocessing: "heardright-frontend-v1",
|
|
27
|
+
license: "CC-BY-4.0",
|
|
28
|
+
provenance: "nvidia/parakeet-tdt-0.6b-v3",
|
|
29
|
+
},
|
|
30
|
+
provenance: {
|
|
31
|
+
source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
|
|
32
|
+
sourceRevision: "revision-1",
|
|
33
|
+
licenseId: "CC-BY-4.0",
|
|
34
|
+
noticeSha256: notice,
|
|
35
|
+
},
|
|
36
|
+
promotion: {
|
|
37
|
+
authorityAppKey: "heardright",
|
|
38
|
+
promotionId: "hr-asr-2026-07-14",
|
|
39
|
+
promotedAt: "2026-07-14T00:00:00.000Z",
|
|
40
|
+
evidenceSha256: evidence,
|
|
41
|
+
},
|
|
42
|
+
...overrides,
|
|
43
|
+
});
|
|
44
|
+
return signRuntimeArtifactManifest(manifest, privateKey, "rightkit-runtime-artifacts-2026-07");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("accepts app-scoped pointers with one exact HeardRight-certified ASR identity", () => {
|
|
48
|
+
const result = assertAsrArtifactAdoption({
|
|
49
|
+
authorityEnvelope: envelope("heardright"),
|
|
50
|
+
consumerEnvelope: envelope("scraperight"),
|
|
51
|
+
publicKey,
|
|
52
|
+
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.equal(result.sha256, digest);
|
|
56
|
+
assert.equal(result.promotionId, "hr-asr-2026-07-14");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("rejects a validly signed ScrapeRight manifest with ASR drift", () => {
|
|
60
|
+
const drifted = envelope("scraperight", {
|
|
61
|
+
versions: {
|
|
62
|
+
runtime: "rightkit-asr@0.1.1",
|
|
63
|
+
model: "parakeet-unified-en-0.6b",
|
|
64
|
+
tokenizer: "sha256:tokenizer-v1",
|
|
65
|
+
preprocessing: "heardright-frontend-v1",
|
|
66
|
+
license: "CC-BY-4.0",
|
|
67
|
+
provenance: "nvidia/parakeet-tdt-0.6b-v3",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
assert.throws(() => assertAsrArtifactAdoption({
|
|
72
|
+
authorityEnvelope: envelope("heardright"),
|
|
73
|
+
consumerEnvelope: drifted,
|
|
74
|
+
publicKey,
|
|
75
|
+
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
76
|
+
}), /ASR drift: versions\.runtime/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("rejects wrong roles, authority, target, signature key, and artifact kind", () => {
|
|
80
|
+
const base = {
|
|
81
|
+
authorityEnvelope: envelope("heardright"),
|
|
82
|
+
consumerEnvelope: envelope("scraperight"),
|
|
83
|
+
publicKey,
|
|
84
|
+
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
85
|
+
};
|
|
86
|
+
assert.throws(() => assertAsrArtifactAdoption({ ...base, authorityEnvelope: envelope("scraperight") }), /authority manifest must belong to heardright/);
|
|
87
|
+
assert.throws(() => assertAsrArtifactAdoption({ ...base, consumerEnvelope: envelope("heardright") }), /consumer manifest must belong to scraperight/);
|
|
88
|
+
assert.throws(() => assertAsrArtifactAdoption({ ...base, trustedKeyId: "rotated-without-app-update" }), /untrusted runtime artifact key id/);
|
|
89
|
+
assert.throws(() => assertAsrArtifactAdoption({
|
|
90
|
+
...base,
|
|
91
|
+
consumerEnvelope: envelope("scraperight", { target: { os: "darwin", arch: "aarch64" } }),
|
|
92
|
+
}), /ASR drift: target/);
|
|
93
|
+
assert.throws(() => assertAsrArtifactAdoption({
|
|
94
|
+
...base,
|
|
95
|
+
consumerEnvelope: envelope("scraperight", { artifactKind: "ocr-model" }),
|
|
96
|
+
}), /must be an asr-model/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("rejects a consumer signed by a different key", () => {
|
|
100
|
+
const other = generateKeyPairSync("ed25519");
|
|
101
|
+
const consumer = signRuntimeArtifactManifest(
|
|
102
|
+
envelope("scraperight").manifest,
|
|
103
|
+
other.privateKey,
|
|
104
|
+
"rightkit-runtime-artifacts-2026-07",
|
|
105
|
+
);
|
|
106
|
+
assert.throws(() => assertAsrArtifactAdoption({
|
|
107
|
+
authorityEnvelope: envelope("heardright"),
|
|
108
|
+
consumerEnvelope: consumer,
|
|
109
|
+
publicKey,
|
|
110
|
+
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
111
|
+
}), /signature verification failed/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("locks the thin app adapters to HeardRight authority and private Pro delivery", () => {
|
|
115
|
+
const authority = {
|
|
116
|
+
schema: 1, appKey: "heardright", role: "authority", authorityAppKey: "heardright",
|
|
117
|
+
artifactKind: "asr-model", delivery: "private-r2", entitlementTier: "pro",
|
|
118
|
+
};
|
|
119
|
+
const consumer = { ...authority, appKey: "scraperight", role: "consumer" };
|
|
120
|
+
assert.equal(assertAsrAdapterPair(authority, consumer), true);
|
|
121
|
+
assert.throws(() => assertAsrAdapterPair(authority, { ...consumer, authorityAppKey: "scraperight" }), /ASR drift/);
|
|
122
|
+
});
|
package/cargo-contract.mjs
CHANGED
|
@@ -101,11 +101,34 @@ function assertNoRightKitOverrides(parsed, label, source) {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// `tomllib` is Python 3.11+. Windows pins `py -3.11`, but a bare `python3` on macOS is the system
|
|
105
|
+
// 3.9, which has no tomllib — so every app's release:doctor died on a stock Mac, and reported the
|
|
106
|
+
// manifest as invalid TOML when the real problem was the interpreter. Probe for one that can.
|
|
107
|
+
let tomlRunner;
|
|
108
|
+
function resolveTomlRunner() {
|
|
109
|
+
if (tomlRunner) return tomlRunner;
|
|
110
|
+
const probe = "import sys,tomllib";
|
|
111
|
+
const candidates =
|
|
112
|
+
process.platform === "win32"
|
|
113
|
+
? [["py", ["-3.11"]], ["py", ["-3"]], ["python", []]]
|
|
114
|
+
: [["python3.13", []], ["python3.12", []], ["python3.11", []], ["python3", []]];
|
|
115
|
+
for (const [command, prefix] of candidates) {
|
|
116
|
+
const check = spawnSync(command, [...prefix, "-c", probe], { encoding: "utf8", windowsHide: true });
|
|
117
|
+
if (check.status === 0) {
|
|
118
|
+
tomlRunner = { command, prefix };
|
|
119
|
+
return tomlRunner;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new Error(
|
|
123
|
+
"no Python with tomllib found (needs 3.11+). Install one, or put it on PATH: the release " +
|
|
124
|
+
"contract reads Cargo manifests through it.",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
104
128
|
function readToml(file, label) {
|
|
105
129
|
const script = "import json,pathlib,sys,tomllib; json.dump(tomllib.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')),sys.stdout,default=str)";
|
|
106
|
-
const command
|
|
107
|
-
const
|
|
108
|
-
const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
|
|
130
|
+
const { command, prefix } = resolveTomlRunner();
|
|
131
|
+
const result = spawnSync(command, [...prefix, "-c", script, file], { encoding: "utf8", windowsHide: true });
|
|
109
132
|
if (result.status !== 0) throw new Error(`${label} is not valid TOML: ${String(result.stderr ?? "").trim()}`);
|
|
110
133
|
return JSON.parse(result.stdout);
|
|
111
134
|
}
|
package/cli/right-release.mjs
CHANGED
|
@@ -44,6 +44,8 @@ if (first === "--version" || first === "-v") {
|
|
|
44
44
|
const rest = args.slice(1);
|
|
45
45
|
if (rest[0] === "cargo") {
|
|
46
46
|
run("publish-cargo.mjs", rest.slice(1));
|
|
47
|
+
} else if (rest[0] === "swift") {
|
|
48
|
+
run("publish-swift.mjs", rest.slice(1));
|
|
47
49
|
} else {
|
|
48
50
|
run("release.mjs", rest.includes("--upload") || rest.includes("--publish") ? rest : [...rest, "--upload"]);
|
|
49
51
|
}
|
|
@@ -120,6 +122,7 @@ Commands:
|
|
|
120
122
|
release [--platform mac|win] --tier patch|update Build/package through the signed release lane
|
|
121
123
|
publish [--platform mac|win] --tier patch|update Release plus R2 upload + RightApps registration
|
|
122
124
|
publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
|
|
125
|
+
publish swift [--scope <s>] [--url <u>] [--dry-run] Test, archive, scan, and publish RightKitSwift to a Swift registry
|
|
123
126
|
model promote --authority heardright --config <file> [--dry-run]
|
|
124
127
|
Sign and promote one runtime/model artifact
|
|
125
128
|
doctor [--platform mac|win] Inspect one app's release config
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
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": {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Publish RightKitSwift to a Swift package registry, gated exactly like `publish cargo`:
|
|
3
|
+
// secret scan -> swift test -> release build -> deterministic source archive + hash ->
|
|
4
|
+
// registry dry-run -> publish. Canonical source stays under tools/rightkit/swift; the
|
|
5
|
+
// registry package is an output only. No Git distribution repo, no path/Git dependency.
|
|
6
|
+
//
|
|
7
|
+
// swift-tools is macOS/Linux only, so the plan builder, arg parser, and version check are
|
|
8
|
+
// pure and unit-tested on any OS (publish-swift.test.mjs); the swift steps run where swift exists.
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { scanCrateForSecrets } from "./publish-cargo.mjs";
|
|
14
|
+
|
|
15
|
+
const IDENTIFIER = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
|
|
16
|
+
|
|
17
|
+
export function parseSwiftPublishArgs(argv, env = process.env) {
|
|
18
|
+
const allowed = new Set(["--scope", "--url", "--dry-run", "--allow-dirty"]);
|
|
19
|
+
let scope = env.RIGHTKIT_SWIFT_REGISTRY_SCOPE ?? "rightsuite";
|
|
20
|
+
let url = env.RIGHTKIT_SWIFT_REGISTRY_URL ?? "";
|
|
21
|
+
let dryRun = false;
|
|
22
|
+
let allowDirty = false;
|
|
23
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
24
|
+
const arg = argv[index];
|
|
25
|
+
if (!allowed.has(arg)) throw new Error(`unexpected argument: ${arg}`);
|
|
26
|
+
if (arg === "--dry-run") dryRun = true;
|
|
27
|
+
else if (arg === "--allow-dirty") allowDirty = true;
|
|
28
|
+
else {
|
|
29
|
+
const value = argv[(index += 1)];
|
|
30
|
+
if (value === undefined) throw new Error(`${arg} needs a value`);
|
|
31
|
+
if (arg === "--scope") scope = value;
|
|
32
|
+
if (arg === "--url") url = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!IDENTIFIER.test(scope)) throw new Error(`invalid registry scope: ${scope}`);
|
|
36
|
+
if (allowDirty && !dryRun) throw new Error("--allow-dirty is only valid with --dry-run");
|
|
37
|
+
if (!dryRun && !url) throw new Error("registry publish needs --url or RIGHTKIT_SWIFT_REGISTRY_URL");
|
|
38
|
+
return { scope, url, dryRun, allowDirty };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The VERSION file is the single source of truth; Package.swift must name the package and the
|
|
42
|
+
// version must be a clean semver so the registry archive is reproducible.
|
|
43
|
+
export function readPackageIdentity(packageDir) {
|
|
44
|
+
const manifest = path.join(packageDir, "Package.swift");
|
|
45
|
+
if (!fs.existsSync(manifest)) throw new Error(`Package.swift not found: ${manifest}`);
|
|
46
|
+
const versionFile = path.join(packageDir, "VERSION");
|
|
47
|
+
if (!fs.existsSync(versionFile)) throw new Error(`VERSION not found: ${versionFile}`);
|
|
48
|
+
const version = fs.readFileSync(versionFile, "utf8").trim();
|
|
49
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
50
|
+
throw new Error(`VERSION is not clean semver: "${version}"`);
|
|
51
|
+
}
|
|
52
|
+
const nameMatch = fs.readFileSync(manifest, "utf8").match(/name:\s*"([^"]+)"/);
|
|
53
|
+
if (!nameMatch) throw new Error("could not read package name from Package.swift");
|
|
54
|
+
return { name: nameMatch[1], version };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildSwiftPublishPlan({ packageDir, scope, name, version, url, dryRun, allowDirty = false }) {
|
|
58
|
+
const packageId = `${scope}.${name}`;
|
|
59
|
+
const archive = path.join(packageDir, ".build", `${name}-${version}.zip`);
|
|
60
|
+
const plan = [
|
|
61
|
+
{ label: "secret scan", internal: "secret-scan" },
|
|
62
|
+
{ label: "tests", command: "swift", args: ["test", "--package-path", packageDir] },
|
|
63
|
+
{ label: "release build", command: "swift", args: ["build", "-c", "release", "--package-path", packageDir] },
|
|
64
|
+
// Deterministic source archive; its bytes are what the registry stores, so hash it for the log.
|
|
65
|
+
{ label: "source archive", command: "swift", args: ["package", "--package-path", packageDir, "archive-source", "-o", archive] },
|
|
66
|
+
{ label: "archive hash", internal: "archive-hash", archive },
|
|
67
|
+
];
|
|
68
|
+
if (dryRun) {
|
|
69
|
+
plan.push({ label: "registry dry-run (validate only)", internal: "dry-run-note", packageId, version });
|
|
70
|
+
return plan;
|
|
71
|
+
}
|
|
72
|
+
plan.push({
|
|
73
|
+
label: "registry publish",
|
|
74
|
+
command: "swift",
|
|
75
|
+
args: ["package-registry", "publish", packageId, version, "--url", url, "--package-path", packageDir],
|
|
76
|
+
});
|
|
77
|
+
return plan;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function runSwiftPublish(options, execute = spawnSync) {
|
|
81
|
+
const packageDir = options.packageDir;
|
|
82
|
+
const { name, version } = readPackageIdentity(packageDir);
|
|
83
|
+
const plan = buildSwiftPublishPlan({ ...options, packageDir, name, version });
|
|
84
|
+
|
|
85
|
+
for (const step of plan) {
|
|
86
|
+
process.stdout.write(`[right-release] swift ${step.label}\n`);
|
|
87
|
+
if (step.internal === "secret-scan") {
|
|
88
|
+
const violations = scanCrateForSecrets(packageDir);
|
|
89
|
+
if (violations.length) throw new Error(`secret scan rejected: ${violations.join(", ")}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (step.internal === "dry-run-note") {
|
|
93
|
+
process.stdout.write(`[right-release] dry-run OK: ${step.packageId} ${step.version} (not published)\n`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (step.internal === "archive-hash") {
|
|
97
|
+
const bytes = fs.readFileSync(step.archive);
|
|
98
|
+
process.stdout.write(`sha256 ${createHash("sha256").update(bytes).digest("hex")} ${path.basename(step.archive)}\n`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const result = execute(step.command, step.args, { cwd: packageDir, encoding: "utf8", windowsHide: true, stdio: "inherit" });
|
|
102
|
+
if (result.error) {
|
|
103
|
+
if (result.error.code === "ENOENT") {
|
|
104
|
+
throw new Error(`${step.command} not found — run publish swift on macOS/Linux with the Swift toolchain installed`);
|
|
105
|
+
}
|
|
106
|
+
throw result.error;
|
|
107
|
+
}
|
|
108
|
+
if (result.status !== 0) throw new Error(`${step.label} failed with exit code ${result.status}`);
|
|
109
|
+
}
|
|
110
|
+
return { name, version, published: !options.dryRun };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (process.argv[1]?.endsWith("publish-swift.mjs")) {
|
|
114
|
+
try {
|
|
115
|
+
const options = parseSwiftPublishArgs(process.argv.slice(2));
|
|
116
|
+
const packageDir = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/(\w:)/, "$1")), "../../swift/RightKitSwift");
|
|
117
|
+
runSwiftPublish({ ...options, packageDir });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
process.stderr.write(`${error.message}\n`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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/qa-contract.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function validateQaBackdoorContract(root, packageScripts = {}) {
|
|
5
|
+
if (!existsSync(path.join(root, "right-qa.config.mjs"))) return [];
|
|
6
|
+
|
|
7
|
+
const issues = [];
|
|
8
|
+
const cargoPath = path.join(root, "src-tauri", "Cargo.toml");
|
|
9
|
+
if (!existsSync(cargoPath)) return ["right-qa.config.mjs requires src-tauri/Cargo.toml"];
|
|
10
|
+
|
|
11
|
+
const cargoToml = readFileSync(cargoPath, "utf8");
|
|
12
|
+
const dependency = cargoToml.match(/tauri-plugin-wdio-webdriver\s*=\s*\{([^}]*)\}/s)?.[1] ?? "";
|
|
13
|
+
if (!dependency) issues.push("Cargo.toml must declare tauri-plugin-wdio-webdriver");
|
|
14
|
+
else if (!/optional\s*=\s*true/.test(dependency)) issues.push("tauri-plugin-wdio-webdriver must be optional");
|
|
15
|
+
|
|
16
|
+
const qaFeature = cargoToml.match(/qa-native\s*=\s*\[([^\]]*)\]/s)?.[1] ?? "";
|
|
17
|
+
if (!/dep:tauri-plugin-wdio-webdriver/.test(qaFeature)) {
|
|
18
|
+
issues.push("qa-native must enable dep:tauri-plugin-wdio-webdriver");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rustSource = readRustSources(path.join(root, "src-tauri", "src")).replace(/\s+/g, " ");
|
|
22
|
+
if (!/cfg\s*\(\s*all\s*\(\s*not\s*\(\s*debug_assertions\s*\)\s*,\s*feature\s*=\s*"qa-native"\s*\)\s*\)/.test(rustSource)
|
|
23
|
+
|| !/compile_error!/.test(rustSource)) {
|
|
24
|
+
issues.push("Rust must compile_error when qa-native is enabled outside debug builds");
|
|
25
|
+
}
|
|
26
|
+
if (!/cfg\s*\(\s*all\s*\(\s*debug_assertions\s*,\s*feature\s*=\s*"qa-native"\s*\)\s*\)/.test(rustSource)
|
|
27
|
+
|| !/tauri_plugin_wdio_webdriver::init/.test(rustSource)) {
|
|
28
|
+
issues.push("WebDriver registration must require debug_assertions and qa-native");
|
|
29
|
+
}
|
|
30
|
+
if (!/RIGHTKIT_QA_NATIVE/.test(rustSource)) {
|
|
31
|
+
issues.push("WebDriver registration must also require RIGHTKIT_QA_NATIVE");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [name, command] of Object.entries(packageScripts)) {
|
|
35
|
+
if (!/qa-native|RIGHTKIT_QA_NATIVE/.test(command)) continue;
|
|
36
|
+
if (/^(qa|test|dev)(:|$)/.test(name)) continue;
|
|
37
|
+
if (/(release|publish|build|dmg|installer|package)/i.test(name) || /\btauri\s+build\b/.test(command)) {
|
|
38
|
+
issues.push(`package script ${name} must not enable native QA`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return issues;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function assertQaBackdoorContract(root, packageScripts = {}) {
|
|
45
|
+
const issues = validateQaBackdoorContract(root, packageScripts);
|
|
46
|
+
if (issues.length) throw new Error(`production QA backdoor contract failed:\n- ${issues.join("\n- ")}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readRustSources(root) {
|
|
50
|
+
if (!existsSync(root)) return "";
|
|
51
|
+
const files = [];
|
|
52
|
+
const visit = (dir) => {
|
|
53
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
const absolute = path.join(dir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) visit(absolute);
|
|
56
|
+
else if (entry.isFile() && entry.name.endsWith(".rs")) files.push(absolute);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
visit(root);
|
|
60
|
+
return files.sort().map((file) => readFileSync(file, "utf8")).join("\n");
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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/release.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { validateRightKitCargoContract } from "./cargo-contract.mjs";
|
|
8
|
+
import { assertQaBackdoorContract } from "./qa-contract.mjs";
|
|
8
9
|
|
|
9
10
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
|
|
@@ -141,6 +142,8 @@ if (opts.upload && !target.publish) {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
sweepStaleRustArtifacts();
|
|
146
|
+
|
|
144
147
|
console.log(`right-release: done (${Date.now() - started}ms)`);
|
|
145
148
|
releaseLock.release();
|
|
146
149
|
|
|
@@ -160,6 +163,32 @@ async function mustExist(file, message) {
|
|
|
160
163
|
await access(file).catch(() => fail(message));
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
// Post-release target/ hygiene: `cargo sweep --installed` deletes artifacts left
|
|
167
|
+
// by toolchains rustup no longer has (each update orphans a multi-GB pile per
|
|
168
|
+
// app) and never touches anything the current toolchains produced. Runs after
|
|
169
|
+
// publish so it cannot race an artifact, and is best-effort: a missing
|
|
170
|
+
// cargo-sweep or a sweep failure must never fail a release.
|
|
171
|
+
function sweepStaleRustArtifacts() {
|
|
172
|
+
const candidates = [...new Set([root, workdir].flatMap((base) => [base, path.join(base, "src-tauri")]))];
|
|
173
|
+
const projects = candidates.filter((dir) => existsSync(path.join(dir, "target")));
|
|
174
|
+
for (const dir of projects) {
|
|
175
|
+
if (opts.dryRun) {
|
|
176
|
+
console.log(`dry-run: cargo sweep --installed ${dir}`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const result = spawnSync("cargo", ["sweep", "--installed", dir], { stdio: "inherit" });
|
|
181
|
+
if (result.status !== 0) {
|
|
182
|
+
console.log("right-release: stale-artifact sweep skipped (install with: cargo install cargo-sweep)");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
console.log("right-release: stale-artifact sweep skipped (install with: cargo install cargo-sweep)");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
163
192
|
async function runInstall(pm, cwd) {
|
|
164
193
|
if (pm === "pnpm") await run("pnpm", ["install", "--frozen-lockfile"], cwd);
|
|
165
194
|
else if (pm === "npm") await run("npm", ["ci"], cwd);
|
|
@@ -180,6 +209,7 @@ async function validateRightKitPackageContract(root, appName) {
|
|
|
180
209
|
const packageJsonPath = path.join(root, "package.json");
|
|
181
210
|
const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
182
211
|
const scripts = pkg.scripts ?? {};
|
|
212
|
+
assertQaBackdoorContract(root, scripts);
|
|
183
213
|
const workflowDir = path.join(root, ".github", "workflows");
|
|
184
214
|
if (existsSync(workflowDir)) {
|
|
185
215
|
const workflows = await readdir(workflowDir);
|
package/release.test.mjs
CHANGED
|
@@ -264,6 +264,21 @@ test("Windows update re-signs the updater artifact after Azure code signing", ()
|
|
|
264
264
|
assert.ok(publishAt > updaterAt, result.stdout);
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
+
test("release sweeps stale rust artifacts when a target dir exists", () => {
|
|
268
|
+
const config = fixture();
|
|
269
|
+
const dir = path.dirname(config);
|
|
270
|
+
mkdirSync(path.join(dir, "src-tauri", "target"), { recursive: true });
|
|
271
|
+
const result = run(config, "--tier=patch");
|
|
272
|
+
assert.equal(result.status, 0, result.stderr);
|
|
273
|
+
assert.match(result.stdout, /dry-run: cargo sweep --installed .*src-tauri/);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("release skips the sweep when no target dir exists", () => {
|
|
277
|
+
const result = run(fixture(), "--tier=patch");
|
|
278
|
+
assert.equal(result.status, 0, result.stderr);
|
|
279
|
+
assert.doesNotMatch(result.stdout, /cargo sweep/);
|
|
280
|
+
});
|
|
281
|
+
|
|
267
282
|
test("release lock is single-flight and is cleaned after success", () => {
|
|
268
283
|
const { dir, config } = lockFixture();
|
|
269
284
|
const lockPath = path.join(dir, ".cache", "right-release", "mac.lock.json");
|
|
@@ -10,8 +10,13 @@ import {
|
|
|
10
10
|
assertPublishedRightKitCargoDependencies,
|
|
11
11
|
validateRightKitCargoContract,
|
|
12
12
|
} from "./cargo-contract.mjs";
|
|
13
|
+
import { assertAsrAdapterPair } from "./asr-artifact-adoption.mjs";
|
|
13
14
|
|
|
14
15
|
const workspace = path.resolve(new URL("../../../..", import.meta.url).pathname.replace(/^\/(\w:)/, "$1"));
|
|
16
|
+
// The Right Suite web layer's repo + filesystem path is `rightsites` post the 2026-07-15 naming lock;
|
|
17
|
+
// a not-yet-renamed checkout may still have `rightapps`. Resolve whichever exists so the contract
|
|
18
|
+
// passes on both. (The runtime service namespace stays `rightapps` — that is deliberately untouched.)
|
|
19
|
+
const siteRepoDir = existsSync(path.join(workspace, "rightsites")) ? "rightsites" : "rightapps";
|
|
15
20
|
const versionsPath = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/(\w:)/, "$1")), "rightkit-versions.json");
|
|
16
21
|
const versions = JSON.parse(readFileSync(versionsPath, "utf8"));
|
|
17
22
|
const isolatedCargoHome = mkdtempSync(path.join(tmpdir(), "rightkit-cargo-home-"));
|
|
@@ -21,7 +26,9 @@ const apps = [
|
|
|
21
26
|
{ key: "viewright", root: "viewright", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/build-mac-notarized.sh"] },
|
|
22
27
|
{ key: "scraperight", root: "scraperight", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["package.sh"] },
|
|
23
28
|
{ key: "heardright", root: "heardright/tauri-app-next", repoRoot: "heardright", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/mac-dmg.mjs", "scripts/publish-release.mjs"] },
|
|
24
|
-
|
|
29
|
+
// mac-dmg.mjs is the release file, as in HeardRight/CodeRight. build-mac.sh only delegates to it,
|
|
30
|
+
// so asserting the mirror call against the wrapper failed a pipeline that does mirror.
|
|
31
|
+
{ key: "mailright", root: "mailright", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/mac-dmg.mjs"] },
|
|
25
32
|
{ key: "coderight", root: "coderight/apps/coderight-tauri", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/mac-dmg.mjs"] },
|
|
26
33
|
];
|
|
27
34
|
|
|
@@ -310,7 +317,7 @@ test("Cargo version contract rejects staged versions that mismatch canonical man
|
|
|
310
317
|
test("RightKit exposes one current version manifest", () => {
|
|
311
318
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
312
319
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
313
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
320
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.22");
|
|
314
321
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
315
322
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
|
316
323
|
assert.equal(versions.npm["@rightkit/tauri"], "0.1.0");
|
|
@@ -329,7 +336,7 @@ test("license v2 public vector is identical at every portable consumer boundary"
|
|
|
329
336
|
);
|
|
330
337
|
for (const relativePath of [
|
|
331
338
|
"tools/rightkit/packages/license/test-vectors/license-v2.json",
|
|
332
|
-
|
|
339
|
+
`${siteRepoDir}/packages/api/src/licensing/test-vectors/license-v2.json`,
|
|
333
340
|
"scraperight/tests/fixtures/license-v2.json",
|
|
334
341
|
]) {
|
|
335
342
|
assert.equal(
|
|
@@ -450,6 +457,12 @@ for (const app of apps) {
|
|
|
450
457
|
});
|
|
451
458
|
}
|
|
452
459
|
|
|
460
|
+
test("HeardRight and ScrapeRight expose one locked ASR promotion adapter contract", async () => {
|
|
461
|
+
const heard = (await import(`${pathToFileURL(path.join(workspace, "heardright/tauri-app-next/right-release.config.mjs"))}?asr=${Date.now()}`)).default;
|
|
462
|
+
const scrape = (await import(`${pathToFileURL(path.join(workspace, "scraperight/right-release.config.mjs"))}?asr=${Date.now()}`)).default;
|
|
463
|
+
assert.equal(assertAsrAdapterPair(heard.runtimeArtifacts?.asr, scrape.runtimeArtifacts?.asr), true);
|
|
464
|
+
});
|
|
465
|
+
|
|
453
466
|
function findFiles(root, filename) {
|
|
454
467
|
const found = [];
|
|
455
468
|
const visit = (dir) => {
|
|
@@ -488,7 +501,7 @@ test("Right Suite has no hosted workflow files", () => {
|
|
|
488
501
|
|
|
489
502
|
test("RightApps brand hosts expose app-keyed update manifest proxies", () => {
|
|
490
503
|
for (const app of apps) {
|
|
491
|
-
const siteRoot = path.join(workspace,
|
|
504
|
+
const siteRoot = path.join(workspace, siteRepoDir, app.key);
|
|
492
505
|
for (const route of ["patches", "latest"]) {
|
|
493
506
|
const routeFile = path.join(siteRoot, "src", "routes", "releases", `${route}.json`, "index.ts");
|
|
494
507
|
const source = readFileSync(routeFile, "utf8");
|
package/rightkit-versions.json
CHANGED
|
@@ -4,14 +4,23 @@
|
|
|
4
4
|
"npm": {
|
|
5
5
|
"@rightkit/license": "0.1.5",
|
|
6
6
|
"@rightkit/logs": "0.1.3",
|
|
7
|
-
"@rightkit/
|
|
7
|
+
"@rightkit/platform-ui": "0.1.0",
|
|
8
|
+
"@rightkit/qa": "0.1.0",
|
|
9
|
+
"@rightkit/release": "0.2.22",
|
|
8
10
|
"@rightkit/tauri": "0.1.0",
|
|
9
11
|
"@rightkit/updates": "0.2.3"
|
|
10
12
|
},
|
|
11
13
|
"cargo": {
|
|
12
14
|
"rightkit-license": "0.1.2",
|
|
13
15
|
"rightkit-logs": "0.1.0",
|
|
16
|
+
"rightkit-process": "0.1.0",
|
|
14
17
|
"rightkit-tauri": "0.1.0"
|
|
15
18
|
},
|
|
16
|
-
"stagedCargo": {}
|
|
19
|
+
"stagedCargo": {},
|
|
20
|
+
"swift": {
|
|
21
|
+
"rightkit-swift": {
|
|
22
|
+
"url": "https://github.com/bogusyogi/rightkit-swift.git",
|
|
23
|
+
"version": "0.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
17
26
|
}
|