@rightkit/release 0.4.0 → 0.4.1
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 +49 -6
- package/adoption-evidence.mjs +650 -0
- package/apple-profile-create.mjs +113 -10
- package/bin/darwin/rightkit-swap +0 -0
- package/binary-license-notices.mjs +161 -0
- package/cargo-license-notices.mjs +111 -0
- package/cli/right-release.mjs +10 -0
- package/local-publish.mjs +133 -19
- package/ownership-dependency-allowlist.json +2 -0
- package/ownership-mandatory-set.json +117 -67
- package/ownership-mandatory-set.mjs +75 -5
- package/package.json +1 -1
- package/pipeline-admission.mjs +375 -0
- package/pipeline-config.mjs +100 -0
- package/pipeline-generators.json +4 -0
- package/pipeline-generators.mjs +137 -0
- package/pipeline-scan.mjs +285 -0
- package/pipeline-tools.mjs +171 -0
- package/production-mode.mjs +42 -0
- package/publication-gate.mjs +83 -32
- package/publish-cargo.mjs +11 -6
- package/publish-swift.mjs +12 -3
- package/qa-control-scan.mjs +4 -0
- package/release-config.mjs +330 -0
- package/release-switch-scan.mjs +44 -5
- package/release.mjs +14 -4
- package/rightkit-sync.mjs +8 -0
- package/rightkit-versions.json +132 -88
- package/runtime-budget-crates.mjs +14 -0
- package/runtime-budgets.mjs +128 -31
- package/stage-sidecar.mjs +47 -8
- package/swap-helper-bundle.mjs +7 -3
- package/bin/darwin/rightkit-swap.sign.json +0 -6
package/local-publish.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { GATE_SCHEMA, MAX_CARGO_JOBS, defaultEvidencePath, enforcePublicationGat
|
|
|
25
25
|
import { runCargoPublish, runCargoReadOnly, scanCrateForSecrets } from "./publish-cargo.mjs";
|
|
26
26
|
import { cargoIndexUrl } from "./cargo-registry-readback.mjs";
|
|
27
27
|
import { pathDependencyDirs, readCargoPackage } from "./crate-graph.mjs";
|
|
28
|
-
import { recordPublished, stageSourceVersions } from "./rightkit-sync.mjs";
|
|
28
|
+
import { recordPublished, stageSourceVersions, workspaceFeedOptions } from "./rightkit-sync.mjs";
|
|
29
29
|
import { prepareSwapHelper, SWAP_HELPER_CARGO_ARGS, SWAP_HELPER_PACKAGE } from "./swap-helper-bundle.mjs";
|
|
30
30
|
|
|
31
31
|
export const LOCAL_OPERATOR = "agent (local-build grant)";
|
|
@@ -236,6 +236,99 @@ export function allFeatureCheckSets(entry = {}) {
|
|
|
236
236
|
return entry.checkFeatureSets ?? [["--all-features"]];
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
const CONSUMER_ROW_KEYS = new Set(["manifestPath", "args", "kind", "targets", "locked"]);
|
|
240
|
+
const CONSUMER_KINDS = new Set(["check"]);
|
|
241
|
+
const CONSUMER_MANIFEST = /^ci\/consumers\/(?:[A-Za-z0-9._-]+\/)+Cargo\.toml$/;
|
|
242
|
+
const CONSUMER_TARGET = /^[A-Za-z0-9_]+(?:-[A-Za-z0-9_.]+){2,3}$/;
|
|
243
|
+
// The row owns manifest, lock, target and output format; arguments may only choose features and target kinds.
|
|
244
|
+
// An ALLOWLIST (0.4.1 review A1): any token not listed is refused, so --help/-h/--version (a "passing" no-op),
|
|
245
|
+
// --target-dir, -C/--directory, --color, --timings, -Z and every other Cargo option stay out of a row.
|
|
246
|
+
// Keep in step with scripts/ci/gate.mjs.
|
|
247
|
+
const CONSUMER_ARG_FLAGS = new Set(["--no-default-features", "--all-features", "--lib", "--bins", "--tests", "--examples", "--benches", "--all-targets"]);
|
|
248
|
+
const CONSUMER_FEATURE_FLAGS = new Set(["--features", "-F"]);
|
|
249
|
+
const CONSUMER_FEATURE_LIST = /^[A-Za-z0-9_][A-Za-z0-9_.+/?:,-]*$/;
|
|
250
|
+
const CONSUMER_ARGS_ALLOWED = `--features <list> (or --features=<list>, -F <list>), ${[...CONSUMER_ARG_FLAGS].join(", ")}`;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Validate the `consumerRows` of one crate's ci/gate.config.json entry and return them normalised. A consumer row
|
|
254
|
+
* checks the owning crate through a SEPARATE Cargo workspace (its own Cargo.lock), run as
|
|
255
|
+
* `cargo check --manifest-path <manifestPath> --locked [--target <t>] <args>`. Unknown fields are refused.
|
|
256
|
+
*/
|
|
257
|
+
export function consumerRows(unit, entry = {}) {
|
|
258
|
+
const rows = entry.consumerRows;
|
|
259
|
+
if (rows === undefined) return [];
|
|
260
|
+
const where = (index) => `${unit} consumerRows[${index}]`;
|
|
261
|
+
if (!Array.isArray(rows)) refuse(`${unit}: consumerRows must be an array`);
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
return rows.map((row, index) => {
|
|
264
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) refuse(`${where(index)} must be an object`);
|
|
265
|
+
for (const key of Object.keys(row)) if (!CONSUMER_ROW_KEYS.has(key)) refuse(`${where(index)}: unknown field ${JSON.stringify(key)} (allowed: ${[...CONSUMER_ROW_KEYS].join(", ")})`);
|
|
266
|
+
const manifestPath = row.manifestPath;
|
|
267
|
+
if (typeof manifestPath !== "string" || !CONSUMER_MANIFEST.test(manifestPath) || manifestPath.split("/").some((part) => part === "." || part === "..")) {
|
|
268
|
+
refuse(`${where(index)}: manifestPath must be a relative ci/consumers/<name>/Cargo.toml path with forward slashes, got ${JSON.stringify(manifestPath)}`);
|
|
269
|
+
}
|
|
270
|
+
const kind = row.kind ?? "check";
|
|
271
|
+
if (!CONSUMER_KINDS.has(kind)) refuse(`${where(index)}: kind must be one of ${[...CONSUMER_KINDS].join(", ")}, got ${JSON.stringify(kind)}`);
|
|
272
|
+
if (row.locked !== undefined && row.locked !== true) refuse(`${where(index)}: locked must be true; a consumer row never runs without its lock file`);
|
|
273
|
+
const args = row.args ?? [];
|
|
274
|
+
if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string" || arg === "")) refuse(`${where(index)}: args must be an array of non-empty strings`);
|
|
275
|
+
for (let position = 0; position < args.length; position += 1) {
|
|
276
|
+
const arg = args[position];
|
|
277
|
+
if (CONSUMER_ARG_FLAGS.has(arg)) continue;
|
|
278
|
+
if (CONSUMER_FEATURE_FLAGS.has(arg)) {
|
|
279
|
+
const list = args[position + 1];
|
|
280
|
+
if (typeof list !== "string" || !CONSUMER_FEATURE_LIST.test(list)) refuse(`${where(index)}: ${arg} needs a feature list (names separated by commas, not starting with "-"), got ${JSON.stringify(list)}`);
|
|
281
|
+
position += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (arg.startsWith("--features=") && CONSUMER_FEATURE_LIST.test(arg.slice("--features=".length))) continue;
|
|
285
|
+
refuse(`${where(index)}: args may not contain ${JSON.stringify(arg)}; the row sets manifest path, lock, target and output itself, and allows only ${CONSUMER_ARGS_ALLOWED}`);
|
|
286
|
+
}
|
|
287
|
+
const targets = row.targets ?? [];
|
|
288
|
+
if (!Array.isArray(targets) || targets.some((target) => typeof target !== "string" || !CONSUMER_TARGET.test(target))) refuse(`${where(index)}: targets must be an array of target triples`);
|
|
289
|
+
if (new Set(targets).size !== targets.length) refuse(`${where(index)}: targets lists a triple twice`);
|
|
290
|
+
const key = JSON.stringify([manifestPath, args, targets]);
|
|
291
|
+
if (seen.has(key)) refuse(`${where(index)} repeats an earlier row`);
|
|
292
|
+
seen.add(key);
|
|
293
|
+
return { manifestPath, kind, args: [...args], targets: [...targets], locked: true };
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** One Cargo step per target (or one host step when no target is named) for one consumer row. */
|
|
298
|
+
export function consumerRowInvocations(row) {
|
|
299
|
+
const targets = row.targets.length ? row.targets : [null];
|
|
300
|
+
return targets.map((target) => ({ command: "cargo", args: [row.kind, "--manifest-path", row.manifestPath, "--locked", ...(target ? ["--target", target] : []), ...row.args], test: false }));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Refuse a consumer row whose manifest or lock is missing from the workspace, is not tracked by git, or resolves
|
|
305
|
+
* (through a symlink) to anywhere but its own path inside the workspace (fail closed before any command;
|
|
306
|
+
* 0.4.1 review A5). The clean-tree check covers only the files git sees under the consumer directory, so a
|
|
307
|
+
* symlinked or ignored file would otherwise be a checked file nobody reviewed.
|
|
308
|
+
*/
|
|
309
|
+
function assertConsumerFiles(workspaceRoot, unit, rows, git = defaultGit) {
|
|
310
|
+
let realRoot;
|
|
311
|
+
try { realRoot = fs.realpathSync(workspaceRoot); } catch { refuse(`${unit}: cannot resolve the workspace ${workspaceRoot}`); }
|
|
312
|
+
for (const row of rows) {
|
|
313
|
+
for (const file of [row.manifestPath, row.manifestPath.replace(/Cargo\.toml$/, "Cargo.lock")]) {
|
|
314
|
+
const absolute = path.join(workspaceRoot, file);
|
|
315
|
+
if (!fs.existsSync(absolute)) refuse(`${unit}: consumer row needs ${file}, which is not in the workspace`);
|
|
316
|
+
let real;
|
|
317
|
+
try { real = fs.realpathSync(absolute); } catch { refuse(`${unit}: consumer row file ${file} cannot be resolved`); }
|
|
318
|
+
if (path.relative(realRoot, real) !== path.normalize(file)) refuse(`${unit}: consumer row file ${file} resolves outside its own path in the workspace (${real}); a symlink is not allowed`);
|
|
319
|
+
if (git(["ls-files", "--error-unmatch", "--", file], workspaceRoot) === null) refuse(`${unit}: consumer row file ${file} is not tracked by git; commit it first`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The directories whose cleanliness a set of consumer rows depends on. */
|
|
325
|
+
const consumerDirs = (rows) => [...new Set(rows.map((row) => path.posix.dirname(row.manifestPath)))];
|
|
326
|
+
|
|
327
|
+
// The files that decide WHICH steps a publish runs: an uncommitted edit would change the plan the evidence is bound to
|
|
328
|
+
// (0.4.1 review A3). Chosen over reading the HEAD blob so the publisher, the replay and the batch apply one rule:
|
|
329
|
+
// the file on disk must be the committed file, or the publish is refused.
|
|
330
|
+
export const GATE_CONFIG_FILES = Object.freeze(["ci/gate.config.json", "ci/asset-gated.json"]);
|
|
331
|
+
|
|
239
332
|
/** The CI gate's commands for one unit (scripts/ci/gate.mjs npmGate/rustGate), mirrored exactly. */
|
|
240
333
|
export function localTestPlan({ kind, unit, dir, config, manifest, batch = false, platform = "darwin" }) {
|
|
241
334
|
if (kind === "npm") {
|
|
@@ -260,9 +353,13 @@ export function localTestPlan({ kind, unit, dir, config, manifest, batch = false
|
|
|
260
353
|
if (entry.alsoSerial) steps.push({ command: "cargo", args: ["test", "-p", unit, "--message-format=json-render-diagnostics", ...features, "--no-fail-fast", "--", "--test-threads=1", "--show-output"], test: true });
|
|
261
354
|
}
|
|
262
355
|
// Batch mode lints the whole batch once (batchClippyInvocations) and records it as a batch receipt.
|
|
263
|
-
|
|
356
|
+
// Every tested feature set is linted (-D warnings), not only the first.
|
|
357
|
+
if (!batch) for (const features of featureSets) steps.push({ command: "cargo", args: ["clippy", "-p", unit, "--message-format=json-render-diagnostics", "--no-deps", "--all-targets", ...features, "--", "-D", "warnings"], test: false });
|
|
264
358
|
for (const features of allFeatureCheckSets(entry)) steps.push({ command: "cargo", args: ["check", "-p", unit, "--message-format=json-render-diagnostics", "--all-targets", ...features], test: false });
|
|
265
|
-
|
|
359
|
+
// Consumer rows (separate Cargo workspaces) gate only this crate: they come from this crate's own entry.
|
|
360
|
+
const consumers = consumerRows(unit, entry);
|
|
361
|
+
for (const row of consumers) steps.push(...consumerRowInvocations(row));
|
|
362
|
+
return { id: `rust:${unit}`, featureLabels: featureSets.map((features) => features.join(" ") || "default"), consumerRows: consumers, steps };
|
|
266
363
|
}
|
|
267
364
|
|
|
268
365
|
/**
|
|
@@ -274,14 +371,19 @@ export function localTestPlan({ kind, unit, dir, config, manifest, batch = false
|
|
|
274
371
|
* unify extra features into a selected crate and each is linted with exactly the features it was before.
|
|
275
372
|
* Crates with a different RIGHTKIT_REQUIRE_ASSETS (asset-gated) stay apart so the environment is unchanged.
|
|
276
373
|
*/
|
|
277
|
-
export function batchClippyInvocations(layer, { featuresOf = () => [], assetGatedOf = () => false } = {}) {
|
|
374
|
+
export function batchClippyInvocations(layer, { featuresOf = () => [], featureSetsOf = (unit) => [featuresOf(unit)], assetGatedOf = () => false } = {}) {
|
|
278
375
|
const groups = new Map();
|
|
279
376
|
for (const unit of layer) {
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
377
|
+
// Every tested feature set of a crate is linted, in order; identical sets of one crate collapse to one run.
|
|
378
|
+
const sets = featureSetsOf(unit) ?? [[]];
|
|
379
|
+
const unique = sets.filter((set, index) => sets.findIndex((other) => JSON.stringify(other ?? []) === JSON.stringify(set ?? [])) === index);
|
|
380
|
+
for (const set of unique) {
|
|
381
|
+
const features = set ?? [];
|
|
382
|
+
const shared = features.length === 0 || (features.length === 1 && features[0] === "--all-features");
|
|
383
|
+
const key = shared ? JSON.stringify([features, assetGatedOf(unit)]) : `unit:${unit}:${JSON.stringify(features)}`;
|
|
384
|
+
if (!groups.has(key)) groups.set(key, { crates: [], features, assetGated: assetGatedOf(unit) });
|
|
385
|
+
groups.get(key).crates.push(unit);
|
|
386
|
+
}
|
|
285
387
|
}
|
|
286
388
|
return [...groups.values()].map(({ crates, features, assetGated }) => ({
|
|
287
389
|
crates, assetGated,
|
|
@@ -354,9 +456,9 @@ async function publishUnit(parsed, deps, batch) {
|
|
|
354
456
|
const tree = git(["rev-parse", "HEAD^{tree}"], workspaceRoot);
|
|
355
457
|
if (tree !== batch.tree) refuse(`${unit}: HEAD tree ${tree} differs from the batch tree ${batch.tree}`);
|
|
356
458
|
}
|
|
357
|
-
const dirty = git(["status", "--porcelain", "--", dir, ...(parsed.qualifyOnly ? ["Cargo.toml", "Cargo.lock"] : [])], workspaceRoot);
|
|
459
|
+
const dirty = git(["status", "--porcelain", "--", dir, ...GATE_CONFIG_FILES, ...(parsed.qualifyOnly ? ["Cargo.toml", "Cargo.lock"] : [])], workspaceRoot);
|
|
358
460
|
if (dirty === null) refuse(`cannot read git status for ${dir}`);
|
|
359
|
-
if (dirty !== "") refuse(`tree is not clean for ${dir}; commit first:\n${dirty}`);
|
|
461
|
+
if (dirty !== "") refuse(`tree is not clean for ${dir} or the gate config (${GATE_CONFIG_FILES.join(", ")}); commit first:\n${dirty}`);
|
|
360
462
|
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`);
|
|
361
463
|
log(`[right-release] local publish ${kind}:${unit}@${version} at ${head.slice(0, 12)} (local-build grant, ${LOCAL_HOST_POLICIES[platform]} policy)`);
|
|
362
464
|
|
|
@@ -366,6 +468,13 @@ async function publishUnit(parsed, deps, batch) {
|
|
|
366
468
|
const gatedSection = assetGated[kind === "npm" ? "npm" : "crates"] ?? {};
|
|
367
469
|
const isAssetGated = Object.hasOwn(gatedSection, unit);
|
|
368
470
|
const plan = localTestPlan({ kind, unit, dir, config: gateConfig, manifest, batch: Boolean(batch), platform });
|
|
471
|
+
if (plan.consumerRows?.length) {
|
|
472
|
+
// The consumer workspaces are part of the qualified commit: present, and unmodified like the unit itself.
|
|
473
|
+
assertConsumerFiles(workspaceRoot, unit, plan.consumerRows, git);
|
|
474
|
+
const consumerDirty = git(["status", "--porcelain", "--", ...consumerDirs(plan.consumerRows)], workspaceRoot);
|
|
475
|
+
if (consumerDirty === null) refuse(`cannot read git status for the consumer rows of ${unit}`);
|
|
476
|
+
if (consumerDirty !== "") refuse(`tree is not clean for the consumer rows of ${unit}; commit first:\n${consumerDirty}`);
|
|
477
|
+
}
|
|
369
478
|
if (parsed.qualifyOnly) {
|
|
370
479
|
const violations = (deps.scanSecrets ?? scanCrateForSecrets)(unitPath);
|
|
371
480
|
if (violations.length) refuse(`qualification secret scan rejected: ${violations.join(", ")}`);
|
|
@@ -486,14 +595,14 @@ async function publishUnit(parsed, deps, batch) {
|
|
|
486
595
|
|
|
487
596
|
// 5. The EXISTING publish path.
|
|
488
597
|
if (kind === "npm") {
|
|
489
|
-
(deps.publishGatedTarball ?? publishGatedTarball)({ name: unit, version, tarball: artifactFile, evidencePath }, deps.publishDeps ?? {});
|
|
598
|
+
(deps.publishGatedTarball ?? publishGatedTarball)({ name: unit, version, tarball: artifactFile, evidencePath, workspaceRoot }, deps.publishDeps ?? {});
|
|
490
599
|
} else {
|
|
491
600
|
// trustEvidenceSteps: the evidence just written (and validated by the gate above) proves this commit's tests
|
|
492
601
|
// and clippy, so the publisher does not repeat them; verifyOnPublish: Cargo's verify build runs in the
|
|
493
602
|
// publish itself. Package contents, the runtime budget, the archive hash and the readback always run.
|
|
494
603
|
const execute = deps.cargoExecute ?? (batch ? batchCargoExecute(unit) : undefined);
|
|
495
604
|
const published = await (deps.runCargoPublish ?? runCargoPublish)({ crate: unit, dryRun: false, allowDirty: false, workspaceRoot, evidence: evidencePath, trustEvidenceSteps: true, verifyOnPublish: true, offlineReadOnly: true }, execute, deps.cargoDeps ?? {});
|
|
496
|
-
await withVersionsLock(() => (deps.recordPublished ?? recordPublished)({ registry: "cargo", name: published.name, version: published.version }));
|
|
605
|
+
await withVersionsLock(() => (deps.recordPublished ?? recordPublished)({ registry: "cargo", name: published.name, version: published.version }, workspaceFeedOptions(workspaceRoot)));
|
|
497
606
|
}
|
|
498
607
|
log(`[right-release] published ${unit}@${version}`);
|
|
499
608
|
return { kind, name: unit, version, commit: head, artifactSha256, evidencePath };
|
|
@@ -531,6 +640,7 @@ async function runLocalPublishBatch(parsed, deps) {
|
|
|
531
640
|
const assetGated = readJson(path.join(workspaceRoot, "ci", "asset-gated.json")) ?? {};
|
|
532
641
|
const isAssetGated = (unit) => Object.hasOwn(assetGated.crates ?? {}, unit);
|
|
533
642
|
const versions = new Map();
|
|
643
|
+
const batchConsumerDirs = new Set();
|
|
534
644
|
for (const unit of crates) {
|
|
535
645
|
const dir = `crates/${unit}`;
|
|
536
646
|
if (!fs.existsSync(path.join(workspaceRoot, dir))) refuse(`unit directory not found: ${path.join(workspaceRoot, dir)}`);
|
|
@@ -538,16 +648,20 @@ async function runLocalPublishBatch(parsed, deps) {
|
|
|
538
648
|
if (pkg.name !== unit) refuse(`Cargo.toml [package].name ${JSON.stringify(pkg.name)} differs from --crates entry ${unit}`);
|
|
539
649
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) refuse(`${unit} has no literal version`);
|
|
540
650
|
versions.set(unit, pkg.version);
|
|
541
|
-
|
|
651
|
+
// Refuses an OS-restricted crate, a malformed consumer row, or an untracked or symlinked consumer file now, not mid-batch.
|
|
652
|
+
const rows = localTestPlan({ kind: "cargo", unit, dir, config: gateConfig, batch: true, platform }).consumerRows ?? [];
|
|
653
|
+
assertConsumerFiles(workspaceRoot, unit, rows, git);
|
|
654
|
+
for (const consumerDir of consumerDirs(rows)) batchConsumerDirs.add(consumerDir);
|
|
542
655
|
}
|
|
543
656
|
const head = git(["rev-parse", "HEAD"], workspaceRoot);
|
|
544
657
|
if (!head || !/^[0-9a-f]{40}$/.test(head)) refuse("cannot resolve HEAD");
|
|
545
658
|
const tree = git(["rev-parse", "HEAD^{tree}"], workspaceRoot);
|
|
546
659
|
if (!tree || !/^[0-9a-f]{40}$/.test(tree)) refuse("cannot resolve the HEAD tree");
|
|
547
|
-
// Every batch crate
|
|
548
|
-
|
|
660
|
+
// Every batch crate, the workspace manifest and lock, the gate config and EVERY crate's consumer directories must
|
|
661
|
+
// match the commit the batch checks are bound to, before the first layer publishes (0.4.1 review A3, A4).
|
|
662
|
+
const dirty = git(["status", "--porcelain", "--", ...crates.map((unit) => `crates/${unit}`), "Cargo.toml", "Cargo.lock", ...GATE_CONFIG_FILES, ...batchConsumerDirs], workspaceRoot);
|
|
549
663
|
if (dirty === null) refuse("cannot read git status for the batch");
|
|
550
|
-
if (dirty !== "") refuse(`tree is not clean for the batch crates
|
|
664
|
+
if (dirty !== "") refuse(`tree is not clean for the batch crates, the workspace manifest, the gate config or a consumer directory; commit first:\n${dirty}`);
|
|
551
665
|
const registryHas = deps.registryHas ?? defaultRegistryHas;
|
|
552
666
|
const published = (await Promise.all(crates.map(async (unit) => ((await registryHas({ kind: "cargo", name: unit, version: versions.get(unit) })) ? `${unit}@${versions.get(unit)}` : null)))).filter(Boolean);
|
|
553
667
|
if (published.length) refuse(`already on the crates.io registry: ${published.join(", ")}; bump the version or remove them from the batch`);
|
|
@@ -573,9 +687,9 @@ async function runLocalPublishBatch(parsed, deps) {
|
|
|
573
687
|
const scanText = `scanCrateForSecrets ${crates.map((unit) => `crates/${unit}`).join(" ")}`;
|
|
574
688
|
checks.push({ name: "secret-scan", command: scanText, ok: true, ...stamp, crates });
|
|
575
689
|
commands.push(scanText);
|
|
576
|
-
const
|
|
690
|
+
const featureSetsOf = (unit) => gateConfig?.rust?.[unit]?.featureSets ?? [[]];
|
|
577
691
|
for (const layer of layers) {
|
|
578
|
-
for (const invocation of batchClippyInvocations(layer, {
|
|
692
|
+
for (const invocation of batchClippyInvocations(layer, { featureSetsOf, assetGatedOf: isAssetGated })) {
|
|
579
693
|
await batchCheck("clippy", "cargo", invocation.args, invocation.crates, testEnv(deps, invocation.assetGated));
|
|
580
694
|
}
|
|
581
695
|
}
|
|
@@ -795,12 +795,14 @@
|
|
|
795
795
|
"cargo:serde_json",
|
|
796
796
|
"cargo:tauri",
|
|
797
797
|
"cargo:tauri-build",
|
|
798
|
+
"cargo:tauri-plugin-http",
|
|
798
799
|
"cargo:tauri-plugin-os",
|
|
799
800
|
"cargo:tauri-plugin-process",
|
|
800
801
|
"cargo:tauri-plugin-updater",
|
|
801
802
|
"npm:@tailwindcss/vite",
|
|
802
803
|
"npm:@tauri-apps/api",
|
|
803
804
|
"npm:@tauri-apps/cli",
|
|
805
|
+
"npm:@tauri-apps/plugin-http",
|
|
804
806
|
"npm:@tauri-apps/plugin-os",
|
|
805
807
|
"npm:@tauri-apps/plugin-process",
|
|
806
808
|
"npm:@tauri-apps/plugin-updater",
|