@rightkit/release 0.2.172 → 0.2.174

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.
@@ -199,7 +199,7 @@ export function renderRuntimeBudgetBody(budget) {
199
199
  const host = `${m.host?.os}/${m.host?.arch}${m.host?.model ? ` (${m.host.model})` : ""}`;
200
200
  const scale = `${count(m.dataScale?.count)} ${m.dataScale?.unit} (${m.dataScale?.description})`;
201
201
  const app = m.scope === "installed" && m.app ? `, app ${m.app.name} ${m.app.version}` : "";
202
- const observed = BUDGET_FIGURES.map((k) => `${FIGURE_LABEL[k]} ${m.observed?.[k] === undefined ? "n/a" : FORMAT[k](m.observed[k])}`).join(", ");
202
+ const observed = BUDGET_FIGURES.map((k) => `${FIGURE_LABEL[k]} ${m.observed?.[k] === undefined || m.observed?.[k] === null ? "n/a (not measurable on this host)" : FORMAT[k](m.observed[k])}`).join(", ");
203
203
  out.push(`- Measured: ${host}, ${budget.crate} ${m.crateVersion} at ${String(m.commit).slice(0, 12)}, ${scale}, ${m.scope}${app}, \`${m.command}\`, ${m.date}, evidence ${String(m.evidence?.sha256 ?? "none").slice(0, 12)}: ${observed}.`);
204
204
  }
205
205
  return out;
