@rightkit/release 0.2.154 → 0.2.156
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 +6 -1
- package/deps.mjs +0 -0
- package/execution-policy.mjs +1 -1
- package/local-publish.mjs +246 -0
- package/lsclean.sh +0 -0
- package/msix-repair-guard.ps1 +74 -15
- package/msix-signer.mjs +12 -5
- package/package.json +9 -11
- package/release.mjs +0 -0
- package/rightkit-versions.json +4 -3
- package/upload-large.mjs +0 -0
package/cli/right-release.mjs
CHANGED
|
@@ -86,7 +86,9 @@ if (first === "--version" || first === "-v") {
|
|
|
86
86
|
run("apple-profile-renew.mjs", args.slice(1));
|
|
87
87
|
} else if (first === "publish") {
|
|
88
88
|
const rest = args.slice(1);
|
|
89
|
-
if (rest[0] === "cargo") {
|
|
89
|
+
if ((rest[0] === "cargo" || rest[0] === "npm") && rest.includes("--local")) {
|
|
90
|
+
run("local-publish.mjs", rest);
|
|
91
|
+
} else if (rest[0] === "cargo") {
|
|
90
92
|
run("publish-cargo.mjs", rest.slice(1));
|
|
91
93
|
} else if (rest[0] === "npm") {
|
|
92
94
|
run("publication-gate.mjs", rest.slice(1));
|
|
@@ -202,6 +204,9 @@ Commands:
|
|
|
202
204
|
Publish the gated tarball with the Keychain token
|
|
203
205
|
(npm-rightkit-publish) and verify registry bytes
|
|
204
206
|
publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
|
|
207
|
+
publish npm --package @rightkit/<name> --local Mac-only local publish under the committed local-build grant
|
|
208
|
+
publish cargo --crate <name> --local (.rightkit-local-build.json): runs the unit's CI tests, packs, writes darwin-only
|
|
209
|
+
evidence, then the same publication gate and publisher; refuses without the grant
|
|
205
210
|
publish swift [--dry-run] [--evidence <file>] [--workspace <dir>]
|
|
206
211
|
Check main, a clean tree, and the publication evidence, then tag RightKitSwift at its VERSION on origin
|
|
207
212
|
inventory check [--json] [--warn-only] [--md <file>] [--no-cache]
|
package/deps.mjs
CHANGED
|
File without changes
|
package/execution-policy.mjs
CHANGED
|
@@ -41,7 +41,7 @@ export const LOCAL_BUILD_FILE = ".rightkit-local-build.json";
|
|
|
41
41
|
|
|
42
42
|
// Adrian's local-build opt-in (2026-10-14): a committed .rightkit-local-build.json naming this exact
|
|
43
43
|
// repository allows local build, test and publish. Mirrors the build-control hook's check.
|
|
44
|
-
function localBuildGranted(root) {
|
|
44
|
+
export function localBuildGranted(root) {
|
|
45
45
|
if (!root) return false;
|
|
46
46
|
try {
|
|
47
47
|
const grant = JSON.parse(readFileSync(path.join(root, LOCAL_BUILD_FILE), "utf8"));
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `right-release publish npm|cargo ... --local`: publish ONE @rightkit npm package or crate from the Mac under
|
|
3
|
+
// Adrian's local-build grant (2026-10-14, committed .rightkit-local-build.json). It does not weaken the
|
|
4
|
+
// publication gate: it runs the unit's own CI tests (ci/gate.config.json, ci/asset-gated.json,
|
|
5
|
+
// scripts/ci/gate.mjs semantics), packs the artifact, writes a schema-1 evidence file with one darwin receipt
|
|
6
|
+
// (hostPolicy "darwin-only"), then calls the EXISTING enforcePublicationGate and the EXISTING publisher
|
|
7
|
+
// (publishGatedTarball for npm, runCargoPublish for cargo). There is no way to skip a test or the evidence.
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
+
import { spawnSync } from "./background-process.mjs";
|
|
14
|
+
import { runCargoSync } from "./managed-cargo.mjs";
|
|
15
|
+
import { localBuildGranted, LOCAL_BUILD_FILE } from "./execution-policy.mjs";
|
|
16
|
+
import { GATE_SCHEMA, defaultEvidencePath, enforcePublicationGate, publishGatedTarball } from "./publication-gate.mjs";
|
|
17
|
+
import { runCargoPublish } from "./publish-cargo.mjs";
|
|
18
|
+
import { cargoIndexUrl } from "./cargo-registry-readback.mjs";
|
|
19
|
+
import { readCargoPackage } from "./crate-graph.mjs";
|
|
20
|
+
import { recordPublished } from "./rightkit-sync.mjs";
|
|
21
|
+
|
|
22
|
+
export const LOCAL_OPERATOR = "agent (local-build grant)";
|
|
23
|
+
export const LOCAL_HOST_POLICY_REASON = "Adrian 2026-10-14 local-build grant (.rightkit-local-build.json); Windows covered by the next CI run";
|
|
24
|
+
const STEP_TIMEOUT_MS = 30 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
export class LocalPublishError extends Error {
|
|
27
|
+
constructor(message) { super(message); this.name = "LocalPublishError"; }
|
|
28
|
+
}
|
|
29
|
+
const refuse = (message) => { throw new LocalPublishError(message); };
|
|
30
|
+
|
|
31
|
+
export function parseLocalPublishArgs(kind, argv, cwd = process.cwd()) {
|
|
32
|
+
const flag = kind === "npm" ? "--package" : "--crate";
|
|
33
|
+
let unit; let workspace; let local = false;
|
|
34
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
35
|
+
const arg = argv[i];
|
|
36
|
+
if (arg === "--local") local = true;
|
|
37
|
+
else if (arg === flag) unit = argv[++i];
|
|
38
|
+
else if (arg === "--workspace") workspace = argv[++i];
|
|
39
|
+
else refuse(`unknown publish ${kind} --local option: ${arg} (accepted: ${flag} <name>, --workspace <dir>, --local)`);
|
|
40
|
+
}
|
|
41
|
+
if (!local) refuse("--local is required");
|
|
42
|
+
if (kind === "npm" && !/^@rightkit\/[a-z0-9-]+$/.test(unit ?? "")) refuse("publish npm --local requires --package @rightkit/<name>");
|
|
43
|
+
if (kind === "cargo" && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(unit ?? "")) refuse("publish cargo --local requires --crate <name>");
|
|
44
|
+
return { kind, unit, workspaceRoot: path.resolve(workspace ?? cwd) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function defaultGit(args, cwd) {
|
|
48
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true });
|
|
49
|
+
if (result.error || result.status !== 0) return null;
|
|
50
|
+
return (result.stdout ?? "").trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const defaultReadJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } };
|
|
54
|
+
|
|
55
|
+
async function defaultRegistryHas({ kind, name, version }) {
|
|
56
|
+
if (kind === "npm") {
|
|
57
|
+
const view = spawnSync("npm", ["view", `${name}@${version}`, "version", "--prefer-online", "--registry", "https://registry.npmjs.org/"], { encoding: "utf8", windowsHide: true });
|
|
58
|
+
const out = `${view.stdout ?? ""}${view.stderr ?? ""}`;
|
|
59
|
+
if (!view.error && view.status === 0) return (view.stdout ?? "").trim() === version;
|
|
60
|
+
if (/E404|404 Not Found/i.test(out)) return false;
|
|
61
|
+
throw new Error(`cannot read the npm registry for ${name}@${version}: ${out.trim().slice(0, 200)}`);
|
|
62
|
+
}
|
|
63
|
+
const response = await fetch(cargoIndexUrl(name), { headers: { Accept: "text/plain", "Cache-Control": "no-cache", "User-Agent": "rightkit-release" }, redirect: "error", signal: AbortSignal.timeout(30_000) });
|
|
64
|
+
if (response.status === 404) return false;
|
|
65
|
+
if (!response.ok) throw new Error(`crates.io index answered ${response.status} for ${name}`);
|
|
66
|
+
return (await response.text()).split(/\r?\n/).filter((line) => line.trim()).some((line) => { try { return JSON.parse(line).vers === version; } catch { return false; } });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function defaultRun(command, args, options) {
|
|
70
|
+
const settings = { encoding: "utf8", maxBuffer: 512 * 1024 * 1024, timeout: STEP_TIMEOUT_MS, windowsHide: true, ...options };
|
|
71
|
+
return command === "cargo" ? runCargoSync(args, settings) : spawnSync(command, args, settings);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function defaultLoadWorkspaceModules(workspaceRoot) {
|
|
75
|
+
const load = (file) => import(pathToFileURL(path.join(workspaceRoot, "scripts", "ci", file)).href);
|
|
76
|
+
const [results, upload] = await Promise.all([load("test-results.mjs"), load("upload.mjs")]);
|
|
77
|
+
return { results, checkNpmTarball: upload.checkNpmTarball };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The CI gate's commands for one unit (scripts/ci/gate.mjs npmGate/rustGate), mirrored exactly. */
|
|
81
|
+
export function localTestPlan({ kind, unit, dir, config, manifest }) {
|
|
82
|
+
if (kind === "npm") {
|
|
83
|
+
if (!manifest?.scripts?.test) refuse(`${unit} has no test script; --local never publishes an untested package`);
|
|
84
|
+
return {
|
|
85
|
+
id: `npm:${dir}`, featureLabels: ["default"],
|
|
86
|
+
steps: [
|
|
87
|
+
{ command: "pnpm", args: ["-r", "--workspace-concurrency=1", "run", "build"], test: false },
|
|
88
|
+
{ command: "pnpm", args: ["--dir", dir, "run", "test"], test: true },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const entry = config?.rust?.[unit] ?? {};
|
|
93
|
+
if (entry.os && !entry.os.includes("macos")) refuse(`${unit} is restricted to ${entry.os.join(", ")} by ci/gate.config.json; it cannot be qualified on this Mac`);
|
|
94
|
+
const featureSets = entry.featureSets ?? [[]];
|
|
95
|
+
const steps = [];
|
|
96
|
+
for (const features of featureSets) {
|
|
97
|
+
steps.push({ command: "cargo", args: ["test", "-p", unit, ...features, "--no-fail-fast", "--", "--show-output"], test: true });
|
|
98
|
+
if (entry.alsoSerial) steps.push({ command: "cargo", args: ["test", "-p", unit, ...features, "--no-fail-fast", "--", "--test-threads=1", "--show-output"], test: true });
|
|
99
|
+
}
|
|
100
|
+
steps.push({ command: "cargo", args: ["clippy", "-p", unit, "--no-deps", "--all-targets", ...featureSets[0], "--", "-D", "warnings"], test: false });
|
|
101
|
+
return { id: `rust:${unit}`, featureLabels: featureSets.map((features) => features.join(" ") || "default"), steps };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
105
|
+
const tarballName = (name, version) => `${name.replace(/^@/, "").replace("/", "-")}-${version}.tgz`;
|
|
106
|
+
const tail = (text, lines = 40) => String(text ?? "").split(/\r?\n/).slice(-lines).join("\n");
|
|
107
|
+
|
|
108
|
+
export async function runLocalPublish(kind, argv, deps = {}) {
|
|
109
|
+
const log = deps.log ?? ((line) => process.stdout.write(`${line}\n`));
|
|
110
|
+
const { unit, workspaceRoot } = parseLocalPublishArgs(kind, argv, deps.cwd);
|
|
111
|
+
const platform = deps.platform ?? process.platform;
|
|
112
|
+
const git = deps.git ?? defaultGit;
|
|
113
|
+
const readJson = deps.readJson ?? defaultReadJson;
|
|
114
|
+
const run = deps.run ?? defaultRun;
|
|
115
|
+
|
|
116
|
+
// 1. Preconditions, fail closed.
|
|
117
|
+
if (!(deps.localBuildGranted ?? localBuildGranted)(workspaceRoot)) refuse(`--local needs the local-build grant: a committed ${LOCAL_BUILD_FILE} (allow "build-test-publish") naming this repository at ${workspaceRoot}`);
|
|
118
|
+
if (platform !== "darwin") refuse(`--local publishes from the Mac only (host policy: darwin-only); this host is ${platform}`);
|
|
119
|
+
const dir = kind === "npm" ? `packages/${unit.slice("@rightkit/".length)}` : `crates/${unit}`;
|
|
120
|
+
const unitPath = path.join(workspaceRoot, dir);
|
|
121
|
+
if (!fs.existsSync(unitPath)) refuse(`unit directory not found: ${unitPath}`);
|
|
122
|
+
let manifest = null; let version;
|
|
123
|
+
if (kind === "npm") {
|
|
124
|
+
manifest = readJson(path.join(unitPath, "package.json")) ?? refuse(`cannot read ${dir}/package.json`);
|
|
125
|
+
if (manifest.name !== unit) refuse(`${dir}/package.json is ${manifest.name}, not ${unit}`);
|
|
126
|
+
if (manifest.private === true) refuse(`${unit} is private and is never published`);
|
|
127
|
+
version = manifest.version;
|
|
128
|
+
} else {
|
|
129
|
+
const pkg = (deps.readCargoPackage ?? readCargoPackage)(unitPath);
|
|
130
|
+
if (pkg.name !== unit) refuse(`Cargo.toml [package].name ${JSON.stringify(pkg.name)} differs from --crate ${unit}`);
|
|
131
|
+
version = pkg.version;
|
|
132
|
+
}
|
|
133
|
+
if (typeof version !== "string" || !version.trim()) refuse(`${unit} has no literal version`);
|
|
134
|
+
const head = git(["rev-parse", "HEAD"], workspaceRoot);
|
|
135
|
+
if (!head || !/^[0-9a-f]{40}$/.test(head)) refuse("cannot resolve HEAD");
|
|
136
|
+
const dirty = git(["status", "--porcelain", "--", dir], workspaceRoot);
|
|
137
|
+
if (dirty === null) refuse(`cannot read git status for ${dir}`);
|
|
138
|
+
if (dirty !== "") refuse(`tree is not clean for ${dir}; commit first:\n${dirty}`);
|
|
139
|
+
if (await (deps.registryHas ?? defaultRegistryHas)({ kind, name: unit, version })) refuse(`${unit}@${version} is already on the ${kind === "npm" ? "npm" : "crates.io"} registry; bump the version`);
|
|
140
|
+
log(`[right-release] local publish ${kind}:${unit}@${version} at ${head.slice(0, 12)} (local-build grant, darwin-only policy)`);
|
|
141
|
+
|
|
142
|
+
// 2. The unit's own tests, exactly as the CI gate defines them.
|
|
143
|
+
const gateConfig = readJson(path.join(workspaceRoot, "ci", "gate.config.json")) ?? {};
|
|
144
|
+
const assetGated = readJson(path.join(workspaceRoot, "ci", "asset-gated.json")) ?? {};
|
|
145
|
+
const gatedSection = assetGated[kind === "npm" ? "npm" : "crates"] ?? {};
|
|
146
|
+
const isAssetGated = Object.hasOwn(gatedSection, unit);
|
|
147
|
+
const plan = localTestPlan({ kind, unit, dir, config: gateConfig, manifest });
|
|
148
|
+
const modules = await (deps.loadWorkspaceModules ?? defaultLoadWorkspaceModules)(workspaceRoot);
|
|
149
|
+
const { ResultScanner, TEST_COMMAND, judgeUnit } = modules.results;
|
|
150
|
+
// Run the unit's tests under hosted-CI conditions: no inherited credentials (a managed host refuses a
|
|
151
|
+
// token from the environment) and no sibling product checkouts (CI holds rightkit alone).
|
|
152
|
+
const inherited = Object.fromEntries(Object.entries(deps.env ?? process.env).filter(([key]) => !/(TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)/i.test(key)));
|
|
153
|
+
const env = { ...inherited, RIGHTKIT_REQUIRE_ASSETS: isAssetGated ? "0" : "1", RIGHTKIT_ISOLATED_CHECKOUT: "1", CARGO_TERM_COLOR: "never" };
|
|
154
|
+
const steps = [];
|
|
155
|
+
for (const step of plan.steps) {
|
|
156
|
+
const commandText = `${step.command} ${step.args.join(" ")}`;
|
|
157
|
+
log(`[right-release] ${plan.id}: ${commandText}`);
|
|
158
|
+
const result = run(step.command, step.args, { cwd: workspaceRoot, env });
|
|
159
|
+
const row = { command: commandText, ok: false, isTest: step.test && TEST_COMMAND.test(commandText) };
|
|
160
|
+
if (result.error || result.status !== 0) {
|
|
161
|
+
steps.push(row);
|
|
162
|
+
refuse(`${plan.id} step failed (${result.error?.message ?? `exit ${result.status}`}): ${commandText}\n${tail(`${result.stdout ?? ""}\n${result.stderr ?? ""}`)}`);
|
|
163
|
+
}
|
|
164
|
+
row.ok = true;
|
|
165
|
+
if (row.isTest) {
|
|
166
|
+
const out = new ResultScanner(); const err = new ResultScanner();
|
|
167
|
+
out.push(result.stdout ?? ""); err.push(result.stderr ?? "");
|
|
168
|
+
const a = out.finish(); const b = err.finish();
|
|
169
|
+
Object.assign(row, { executed: a.executed + b.executed, failed: a.failed + b.failed, ignored: a.ignored + b.ignored, skipped: a.skipped + b.skipped, binaries: a.binaries + b.binaries });
|
|
170
|
+
}
|
|
171
|
+
steps.push(row);
|
|
172
|
+
}
|
|
173
|
+
const verdict = judgeUnit({ name: unit, steps: steps.map((s) => ({ ...s, unit: plan.id })), allowIgnored: [], allowMissingAssets: isAssetGated ? [unit] : [] });
|
|
174
|
+
if (verdict.status === "fail" || steps.some((s) => s.failed > 0)) refuse(`${plan.id} not qualified: ${verdict.problems.join("; ") || "failed tests reported"}`);
|
|
175
|
+
if (!(verdict.executed > 0)) refuse(`${plan.id} executed no test`);
|
|
176
|
+
log(`[right-release] ${plan.id}: ${verdict.executed} passed, 0 failed, ${verdict.skipped} skipped, ${verdict.ignored} ignored${isAssetGated ? " (asset-gated: RIGHTKIT_REQUIRE_ASSETS=0, skips recorded)" : ""}`);
|
|
177
|
+
|
|
178
|
+
// 3. Pack the artifact and hash it.
|
|
179
|
+
const scratch = fs.mkdtempSync(path.join(deps.tmpdir ?? os.tmpdir(), "rk-local-publish-"));
|
|
180
|
+
const cleanup = () => fs.rmSync(scratch, { recursive: true, force: true });
|
|
181
|
+
try {
|
|
182
|
+
let artifactFile; const packCommands = [];
|
|
183
|
+
if (kind === "npm") {
|
|
184
|
+
const args = ["--dir", dir, "pack", "--pack-destination", scratch];
|
|
185
|
+
packCommands.push(`pnpm ${args.join(" ")}`);
|
|
186
|
+
const packed = run("pnpm", args, { cwd: workspaceRoot, env });
|
|
187
|
+
if (packed.error || packed.status !== 0) refuse(`pnpm pack failed for ${unit}\n${tail(`${packed.stdout ?? ""}\n${packed.stderr ?? ""}`)}`);
|
|
188
|
+
artifactFile = path.join(scratch, tarballName(unit, version));
|
|
189
|
+
if (!fs.existsSync(artifactFile)) refuse(`pnpm pack did not produce ${path.basename(artifactFile)}`);
|
|
190
|
+
const checked = modules.checkNpmTarball({ file: artifactFile, name: unit, version, dir, spawn: deps.spawnSync ?? spawnSync });
|
|
191
|
+
if (checked.problems.length) refuse(`${unit}: packed tarball refused:\n - ${checked.problems.join("\n - ")}`);
|
|
192
|
+
} else {
|
|
193
|
+
packCommands.push(`cargo package --locked -p ${unit}`);
|
|
194
|
+
const packed = run("cargo", ["package", "--locked", "-p", unit], { cwd: workspaceRoot, env });
|
|
195
|
+
if (packed.error || packed.status !== 0) refuse(`cargo package failed for ${unit}\n${tail(`${packed.stdout ?? ""}\n${packed.stderr ?? ""}`)}`);
|
|
196
|
+
packCommands.push("cargo metadata --format-version 1 --no-deps");
|
|
197
|
+
const meta = run("cargo", ["metadata", "--format-version", "1", "--no-deps"], { cwd: workspaceRoot, env });
|
|
198
|
+
let targetDir;
|
|
199
|
+
try { targetDir = JSON.parse(meta.stdout).target_directory; } catch { /* refused below */ }
|
|
200
|
+
if (meta.status !== 0 || typeof targetDir !== "string" || !path.isAbsolute(targetDir)) refuse("cannot read the Cargo target directory");
|
|
201
|
+
artifactFile = path.join(targetDir, "package", `${unit}-${version}.crate`);
|
|
202
|
+
if (!fs.existsSync(artifactFile)) refuse(`cargo package did not produce ${artifactFile}`);
|
|
203
|
+
}
|
|
204
|
+
const artifactSha256 = sha256(fs.readFileSync(artifactFile));
|
|
205
|
+
|
|
206
|
+
// 4. Evidence in the normal schema, then the EXISTING gate.
|
|
207
|
+
const commands = [...plan.steps.map((s) => `${s.command} ${s.args.join(" ")}`), ...packCommands];
|
|
208
|
+
const timestamp = (deps.now ?? (() => new Date().toISOString()))();
|
|
209
|
+
const evidence = {
|
|
210
|
+
schema: GATE_SCHEMA, kind, name: unit, version, commit: head, artifactSha256,
|
|
211
|
+
hostPolicy: "darwin-only", hostPolicyReason: LOCAL_HOST_POLICY_REASON,
|
|
212
|
+
runs: [{
|
|
213
|
+
host: { os: "darwin", arch: deps.arch ?? process.arch }, commit: head, features: plan.featureLabels, artifactSha256,
|
|
214
|
+
command: commands.join(" && "), operator: LOCAL_OPERATOR, timestamp,
|
|
215
|
+
passed: verdict.executed, failed: 0, skipped: verdict.skipped, ignored: verdict.ignored,
|
|
216
|
+
requireAssets: env.RIGHTKIT_REQUIRE_ASSETS, assetGated: isAssetGated,
|
|
217
|
+
steps: steps.map(({ command, ok, executed, failed, ignored, skipped, binaries }) => ({ command, ok, ...(executed === undefined ? {} : { executed, failed, ignored, skipped, binaries }) })),
|
|
218
|
+
}],
|
|
219
|
+
};
|
|
220
|
+
const evidencePath = path.resolve(defaultEvidencePath(workspaceRoot, kind, unit));
|
|
221
|
+
fs.mkdirSync(path.dirname(evidencePath), { recursive: true });
|
|
222
|
+
fs.writeFileSync(evidencePath, `${JSON.stringify(evidence, null, 2)}\n`);
|
|
223
|
+
log(`[right-release] wrote ${evidencePath} (artifact sha256 ${artifactSha256})`);
|
|
224
|
+
const gateOptions = { kind, name: unit, version, packagePath: unitPath, evidencePath };
|
|
225
|
+
(deps.enforceGate ?? enforcePublicationGate)(gateOptions, { write: (line) => log(line.replace(/\n$/, "")) });
|
|
226
|
+
|
|
227
|
+
// 5. The EXISTING publish path.
|
|
228
|
+
if (kind === "npm") {
|
|
229
|
+
(deps.publishGatedTarball ?? publishGatedTarball)({ name: unit, version, tarball: artifactFile, evidencePath }, deps.publishDeps ?? {});
|
|
230
|
+
} else {
|
|
231
|
+
const published = await (deps.runCargoPublish ?? runCargoPublish)({ crate: unit, dryRun: false, allowDirty: false, workspaceRoot, evidence: evidencePath }, undefined, deps.cargoDeps ?? {});
|
|
232
|
+
(deps.recordPublished ?? recordPublished)({ registry: "cargo", name: published.name, version: published.version });
|
|
233
|
+
}
|
|
234
|
+
log(`[right-release] published ${unit}@${version}`);
|
|
235
|
+
return { kind, name: unit, version, commit: head, artifactSha256, evidencePath };
|
|
236
|
+
} finally {
|
|
237
|
+
cleanup();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const realOrSelf = (file) => { try { return fs.realpathSync(file); } catch { return path.resolve(file); } };
|
|
242
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
243
|
+
const [kind, ...rest] = process.argv.slice(2);
|
|
244
|
+
if (kind !== "npm" && kind !== "cargo") { process.stderr.write("right-release publish --local: expected npm or cargo\n"); process.exitCode = 2; }
|
|
245
|
+
else runLocalPublish(kind, rest).catch((error) => { process.stderr.write(`right-release publish ${kind} --local: ${error.message}\n`); process.exitCode = 1; });
|
|
246
|
+
}
|
package/lsclean.sh
CHANGED
|
File without changes
|
package/msix-repair-guard.ps1
CHANGED
|
@@ -19,8 +19,19 @@
|
|
|
19
19
|
# (logged; a retry is scheduled).
|
|
20
20
|
#
|
|
21
21
|
# Retry: after a failed repair (exit 1 or 4) the script gives the task a one-shot time trigger at +2, +5, +15, +60 minutes and then
|
|
22
|
-
# hourly, for
|
|
23
|
-
#
|
|
22
|
+
# hourly, for the first 24 hours from the first failure. After that it does NOT stop: it keeps exactly one attempt per 24 h window
|
|
23
|
+
# (a one-shot trigger 24 h after the previous attempt; retry.json records dailyMode, windowStartEpoch and windowAttempts), never a
|
|
24
|
+
# tight loop. The trigger is removed on success, when the package is healthy, or when the app is uninstalled. A later logon or
|
|
25
|
+
# event 751 run still starts a repair at any time.
|
|
26
|
+
#
|
|
27
|
+
# Event 751: the task's event trigger fires for every package's 751, and Windows event XPath has no contains(), so the task passes the
|
|
28
|
+
# event's EventRecordID ($(EventRecordID) value query) as -EventRecordID and the script reads that event and exits quietly (no
|
|
29
|
+
# repair, last-result.json untouched) unless it names this app's package (Name_ and the publisher id of the family). Logon and
|
|
30
|
+
# retry runs carry no record id and are never filtered; an unreadable event fails open (the repair proceeds).
|
|
31
|
+
#
|
|
32
|
+
# Receipts: last-result.json / install-result.json carry guardSha256 (sha256 of this script file as it is on disk, computed at run
|
|
33
|
+
# time) and guardSha256Lf (same over the LF-normalised text; equals msixRepairGuard().sha256 only when the deployed copy is byte-exact, including the final newline), plus attemptLaunched (an
|
|
34
|
+
# Add-AppxPackage was actually started this run), launchFailure (why none was started) and lastLaunchEpoch (retry.json).
|
|
24
35
|
#
|
|
25
36
|
# Uninstall cleanup: a run that finds the package absent (twice, 10 s apart) removes this folder and the task. It runs at the next
|
|
26
37
|
# logon, the next event 751, or the next retry tick; Windows has no "package removed" trigger for a per-user task.
|
|
@@ -31,7 +42,7 @@
|
|
|
31
42
|
# Requirement: the app's data directory must be physical (MSIX fileSystemWrite virtualization disabled), otherwise the packaged app
|
|
32
43
|
# writes this folder into its private LocalCache and the task cannot see it.
|
|
33
44
|
[CmdletBinding()]
|
|
34
|
-
param([switch]$Install, [string]$Pfn = '', [string]$Feed = '')
|
|
45
|
+
param([switch]$Install, [string]$Pfn = '', [string]$Feed = '', [string]$EventRecordID = '')
|
|
35
46
|
$ErrorActionPreference = 'Stop'
|
|
36
47
|
$ProgressPreference = 'SilentlyContinue' # Windows PowerShell 5.1 renders a progress bar per downloaded chunk: a 30 MB Invoke-WebRequest -OutFile then takes minutes
|
|
37
48
|
$GuardVersion = 2
|
|
@@ -44,9 +55,25 @@ $ResultPath = Join-Path $Dir $(if ($Install) { 'install-result.json' } else { 'l
|
|
|
44
55
|
$Mode = $(if ($Install) { 'install' } else { 'repair' })
|
|
45
56
|
$RetryDelaysMinutes = @(2, 5, 15, 60) # then hourly
|
|
46
57
|
$RetryWindowHours = 24
|
|
58
|
+
$DailyWindowHours = 24 # after the first window: one attempt per window
|
|
59
|
+
$script:Launched = $false
|
|
60
|
+
$script:LaunchFailure = ''
|
|
47
61
|
$PfnPattern = '^[A-Za-z0-9][A-Za-z0-9.\-]*_[a-z0-9]{13}$'
|
|
48
62
|
$script:Ctx = ''
|
|
49
63
|
|
|
64
|
+
function Get-ScriptHashes {
|
|
65
|
+
$h = [ordered]@{ guardSha256 = ''; guardSha256Lf = '' }
|
|
66
|
+
try {
|
|
67
|
+
$bytes = [IO.File]::ReadAllBytes($PSCommandPath)
|
|
68
|
+
$sha = [Security.Cryptography.SHA256]::Create()
|
|
69
|
+
$h.guardSha256 = ([BitConverter]::ToString($sha.ComputeHash($bytes)) -replace '-', '').ToLowerInvariant()
|
|
70
|
+
$lf = (New-Object Text.UTF8Encoding($false)).GetString($bytes).Replace("`r`n", "`n")
|
|
71
|
+
$h.guardSha256Lf = ([BitConverter]::ToString($sha.ComputeHash((New-Object Text.UTF8Encoding($false)).GetBytes($lf))) -replace '-', '').ToLowerInvariant()
|
|
72
|
+
} catch { }
|
|
73
|
+
return $h
|
|
74
|
+
}
|
|
75
|
+
$ScriptHashes = Get-ScriptHashes
|
|
76
|
+
|
|
50
77
|
function Log([string]$m) {
|
|
51
78
|
try {
|
|
52
79
|
if ((Test-Path -LiteralPath $LogPath) -and ((Get-Item -LiteralPath $LogPath).Length -gt 262144)) { Move-Item -LiteralPath $LogPath -Destination ($LogPath + '.old') -Force }
|
|
@@ -61,8 +88,11 @@ function Write-Json([string]$path, $obj) {
|
|
|
61
88
|
} catch { Log ('could not write ' + $path + ': ' + $_.Exception.Message) }
|
|
62
89
|
}
|
|
63
90
|
function Write-Result([string]$status, [int]$code, [string]$detail) {
|
|
64
|
-
$r = [ordered]@{ status = $status; code = $code; mode = $Mode; timeUtc = (Get-Date).ToUniversalTime().ToString('o'); guardVersion = $GuardVersion; detail = $detail }
|
|
91
|
+
$r = [ordered]@{ status = $status; code = $code; mode = $Mode; timeUtc = (Get-Date).ToUniversalTime().ToString('o'); guardVersion = $GuardVersion; guardSha256 = $ScriptHashes.guardSha256; guardSha256Lf = $ScriptHashes.guardSha256Lf; detail = $detail }
|
|
92
|
+
if (-not $Install) { $r.attemptLaunched = [bool]$script:Launched; $r.launchFailure = [string]$script:LaunchFailure }
|
|
65
93
|
$st = Read-Json $RetryPath
|
|
94
|
+
if ($st -and $st.lastLaunchEpoch) { $r.lastLaunchUtc = ([datetime]'1970-01-01T00:00:00Z').ToUniversalTime().AddSeconds([double]$st.lastLaunchEpoch).ToString('o') }
|
|
95
|
+
if ($st -and $st.dailyMode) { $r.retryDailyMode = $true }
|
|
66
96
|
if ($st -and $st.nextEpoch) { $r.nextRetryUtc = ([datetime]'1970-01-01T00:00:00Z').ToUniversalTime().AddSeconds([double]$st.nextEpoch).ToString('o'); $r.retryAttempt = [int]$st.attempt }
|
|
67
97
|
Write-Json $ResultPath $r
|
|
68
98
|
}
|
|
@@ -79,7 +109,7 @@ function Get-TaskXml([string]$family, $retryLocal) {
|
|
|
79
109
|
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
80
110
|
$esc = { param($s) [Security.SecurityElement]::Escape([string]$s) }
|
|
81
111
|
$sub = '<QueryList><Query Id="0" Path="Microsoft-Windows-AppXDeploymentServer/Operational"><Select Path="Microsoft-Windows-AppXDeploymentServer/Operational">*[System[(EventID=751)]]</Select></Query></QueryList>'
|
|
82
|
-
$taskArgs = '--headless powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $PSCommandPath + '"'
|
|
112
|
+
$taskArgs = '--headless powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $PSCommandPath + '" -EventRecordID $(EventRecordID)'
|
|
83
113
|
$retryXml = ''
|
|
84
114
|
if ($retryLocal) { $retryXml = '<TimeTrigger><StartBoundary>' + ([datetime]$retryLocal).ToString('yyyy-MM-ddTHH:mm:ss') + '</StartBoundary><Enabled>true</Enabled></TimeTrigger>' }
|
|
85
115
|
return @"
|
|
@@ -87,7 +117,7 @@ function Get-TaskXml([string]$family, $retryLocal) {
|
|
|
87
117
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
88
118
|
<RegistrationInfo><Description>Repair guard for $(& $esc $family): re-adds the app from its App Installer feed when an interrupted update leaves it NeedsRemediation; retries after a failure. Removes itself when the app is uninstalled.</Description></RegistrationInfo>
|
|
89
119
|
<Triggers>
|
|
90
|
-
<EventTrigger><Enabled>true</Enabled><Subscription>$(& $esc $sub)</Subscription></EventTrigger>
|
|
120
|
+
<EventTrigger><Enabled>true</Enabled><Subscription>$(& $esc $sub)</Subscription><ValueQueries><Value name="EventRecordID">Event/System/EventRecordID</Value></ValueQueries></EventTrigger>
|
|
91
121
|
<LogonTrigger><Enabled>true</Enabled><UserId>$(& $esc $id.Name)</UserId></LogonTrigger>
|
|
92
122
|
$retryXml
|
|
93
123
|
</Triggers>
|
|
@@ -127,19 +157,41 @@ function Set-Retry([string]$family, [string]$why) {
|
|
|
127
157
|
$now = [datetime]::UtcNow
|
|
128
158
|
$nowEpoch = [math]::Floor(($now - $epoch0.ToUniversalTime()).TotalSeconds)
|
|
129
159
|
$st = Read-Json $RetryPath
|
|
130
|
-
$attempt = 1; $first = $nowEpoch
|
|
131
|
-
if ($st -and $st.firstFailureEpoch
|
|
160
|
+
$attempt = 1; $first = $nowEpoch; $daily = $false; $windowStart = $null; $windowAttempts = 0; $lastLaunch = $null
|
|
161
|
+
if ($st -and $st.firstFailureEpoch) {
|
|
162
|
+
$attempt = [int]$st.attempt + 1; $first = [double]$st.firstFailureEpoch
|
|
163
|
+
if ($st.dailyMode) { $daily = $true; if ($st.windowStartEpoch) { $windowStart = [double]$st.windowStartEpoch }; if ($st.windowAttempts) { $windowAttempts = [int]$st.windowAttempts } }
|
|
164
|
+
}
|
|
165
|
+
# only a started installer counts as a launch; a failure before that (feed down, refused, corrupt) keeps the previous value
|
|
166
|
+
if ($st -and $st.lastLaunchEpoch) { $lastLaunch = [double]$st.lastLaunchEpoch }
|
|
167
|
+
if ($script:Launched) { $lastLaunch = $nowEpoch }
|
|
168
|
+
if (-not $daily -and (($nowEpoch - $first) -ge ($RetryWindowHours * 3600))) { $daily = $true }
|
|
132
169
|
$delay = $(if ($attempt -le $RetryDelaysMinutes.Count) { $RetryDelaysMinutes[$attempt - 1] } else { 60 })
|
|
133
170
|
$next = $nowEpoch + ($delay * 60)
|
|
134
|
-
if (($next - $first) -gt ($RetryWindowHours * 3600)) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
if (-not $daily -and (($next - $first) -gt ($RetryWindowHours * 3600))) { $daily = $true }
|
|
172
|
+
if ($daily) {
|
|
173
|
+
# first 24 h used up: one attempt per window, the next one a full window after this attempt (never a tight loop)
|
|
174
|
+
$windowStart = $nowEpoch; $windowAttempts += 1
|
|
175
|
+
$delay = $DailyWindowHours * 60
|
|
176
|
+
$next = $nowEpoch + ($delay * 60)
|
|
139
177
|
}
|
|
140
|
-
|
|
178
|
+
$o = [ordered]@{ attempt = $attempt; firstFailureEpoch = $first; nextEpoch = $next; delayMinutes = $delay; reason = $why }
|
|
179
|
+
if ($daily) { $o.dailyMode = $true; $o.windowStartEpoch = $windowStart; $o.windowAttempts = $windowAttempts }
|
|
180
|
+
if ($lastLaunch) { $o.lastLaunchEpoch = $lastLaunch }
|
|
181
|
+
Write-Json $RetryPath $o
|
|
141
182
|
$local = $epoch0.ToUniversalTime().AddSeconds($next).ToLocalTime()
|
|
142
|
-
try { Register-GuardTask $family $local; Log ('retry ' + $attempt + ' scheduled in ' + $delay + ' min at ' + $local.ToString('s')) } catch { Log ('could not schedule the retry: ' + $_.Exception.Message) }
|
|
183
|
+
try { Register-GuardTask $family $local; Log ('retry ' + $attempt + ' scheduled in ' + $delay + ' min at ' + $local.ToString('s') + $(if ($daily) { ' (daily window ' + $windowAttempts + ')' } else { '' })) } catch { Log ('could not schedule the retry: ' + $_.Exception.Message) }
|
|
184
|
+
}
|
|
185
|
+
function Test-ForeignEvent([string]$family, [string]$recordId) {
|
|
186
|
+
# '' = proceed (not an event run, or the event names this package, or it could not be read); else why the event is not ours
|
|
187
|
+
if (-not $recordId -or $recordId -notmatch '^\d{1,20}$') { return '' }
|
|
188
|
+
try {
|
|
189
|
+
$e = Get-WinEvent -LogName 'Microsoft-Windows-AppXDeploymentServer/Operational' -FilterXPath ('*[System[EventID=751 and EventRecordID=' + $recordId + ']]') -MaxEvents 1 -ErrorAction Stop
|
|
190
|
+
$text = [string]$e.Message + ' ' + [string]$e.ToXml()
|
|
191
|
+
$name = $family.Substring(0, $family.LastIndexOf('_')); $pub = $family.Substring($family.LastIndexOf('_') + 1)
|
|
192
|
+
if (($text.IndexOf($name + '_', [StringComparison]::OrdinalIgnoreCase) -ge 0) -and ($text.IndexOf($pub, [StringComparison]::OrdinalIgnoreCase) -ge 0)) { return '' }
|
|
193
|
+
return ('event 751 record ' + $recordId + ' does not name ' + $family)
|
|
194
|
+
} catch { Log ('event ' + $recordId + ' unreadable, proceeding: ' + $_.Exception.Message); return '' }
|
|
143
195
|
}
|
|
144
196
|
|
|
145
197
|
# ------------------------------------------------------------------ package state
|
|
@@ -264,6 +316,9 @@ if ((Split-Path -Leaf $Dir) -ne 'repair') { Finish 'refused' 2 ('script folder i
|
|
|
264
316
|
if ($feedUri -and -not (Test-AllowedUri $feedUri)) { Finish 'refused' 2 ('guard.json feed uri is not https (or http to loopback): ' + $feedUri) }
|
|
265
317
|
if ($msix -and -not (Test-AllowedUri $msix)) { Finish 'refused' 2 ('guard.json msix uri is not https (or http to loopback): ' + $msix) }
|
|
266
318
|
$script:Ctx = $family
|
|
319
|
+
$foreign = Test-ForeignEvent $family $EventRecordID
|
|
320
|
+
if ($foreign) { Log ('ignored: ' + $foreign); exit 0 }
|
|
321
|
+
Log ('guard sha256 ' + $ScriptHashes.guardSha256 + ' (lf ' + $ScriptHashes.guardSha256Lf + ')')
|
|
267
322
|
$mutex = [Threading.Mutex]::new($false, ('Local\' + $family + '-Repair'))
|
|
268
323
|
if (-not $mutex.WaitOne(0)) { Log 'another repair run is active; exiting (last-result.json left to that run)'; exit 0 }
|
|
269
324
|
$tmpMsix = ''
|
|
@@ -295,6 +350,7 @@ try {
|
|
|
295
350
|
}
|
|
296
351
|
if (-not $expect.name -or -not $expect.publisher) {
|
|
297
352
|
Log 'FAILED: expected package identity unknown; refusing to install anything'
|
|
353
|
+
$script:LaunchFailure = 'identity unknown: no installer started'
|
|
298
354
|
Set-Retry $family 'identity unknown'
|
|
299
355
|
Write-Result 'failed' 1 'expected package identity unknown'
|
|
300
356
|
exit 1
|
|
@@ -312,6 +368,7 @@ try {
|
|
|
312
368
|
$feedOk = $true
|
|
313
369
|
if ($fi.uri) { $msix = $fi.uri } # newer than the one recorded
|
|
314
370
|
Log ('Add-AppxPackage -AppInstallerFile ' + $feedUri + ' (declares ' + $fi.version + ')')
|
|
371
|
+
$script:Launched = $true
|
|
315
372
|
try { Add-AppxPackage -AppInstallerFile $feedUri -ErrorAction Stop } catch { Log ('-AppInstallerFile failed: ' + $_.Exception.Message + ' | ' + $_.FullyQualifiedErrorId); $why += ('-AppInstallerFile failed: ' + $_.Exception.Message) }
|
|
316
373
|
if (Test-Healthy $family) {
|
|
317
374
|
$now = @(Get-Family $family) | Select-Object -First 1
|
|
@@ -334,6 +391,7 @@ try {
|
|
|
334
391
|
if ($bad) { Log ('REFUSED msix ' + $msix + ': ' + $bad); $why += ('msix refused: ' + $bad) }
|
|
335
392
|
else {
|
|
336
393
|
Log ('Add-AppxPackage -Path ' + $msix + ' (' + $mi.kind + ' ' + $mi.version + ')')
|
|
394
|
+
$script:Launched = $true
|
|
337
395
|
try { Add-AppxPackage -Path $tmpMsix -ErrorAction Stop } catch { Log ('-Path failed: ' + $_.Exception.Message + ' | ' + $_.FullyQualifiedErrorId); $why += ('-Path failed: ' + $_.Exception.Message) }
|
|
338
396
|
if (Test-Healthy $family) {
|
|
339
397
|
$now = @(Get-Family $family) | Select-Object -First 1
|
|
@@ -345,6 +403,7 @@ try {
|
|
|
345
403
|
}
|
|
346
404
|
}
|
|
347
405
|
} else { $why += 'no msix uri recorded' }
|
|
406
|
+
if (-not $script:Launched) { $script:LaunchFailure = 'no installer started: ' + ($why -join ' | ') }
|
|
348
407
|
$detail = 'FAILED: package still ' + (Get-StateText $family) + '. Reasons: ' + ($why -join ' | ')
|
|
349
408
|
Set-Retry $family 'repair failed'
|
|
350
409
|
Write-Result 'failed' 1 $detail
|
package/msix-signer.mjs
CHANGED
|
@@ -182,19 +182,26 @@ export function signerCertificate(parsed) {
|
|
|
182
182
|
return id.serial === parsed.signer.serial && id.issuer.equals(parsed.signer.issuer);
|
|
183
183
|
} catch { return false; }
|
|
184
184
|
});
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
// Real Azure Artifact Signing packages embed the signer certificate more than once: drop byte-identical
|
|
186
|
+
// duplicates, then, if distinct certificates still match, keep the one that is not a CA.
|
|
187
|
+
const distinct = [...new Map(match.map((c) => [c.toString("hex"), c])).values()];
|
|
188
|
+
const leaves = distinct.length > 1 ? distinct.filter((c) => { try { return !new X509Certificate(c).ca; } catch { return false; } }) : distinct;
|
|
189
|
+
if (leaves.length !== 1) throw new Error(`signer certificate not found in the signature (${match.length} matches, ${distinct.length} distinct, ${leaves.length} non-CA)`);
|
|
190
|
+
return new X509Certificate(leaves[0]);
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
// --- subject rendering ---------------------------------------------------------------------------
|
|
190
194
|
const needsQuote = (v) => /[,+"\\<>;=\r\n]/.test(v) || /^[ #]|\s$/.test(v);
|
|
191
|
-
|
|
195
|
+
// Windows (.NET X500DistinguishedName, Get-AuthenticodeSignature, MSIX Identity/@Publisher) names these
|
|
196
|
+
// attributes differently from OpenSSL/Node.
|
|
197
|
+
const WINDOWS_ATTRIBUTE = { ST: "S", emailAddress: "E" };
|
|
198
|
+
/** Render the subject exactly as Windows does: most specific RDN first ("CN=a, O=b, L=c, S=d, C=e"), Windows attribute names. */
|
|
192
199
|
export function renderSubject(x509) {
|
|
193
200
|
return String(x509.subject).split("\n").filter(Boolean).map((line) => {
|
|
194
201
|
const at = line.indexOf("=");
|
|
195
202
|
const key = line.slice(0, at); const value = line.slice(at + 1);
|
|
196
|
-
return `${key}=${needsQuote(value) ? `"${value.replace(/"/g, '""')}"` : value}`;
|
|
197
|
-
}).join(", ");
|
|
203
|
+
return `${WINDOWS_ATTRIBUTE[key] ?? key}=${needsQuote(value) ? `"${value.replace(/"/g, '""')}"` : value}`;
|
|
204
|
+
}).reverse().join(", ");
|
|
198
205
|
}
|
|
199
206
|
|
|
200
207
|
/** Read the signer certificate subject of a signed .msix / .msixbundle from AppxSignature.p7x. Throws MSIX_PUBLISHER_MISMATCH. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.156",
|
|
4
4
|
"description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,14 +29,6 @@
|
|
|
29
29
|
"!standalone-clone-evidence.json"
|
|
30
30
|
],
|
|
31
31
|
"sideEffects": false,
|
|
32
|
-
"scripts": {
|
|
33
|
-
"test": "node --test --test-concurrency=1 --test-force-exit *.test.mjs",
|
|
34
|
-
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
|
35
|
-
"test:signer:windows": "node sign-release-manifest.integration.mjs",
|
|
36
|
-
"prepublishOnly": "pnpm test && pnpm test:signer:windows && pnpm test:registry-parity",
|
|
37
|
-
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
38
|
-
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
39
|
-
},
|
|
40
32
|
"publishConfig": {
|
|
41
33
|
"registry": "https://registry.npmjs.org/",
|
|
42
34
|
"access": "public"
|
|
@@ -46,8 +38,14 @@
|
|
|
46
38
|
"url": "git+https://github.com/Orthic-Labs/rightkit.git",
|
|
47
39
|
"directory": "packages/release"
|
|
48
40
|
},
|
|
49
|
-
"packageManager": "pnpm@11.24.0",
|
|
50
41
|
"dependencies": {
|
|
51
42
|
"smol-toml": "1.9.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "node --test --test-concurrency=1 --test-force-exit *.test.mjs",
|
|
46
|
+
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
|
47
|
+
"test:signer:windows": "node sign-release-manifest.integration.mjs",
|
|
48
|
+
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
49
|
+
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
52
50
|
}
|
|
53
|
-
}
|
|
51
|
+
}
|
package/release.mjs
CHANGED
|
File without changes
|
package/rightkit-versions.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@rightkit/platform-ui": "0.2.7",
|
|
28
28
|
"@rightkit/qa": "0.4.7",
|
|
29
29
|
"@rightkit/qa-chrome": "0.1.5",
|
|
30
|
-
"@rightkit/release": "0.2.
|
|
30
|
+
"@rightkit/release": "0.2.156",
|
|
31
31
|
"@rightkit/settings-ui": "0.4.3",
|
|
32
32
|
"@rightkit/tauri": "0.2.0",
|
|
33
33
|
"@rightkit/theme": "0.2.3"
|
|
@@ -190,7 +190,8 @@
|
|
|
190
190
|
"0.2.148",
|
|
191
191
|
"0.2.149",
|
|
192
192
|
"0.2.150",
|
|
193
|
-
"0.2.151"
|
|
193
|
+
"0.2.151",
|
|
194
|
+
"0.2.155"
|
|
194
195
|
],
|
|
195
196
|
"@rightkit/qa": [
|
|
196
197
|
"0.1.0",
|
|
@@ -443,7 +444,7 @@
|
|
|
443
444
|
"@rightkit/platform-ui": "0.2.7",
|
|
444
445
|
"@rightkit/qa": "0.4.7",
|
|
445
446
|
"@rightkit/qa-chrome": "0.1.5",
|
|
446
|
-
"@rightkit/release": "0.2.
|
|
447
|
+
"@rightkit/release": "0.2.155",
|
|
447
448
|
"@rightkit/settings-ui": "0.4.3",
|
|
448
449
|
"@rightkit/shell": "0.1.1",
|
|
449
450
|
"@rightkit/tauri": "0.2.0",
|
package/upload-large.mjs
CHANGED
|
File without changes
|