@rightkit/release 0.6.10 → 0.6.11
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/README.md +7 -5
- package/admission-files.mjs +3 -0
- package/admission.mjs +2 -2
- package/advisory-exceptions.mjs +41 -5
- package/android-play-upload.mjs +16 -15
- package/app-check.mjs +13 -9
- package/bin/rightkit-gate.mjs +762 -345
- package/broker-pins.mjs +152 -0
- package/build-release.mjs +80 -27
- package/cargo-ledger.mjs +62 -0
- package/cargo-target-shim.mjs +98 -26
- package/cli/right-release.mjs +28 -4
- package/clippy-equivalence.mjs +202 -0
- package/clippy-route.mjs +543 -0
- package/committed-source.mjs +39 -5
- package/crate-workspaces.mjs +87 -0
- package/create-mac-updater.mjs +14 -2
- package/deterministic-scans.mjs +196 -21
- package/development.mjs +13 -4
- package/direct-receipt.mjs +135 -0
- package/door-policy.mjs +10 -0
- package/door.mjs +6 -6
- package/freshness-census.mjs +257 -0
- package/gate-stages.mjs +76 -10
- package/hook-retirement.mjs +65 -0
- package/init-command.mjs +1 -1
- package/inventory-check.mjs +11 -8
- package/ios-upload.mjs +20 -15
- package/local-publish.mjs +77 -41
- package/local-qualify.mjs +5 -3
- package/local-runtime.mjs +5 -4
- package/mac-package-recipe.mjs +4 -1
- package/mirror-root-artifact.mjs +18 -5
- package/ownership-class-rules.mjs +1 -1
- package/ownership-evidence.mjs +37 -13
- package/ownership-hooks.mjs +20 -59
- package/ownership-lockfiles.mjs +14 -1
- package/ownership-policy-bundle.mjs +3 -2
- package/ownership-staged.mjs +2 -1
- package/ownership.mjs +24 -0
- package/package-recipe-targets.mjs +35 -6
- package/package.json +1 -1
- package/pipeline-admission.mjs +4 -4
- package/preflight.mjs +27 -4
- package/publish-cargo.mjs +4 -3
- package/recipe-build.mjs +38 -14
- package/release-artifact-path.mjs +341 -0
- package/release-invocation.mjs +8 -1
- package/release-qa-command.mjs +25 -9
- package/release-source-brand.mjs +17 -0
- package/release-source-identity.mjs +171 -0
- package/release.mjs +44 -17
- package/rightkit-versions.json +62 -42
- package/runtime-budgets.mjs +6 -2
- package/scan-digest.mjs +71 -5
- package/sign-updater.mjs +6 -3
- package/snapshot-broker.mjs +172 -0
- package/snapshot-check.mjs +12 -6
- package/snapshot-request.mjs +207 -5
- package/snapshot.mjs +123 -10
- package/source-split.mjs +73 -0
- package/stage-commands.mjs +2 -2
- package/stage-ledger.mjs +204 -0
- package/stage-sidecar-command.mjs +5 -1
- package/stage-sidecar.mjs +100 -20
- package/swap-helper-bundle.mjs +5 -2
- package/unit-gate.mjs +45 -3
- package/web-build.mjs +6 -4
- package/win-package-recipe.mjs +20 -7
package/stage-sidecar.mjs
CHANGED
|
@@ -665,8 +665,8 @@ async function defaultFetchBytes(url) {
|
|
|
665
665
|
/** Absolute catalog file for a validated spec. Crate resolution reads the rightkit metadata listing; npm walks up node_modules. */
|
|
666
666
|
function resolveCatalogFile(spec, ctx, kind) {
|
|
667
667
|
const label = kind === "ffmpeg" ? "rightkit-ffmpeg" : "native-libraries";
|
|
668
|
-
if (spec.via === "absolute") return spec.file;
|
|
669
|
-
if (spec.via === "path") return path.resolve(ctx.appRoot, spec.file);
|
|
668
|
+
if (spec.via === "absolute") return requireSnapshotSource(ctx, spec.file, `the ${label} catalog`);
|
|
669
|
+
if (spec.via === "path") return requireSnapshotSource(ctx, path.resolve(ctx.appRoot, spec.file), `the ${label} catalog`);
|
|
670
670
|
if (spec.via === "crate") {
|
|
671
671
|
const meta = ctx.run("rustright", ["cargo", "metadata", "--format-version", "1"], { cwd: ctx.appRoot, env: ctx.env });
|
|
672
672
|
let packages;
|
|
@@ -751,6 +751,23 @@ function cleanDest(ctx, entry, dest, { directory = false } = {}) {
|
|
|
751
751
|
try { ctx.fs.rmSync(dest, { recursive: directory, force: true }); } catch (error) { fail("STAGE_SIDECAR_CLEAN_REFUSED", `cannot clean ${dest} (${error.message}); a directory is only removed for a directory resource`, { dest, cause: error }); }
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Warm-cache rule (GATE-CONTRACT "rebuild only what changed"): a staged file carries the mtime of the cache file it was copied from, a
|
|
756
|
+
* lipo or merge output the newest mtime of its inputs, so a re-stage after the snapshot sync (which removes every ledger entry) does not
|
|
757
|
+
* bump the mtime and re-trigger the app crate (tauri-build emits rerun-if-changed for the staged sidecar). Bytes are bound by the
|
|
758
|
+
* ledger, never by mtime. A file system without `utimesSync` or `statSync().mtime` (a test double) is left as written.
|
|
759
|
+
*/
|
|
760
|
+
function carryMtime(ctx, dest, sources) {
|
|
761
|
+
const { fs } = ctx;
|
|
762
|
+
if (typeof fs.utimesSync !== "function") return;
|
|
763
|
+
let newest = null;
|
|
764
|
+
for (const source of sources) {
|
|
765
|
+
let st; try { st = fs.statSync(source); } catch { continue; }
|
|
766
|
+
if (st?.mtime instanceof Date && (!newest || st.mtime > newest)) newest = st.mtime;
|
|
767
|
+
}
|
|
768
|
+
if (newest) { try { fs.utimesSync(dest, newest, newest); } catch { /* the mtime is a cache hint, never a binding */ } }
|
|
769
|
+
}
|
|
770
|
+
|
|
754
771
|
function copyInto(ctx, source, dest, { executable }) {
|
|
755
772
|
const { fs, windows } = ctx;
|
|
756
773
|
const sourceSha256 = sha256Of(fs.readFileSync(source));
|
|
@@ -765,11 +782,44 @@ function copyInto(ctx, source, dest, { executable }) {
|
|
|
765
782
|
} else if (executable && !windows) fs.chmodSync(dest, 0o755);
|
|
766
783
|
const sha256 = sha256Of(fs.readFileSync(dest));
|
|
767
784
|
if (sha256 !== sourceSha256) fail("STAGE_SIDECAR_HASH_MISMATCH", `staged ${dest} differs from its source ${source}`, { expected: sourceSha256, actual: sha256 });
|
|
785
|
+
carryMtime(ctx, dest, [source]);
|
|
768
786
|
return { sha256, sizeBytes: fs.statSync(dest).size, changed };
|
|
769
787
|
}
|
|
770
788
|
|
|
771
789
|
const rel = (ctx, file) => path.relative(ctx.appRoot, file).replaceAll("\\", "/");
|
|
772
790
|
|
|
791
|
+
/**
|
|
792
|
+
* The boundary of a split run (row 7.05a): the only checkout bytes it may read are cache entries whose digest the commit pins or
|
|
793
|
+
* tool-invalidated build caches, both under `ctx.cacheRoot`. A SOURCE the config names (a local `source.dir`, an ffmpeg `path`) must
|
|
794
|
+
* resolve inside the snapshot's app root, never into the checkout or anywhere else.
|
|
795
|
+
*/
|
|
796
|
+
function requireSnapshotSource(ctx, file, label) {
|
|
797
|
+
if (!ctx.split) return file;
|
|
798
|
+
const relative = path.relative(ctx.appRoot, path.resolve(file));
|
|
799
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) fail("STAGE_SIDECAR_SOURCE_OUTSIDE_SNAPSHOT", `${label} resolves to ${file}, outside the snapshot's app root ${ctx.appRoot}; a split run reads sources only from the snapshot (checkout bytes enter only as digest-pinned cache entries)`, { file });
|
|
800
|
+
return file;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* The environment a split run hands to the commands it starts (a `command` entry, an ffmpeg `stage` command): Cargo's output directory is
|
|
804
|
+
* the checkout's, resolved there once and passed absolute, so a command that reaches Cargo (`rustright cargo run -p ...`) never writes
|
|
805
|
+
* `target/` into the snapshot. Without a split, or when the checkout holds no Cargo workspace, the environment is returned as it is.
|
|
806
|
+
*/
|
|
807
|
+
function splitCommandEnv(ctx) {
|
|
808
|
+
if (!ctx.split) return ctx.env;
|
|
809
|
+
if (ctx.splitEnv === undefined) {
|
|
810
|
+
const resolved = ctx.run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps"], { cwd: ctx.cacheRoot, env: ctx.env });
|
|
811
|
+
let dir;
|
|
812
|
+
try { dir = JSON.parse(resolved?.stdout).target_directory; } catch { /* no workspace in the checkout: nothing to pin */ }
|
|
813
|
+
ctx.splitEnv = !resolved?.error && resolved?.status === 0 && typeof dir === "string" && path.isAbsolute(dir) ? { ...ctx.env, CARGO_TARGET_DIR: path.resolve(dir) } : ctx.env;
|
|
814
|
+
}
|
|
815
|
+
return ctx.splitEnv;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** A cache directory the config names (`cacheDir`): the checkout's `.right-release` under a split, the app root otherwise. */
|
|
819
|
+
const cacheDirFor = (ctx, dir) => (ctx.split ? path.resolve(ctx.cacheRoot, ".right-release", dir) : path.resolve(ctx.appRoot, dir));
|
|
820
|
+
/** The per-arch slice directory of a universal staging: absolute under the checkout's cache root on a split, app-relative otherwise. */
|
|
821
|
+
const sliceDirFor = (ctx, triple) => (ctx.split ? path.resolve(ctx.cacheRoot, SLICE_ROOT, triple) : `${SLICE_ROOT}/${triple}`);
|
|
822
|
+
|
|
773
823
|
/** `lipo -archs <file>` as a list; a failing lipo is a typed error. */
|
|
774
824
|
function lipoArchs(ctx, file) {
|
|
775
825
|
const result = ctx.run("lipo", ["-archs", file], { cwd: ctx.appRoot, env: ctx.env });
|
|
@@ -804,6 +854,7 @@ function lipoMergeInto(ctx, slices, dest, label, { executable = false } = {}) {
|
|
|
804
854
|
fs.rmSync(temporary, { force: true });
|
|
805
855
|
if (executable) fs.chmodSync(dest, 0o755);
|
|
806
856
|
}
|
|
857
|
+
carryMtime(ctx, dest, slices);
|
|
807
858
|
return { sha256, sizeBytes: fs.statSync(dest).size, changed };
|
|
808
859
|
}
|
|
809
860
|
|
|
@@ -834,6 +885,7 @@ function stageCargoBin(entry, ctx) {
|
|
|
834
885
|
const changed = !(fs.existsSync(dest) && sha256Of(fs.readFileSync(dest)) === sha256);
|
|
835
886
|
fs.chmodSync(temporary, 0o755);
|
|
836
887
|
fs.renameSync(temporary, dest);
|
|
888
|
+
carryMtime(ctx, dest, slices.map((slice) => slice.source));
|
|
837
889
|
return [{ kind: "cargo-bin", name: entry.name, path: rel(ctx, dest), sha256, sizeBytes: fs.statSync(dest).size, changed, sourceSha256: sha256, package: entry.package, bin: entry.bin, profile: entry.profile, features: entry.features, externalBin: !entry.dest, slices: slices.map((slice) => ({ triple: slice.triple, sha256: slice.sourceSha256 })) }];
|
|
838
890
|
}
|
|
839
891
|
|
|
@@ -851,17 +903,29 @@ function stageCargoBuild(entry, ctx, triple) {
|
|
|
851
903
|
"-p", entry.package, "--bin", entry.bin, "--target", triple, "--profile", entry.profile,
|
|
852
904
|
...(entry.features.length ? ["--features", entry.features.join(",")] : []), "--message-format=json-render-diagnostics"];
|
|
853
905
|
ctx.log(`stage-sidecar: rightkit ${args.join(" ")}`);
|
|
854
|
-
|
|
906
|
+
// From a snapshot, Cargo would resolve `target/` (or a relative build.target-dir) against the snapshot: its output directory is
|
|
907
|
+
// resolved in the checkout instead and passed as an absolute CARGO_TARGET_DIR, so the warm outputs stay the checkout's and the
|
|
908
|
+
// snapshot holds no target/ (docs/GATE-CONTRACT.md "Output directory & cache").
|
|
909
|
+
let buildEnv = env;
|
|
910
|
+
if (ctx.split) {
|
|
911
|
+
const checkoutManifest = entry.manifestPath ? ["--manifest-path", path.resolve(ctx.cacheRoot, entry.manifestPath)] : [];
|
|
912
|
+
const resolved = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...checkoutManifest], { cwd: ctx.cacheRoot, env });
|
|
913
|
+
let pinned;
|
|
914
|
+
try { pinned = JSON.parse(resolved?.stdout).target_directory; } catch { /* refused below */ }
|
|
915
|
+
if (resolved?.error || resolved?.status !== 0 || typeof pinned !== "string" || !path.isAbsolute(pinned)) fail("STAGE_SIDECAR_BUILD_FAILED", `the checkout's cargo output directory for ${entry.package} cannot be resolved (${resolved?.error?.message ?? `exit ${resolved?.status}`}); nothing was built`, { package: entry.package });
|
|
916
|
+
buildEnv = { ...env, CARGO_TARGET_DIR: path.resolve(pinned) };
|
|
917
|
+
}
|
|
918
|
+
const built = run("rustright", args, { cwd: appRoot, env: buildEnv });
|
|
855
919
|
if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `rustright cargo build of ${entry.package} failed (${built?.error?.message ?? `exit ${built?.status}`})`, { package: entry.package, stderr: tail(built?.stderr) });
|
|
856
920
|
let source = artifactFromStream(built.stdout, entry.bin);
|
|
857
921
|
if (!source || !fs.existsSync(source)) {
|
|
858
|
-
const meta = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...(entry.manifestPath ? ["--manifest-path", path.resolve(appRoot, entry.manifestPath)] : [])], { cwd: appRoot, env });
|
|
922
|
+
const meta = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...(entry.manifestPath ? ["--manifest-path", path.resolve(appRoot, entry.manifestPath)] : [])], { cwd: appRoot, env: buildEnv });
|
|
859
923
|
let targetDir;
|
|
860
924
|
try { targetDir = JSON.parse(meta.stdout).target_directory; } catch { /* refused below */ }
|
|
861
925
|
if (meta?.status !== 0 || typeof targetDir !== "string" || !path.isAbsolute(targetDir)) targetDir = undefined;
|
|
862
926
|
// A managed target root: the broker/cargo metadata target_directory (which cargo derives from CARGO_TARGET_DIR) is
|
|
863
927
|
// authoritative; an explicit CARGO_TARGET_DIR in this environment is tried too, resolved against the app root.
|
|
864
|
-
const envDir = typeof
|
|
928
|
+
const envDir = typeof buildEnv.CARGO_TARGET_DIR === "string" && buildEnv.CARGO_TARGET_DIR.trim() ? path.resolve(appRoot, buildEnv.CARGO_TARGET_DIR) : undefined;
|
|
865
929
|
if (!targetDir && !envDir) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `cargo reported no ${entry.bin} executable and its target directory cannot be read`, { bin: entry.bin });
|
|
866
930
|
const candidates = builtBinaryCandidates({ targetDir, targetDirs: envDir ? [envDir] : [], triple, profile: entry.profile, bin: entry.bin, windows: ctx.windows });
|
|
867
931
|
source = candidates.find((file) => fs.existsSync(file));
|
|
@@ -877,7 +941,7 @@ const ffmpegSourceFor = (sources, tool, arch) => { const spec = sources?.[tool];
|
|
|
877
941
|
/** The binary a local ffmpeg source names: the file itself, or `<dir>/<member>` / `<dir>/bin/<member>` (`.exe` added for a Windows target). */
|
|
878
942
|
function resolveLocalFfmpegSource(ctx, spec, tool) {
|
|
879
943
|
const { fs } = ctx;
|
|
880
|
-
const base = path.resolve(ctx.appRoot, spec.path);
|
|
944
|
+
const base = requireSnapshotSource(ctx, path.resolve(ctx.appRoot, spec.path), `the ffmpeg ${tool} source path`);
|
|
881
945
|
if (fs.existsSync(base) && fs.statSync(base).isFile()) return base;
|
|
882
946
|
const member = spec.member ?? `${tool}${ctx.windows ? ".exe" : ""}`;
|
|
883
947
|
const candidates = [path.join(base, member), path.join(base, "bin", member)];
|
|
@@ -955,15 +1019,17 @@ function probeFfmpegBinary(ctx, { file, tool, target }) {
|
|
|
955
1019
|
*/
|
|
956
1020
|
async function stageFfmpegSlice(entry, ctx, resolved, { config, t, writeNotices, flat = false }) {
|
|
957
1021
|
const { appRoot, run, env, fs } = ctx;
|
|
958
|
-
|
|
1022
|
+
// A universal slice is staged under SLICE_ROOT, which a split keeps in the checkout's cache root, never in the snapshot.
|
|
1023
|
+
const sliceRoot = ctx.split && String(config.stageDir ?? "").startsWith(SLICE_ROOT) ? ctx.cacheRoot : appRoot;
|
|
1024
|
+
const planned = planMediaSidecar({ config, catalog: resolved.catalog, crateDir: resolved.crateDir, crateVersion: resolved.crateVersion, catalogBytes: resolved.catalogBytes, root: sliceRoot });
|
|
959
1025
|
const plan = flat ? { ...planned, files: planned.files.map((file) => ({ ...file, stagedPath: path.resolve(appRoot, entry.resourceDir, file.bundledName) })) } : planned;
|
|
960
1026
|
await fetchFfmpegSources(entry, ctx, plan, t.arch);
|
|
961
1027
|
if (config.stage) {
|
|
962
1028
|
const universal = ctx.target.arch === "universal";
|
|
963
1029
|
// A universal entry stages once per slice: `{arch}`, `{triple}` and `{stageDir}` in the stage command's arguments name the slice.
|
|
964
|
-
const args = (config.stage.args ?? []).map((arg) => (universal ? String(arg).replaceAll("{arch}", t.arch).replaceAll("{triple}", t.triple).replaceAll("{stageDir}", config.stageDir) : arg));
|
|
1030
|
+
const args = (config.stage.args ?? []).map((arg) => (universal ? String(arg).replaceAll("{arch}", t.arch).replaceAll("{triple}", t.triple).replaceAll("{stageDir}", sliceRoot === appRoot ? config.stageDir : path.resolve(sliceRoot, config.stageDir)) : arg));
|
|
965
1031
|
ctx.log(`stage-sidecar: ${config.stage.cmd} ${args.join(" ")}`);
|
|
966
|
-
const staged = run(config.stage.cmd, args, { cwd: appRoot, env });
|
|
1032
|
+
const staged = run(config.stage.cmd, args, { cwd: appRoot, env: splitCommandEnv(ctx) });
|
|
967
1033
|
if (staged?.error || staged?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `ffmpeg stage command failed (${staged?.error?.message ?? `exit ${staged?.status}`})`);
|
|
968
1034
|
}
|
|
969
1035
|
const noticeFiles = writeNotices ? writeMediaSidecarNotices(plan, { exists: fs.existsSync, mkdir: fs.mkdirSync, copy: fs.copyFileSync, write: fs.writeFileSync }) : [];
|
|
@@ -987,13 +1053,20 @@ async function stageFfmpegUniversal(entry, ctx, resolved) {
|
|
|
987
1053
|
results.push({ slice, ...(await stageFfmpegSlice(entry, ctx, resolved, { config: slice.media, t: parseTargetTriple(slice.triple), writeNotices: false })) });
|
|
988
1054
|
}
|
|
989
1055
|
// One notice set and one source offer that names the rows of every slice (a per-slice offer would overwrite the other).
|
|
1056
|
+
// A split plans each slice under the checkout's cache root (SLICE_ROOT), so the slice plans name their notices there too. The notices are FINAL
|
|
1057
|
+
// outputs and belong in the snapshot beside the merged file: they are rebased onto the app root before anything is written, never into the checkout.
|
|
1058
|
+
const toApp = (file) => {
|
|
1059
|
+
if (!ctx.split) return file;
|
|
1060
|
+
const relative = path.relative(ctx.cacheRoot, file);
|
|
1061
|
+
return relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) ? file : path.resolve(appRoot, relative);
|
|
1062
|
+
};
|
|
990
1063
|
const noticeByDest = new Map();
|
|
991
|
-
for (const { plan } of results) for (const notice of plan.notices) noticeByDest.set(notice.dest, notice);
|
|
1064
|
+
for (const { plan } of results) for (const notice of plan.notices) noticeByDest.set(toApp(notice.dest), { ...notice, dest: toApp(notice.dest) });
|
|
992
1065
|
const rowsById = new Map();
|
|
993
1066
|
for (const { plan } of results) for (const row of plan.rows) rowsById.set(row.id, row);
|
|
994
1067
|
const combined = {
|
|
995
1068
|
notices: [...noticeByDest.values()],
|
|
996
|
-
offerPath: results[0].plan.offerPath,
|
|
1069
|
+
offerPath: toApp(results[0].plan.offerPath),
|
|
997
1070
|
offer: sourceOfferText({ rows: [...rowsById.values()], contact: entry.media.notices.sourceOfferContact, crateVersion: resolved.crateVersion }),
|
|
998
1071
|
};
|
|
999
1072
|
const noticeFiles = writeMediaSidecarNotices(combined, { exists: fs.existsSync, mkdir: fs.mkdirSync, copy: fs.copyFileSync, write: fs.writeFileSync });
|
|
@@ -1229,14 +1302,14 @@ async function prepareNativeFile(entry, ctx, loaded, item, { destDir, scratchDir
|
|
|
1229
1302
|
if (fs.existsSync(dest) && !nativeDigestMismatch(entry, row, fs.readFileSync(dest), { fromSource: Boolean(entry.source) })) return prepared;
|
|
1230
1303
|
|
|
1231
1304
|
if (entry.source) {
|
|
1232
|
-
const file = path.resolve(appRoot, entry.source.dir, row.artifactFileName);
|
|
1305
|
+
const file = requireSnapshotSource(ctx, path.resolve(appRoot, entry.source.dir, row.artifactFileName), `source.dir of the ${where} row ${row.id}`);
|
|
1233
1306
|
if (!(fs.existsSync(file) && fs.statSync(file).isFile())) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `the ${where} row ${row.id} is staged from source.dir ${entry.source.dir}, which has no ${row.artifactFileName} (looked at ${file}); nothing is downloaded for a source dir`, { file });
|
|
1234
1307
|
const mismatch = nativeDigestMismatch(entry, row, fs.readFileSync(file), { fromSource: true });
|
|
1235
1308
|
if (mismatch) failNativeMismatch(row, file, mismatch);
|
|
1236
1309
|
return { ...prepared, from: file };
|
|
1237
1310
|
}
|
|
1238
1311
|
if (!row.archive) fail("STAGE_SIDECAR_CONFIG_INVALID", `the ${where} row ${row.id} has no archive, so it is staged from a local build: set source: { dir } on this entry to the directory holding ${row.artifactFileName}`);
|
|
1239
|
-
const archive = path.
|
|
1312
|
+
const archive = path.join(cacheDirFor(ctx, entry.cacheDir), archiveFileName(row.archive.url));
|
|
1240
1313
|
if (!(fs.existsSync(archive) && sha256Of(fs.readFileSync(archive)) === row.archive.sha256)) {
|
|
1241
1314
|
const bytes = await ctx.fetchBytes(row.archive.url);
|
|
1242
1315
|
const actual = sha256Of(bytes);
|
|
@@ -1378,7 +1451,7 @@ async function stageNativeLibrary(entry, ctx) {
|
|
|
1378
1451
|
}
|
|
1379
1452
|
// Otherwise each arch's files are staged thin under SLICE_ROOT, then merged file by file into the entry's resource directory.
|
|
1380
1453
|
const slices = [];
|
|
1381
|
-
for (const plan of plans) slices.push({ triple: plan.triple, files: await stageNativeSet(entry, ctx, loaded, plan.items, { destDir:
|
|
1454
|
+
for (const plan of plans) slices.push({ triple: plan.triple, files: await stageNativeSet(entry, ctx, loaded, plan.items, { destDir: sliceDirFor(ctx, plan.triple) }) });
|
|
1382
1455
|
const [first, second] = slices.map((slice) => slice.files.map((file) => file.outName));
|
|
1383
1456
|
if ([...first].sort().join("\n") !== [...second].sort().join("\n")) fail("STAGE_SIDECAR_NOT_APPROVED", `the arch rows name different library files (aarch64: ${first.join(", ")}; x86_64: ${second.join(", ")}); each file needs one name on both arches (a single file can be renamed with "as")`);
|
|
1384
1457
|
return first.map((name) => {
|
|
@@ -1446,7 +1519,7 @@ function stageCommand(entry, ctx) {
|
|
|
1446
1519
|
const cwd = entry.cwd ? path.resolve(appRoot, entry.cwd) : appRoot;
|
|
1447
1520
|
cleanDest(ctx, entry, destFor(entry, ctx));
|
|
1448
1521
|
ctx.log(`stage-sidecar: ${entry.cmd} ${entry.args.join(" ")}`);
|
|
1449
|
-
const built = run(entry.cmd, entry.args, { cwd, env });
|
|
1522
|
+
const built = run(entry.cmd, entry.args, { cwd, env: splitCommandEnv(ctx) });
|
|
1450
1523
|
if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `command ${entry.name} (${entry.cmd}) failed (${built?.error?.message ?? `exit ${built?.status}`})`, { name: entry.name, stderr: tail(built?.stderr) });
|
|
1451
1524
|
const source = path.resolve(appRoot, entry.output);
|
|
1452
1525
|
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `command ${entry.name} did not produce ${entry.output}`, { name: entry.name, output: entry.output });
|
|
@@ -1463,8 +1536,8 @@ function stageCommand(entry, ctx) {
|
|
|
1463
1536
|
* fails closed with nothing staged. Returns { cached, fromCache }.
|
|
1464
1537
|
*/
|
|
1465
1538
|
async function ensureCachedDownload(ctx, { url, sha256, size }) {
|
|
1466
|
-
const {
|
|
1467
|
-
const cached = path.resolve(
|
|
1539
|
+
const { fs } = ctx;
|
|
1540
|
+
const cached = path.resolve(ctx.cacheRoot, REMOTE_CACHE_DIR, sha256);
|
|
1468
1541
|
if (fs.existsSync(cached) && sha256Of(fs.readFileSync(cached)) === sha256) return { cached, fromCache: true };
|
|
1469
1542
|
ctx.log(`stage-sidecar: GET ${url}`);
|
|
1470
1543
|
let response;
|
|
@@ -1596,7 +1669,7 @@ function stageCmake(entry, ctx) {
|
|
|
1596
1669
|
const { appRoot, fs, run, env, target } = ctx;
|
|
1597
1670
|
const cmake = resolveCmakeBinary(ctx, entry);
|
|
1598
1671
|
const generator = entry.platformGenerators?.[target.platform] ?? entry.generator;
|
|
1599
|
-
const root = path.resolve(
|
|
1672
|
+
const root = path.resolve(ctx.cacheRoot, CMAKE_ROOT, entry.name);
|
|
1600
1673
|
const srcDir = path.join(root, "src");
|
|
1601
1674
|
const buildDir = path.join(root, "build");
|
|
1602
1675
|
const dest = destFor(entry, ctx);
|
|
@@ -1783,15 +1856,22 @@ export function planSidecarStaging({ manifest, target, env = process.env } = {})
|
|
|
1783
1856
|
* fs (existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, renameSync, chmodSync, statSync, readdirSync, rmSync), run, fetch (https downloads of `remote`), fetchBytes, extractMember, loadMediaCatalog, loadNativeCatalog, makeTempDir, removeDir, now, pid, log (progress), warn (warnings; defaults to log), env.
|
|
1784
1857
|
* The API is quiet by default; the `right-release stage-sidecar` command passes `warn` so the CLI prints warnings on stderr.
|
|
1785
1858
|
*/
|
|
1786
|
-
export async function stageSidecars({ appRoot, manifest, target, deps = {} } = {}) {
|
|
1859
|
+
export async function stageSidecars({ appRoot, manifest, target, cacheRoot, deps = {} } = {}) {
|
|
1787
1860
|
if (typeof appRoot !== "string" || !path.isAbsolute(appRoot)) fail("STAGE_SIDECAR_CONFIG_INVALID", "appRoot must be an absolute path");
|
|
1861
|
+
if (cacheRoot !== undefined && (typeof cacheRoot !== "string" || !path.isAbsolute(cacheRoot))) fail("STAGE_SIDECAR_CONFIG_INVALID", "cacheRoot must be an absolute path");
|
|
1788
1862
|
const env = deps.env ?? process.env;
|
|
1789
1863
|
const plan = validateSidecarManifest(manifest, { target, mode: resolveBuildMode(env) });
|
|
1790
1864
|
assertNotNested(plan, env);
|
|
1865
|
+
// A source split (source-split.mjs; rightkit-gate's snapshot route): `appRoot` is the snapshot of the commit, where the declared
|
|
1866
|
+
// sources are read and where the staged files and the receipt go (the Tauri build script reads them there). `cacheRoot` is the
|
|
1867
|
+
// checkout's app directory, which keeps every cache, archive, slice and build tree right-release owns (`.right-release/...`) and
|
|
1868
|
+
// where Cargo's target directory is resolved: only final outputs and the receipt are written under the snapshot (row 7.05a). Checkout
|
|
1869
|
+
// bytes enter a split run only as cache entries whose digest the commit pins, or as tool-invalidated build caches.
|
|
1870
|
+
const split = cacheRoot !== undefined && path.resolve(cacheRoot) !== path.resolve(appRoot);
|
|
1791
1871
|
const fs = deps.fs ?? nodeFs;
|
|
1792
1872
|
const run = deps.run ?? defaultRun;
|
|
1793
1873
|
const ctx = {
|
|
1794
|
-
appRoot, target: plan.target, stageDir: plan.stageDir, fs, env, run, protectedPaths: protectedOutputPaths(plan),
|
|
1874
|
+
appRoot, cacheRoot: split ? path.resolve(cacheRoot) : appRoot, split, target: plan.target, stageDir: plan.stageDir, fs, env, run, protectedPaths: protectedOutputPaths(plan),
|
|
1795
1875
|
windows: plan.target.os === "windows",
|
|
1796
1876
|
pid: deps.pid ?? process.pid,
|
|
1797
1877
|
host: deps.host ?? { os: { darwin: "macos", win32: "windows", linux: "linux" }[process.platform] ?? process.platform, arch: { arm64: "aarch64", x64: "x86_64" }[process.arch] ?? process.arch },
|
package/swap-helper-bundle.mjs
CHANGED
|
@@ -18,7 +18,10 @@ export const SWAP_HELPER_PACKAGE = "@rightkit/release";
|
|
|
18
18
|
export const SWAP_HELPER_BIN = ["bin", "darwin", "rightkit-swap"];
|
|
19
19
|
export const SWAP_HELPER_RECEIPT = ["bin", "darwin", "rightkit-swap.sign.json"];
|
|
20
20
|
// rightkit-swap is gated on the crate feature "fs" (crates/rightkit-fs/Cargo.toml [[bin]] required-features).
|
|
21
|
-
|
|
21
|
+
// Every crate is its own Cargo workspace (rightkit#1285): there is no root manifest, so the build and the target-directory read name crates/rightkit-fs.
|
|
22
|
+
export const SWAP_HELPER_MANIFEST = "crates/rightkit-fs/Cargo.toml";
|
|
23
|
+
export const SWAP_HELPER_CARGO_ARGS = ["build", "--manifest-path", SWAP_HELPER_MANIFEST, "--release", "-p", "rightkit-fs", "--features", "fs", "--bin", "rightkit-swap"];
|
|
24
|
+
export const SWAP_HELPER_METADATA_ARGS = ["metadata", "--format-version", "1", "--no-deps", "--manifest-path", SWAP_HELPER_MANIFEST];
|
|
22
25
|
|
|
23
26
|
/** Typed error. Codes: SWAP_HELPER_BUILD_FAILED, SWAP_HELPER_SIGN_FAILED, SWAP_HELPER_MISSING, SWAP_HELPER_UNSIGNED. */
|
|
24
27
|
export class SwapHelperError extends Error {
|
|
@@ -67,7 +70,7 @@ export function prepareSwapHelper({
|
|
|
67
70
|
const fail = (code, message) => { throw new SwapHelperError(code, message); };
|
|
68
71
|
const built = run("cargo", SWAP_HELPER_CARGO_ARGS, { cwd: workspaceRoot, env });
|
|
69
72
|
if (built.error || built.status !== 0) fail("SWAP_HELPER_BUILD_FAILED", `rustright cargo ${SWAP_HELPER_CARGO_ARGS.join(" ")} failed (${built.error?.message ?? `exit ${built.status}`})\n${tail(`${built.stdout ?? ""}\n${built.stderr ?? ""}`)}`);
|
|
70
|
-
const meta = run("cargo",
|
|
73
|
+
const meta = run("cargo", SWAP_HELPER_METADATA_ARGS, { cwd: workspaceRoot, env });
|
|
71
74
|
let targetDir;
|
|
72
75
|
try { targetDir = JSON.parse(meta.stdout).target_directory; } catch { /* refused below */ }
|
|
73
76
|
if (meta.status !== 0 || typeof targetDir !== "string" || !path.isAbsolute(targetDir)) fail("SWAP_HELPER_BUILD_FAILED", "cannot read the Cargo target directory to find rightkit-swap");
|
package/unit-gate.mjs
CHANGED
|
@@ -183,12 +183,52 @@ export function resolveUnit(selector, root, { granted = localBuildGranted, readJ
|
|
|
183
183
|
return { kind, name, version: manifest.version };
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/** `ci/known-red.json` hosts by `process.platform`; any other platform (Linux) has no known-red table. */
|
|
187
|
+
const KNOWN_RED_HOST = { darwin: "macos", win32: "windows" };
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The ci/known-red.json entry that bars this unit from a compile, or null. A unit listed for the host platform is not compiled,
|
|
191
|
+
* packaged or qualified there; one that is headed to release is barred when it is listed for either host (a known-red unit is never
|
|
192
|
+
* published, AGENTS.md; scripts/ci/publish.mjs refuses it the same way). An entry is keyed by the crate name or `rust:<crate>`, the
|
|
193
|
+
* unit's directory or `npm:<directory>` (the ids scripts/ci/gate.mjs and publish.mjs use). A missing file lists nothing; an unreadable
|
|
194
|
+
* one refuses (a table that cannot be read is not an empty table).
|
|
195
|
+
*/
|
|
196
|
+
export function knownRedEntry({ unit, root, platform, toRelease = false, readText = (file) => fs.readFileSync(file, "utf8") }) {
|
|
197
|
+
let table;
|
|
198
|
+
try { table = JSON.parse(readText(path.join(root, "ci", "known-red.json"))); }
|
|
199
|
+
catch (error) { if (error?.code === "ENOENT") return null; throw new Error(`ci/known-red.json cannot be read (${error?.message}); a known-red table that cannot be read is not an empty one`); }
|
|
200
|
+
const dir = unit.kind === "npm" ? `packages/${unit.name.slice("@rightkit/".length)}` : `crates/${unit.name}`;
|
|
201
|
+
const keys = [unit.name, `rust:${unit.name}`, `${unit.kind}:${unit.name}`, dir, `npm:${dir}`];
|
|
202
|
+
const hosts = toRelease ? Object.keys(table ?? {}) : [KNOWN_RED_HOST[platform]].filter(Boolean);
|
|
203
|
+
for (const host of hosts) {
|
|
204
|
+
const key = keys.find((k) => Object.hasOwn(table?.[host] ?? {}, k));
|
|
205
|
+
if (key !== undefined) {
|
|
206
|
+
const reason = table[host][key];
|
|
207
|
+
return { host, key, reason: typeof reason === "string" ? reason : JSON.stringify(reason) };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
186
213
|
/** One invocation reruns all owed stages; reports are never read as admission evidence. */
|
|
187
|
-
|
|
214
|
+
/**
|
|
215
|
+
* `source` ({ checkout, commit, tree }) marks a run built from the door's snapshot of that commit (row 7.06a): `root` is then the
|
|
216
|
+
* snapshot, `commonDir` the checkout's, and the stages take their identity from the selection (local-publish.mjs sourceGit). `stageGuard`
|
|
217
|
+
* ({ start(stage), end(stage) } returning true or false) is the door's ledger verify around each stage (7.08); `envExtra` carries the
|
|
218
|
+
* Cargo output pin. Without `source` every behavior is the checkout route's, unchanged.
|
|
219
|
+
*/
|
|
220
|
+
export async function runUnitGate({ selector, to, root, commonDir, commit, tree, executor, env, platform, check, source = null, stageGuard = null, envExtra = {}, loadOwner = () => import("./local-publish.mjs"), out = console.log, err = console.error, releaser = null, resolve = resolveUnit, writeReport, fetchReceipt = readBrokerReceipt, stageCommands = defaultStageCommands, readKnownRed }) {
|
|
188
221
|
const stages = stagesTo(to);
|
|
189
222
|
if (!stages) { err(`rightkit-gate: invalid unit stage ${to}`); return 2; }
|
|
190
223
|
let unit;
|
|
191
224
|
try { unit = resolve(selector, root); } catch (error) { err(`rightkit-gate: BLOCKED: ${error.message}`); return 1; }
|
|
225
|
+
// Before any compile (so --to compile|package|qualify|release; --to check compiles nothing): a unit recorded red is refused, not built.
|
|
226
|
+
if (stages.includes("compile")) {
|
|
227
|
+
try {
|
|
228
|
+
const red = knownRedEntry({ unit, root, platform, toRelease: stages.includes("release"), ...(readKnownRed ? { readText: readKnownRed } : {}) });
|
|
229
|
+
if (red) { err(`rightkit-gate: BLOCKED: ${selector} is listed in ci/known-red.json for ${red.host} (${red.key}: ${red.reason}); a known-red unit is not compiled, packaged, qualified or published. Fix the defect and remove the entry first`); return 1; }
|
|
230
|
+
} catch (error) { err(`rightkit-gate: BLOCKED: ${error.message}`); return 1; }
|
|
231
|
+
}
|
|
192
232
|
const scratch = platform === "win32" ? "D:\\scratch" : "/Volumes/D/scratch";
|
|
193
233
|
const bundle = path.join(scratch, `rk-unit-${randomBytes(12).toString("hex")}`);
|
|
194
234
|
const argv = ["--local", unit.kind === "npm" ? "--package" : "--crate", unit.name, "--workspace", root, "--qualify-only", "--bundle", bundle];
|
|
@@ -204,7 +244,7 @@ export async function runUnitGate({ selector, to, root, commonDir, commit, tree,
|
|
|
204
244
|
const stageEnv = async (stage, action) => {
|
|
205
245
|
const run = startRun({ commonDir, commit, tree, stage });
|
|
206
246
|
const nonce = attested && ATTESTED_STAGES.includes(stage) ? { RIGHTKIT_ATTEST_NONCE: qualificationNonce({ commit, tree, unit: `${unit.kind}:${unit.name}`, stage }) } : {};
|
|
207
|
-
try { return await action({ ...env, ...nonce, ...run.env }); } finally { endRun(run); }
|
|
247
|
+
try { return await action({ ...env, ...envExtra, ...nonce, ...run.env }); } finally { endRun(run); }
|
|
208
248
|
};
|
|
209
249
|
const receipts = {};
|
|
210
250
|
const collectReceipts = (stage, ids) => {
|
|
@@ -222,11 +262,12 @@ export async function runUnitGate({ selector, to, root, commonDir, commit, tree,
|
|
|
222
262
|
try {
|
|
223
263
|
for (const stage of stages) {
|
|
224
264
|
currentStage = stage;
|
|
265
|
+
if (stageGuard && !(await stageGuard.start(stage))) throw new Error(`RK_CHECK_ERROR: the snapshot is not the commit's bytes plus the ledger before the ${stage} stage`);
|
|
225
266
|
// A unit run headed to qualify or release owes the release tier: it runs here, before the compile, and qualify does not run it again.
|
|
226
267
|
if (stage === "check") { checkTier = stages.includes("qualify") ? "release" : "compile"; if (await check(checkTier) !== 0) throw new Error(`class A check (${checkTier} tier) failed`); }
|
|
227
268
|
else if (stage === "compile") {
|
|
228
269
|
owner = await loadOwner();
|
|
229
|
-
state = await stageEnv(stage, (runEnv) => owner.compileLocalUnit(unit.kind, argv, { env: runEnv }));
|
|
270
|
+
state = await stageEnv(stage, (runEnv) => owner.compileLocalUnit(unit.kind, argv, { env: runEnv, ...(source ? { source } : {}) }));
|
|
230
271
|
collectReceipts(stage, owner.localUnitCompileRequests?.(state));
|
|
231
272
|
} else if (stage === "package") {
|
|
232
273
|
artifact = await stageEnv(stage, (runEnv) => owner.packageLocalUnit(state, { env: runEnv }));
|
|
@@ -246,6 +287,7 @@ export async function runUnitGate({ selector, to, root, commonDir, commit, tree,
|
|
|
246
287
|
const qualification = writeQualification({ report, artifact, unit, commit, tree, runtime, executor });
|
|
247
288
|
released = await stageEnv(stage, (runEnv) => (releaser ?? releaseUnit)({ unit, root, commit, executor, env: runEnv, artifact, qualification, platform, out }));
|
|
248
289
|
}
|
|
290
|
+
if (stageGuard && !(await stageGuard.end(stage))) throw new Error(`RK_CHECK_ERROR: the ${stage} stage's result covers no known bytes (a snapshot byte changed, or a write is outside the ledger)`);
|
|
249
291
|
report.stages[stage] = { ok: true, ...(stage === "package" || stage === "qualify" ? { artifactFile: artifact.artifactFile, artifactSha256: artifact.artifactSha256 } : {}), ...(receipts[stage] ? { receipts: receipts[stage] } : {}), ...(released ?? {}) };
|
|
250
292
|
save();
|
|
251
293
|
out(`rightkit-gate: [${stage}] ${unit.kind}:${unit.name} passed`);
|
package/web-build.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { buildLaunch } from "./build-launch.mjs";
|
|
|
23
23
|
import { CommandSpecError, loadAppConfig, resolveInvocation, validateStep } from "./command-spec.mjs";
|
|
24
24
|
import { defaultReleaseConfigFile } from "./release-config.mjs";
|
|
25
25
|
import { requireDoorRun } from "./gate-stages.mjs";
|
|
26
|
+
import { doorContextFor } from "./source-split.mjs";
|
|
26
27
|
|
|
27
28
|
export function parseWebBuildArgs(argv) {
|
|
28
29
|
const opts = { config: defaultReleaseConfigFile(), dryRun: false };
|
|
@@ -53,10 +54,11 @@ export function planWebBuild(config) {
|
|
|
53
54
|
* bound to a commit: refused. `spawnSyncFn` runs git; `requireDoor` is gate-stages.mjs requireDoorRun.
|
|
54
55
|
*/
|
|
55
56
|
export function webBuildDoorRefusal({ appRoot, env = process.env, spawnSyncFn = spawnSync, requireDoor = requireDoorRun }) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Inside a source split (the door's snapshot of a commit) the git context is the door run's checkout and commit, not the cwd's.
|
|
58
|
+
let context;
|
|
59
|
+
try { context = doorContextFor({ cwd: appRoot, env, run: spawnSyncFn }); } catch (error) { return `right-release web-build: ${error.code ?? "RK_SOURCE_SPLIT"}: ${error.message}`; }
|
|
60
|
+
if (!context) return "right-release web-build runs only inside a git repository, under a door run";
|
|
61
|
+
return requireDoor({ env, commonDir: context.commonDir, commit: context.commit, command: "right-release web-build" });
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
function doorError(refusal) { const error = new Error(refusal); error.code = "RK_DOOR"; return error; }
|
package/win-package-recipe.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import path from "node:path";
|
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { spawnSync } from "./background-process.mjs";
|
|
22
22
|
import { verifyNsisEmbeddedBinary } from "./nsis-payload.mjs";
|
|
23
|
+
import { resolveArtifactPath, splitArtifactContext } from "./release-artifact-path.mjs";
|
|
23
24
|
import { signingReceiptPath } from "./signing-receipts.mjs";
|
|
24
25
|
import { patchTauriBundleType } from "./tauri-bundle-marker.mjs";
|
|
25
26
|
|
|
@@ -198,7 +199,9 @@ export function defaultWinPackageDeps({ appRoot, target, env }) {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
/** Where a produced artifact really is: the declared path, else the managed artifact directory (same rule as release.mjs). */
|
|
201
|
-
function locate(declared, { env, fs }) {
|
|
202
|
+
function locate(declared, { env, fs, split = false }) {
|
|
203
|
+
// A door split (row 7.04c, R-T2): the declared path is already the bound target's; there is no artifact-directory fallback for it.
|
|
204
|
+
if (split) return declared;
|
|
202
205
|
if (fs.existsSync(declared)) return declared;
|
|
203
206
|
const managed = env.RIGHT_RELEASE_ARTIFACT_DIR;
|
|
204
207
|
if (managed) {
|
|
@@ -219,6 +222,15 @@ export function runWinPackageRecipe({ appRoot, config = {}, target, deps = {} }
|
|
|
219
222
|
const base = defaultWinPackageDeps({ appRoot: plan.appRoot, target, env });
|
|
220
223
|
const d = { ...base, ...deps, env, fs: { ...base.fs, ...(deps.fs ?? {}) } };
|
|
221
224
|
const { fs } = d;
|
|
225
|
+
// A door split has no target directory in its snapshot: the plan's `<app>/src-tauri/target/...` paths mean the bound target (R-T1, R-T3), where the
|
|
226
|
+
// raw EXE is deleted before the build, signed in place, bundled and verified. Outside a split this is the identity.
|
|
227
|
+
const artifactPaths = d.artifactPaths ?? splitArtifactContext({ cwd: plan.appRoot, env, target });
|
|
228
|
+
const split = artifactPaths.mode === "split";
|
|
229
|
+
const placed = (declared) => {
|
|
230
|
+
if (!split) return declared;
|
|
231
|
+
const relative = path.relative(plan.appRoot, declared).replaceAll("\\", "/");
|
|
232
|
+
return resolveArtifactPath(relative.startsWith("..") || path.isAbsolute(relative) ? declared : relative, artifactPaths);
|
|
233
|
+
};
|
|
222
234
|
const dry = d.dryRun === true;
|
|
223
235
|
const phases = [];
|
|
224
236
|
const phase = (name, work) => {
|
|
@@ -244,15 +256,16 @@ export function runWinPackageRecipe({ appRoot, config = {}, target, deps = {} }
|
|
|
244
256
|
|
|
245
257
|
if (d.platform !== "win32" && !dry) throw new WinPackageRecipeError("build", `the Windows package recipe must run on Windows, not ${d.platform}`);
|
|
246
258
|
|
|
247
|
-
|
|
259
|
+
const rawExe = placed(plan.rawExe);
|
|
260
|
+
let raw = rawExe;
|
|
248
261
|
const sidecars = [...plan.sidecars];
|
|
249
262
|
|
|
250
263
|
phase("build", () => {
|
|
251
|
-
if (!dry) fs.rmSync(
|
|
264
|
+
if (!dry) fs.rmSync(rawExe, { force: true });
|
|
252
265
|
tauri(["build", "--no-bundle", "--target", plan.triple], quiet);
|
|
253
266
|
if (dry) return;
|
|
254
|
-
raw = locate(
|
|
255
|
-
if (!fs.existsSync(raw)) throw new Error(`tauri build did not produce the raw EXE: ${
|
|
267
|
+
raw = locate(rawExe, { env, fs, split });
|
|
268
|
+
if (!fs.existsSync(raw)) throw new Error(`tauri build did not produce the raw EXE: ${rawExe}`);
|
|
256
269
|
for (const sidecar of sidecars) if (!fs.existsSync(sidecar)) throw new Error(`declared sidecar does not exist: ${sidecar}`);
|
|
257
270
|
});
|
|
258
271
|
|
|
@@ -270,14 +283,14 @@ export function runWinPackageRecipe({ appRoot, config = {}, target, deps = {} }
|
|
|
270
283
|
return { sha256: byFile.get(path.resolve(raw).toLowerCase()), sidecars: Object.fromEntries(sidecars.map((file) => [file, byFile.get(path.resolve(file).toLowerCase())])) };
|
|
271
284
|
});
|
|
272
285
|
|
|
273
|
-
const installerBase = plan.installerFile;
|
|
286
|
+
const installerBase = placed(plan.installerFile);
|
|
274
287
|
let installer = installerBase;
|
|
275
288
|
phase("bundle", () => {
|
|
276
289
|
if (!dry) for (const stale of [installerBase, `${installerBase}.sig`, `${installerBase}.zip`, `${installerBase}.zip.sig`]) fs.rmSync(stale, { force: true });
|
|
277
290
|
const override = { bundle: { createUpdaterArtifacts: false, windows: { nsis: { installMode: plan.perMachine ? "perMachine" : "currentUser" } } } };
|
|
278
291
|
tauri(["bundle", "--bundles", "nsis", "--target", plan.triple, "--config", JSON.stringify(override)], quiet);
|
|
279
292
|
if (dry) return;
|
|
280
|
-
installer = locate(installerBase, { env, fs });
|
|
293
|
+
installer = locate(installerBase, { env, fs, split });
|
|
281
294
|
if (!fs.existsSync(installer)) throw new Error(`tauri bundle did not produce the installer: ${installerBase}`);
|
|
282
295
|
});
|
|
283
296
|
|