@@ -291,6 +291,10 @@ export function measurementStaleness(commit, crateDir, git = defaultGit, deps =
291
291
 
292
292
  // ---------------------------------------------------------------- bench evidence
293
293
 
294
+ // Figures a host cannot measure by design. macOS has no per-process disk-operation count (rightkit-child-process
295
+ // usage.rs: ru_inblock/ru_oublock stay 0 there), so disk_ops_per_s may be "unavailable" on darwin only.
296
+ export const HOST_UNAVAILABLE_FIGURES = Object.freeze({ darwin: Object.freeze(["diskOpsPerSecond"]) });
297
+ export const figureUnavailableOn = (os, key) => (HOST_UNAVAILABLE_FIGURES[os] ?? []).includes(key);
294
298
  const RESULT_NUMBERS = Object.freeze({ resident_bytes: "residentMemoryBytes", idle_cpu_percent: "idleCpuPercent", wakeups_per_s: "wakeupsPerSecond", disk_ops_per_s: "diskOpsPerSecond", scale_count: "scaleCount" });
295
299
  const RESULT_WORDS = Object.freeze(["crate", "version", "scale_unit"]);
296
300
 
@@ -312,7 +316,8 @@ export function parseBudgetResult(text) {
312
316
  for (const key of RESULT_WORDS) { if (!nonEmpty(fields[key])) problems.push(`missing field ${key}`); else value[key] = fields[key]; }
313
317
  for (const [key, name] of Object.entries(RESULT_NUMBERS)) {
314
318
  const n = Number(fields[key]);
315
- if (fields[key] === undefined || fields[key] === "" || !Number.isFinite(n) || n < 0) problems.push(`field ${key} must be a non-negative number`);
319
+ if (fields[key] === "unavailable" && name !== "residentMemoryBytes" && name !== "scaleCount") value[name] = null;
320
+ else if (fields[key] === undefined || fields[key] === "" || !Number.isFinite(n) || n < 0) problems.push(`field ${key} must be a non-negative number`);
316
321
  else value[name] = n;
317
322
  }
318
323
  return problems.length ? { ok: false, problems } : { ok: true, problems: [], value };
@@ -412,6 +417,7 @@ function validateMeasurement(m, index) {
412
417
  }
413
418
  if (!nonEmpty(m.command)) problems.push(`${at} has no command`);
414
419
  for (const key of BUDGET_FIGURES) {
420
+ if (m.observed?.[key] === null && figureUnavailableOn(m.host?.os, key)) continue;
415
421
  if (!isNumber(m.observed?.[key]) || m.observed[key] < 0) problems.push(`${at} observed.${key} must be a non-negative number`);
416
422
  }
417
423
  if (isNumber(m.observed?.residentMemoryBytes) && m.observed.residentMemoryBytes === 0) {
@@ -507,12 +513,12 @@ export function evaluateRuntimeBudget({ crate, crateDir, version, evidence, requ
507
513
  }
508
514
  for (const { m, index } of counted) {
509
515
  for (const key of BUDGET_FIGURES) {
510
- if (ceilings[key] !== undefined && m.observed[key] > ceilings[key]) {
516
+ if (ceilings[key] !== undefined && m.observed[key] !== null && m.observed[key] > ceilings[key]) {
511
517
  problems.push(`measurement[${index}] on ${m.host.os}/${m.host.arch} exceeds the ${FIGURE_LABEL[key]} ceiling: observed ${m.observed[key]} > ${ceilings[key]}`);
512
518
  }
513
519
  }
514
520
  }
515
- const withinCeilings = (m) => BUDGET_FIGURES.every((key) => ceilings[key] !== undefined && m.observed[key] <= ceilings[key]);
521
+ const withinCeilings = (m) => BUDGET_FIGURES.every((key) => ceilings[key] !== undefined && (m.observed[key] === null ? figureUnavailableOn(m.host?.os, key) : m.observed[key] <= ceilings[key]));
516
522
  const atScale = (m) => requiredScale !== null && m.dataScale.unit === requiredScale.unit && m.dataScale.count >= requiredScale.minCount;
517
523
  const staleness = new Map();
518
524
  const stale = (m) => {
@@ -768,7 +774,7 @@ export function appRuntimeBudgetAdoptionCheck({ appRoot, app, version, cargoLock
768
774
  if (evidenceProblems.length) { problems.push(`${crate}: ${evidenceProblems.join("; ")}`); return false; }
769
775
  if (m.app.name !== app) { problems.push(`${crate}: measurement[${index}] is for app ${m.app.name}, not ${app}`); return false; }
770
776
  if (version && m.app.version !== version) { problems.push(`${crate}: measurement[${index}] was taken at ${app} ${m.app.version}, not the current ${version}`); return false; }
771
- const over = BUDGET_FIGURES.filter((k) => m.observed[k] > ceilings[k]);
777
+ const over = BUDGET_FIGURES.filter((k) => m.observed[k] !== null && m.observed[k] > ceilings[k]);
772
778
  if (over.length) { problems.push(`${crate}: measurement[${index}] exceeds ${over.join(", ")}`); return false; }
773
779
  if (m.dataScale.unit !== requiredScale.unit || m.dataScale.count < requiredScale.minCount) { problems.push(`${crate}: measurement[${index}] is below the required scale ${requiredScale.minCount} ${requiredScale.unit}`); return false; }
774
780
  return true;
@@ -813,6 +819,9 @@ export function recordMeasurement({ crateDir, resultFile, scope, command, descri
813
819
  const v = parsed.value;
814
820
  if (v.crate !== pkg.name) throw new Error(`bench output is for ${v.crate}, crate is ${pkg.name}`);
815
821
  if (v.version !== pkg.version) throw new Error(`bench output is for version ${v.version}, crate is ${pkg.version}`);
822
+ for (const key of BUDGET_FIGURES) {
823
+ if (v[key] === null && !figureUnavailableOn(host.os, key)) throw new Error(`bench output rejected: ${key} is unavailable, which is allowed only where the host cannot measure it (${JSON.stringify(HOST_UNAVAILABLE_FIGURES)})`);
824
+ }
816
825
  if (!SCOPES.includes(scope)) throw new Error(`--scope must be one of ${SCOPES.join(", ")}`);
817
826
  if (scope === "installed" && !(nonEmpty(app?.name) && nonEmpty(app?.version))) throw new Error("--scope installed needs --app and --app-version");
818
827
  if (!nonEmpty(command)) throw new Error("--command (the exact bench command) is required");
package/stage-binary.mjs CHANGED
@@ -9,7 +9,7 @@ import { resolveTargetRoot } from "./cargo-target.mjs";
9
9
  * The build broker writes Cargo output to per-generation folders (`<managed>/rightkit-build-control/ws/<hash>/g/<gen>/...`)
10
10
  * and `cargo-guard` uses a hashed `dev-targets/<project>-<hash>` folder. Those are build output only: their path is not
11
11
  * stable, so NOTHING may run from them. macOS ties privacy grants to the path an app runs from, so a dev or QA binary is
12
- * copied in place to `<root>/.rightkit-dev-bin/<triple-or-host>/<name>` (keeping `<name>.previous`) and run from there.
12
+ * copied in place to `<root>/.rightkit-dev-bin/<triple-or-host>/<name>` (replaced in place, no `.previous` kept) and run from there.
13
13
  */
14
14
  export const DEV_STAGE_DIRECTORY = ".rightkit-dev-bin";
15
15
 
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // `right-release stage-sidecar [--config right-release.config.mjs] [--target <triple>] [--check] [--json]` (bogusyogi/rightkit#345):
3
+ // stages the config's declarative `sidecars` into src-tauri/binaries with Tauri externalBin naming. `--check` validates
4
+ // the manifest and prints the plan without building, downloading, copying or deleting anything (for template gates).
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { planSidecarStaging, resolveTargetTriple, stageSidecars } from "./stage-sidecar.mjs";
8
+
9
+ export function parseStageSidecarArgs(argv) {
10
+ const opts = { config: "right-release.config.mjs", target: undefined, json: false, check: false };
11
+ for (let i = 0; i < argv.length; i += 1) {
12
+ const arg = argv[i];
13
+ if (arg === "--config" || arg === "--target") {
14
+ const value = argv[i + 1];
15
+ if (!value || value.startsWith("--")) throw new Error(`${arg} needs a value`);
16
+ opts[arg.slice(2)] = value;
17
+ i += 1;
18
+ } else if (arg === "--json") opts.json = true;
19
+ else if (arg === "--check") opts.check = true;
20
+ else throw new Error(`unknown stage-sidecar argument: ${arg}`);
21
+ }
22
+ return opts;
23
+ }
24
+
25
+ const NODE_ARCH = { x64: "x86_64", arm64: "aarch64" };
26
+ const NODE_OS = { darwin: "apple-darwin", win32: "pc-windows-msvc", linux: "unknown-linux-gnu" };
27
+
28
+ /** Host triple from Node alone (no subprocess), so `--check` stays free of effects. */
29
+ export function nodeHostTriple(platform = process.platform, arch = process.arch) {
30
+ const cpu = NODE_ARCH[arch];
31
+ const system = NODE_OS[platform];
32
+ if (!cpu || !system) throw new Error(`cannot derive a target triple from ${platform}/${arch}; pass --target`);
33
+ return `${cpu}-${system}`;
34
+ }
35
+
36
+ /** Human-readable plan lines for `--check`. */
37
+ export function formatSidecarPlan(plan) {
38
+ const lines = [`right-release stage-sidecar --check: target ${plan.target}, mode ${plan.mode}, stageDir ${plan.stageDir}${plan.wipeStageDir ? " (wipeStageDir)" : ""}`];
39
+ for (const entry of plan.entries) {
40
+ if (entry.skipped) lines.push(` [${entry.index}] ${entry.kind}: skipped (platforms ${entry.platforms.join(",")})`);
41
+ else lines.push(` [${entry.index}] ${entry.kind}: ${entry.outputs.join(", ")}${entry.externalBin ? " (externalBin)" : ""}${entry.clean ? " (clean)" : ""}${entry.url ? ` <- ${entry.url}` : ""}`);
42
+ }
43
+ if (plan.nestedBrokerRisk) lines.push(" warning: running inside an outer broker build; staging would fail with STAGE_SIDECAR_NESTED_BROKER (run stage-sidecar in recipe prepare, before the Tauri build)");
44
+ lines.push("right-release stage-sidecar --check: manifest is valid; nothing was staged");
45
+ return lines;
46
+ }
47
+
48
+ async function main() {
49
+ const opts = parseStageSidecarArgs(process.argv.slice(2));
50
+ const configPath = path.resolve(opts.config);
51
+ const appRoot = path.dirname(configPath);
52
+ const loaded = await import(pathToFileURL(configPath).href);
53
+ const config = loaded.default ?? loaded.config ?? loaded;
54
+ if (config?.sidecars === undefined) throw new Error(`${opts.config} declares no sidecars`);
55
+ if (opts.check) {
56
+ const target = opts.target ?? process.env.TAURI_ENV_TARGET_TRIPLE ?? nodeHostTriple();
57
+ const plan = planSidecarStaging({ manifest: config.sidecars, target });
58
+ if (opts.json) console.log(JSON.stringify(plan, null, 2));
59
+ else for (const line of formatSidecarPlan(plan)) console.log(line);
60
+ return;
61
+ }
62
+ const target = resolveTargetTriple({ target: opts.target, cwd: appRoot });
63
+ const result = await stageSidecars({ appRoot, manifest: config.sidecars, target });
64
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
65
+ else {
66
+ for (const file of result.files ?? []) console.log(`right-release stage-sidecar: staged ${typeof file === "string" ? file : file.path ?? JSON.stringify(file)}`);
67
+ console.log(`right-release stage-sidecar: externalBin ${JSON.stringify(result.externalBinNames ?? [])}; receipt ${result.receiptPath}`);
68
+ }
69
+ }
70
+
71
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
72
+ main().catch((error) => { console.error(`right-release stage-sidecar: ${error.code ? `[${error.code}] ` : ""}${error.message}`); process.exitCode = 1; });
73
+ }