@intentius/chant 0.124.17 → 0.124.18
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/dist/__golden__/wave-loop/fan-out-applied.json +511 -0
- package/dist/__golden__/wave-loop/fan-out-apply-fails.json +406 -0
- package/dist/__golden__/wave-loop/fan-out-gated.json +707 -0
- package/dist/__golden__/wave-loop/fan-out-no-members.json +898 -0
- package/dist/__golden__/wave-loop/fan-out-only.json +514 -0
- package/dist/__golden__/wave-loop/fan-out-plan-fails.json +359 -0
- package/dist/__golden__/wave-loop/fan-out-same-root.json +311 -0
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +401 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +756 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +340 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +400 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +404 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +62 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +191 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +407 -0
- package/dist/__golden__/wave-loop/op-denied.json +399 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +92 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +340 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +659 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +290 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +176 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +689 -0
- package/dist/__golden__/wave-loop/op-held-on.json +1455 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +673 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +737 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +450 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +97 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +1059 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +693 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +735 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +148 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +1225 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +202 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +286 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +823 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +259 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +196 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +193 -0
- package/dist/__golden__/wave-loop/op-overridden.json +508 -0
- package/dist/__golden__/wave-loop/op-per-run.json +518 -0
- package/dist/__golden__/wave-loop/op-point.json +495 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +397 -0
- package/dist/__golden__/wave-loop/op-sealed.json +125 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +168 -0
- package/dist/__golden__/wave-loop/op-spend.json +328 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +1808 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +291 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +231 -0
- package/dist/__golden__/wave-loop/op-wide.json +440 -0
- package/dist/components/fan-out-run.d.ts +4 -3
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +138 -123
- package/dist/op/op-waves-run.d.ts +7 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +129 -72
- package/dist/wave-engine.d.ts +74 -5
- package/dist/wave-engine.d.ts.map +1 -1
- package/dist/wave-engine.js +47 -5
- package/dist/wave-runtime.mjs +202 -202
- package/package.json +1 -1
- package/src/__golden__/wave-loop/fan-out-applied.json +511 -0
- package/src/__golden__/wave-loop/fan-out-apply-fails.json +406 -0
- package/src/__golden__/wave-loop/fan-out-gated.json +707 -0
- package/src/__golden__/wave-loop/fan-out-no-members.json +898 -0
- package/src/__golden__/wave-loop/fan-out-only.json +514 -0
- package/src/__golden__/wave-loop/fan-out-plan-fails.json +359 -0
- package/src/__golden__/wave-loop/fan-out-same-root.json +311 -0
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +401 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +756 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +340 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +400 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +404 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +62 -0
- package/src/__golden__/wave-loop/op-cost-member.json +191 -0
- package/src/__golden__/wave-loop/op-denied-source.json +407 -0
- package/src/__golden__/wave-loop/op-denied.json +399 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +92 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +340 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +659 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +290 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +176 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +689 -0
- package/src/__golden__/wave-loop/op-held-on.json +1455 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +673 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +737 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +450 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +97 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +1059 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +693 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +735 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +148 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +1225 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +202 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +286 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +823 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +259 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +196 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +193 -0
- package/src/__golden__/wave-loop/op-overridden.json +508 -0
- package/src/__golden__/wave-loop/op-per-run.json +518 -0
- package/src/__golden__/wave-loop/op-point.json +495 -0
- package/src/__golden__/wave-loop/op-pr-review.json +397 -0
- package/src/__golden__/wave-loop/op-sealed.json +125 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +168 -0
- package/src/__golden__/wave-loop/op-spend.json +328 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +1808 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +291 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +231 -0
- package/src/__golden__/wave-loop/op-wide.json +440 -0
- package/src/components/fan-out-run.ts +156 -128
- package/src/meta/peer-deps.test.ts +39 -0
- package/src/op/op-waves-run.ts +184 -68
- package/src/wave-engine.test.ts +38 -1
- package/src/wave-engine.ts +101 -5
- package/src/wave-loop-golden.test.ts +627 -0
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
* ## Or one approval per wave
|
|
32
32
|
*
|
|
33
33
|
* `waveGate` (#3049) is the other mode. Each wave is planned once the waves
|
|
34
|
-
* before it applied, and its gate binds that wave's set digest.
|
|
35
|
-
* the digest and the gate decision come from the wave engine
|
|
36
|
-
* (`../wave-engine.ts`, #3766), which `chant run wave` uses too
|
|
34
|
+
* before it applied, and its gate binds that wave's set digest. The loop,
|
|
35
|
+
* gate names, the digest and the gate decision come from the wave engine
|
|
36
|
+
* (`../wave-engine.ts`, #3766), which `chant run wave` uses too; this module
|
|
37
|
+
* hands it the steps a fan-out plans, decides and applies a wave with. See
|
|
37
38
|
* {@link runGatedWaves}.
|
|
38
39
|
*/
|
|
39
40
|
|
|
@@ -46,13 +47,13 @@ import {
|
|
|
46
47
|
} from "./driver";
|
|
47
48
|
import type { ReleaseIdentity } from "../telemetry-attribution";
|
|
48
49
|
import { downstreamWithin, remainingFanOut, type FanOutPlan, type FanOutProgress, type FanOutSkip } from "./fan-out";
|
|
49
|
-
import { evaluateGate, gitGateLedgerPort, type GateLedgerPort } from "../op/gate";
|
|
50
|
+
import { evaluateGate, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "../op/gate";
|
|
50
51
|
import type { RunProgressEvent } from "./run-progress";
|
|
51
52
|
import type { CapabilityRegistry } from "./capability";
|
|
52
53
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
53
54
|
import type { DeployContext } from "./capability";
|
|
54
55
|
import { planWaveComponent, type WaveComponentPlan } from "./wave-plan";
|
|
55
|
-
import { decideWaveGate, waveGateName, waveSetDigest, type WaveMember, type WaveRecord } from "../wave-engine";
|
|
56
|
+
import { decideWaveGate, runWaveLoop, waveGateName, waveSetDigest, type WaveMember, type WaveRecord } from "../wave-engine";
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* One gate per wave (#3049), each bound to its wave's set digest. The other
|
|
@@ -392,137 +393,164 @@ async function runGatedWaves(
|
|
|
392
393
|
|
|
393
394
|
options.onProgress?.({ type: "run-start", waves: plan.waves });
|
|
394
395
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
396
|
+
/** What planning one wave gave. */
|
|
397
|
+
interface Planned {
|
|
398
|
+
runnable: string[];
|
|
399
|
+
gateName: string;
|
|
400
|
+
contexts: Map<string, DeployContext>;
|
|
401
|
+
members: WaveMember[];
|
|
402
|
+
plansFor: Map<string, Record<string, unknown>>;
|
|
403
|
+
waveFailures: Array<{ component: string; error?: string }>;
|
|
404
|
+
}
|
|
405
|
+
/** `next`: go on to the next wave. `stop`: the run ends here. */
|
|
406
|
+
type Outcome = "next" | "stop";
|
|
407
|
+
|
|
408
|
+
await runWaveLoop<Planned, { check: Extract<GateCheck, { satisfied: true }>; record: WaveRecord; toRun: string[] }, Outcome>({
|
|
409
|
+
first: 1,
|
|
410
|
+
onward: (_k, outcome) => outcome === "next",
|
|
411
|
+
|
|
412
|
+
async plan({ k: waveNum }) {
|
|
413
|
+
const wave = plan.waves[waveNum - 1];
|
|
414
|
+
if (!wave) return { end: "stop" };
|
|
415
|
+
const runnable = wave.filter((name) => left.has(name) && !blockedBy.has(name));
|
|
416
|
+
if (waveGate.only !== undefined && waveNum < waveGate.only) {
|
|
417
|
+
if (runnable.length > 0) throw new EarlierWaveNotAppliedError(waveGate.only, waveNum, runnable);
|
|
418
|
+
return { end: "next" };
|
|
418
419
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
420
|
+
if (waveGate.only !== undefined && waveNum > waveGate.only) return { end: "stop" };
|
|
421
|
+
if (runnable.length === 0) return { end: "next" };
|
|
422
|
+
|
|
423
|
+
const gateName = waveGateName(waveGate.gate, waveNum);
|
|
424
|
+
const contexts = new Map(runnable.map((name) => [name, fanOutContext(options, name)]));
|
|
425
|
+
const planned = await Promise.all(
|
|
426
|
+
runnable.map((name) => planComponent(byName.get(name)!, contexts.get(name)!, componentOutputs)),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const members: WaveMember[] = [];
|
|
430
|
+
const plansFor = new Map<string, Record<string, unknown>>();
|
|
431
|
+
const waveFailures: Array<{ component: string; error?: string }> = [];
|
|
432
|
+
for (const p of planned) {
|
|
433
|
+
if ("error" in p) {
|
|
434
|
+
waveFailures.push({ component: p.component, error: p.error });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
members.push(...p.members);
|
|
438
|
+
plansFor.set(p.component, p.plans);
|
|
439
|
+
}
|
|
440
|
+
return { go: { runnable, gateName, contexts, members, plansFor, waveFailures } };
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
digest(_at, { members, plansFor, waveFailures }) {
|
|
444
|
+
try {
|
|
445
|
+
// A wave where nothing planned has no set, and so no digest.
|
|
446
|
+
return members.length > 0 ? waveSetDigest(members) : "";
|
|
447
|
+
} catch (err) {
|
|
448
|
+
// Two components planning the same root: there is no one plan for it.
|
|
449
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
450
|
+
for (const name of plansFor.keys()) waveFailures.push({ component: name, error });
|
|
451
|
+
plansFor.clear();
|
|
452
|
+
return "";
|
|
453
|
+
}
|
|
454
|
+
},
|
|
439
455
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
digest,
|
|
447
|
-
members: [...members].sort((a, b) => a.member.localeCompare(b.member)),
|
|
448
|
-
status: "failed",
|
|
449
|
-
...(waveFailures.length > 0 ? { failed: waveFailures } : {}),
|
|
450
|
-
};
|
|
451
|
-
if (toRun.length === 0) {
|
|
452
|
-
waves.push(record);
|
|
453
|
-
options.onWaveSettled?.(record);
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
+
async decide({ k: waveNum }, { runnable, gateName, members, plansFor, waveFailures }, digest) {
|
|
457
|
+
for (const f of waveFailures) {
|
|
458
|
+
failed.push(f.component);
|
|
459
|
+
results.push(planFailure(f.component, f.error ?? "plan failed"));
|
|
460
|
+
markBlocked(f.component);
|
|
461
|
+
}
|
|
456
462
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// would read outputs this wave has not written.
|
|
468
|
-
const stopped: WaveRecord = {
|
|
469
|
-
...record,
|
|
470
|
-
status: "gated",
|
|
471
|
-
...(check.mismatch?.approved ? { approved: check.mismatch.approved } : {}),
|
|
463
|
+
const toRun = runnable.filter((name) => plansFor.has(name));
|
|
464
|
+
const record: WaveRecord = {
|
|
465
|
+
wave: waveNum,
|
|
466
|
+
op: waveGate.op,
|
|
467
|
+
gate: gateName,
|
|
468
|
+
components: [...runnable].sort(),
|
|
469
|
+
digest,
|
|
470
|
+
members: [...members].sort((a, b) => a.member.localeCompare(b.member)),
|
|
471
|
+
status: "failed",
|
|
472
|
+
...(waveFailures.length > 0 ? { failed: waveFailures } : {}),
|
|
472
473
|
};
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
options.onProgress?.({ type: "wave-start", wave: waveNum, components: toRun });
|
|
480
|
-
const waveResults = await Promise.all(
|
|
481
|
-
toRun.map(async (name) => {
|
|
482
|
-
options.onProgress?.({ type: "component-start", wave: waveNum, component: name });
|
|
483
|
-
const plans = plansFor.get(name)!;
|
|
484
|
-
const ctx = { ...contexts.get(name)!, ...(Object.keys(plans).length > 0 ? { plans } : {}) };
|
|
485
|
-
const result = await runComponentDeploy(byName.get(name)!, ctx, registry, componentOutputs, options.onProgress, gates);
|
|
486
|
-
options.onProgress?.({
|
|
487
|
-
type: "component-done",
|
|
488
|
-
wave: waveNum,
|
|
489
|
-
component: name,
|
|
490
|
-
status: result.status === "fail" ? "failed" : result.status,
|
|
491
|
-
});
|
|
492
|
-
options.onComponentSettled?.(result, componentOutputs[name]);
|
|
493
|
-
return result;
|
|
494
|
-
}),
|
|
495
|
-
);
|
|
496
|
-
results.push(...waveResults);
|
|
474
|
+
if (toRun.length === 0) {
|
|
475
|
+
waves.push(record);
|
|
476
|
+
options.onWaveSettled?.(record);
|
|
477
|
+
return { end: "next" };
|
|
478
|
+
}
|
|
497
479
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
480
|
+
const check = await decideWaveGate(gates.port, {
|
|
481
|
+
op: waveGate.op,
|
|
482
|
+
gate: gateName,
|
|
483
|
+
digest,
|
|
484
|
+
description: waveGate.description ?? `wave ${waveNum} of ${plan.waves.length}: ${toRun.join(", ")}`,
|
|
485
|
+
...(waveGate.timeout ? { timeout: waveGate.timeout } : {}),
|
|
486
|
+
...(options.now ? { now: options.now } : {}),
|
|
487
|
+
});
|
|
488
|
+
if (!check.satisfied) {
|
|
489
|
+
// Nothing in this wave ran, and no later wave is planned: its plan
|
|
490
|
+
// would read outputs this wave has not written.
|
|
491
|
+
const stopped: WaveRecord = {
|
|
492
|
+
...record,
|
|
493
|
+
status: "gated",
|
|
494
|
+
...(check.mismatch?.approved ? { approved: check.mismatch.approved } : {}),
|
|
495
|
+
};
|
|
496
|
+
waves.push(stopped);
|
|
497
|
+
options.onWaveSettled?.(stopped);
|
|
498
|
+
gatedFact = check.pending;
|
|
499
|
+
return { end: "stop" };
|
|
502
500
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
501
|
+
return { go: { check, record, toRun } };
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
async apply({ k: waveNum }, { contexts, plansFor, waveFailures }, { check, record, toRun }) {
|
|
505
|
+
options.onProgress?.({ type: "wave-start", wave: waveNum, components: toRun });
|
|
506
|
+
const waveResults = await Promise.all(
|
|
507
|
+
toRun.map(async (name) => {
|
|
508
|
+
options.onProgress?.({ type: "component-start", wave: waveNum, component: name });
|
|
509
|
+
const plans = plansFor.get(name)!;
|
|
510
|
+
const ctx = { ...contexts.get(name)!, ...(Object.keys(plans).length > 0 ? { plans } : {}) };
|
|
511
|
+
const result = await runComponentDeploy(byName.get(name)!, ctx, registry, componentOutputs, options.onProgress, gates);
|
|
512
|
+
options.onProgress?.({
|
|
513
|
+
type: "component-done",
|
|
514
|
+
wave: waveNum,
|
|
515
|
+
component: name,
|
|
516
|
+
status: result.status === "fail" ? "failed" : result.status,
|
|
517
|
+
});
|
|
518
|
+
options.onComponentSettled?.(result, componentOutputs[name]);
|
|
519
|
+
return result;
|
|
520
|
+
}),
|
|
521
|
+
);
|
|
522
|
+
results.push(...waveResults);
|
|
523
|
+
|
|
524
|
+
for (const result of waveResults) {
|
|
525
|
+
if (result.status === "ok") {
|
|
526
|
+
completed.push(result.component);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (result.status === "fail") {
|
|
530
|
+
failed.push(result.component);
|
|
531
|
+
waveFailures.push({ component: result.component });
|
|
532
|
+
} else {
|
|
533
|
+
gatedFact ??= result.gate;
|
|
534
|
+
}
|
|
535
|
+
markBlocked(result.component);
|
|
508
536
|
}
|
|
509
|
-
markBlocked(result.component);
|
|
510
|
-
}
|
|
511
537
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
538
|
+
const settled: WaveRecord = {
|
|
539
|
+
...record,
|
|
540
|
+
status: waveFailures.length > 0 ? "failed" : "applied",
|
|
541
|
+
approvedBy: check.resolution.resolvedBy,
|
|
542
|
+
...(waveFailures.length > 0 ? { failed: waveFailures } : {}),
|
|
543
|
+
};
|
|
544
|
+
waves.push(settled);
|
|
545
|
+
options.onWaveSettled?.(settled);
|
|
546
|
+
options.onProgress?.({
|
|
547
|
+
type: "wave-done",
|
|
548
|
+
wave: waveNum,
|
|
549
|
+
status: waveFailures.length > 0 ? "failed" : waveResults.some((r) => r.status === "gated") ? "gated" : "ok",
|
|
550
|
+
});
|
|
551
|
+
return "next";
|
|
552
|
+
},
|
|
553
|
+
});
|
|
526
554
|
|
|
527
555
|
const blocked: FanOutSkip[] = [...blockedBy.entries()]
|
|
528
556
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
@@ -124,3 +124,42 @@ describe("the provider-aware rule, over fixtures", () => {
|
|
|
124
124
|
expect(rangeViolations(skewed, { aws: { typescript: ">=5.0.0" } })).toEqual([]);
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Same rule over every dependency field of every publishable package (#3905).
|
|
130
|
+
* docker, fly, k8s, grafana and helm once depended on sibling lexicons at `*`,
|
|
131
|
+
* which follows `latest` instead of the release they shipped in. Both release
|
|
132
|
+
* recipes now write `^<provider version>`; this fails if any `@intentius/*`
|
|
133
|
+
* range in dependencies, peerDependencies or optionalDependencies is `*` or
|
|
134
|
+
* anything other than the provider's current version.
|
|
135
|
+
*/
|
|
136
|
+
describe("every in-family @intentius/* range pins its provider's current version", () => {
|
|
137
|
+
type Manifest = {
|
|
138
|
+
name: string;
|
|
139
|
+
version: string;
|
|
140
|
+
private?: boolean;
|
|
141
|
+
dependencies?: Record<string, string>;
|
|
142
|
+
peerDependencies?: Record<string, string>;
|
|
143
|
+
optionalDependencies?: Record<string, string>;
|
|
144
|
+
};
|
|
145
|
+
const dirs = [
|
|
146
|
+
...readdirSync(join(repoRoot, "packages")).map((n) => join("packages", n)),
|
|
147
|
+
...lexicons.map((n) => join("lexicons", n)),
|
|
148
|
+
].filter((d) => existsSync(join(repoRoot, d, "package.json")));
|
|
149
|
+
const manifests = dirs.map((d) => JSON.parse(readFileSync(join(repoRoot, d, "package.json"), "utf-8")) as Manifest);
|
|
150
|
+
const publishable = manifests.filter((m) => !m.private);
|
|
151
|
+
const versions = Object.fromEntries(publishable.map((m) => [m.name, m.version]));
|
|
152
|
+
|
|
153
|
+
test.each(publishable.map((m) => m.name))("%s", (name) => {
|
|
154
|
+
const m = publishable.find((p) => p.name === name)!;
|
|
155
|
+
const bad: string[] = [];
|
|
156
|
+
for (const field of ["dependencies", "peerDependencies", "optionalDependencies"] as const) {
|
|
157
|
+
for (const [dep, range] of Object.entries(m[field] ?? {})) {
|
|
158
|
+
if (!dep.startsWith("@intentius/")) continue;
|
|
159
|
+
const want = versions[dep] ? `^${versions[dep]}` : undefined;
|
|
160
|
+
if (range !== want) bad.push(`${field}.${dep} is "${range}", want ${want ?? "a published workspace package"}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
expect(bad).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
});
|