@intentius/sql-yodeler 0.2.0 → 0.4.0
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 +3 -3
- package/dist/apply-steps/index.d.ts +65 -0
- package/dist/apply-steps/index.js +106 -0
- package/dist/apply-steps/index.js.map +1 -0
- package/dist/approval-mode/index.d.ts +78 -0
- package/dist/approval-mode/index.js +105 -0
- package/dist/approval-mode/index.js.map +1 -0
- package/dist/chant/access.d.ts +87 -0
- package/dist/chant/access.js +169 -0
- package/dist/chant/access.js.map +1 -0
- package/dist/chant/build.js +7 -0
- package/dist/chant/build.js.map +1 -1
- package/dist/chant/drift.d.ts +17 -1
- package/dist/chant/drift.js +55 -2
- package/dist/chant/drift.js.map +1 -1
- package/dist/chant/plan.d.ts +18 -2
- package/dist/chant/plan.js +21 -3
- package/dist/chant/plan.js.map +1 -1
- package/dist/ci/render.d.ts +333 -0
- package/dist/ci/render.js +885 -0
- package/dist/ci/render.js.map +1 -0
- package/dist/ci/replay.d.ts +17 -0
- package/dist/ci/replay.js +69 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/cleanup/index.d.ts +86 -0
- package/dist/cleanup/index.js +207 -0
- package/dist/cleanup/index.js.map +1 -0
- package/dist/commands/apply.d.ts +113 -0
- package/dist/commands/apply.js +370 -30
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/approve.d.ts +13 -0
- package/dist/commands/approve.js +239 -13
- package/dist/commands/approve.js.map +1 -1
- package/dist/commands/checkpoint.d.ts +3 -0
- package/dist/commands/checkpoint.js +78 -0
- package/dist/commands/checkpoint.js.map +1 -0
- package/dist/commands/ci.d.ts +3 -0
- package/dist/commands/ci.js +130 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/cleanup.d.ts +13 -0
- package/dist/commands/cleanup.js +138 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.js +136 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/docs.d.ts +11 -0
- package/dist/commands/docs.js +108 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/drift.d.ts +13 -1
- package/dist/commands/drift.js +52 -5
- package/dist/commands/drift.js.map +1 -1
- package/dist/commands/index.js +16 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.js +39 -7
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/new.js +133 -34
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/override.js +48 -16
- package/dist/commands/override.js.map +1 -1
- package/dist/commands/plan.js +47 -6
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/report.d.ts +13 -0
- package/dist/commands/report.js +121 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/revert.d.ts +20 -0
- package/dist/commands/revert.js +246 -0
- package/dist/commands/revert.js.map +1 -0
- package/dist/commands/status.js +32 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/test.d.ts +3 -0
- package/dist/commands/test.js +164 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/config/base.d.ts +69 -0
- package/dist/config/base.js +121 -0
- package/dist/config/base.js.map +1 -0
- package/dist/config/define.d.ts +240 -3
- package/dist/config/define.js +4 -0
- package/dist/config/define.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/load.d.ts +1 -1
- package/dist/config/load.js +156 -7
- package/dist/config/load.js.map +1 -1
- package/dist/config/settings.d.ts +22 -1
- package/dist/config/settings.js +34 -2
- package/dist/config/settings.js.map +1 -1
- package/dist/credentials.d.ts +119 -0
- package/dist/credentials.js +119 -0
- package/dist/credentials.js.map +1 -0
- package/dist/exit.js +1 -1
- package/dist/exit.js.map +1 -1
- package/dist/history/postgres/ddl.d.ts +18 -0
- package/dist/history/postgres/ddl.js +27 -2
- package/dist/history/postgres/ddl.js.map +1 -1
- package/dist/history/postgres/store.d.ts +2 -0
- package/dist/history/postgres/store.js +7 -1
- package/dist/history/postgres/store.js.map +1 -1
- package/dist/history/rows.d.ts +16 -3
- package/dist/history/rows.js +30 -1
- package/dist/history/rows.js.map +1 -1
- package/dist/history/store.d.ts +2 -0
- package/dist/history/store.js +2 -2
- package/dist/history/store.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/init/adopt.js +1 -1
- package/dist/init/adopt.js.map +1 -1
- package/dist/init/clickhouse-access.d.ts +64 -0
- package/dist/init/clickhouse-access.js +211 -0
- package/dist/init/clickhouse-access.js.map +1 -0
- package/dist/init/live.d.ts +4 -0
- package/dist/init/live.js +16 -0
- package/dist/init/live.js.map +1 -1
- package/dist/lint/access.d.ts +18 -0
- package/dist/lint/access.js +34 -0
- package/dist/lint/access.js.map +1 -0
- package/dist/lint/check.js +38 -6
- package/dist/lint/check.js.map +1 -1
- package/dist/lint/config.d.ts +7 -1
- package/dist/lint/config.js +11 -5
- package/dist/lint/config.js.map +1 -1
- package/dist/lint/index.d.ts +2 -0
- package/dist/lint/index.js +2 -0
- package/dist/lint/index.js.map +1 -1
- package/dist/lint/naming.d.ts +62 -0
- package/dist/lint/naming.js +138 -0
- package/dist/lint/naming.js.map +1 -0
- package/dist/lint/report.js +1 -1
- package/dist/lint/report.js.map +1 -1
- package/dist/lint/routines.d.ts +26 -0
- package/dist/lint/routines.js +65 -0
- package/dist/lint/routines.js.map +1 -0
- package/dist/lint/rules.d.ts +51 -6
- package/dist/lint/rules.js +23 -6
- package/dist/lint/rules.js.map +1 -1
- package/dist/lint/schema-checks.d.ts +63 -0
- package/dist/lint/schema-checks.js +128 -0
- package/dist/lint/schema-checks.js.map +1 -0
- package/dist/lint/statements.d.ts +22 -1
- package/dist/lint/statements.js +40 -1
- package/dist/lint/statements.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/migrations/checkpoint.d.ts +45 -0
- package/dist/migrations/checkpoint.js +108 -0
- package/dist/migrations/checkpoint.js.map +1 -0
- package/dist/migrations/checks.d.ts +19 -1
- package/dist/migrations/checks.js +15 -0
- package/dist/migrations/checks.js.map +1 -1
- package/dist/migrations/create.d.ts +13 -4
- package/dist/migrations/create.js +24 -7
- package/dist/migrations/create.js.map +1 -1
- package/dist/migrations/document.d.ts +10 -0
- package/dist/migrations/document.js +1 -0
- package/dist/migrations/document.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.js +2 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/routines.d.ts +21 -0
- package/dist/migrations/routines.js +30 -0
- package/dist/migrations/routines.js.map +1 -0
- package/dist/notify/index.d.ts +124 -0
- package/dist/notify/index.js +210 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/verify.d.ts +53 -0
- package/dist/notify/verify.js +71 -0
- package/dist/notify/verify.js.map +1 -0
- package/dist/pr/model.d.ts +7 -1
- package/dist/pr/model.js +29 -7
- package/dist/pr/model.js.map +1 -1
- package/dist/pr/render.d.ts +4 -1
- package/dist/pr/render.js +49 -9
- package/dist/pr/render.js.map +1 -1
- package/dist/pr/tenants.d.ts +37 -0
- package/dist/pr/tenants.js +138 -0
- package/dist/pr/tenants.js.map +1 -0
- package/dist/probe.d.ts +27 -0
- package/dist/probe.js +106 -0
- package/dist/probe.js.map +1 -0
- package/dist/project.d.ts +35 -2
- package/dist/project.js +62 -2
- package/dist/project.js.map +1 -1
- package/dist/replay/clickhouse.d.ts +31 -0
- package/dist/replay/clickhouse.js +103 -6
- package/dist/replay/clickhouse.js.map +1 -1
- package/dist/replay/replay.d.ts +14 -3
- package/dist/replay/replay.js +62 -31
- package/dist/replay/replay.js.map +1 -1
- package/dist/replay/report.js +1 -1
- package/dist/replay/report.js.map +1 -1
- package/dist/report/audit.d.ts +138 -0
- package/dist/report/audit.js +219 -0
- package/dist/report/audit.js.map +1 -0
- package/dist/report/jsonl.d.ts +21 -0
- package/dist/report/jsonl.js +52 -0
- package/dist/report/jsonl.js.map +1 -0
- package/dist/report/model.d.ts +68 -0
- package/dist/report/model.js +127 -0
- package/dist/report/model.js.map +1 -0
- package/dist/report/render.d.ts +18 -0
- package/dist/report/render.js +127 -0
- package/dist/report/render.js.map +1 -0
- package/dist/schema-docs/render.d.ts +49 -0
- package/dist/schema-docs/render.js +359 -0
- package/dist/schema-docs/render.js.map +1 -0
- package/dist/schemas.d.ts +7 -2
- package/dist/schemas.js +7 -2
- package/dist/schemas.js.map +1 -1
- package/dist/sources/ddl.d.ts +48 -0
- package/dist/sources/ddl.js +53 -0
- package/dist/sources/ddl.js.map +1 -0
- package/dist/sources/index.d.ts +69 -0
- package/dist/sources/index.js +170 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/steps/backfill.d.ts +40 -5
- package/dist/steps/backfill.js +128 -8
- package/dist/steps/backfill.js.map +1 -1
- package/dist/steps/index.d.ts +19 -1
- package/dist/steps/index.js +34 -7
- package/dist/steps/index.js.map +1 -1
- package/dist/steps/kinds.d.ts +42 -4
- package/dist/steps/kinds.js +88 -14
- package/dist/steps/kinds.js.map +1 -1
- package/dist/steps/postgres-migration.d.ts +38 -25
- package/dist/steps/postgres-migration.js +49 -32
- package/dist/steps/postgres-migration.js.map +1 -1
- package/dist/steps/rebuild.d.ts +15 -6
- package/dist/steps/rebuild.js +12 -9
- package/dist/steps/rebuild.js.map +1 -1
- package/dist/tenants/index.d.ts +41 -0
- package/dist/tenants/index.js +121 -0
- package/dist/tenants/index.js.map +1 -0
- package/dist/tenants/target.d.ts +23 -0
- package/dist/tenants/target.js +29 -0
- package/dist/tenants/target.js.map +1 -0
- package/dist/tenants/wave.d.ts +128 -0
- package/dist/tenants/wave.js +254 -0
- package/dist/tenants/wave.js.map +1 -0
- package/dist/testing/define.d.ts +62 -0
- package/dist/testing/define.js +32 -0
- package/dist/testing/define.js.map +1 -0
- package/dist/testing/files.d.ts +5 -0
- package/dist/testing/files.js +18 -0
- package/dist/testing/files.js.map +1 -0
- package/dist/testing/report.d.ts +14 -0
- package/dist/testing/report.js +83 -0
- package/dist/testing/report.js.map +1 -0
- package/dist/testing/run.d.ts +104 -0
- package/dist/testing/run.js +328 -0
- package/dist/testing/run.js.map +1 -0
- package/dist/versioned/approval.d.ts +6 -0
- package/dist/versioned/approval.js +11 -5
- package/dist/versioned/approval.js.map +1 -1
- package/dist/versioned/checks.d.ts +8 -2
- package/dist/versioned/checks.js +34 -13
- package/dist/versioned/checks.js.map +1 -1
- package/dist/versioned/driver.d.ts +2 -2
- package/dist/versioned/engine.d.ts +16 -0
- package/dist/versioned/engine.js +20 -6
- package/dist/versioned/engine.js.map +1 -1
- package/dist/versioned/explain.d.ts +2 -0
- package/dist/versioned/explain.js +3 -1
- package/dist/versioned/explain.js.map +1 -1
- package/dist/versioned/index.d.ts +1 -0
- package/dist/versioned/index.js +1 -0
- package/dist/versioned/index.js.map +1 -1
- package/dist/versioned/op.d.ts +6 -1
- package/dist/versioned/op.js +7 -2
- package/dist/versioned/op.js.map +1 -1
- package/dist/versioned/policy.d.ts +33 -2
- package/dist/versioned/policy.js +59 -8
- package/dist/versioned/policy.js.map +1 -1
- package/dist/versioned/report.js +9 -0
- package/dist/versioned/report.js.map +1 -1
- package/dist/versioned/revert.d.ts +128 -0
- package/dist/versioned/revert.js +251 -0
- package/dist/versioned/revert.js.map +1 -0
- package/dist/versioned/state.d.ts +25 -0
- package/dist/versioned/state.js +37 -4
- package/dist/versioned/state.js.map +1 -1
- package/dist/waves/index.d.ts +224 -0
- package/dist/waves/index.js +353 -0
- package/dist/waves/index.js.map +1 -0
- package/dist/waves/promotion.d.ts +56 -0
- package/dist/waves/promotion.js +108 -0
- package/dist/waves/promotion.js.map +1 -0
- package/dist/waves/record.d.ts +34 -0
- package/dist/waves/record.js +20 -0
- package/dist/waves/record.js.map +1 -0
- package/package.json +11 -7
- package/schemas/apply.schema.json +162 -3
- package/schemas/checkpoint.schema.json +19 -0
- package/schemas/cleanup.schema.json +40 -0
- package/schemas/drift.schema.json +26 -1
- package/schemas/event.schema.json +36 -0
- package/schemas/lint-rules.schema.json +4 -0
- package/schemas/lint.schema.json +4 -0
- package/schemas/new.schema.json +41 -3
- package/schemas/plan.schema.json +157 -0
- package/schemas/report.schema.json +114 -0
- package/schemas/revert.schema.json +69 -0
- package/schemas/status.schema.json +64 -3
- package/schemas/test.schema.json +41 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tenant set's wave (#56): how the apply pipeline runs it, and what
|
|
3
|
+
* `yodel plan` and `yodel approve` show of it.
|
|
4
|
+
*
|
|
5
|
+
* A set given as a list or a file is a run per tenant, `<env>/<tenant>`,
|
|
6
|
+
* written into the rendered pipeline. A set given by a query is read when
|
|
7
|
+
* the wave runs, never when the pipeline is rendered: its wave is `shares`
|
|
8
|
+
* slots (one without `shares`), `<env>/@<i>of<n>`, and a slot is every
|
|
9
|
+
* tenant the query lists at that moment whose name hashes to it
|
|
10
|
+
* ({@link slotIndex}). Tenants come and go without a change to the
|
|
11
|
+
* pipeline, and a tenant always lands in the same slot.
|
|
12
|
+
*
|
|
13
|
+
* A slot's plan (`yodel apply <env>/@<i>of<n> --wave-plan`) is each of its
|
|
14
|
+
* tenants' wave plans and a digest over them, so the wave's one gate binds
|
|
15
|
+
* every tenant's plan whichever way the set is given. `--wave-apply` applies
|
|
16
|
+
* each tenant under its own lock, and refuses (exit 4) a tenant whose plan
|
|
17
|
+
* moved since the decision, naming it, after applying the others.
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { dirname, resolve } from "node:path";
|
|
22
|
+
import { resolveEnvironment, YodelError } from "../project.js";
|
|
23
|
+
import { EXIT_REFUSED } from "../exit.js";
|
|
24
|
+
import { closeVersioned, openVersioned } from "../versioned/engine.js";
|
|
25
|
+
import { statusReport } from "../versioned/index.js";
|
|
26
|
+
import { gateAtBase, planWave, viewWavePlan, WAVES_PIPELINE, WavePlanMoved } from "../waves/index.js";
|
|
27
|
+
import { listTenants, tenantSetOf } from "./index.js";
|
|
28
|
+
import { splitTarget, tenantTarget } from "./target.js";
|
|
29
|
+
import { describeApprovalMode, signFlag, waveApproval } from "../approval-mode/index.js";
|
|
30
|
+
/** A slot's tenant part: `@<i>of<n>`. */
|
|
31
|
+
const SLOT = /^@([1-9][0-9]*)of([1-9][0-9]*)$/;
|
|
32
|
+
/** Which slot of which set's wave a target is, when it is one: `prod/@2of4`. */
|
|
33
|
+
export function splitSlot(target) {
|
|
34
|
+
const { env, tenant } = splitTarget(target);
|
|
35
|
+
const m = tenant === undefined ? null : SLOT.exec(tenant);
|
|
36
|
+
if (!m)
|
|
37
|
+
return undefined;
|
|
38
|
+
const index = Number(m[1]);
|
|
39
|
+
const count = Number(m[2]);
|
|
40
|
+
return index <= count ? { env, index, count } : undefined;
|
|
41
|
+
}
|
|
42
|
+
export const slotTarget = (env, index, count) => tenantTarget(env, `@${String(index)}of${String(count)}`);
|
|
43
|
+
/** The slot, 1 to `count`, a tenant's name hashes to: the same whatever else the set lists. */
|
|
44
|
+
export function slotIndex(tenant, count) {
|
|
45
|
+
return (Number.parseInt(createHash("sha256").update(tenant).digest("hex").slice(0, 8), 16) % count) + 1;
|
|
46
|
+
}
|
|
47
|
+
/** A set given by a query is read when its wave runs. */
|
|
48
|
+
export const readAtRunTime = (set) => set !== undefined && !Array.isArray(set) && "query" in set;
|
|
49
|
+
/**
|
|
50
|
+
* The runs of `env`'s wave, when it has a tenant set: a run per tenant, or
|
|
51
|
+
* for a set given by a query, `shares` slots (one without), with no server
|
|
52
|
+
* read. Undefined for an environment without a tenant set.
|
|
53
|
+
*/
|
|
54
|
+
export async function waveTargets(project, env, shares) {
|
|
55
|
+
const set = tenantSetOf(project, env);
|
|
56
|
+
if (set === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (readAtRunTime(set)) {
|
|
59
|
+
const n = Math.max(1, shares ?? 1);
|
|
60
|
+
return Array.from({ length: n }, (_, i) => slotTarget(env, i + 1, n));
|
|
61
|
+
}
|
|
62
|
+
const tenants = (await listTenants(project, env)) ?? [];
|
|
63
|
+
if (tenants.length === 0)
|
|
64
|
+
throw new YodelError(`${env}'s tenant set lists no tenant, so its wave would apply nothing`);
|
|
65
|
+
return tenants.map((t) => tenantTarget(env, t));
|
|
66
|
+
}
|
|
67
|
+
/** The tenants of a slot now, sorted. */
|
|
68
|
+
async function slotTenants(project, slot) {
|
|
69
|
+
return ((await listTenants(project, slot.env)) ?? []).filter((t) => slotIndex(t, slot.count) === slot.index).sort();
|
|
70
|
+
}
|
|
71
|
+
/** The digest of a slot's plan: over each of its tenants' wave plan digests. */
|
|
72
|
+
export async function slotDigest(environment, tenants) {
|
|
73
|
+
const { computePlanDigest } = await import("@intentius/chant/lifecycle/plan-digest");
|
|
74
|
+
return computePlanDigest("yodel-wave-slot", { environment, tenants: [...tenants].sort((a, b) => (a.target < b.target ? -1 : 1)).map((t) => ({ target: t.target, planDigest: t.planDigest })) });
|
|
75
|
+
}
|
|
76
|
+
/** `yodel apply <env>/@<i>of<n> --wave-plan <file>`: plans every tenant of the slot, as the query lists them now. */
|
|
77
|
+
export async function writeSlotPlan(project, target, ordered, file, options, log) {
|
|
78
|
+
const slot = splitSlot(target);
|
|
79
|
+
if (!slot)
|
|
80
|
+
throw new YodelError(`${target} is not a slot of a tenant set's wave`);
|
|
81
|
+
const path = resolve(file);
|
|
82
|
+
const before = existsSync(path) ? readSlotPlanSoftly(path) : undefined;
|
|
83
|
+
const tenants = [];
|
|
84
|
+
for (const t of await slotTenants(project, slot)) {
|
|
85
|
+
const env = resolveEnvironment(project, tenantTarget(slot.env, t));
|
|
86
|
+
const venv = await openVersioned(project, env);
|
|
87
|
+
try {
|
|
88
|
+
const plan = await planWave(project, venv, ordered, options);
|
|
89
|
+
tenants.push({ target: env.name, plan });
|
|
90
|
+
log(`${env.name}: ${plan.empty ? "nothing to apply" : `${String(plan.pending.length)} pending (${plan.pending.join(", ")})`}; plan ${plan.planDigest}`);
|
|
91
|
+
const was = before?.tenants.find((x) => x.target === env.name)?.plan.planDigest;
|
|
92
|
+
if (was !== undefined && was !== plan.planDigest)
|
|
93
|
+
log(`${env.name}: its plan moved since the plan before this one (${was}, now ${plan.planDigest})`);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await closeVersioned(venv);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const held = tenants.flatMap((t) => (t.plan.held ?? []).map((h) => `${t.target}: ${h}`));
|
|
100
|
+
const doc = {
|
|
101
|
+
planDigest: await slotDigest(target, tenants.map((t) => ({ target: t.target, planDigest: t.plan.planDigest }))),
|
|
102
|
+
destructive: tenants.some((t) => t.plan.destructive),
|
|
103
|
+
empty: tenants.every((t) => t.plan.empty),
|
|
104
|
+
environment: target,
|
|
105
|
+
tenants,
|
|
106
|
+
...(held.length > 0 ? { held } : {}),
|
|
107
|
+
};
|
|
108
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
109
|
+
writeFileSync(path, `${JSON.stringify(doc, null, 2)}\n`);
|
|
110
|
+
log(`${target}: ${String(tenants.length)} tenant${tenants.length === 1 ? "" : "s"} of ${slot.env} (read now); plan ${doc.planDigest}`);
|
|
111
|
+
return doc;
|
|
112
|
+
}
|
|
113
|
+
function readSlotPlanSoftly(path) {
|
|
114
|
+
try {
|
|
115
|
+
const doc = JSON.parse(readFileSync(path, "utf8"));
|
|
116
|
+
return Array.isArray(doc.tenants) ? doc : undefined;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Reads a slot's plan file. */
|
|
123
|
+
export function readSlotPlan(file) {
|
|
124
|
+
const path = resolve(file);
|
|
125
|
+
if (!existsSync(path))
|
|
126
|
+
throw new YodelError(`refused: the slot's plan file is not at ${path}. --wave-apply runs inside chant run wave, after it decided the wave; nothing was applied`, EXIT_REFUSED);
|
|
127
|
+
const doc = readSlotPlanSoftly(path);
|
|
128
|
+
if (!doc || typeof doc.planDigest !== "string")
|
|
129
|
+
throw new YodelError(`${file} is not a plan yodel apply <env>/@<i>of<n> --wave-plan wrote`);
|
|
130
|
+
return doc;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* `yodel apply <env>/@<i>of<n> --wave-apply <file>`: each tenant the slot's
|
|
134
|
+
* plan names, through `applyTenant` (the tenant's own `--wave-apply`, with
|
|
135
|
+
* the decision checked for the slot). The exit code is 1 when a tenant
|
|
136
|
+
* failed, else 4 when a tenant's plan moved since the decision, else 0.
|
|
137
|
+
*/
|
|
138
|
+
export async function applySlot(target, file, applyTenant, log) {
|
|
139
|
+
const planned = readSlotPlan(file);
|
|
140
|
+
if (planned.environment !== target)
|
|
141
|
+
throw new YodelError(`refused: ${file} is ${planned.environment}'s plan, not ${target}'s; nothing was applied`, EXIT_REFUSED);
|
|
142
|
+
const moved = [];
|
|
143
|
+
const failed = [];
|
|
144
|
+
for (const { target: tenant, plan } of planned.tenants) {
|
|
145
|
+
const tenantFile = `${resolve(file)}.${splitTarget(tenant).tenant ?? "tenant"}.json`;
|
|
146
|
+
writeFileSync(tenantFile, `${JSON.stringify(plan, null, 2)}\n`);
|
|
147
|
+
try {
|
|
148
|
+
const code = await applyTenant(tenant, tenantFile, { target, planDigest: planned.planDigest });
|
|
149
|
+
if (code !== 0)
|
|
150
|
+
failed.push(tenant);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
if (e instanceof WavePlanMoved) {
|
|
154
|
+
log(`${tenant}: its plan moved since the wave was decided, so it was not applied. ${e.message}`);
|
|
155
|
+
moved.push(tenant);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
log(`${tenant}: ${e instanceof Error ? e.message : String(e)}`);
|
|
159
|
+
failed.push(tenant);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const applied = planned.tenants.length - moved.length - failed.length;
|
|
164
|
+
log(`${target}: ${String(applied)} of ${String(planned.tenants.length)} tenants applied${moved.length > 0 ? `; moved, not applied: ${moved.join(", ")}` : ""}${failed.length > 0 ? `; failed: ${failed.join(", ")}` : ""}`);
|
|
165
|
+
return failed.length > 0 ? 1 : moved.length > 0 ? EXIT_REFUSED : 0;
|
|
166
|
+
}
|
|
167
|
+
/** Every tenant of `set` now, each against its own history. */
|
|
168
|
+
export async function viewTenants(project, set, ordered, options = {}) {
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const t of (await listTenants(project, set)) ?? []) {
|
|
171
|
+
const env = resolveEnvironment(project, tenantTarget(set, t));
|
|
172
|
+
const venv = await openVersioned(project, env);
|
|
173
|
+
try {
|
|
174
|
+
const report = await statusReport(venv, ordered, { allowOutOfOrder: options.allowOutOfOrder === true, sync: false });
|
|
175
|
+
out.push({ target: env.name, report, ...(report.refusal ? {} : { plan: await viewWavePlan(project, venv, ordered, options) }) });
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await closeVersioned(venv);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* `set`'s wave over `tenants`, when yodel.config.ts `waves` names it: the
|
|
185
|
+
* runs the deciding job plans, their digests, and the set digest over them,
|
|
186
|
+
* which is what one approval binds. The `before` steps are not run here.
|
|
187
|
+
*/
|
|
188
|
+
export async function viewTenantSetWave(project, set, tenants) {
|
|
189
|
+
const waves = project.yodel?.config.waves ?? [];
|
|
190
|
+
const k = waves.findIndex((w) => w.env === set) + 1;
|
|
191
|
+
if (k === 0)
|
|
192
|
+
return undefined;
|
|
193
|
+
const { gate, at } = await gateAtBase(project, set);
|
|
194
|
+
const refused = tenants.filter((t) => !t.plan).map((t) => t.target);
|
|
195
|
+
const blocked = tenants.filter((t) => t.plan?.blocked).map((t) => t.target);
|
|
196
|
+
const planned = tenants.flatMap((t) => (t.plan ? [{ target: t.target, plan: t.plan }] : []));
|
|
197
|
+
const runs = [];
|
|
198
|
+
if (readAtRunTime(tenantSetOf(project, set))) {
|
|
199
|
+
const n = Math.max(1, waves[k - 1]?.shares ?? 1);
|
|
200
|
+
for (let i = 1; i <= n; i++) {
|
|
201
|
+
const target = slotTarget(set, i, n);
|
|
202
|
+
const mine = planned.filter((t) => slotIndex(splitTarget(t.target).tenant ?? "", n) === i).sort((a, b) => (a.target < b.target ? -1 : 1));
|
|
203
|
+
runs.push({
|
|
204
|
+
target,
|
|
205
|
+
tenants: mine.map((t) => t.target),
|
|
206
|
+
planDigest: await slotDigest(target, mine.map((t) => ({ target: t.target, planDigest: t.plan.planDigest }))),
|
|
207
|
+
destructive: mine.some((t) => t.plan.destructive),
|
|
208
|
+
empty: mine.every((t) => t.plan.empty),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
for (const t of planned)
|
|
214
|
+
runs.push({ target: t.target, tenants: [t.target], planDigest: t.plan.planDigest, destructive: t.plan.destructive, empty: t.plan.empty });
|
|
215
|
+
}
|
|
216
|
+
const view = { wave: k, gate, base: at.sha, baseSource: at.base.source, runs, refused, blocked, waits: false };
|
|
217
|
+
if (refused.length > 0)
|
|
218
|
+
return view;
|
|
219
|
+
const { waveSetDigest, waveGateName } = await import("@intentius/chant/gated-waves");
|
|
220
|
+
const { opWaveNeedsApproval } = await import("@intentius/chant/op");
|
|
221
|
+
const members = runs.map((r) => ({ member: r.target, planDigest: r.planDigest, op: "", ...(r.destructive ? { destructive: true } : {}), ...(r.empty ? { empty: true } : {}) }));
|
|
222
|
+
view.digest = waveSetDigest(members);
|
|
223
|
+
view.waits = blocked.length === 0 && opWaveNeedsApproval(gate, members);
|
|
224
|
+
const approval = waveApproval(at.config, set);
|
|
225
|
+
view.approve = `chant approve ${WAVES_PIPELINE} ${waveGateName(WAVES_PIPELINE, k)} --plan ${view.digest}${signFlag(approval)}`;
|
|
226
|
+
if (approval !== "ledger")
|
|
227
|
+
view.approval = approval;
|
|
228
|
+
return view;
|
|
229
|
+
}
|
|
230
|
+
/** The tenants of a set grouped by what is pending on them (the same migrations), in the order first seen. */
|
|
231
|
+
export function groupTenants(tenants, key) {
|
|
232
|
+
const groups = new Map();
|
|
233
|
+
for (const t of tenants)
|
|
234
|
+
groups.set(key(t), [...(groups.get(key(t)) ?? []), t]);
|
|
235
|
+
return [...groups].map(([k, members]) => ({ key: k, members }));
|
|
236
|
+
}
|
|
237
|
+
/** Up to `max` names, then how many more. */
|
|
238
|
+
export function someNames(names, code = (s) => s, max = 30) {
|
|
239
|
+
const shown = names.slice(0, max).map(code).join(", ");
|
|
240
|
+
return names.length > max ? `${shown} and ${String(names.length - max)} more` : shown;
|
|
241
|
+
}
|
|
242
|
+
/** One line on a tenant set's wave, for text and markdown. */
|
|
243
|
+
export function describeTenantSetWave(w, count, code = (s) => s) {
|
|
244
|
+
const at = `gate ${code(w.gate)} at ${code(w.base.slice(0, 12))}`;
|
|
245
|
+
const tenants = `${String(count)} tenant${count === 1 ? "" : "s"}`;
|
|
246
|
+
if (w.refused.length > 0)
|
|
247
|
+
return `The apply pipeline fails wave ${String(w.wave)} until yodel apply stops refusing ${someNames(w.refused, code)} (${at}).`;
|
|
248
|
+
if (w.blocked.length > 0)
|
|
249
|
+
return `The apply pipeline refuses wave ${String(w.wave)} until what is pending on ${someNames(w.blocked, code)} has run where ${w.blocked.length === 1 ? "it requires" : "they require"} first; approve it then (${at}).`;
|
|
250
|
+
if (!w.waits)
|
|
251
|
+
return `The apply pipeline applies this to ${tenants} in wave ${String(w.wave)} with no approval (${at}).`;
|
|
252
|
+
return `The apply pipeline waits in wave ${String(w.wave)} for one approval of ${code(w.digest ?? "")}, for all ${tenants} (${at}).${w.approval && w.approval !== "ledger" ? ` ${describeApprovalMode(w.approval, code)}` : ""}`;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=wave.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wave.js","sourceRoot":"","sources":["../../src/tenants/wave.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for migrations and schema objects (#54): what a project's
|
|
3
|
+
* `tests/*.test.ts` files declare, and the helpers they declare it with.
|
|
4
|
+
*
|
|
5
|
+
* import { defineTests, expectError, expectRows, expectValue } from "@intentius/sql-yodeler";
|
|
6
|
+
*
|
|
7
|
+
* export default defineTests([
|
|
8
|
+
* {
|
|
9
|
+
* name: "fill-country fills every event's country",
|
|
10
|
+
* at: "20261010T0530-add-source",
|
|
11
|
+
* seed: ["INSERT INTO shop.events (id, kind, at) VALUES (1, 'view', '2026-06-01 10:00:00')"],
|
|
12
|
+
* expect: [expectValue("SELECT count() FROM shop.events WHERE country = ''", 0)],
|
|
13
|
+
* },
|
|
14
|
+
* ]);
|
|
15
|
+
*
|
|
16
|
+
* A case runs on a database of its own, built by `yodel test` from the
|
|
17
|
+
* migrations: every migration up to `at` (all of them when `at` is not
|
|
18
|
+
* given) is replayed into an empty database, the `seed` statements run,
|
|
19
|
+
* the migrations after `at` up to `through` (default: the newest) run with
|
|
20
|
+
* their steps, backfills included, and then each expectation is checked.
|
|
21
|
+
* A case without `at` tests the schema as the newest migration leaves it:
|
|
22
|
+
* a view's rows, a function's result, a constraint that refuses a row.
|
|
23
|
+
*/
|
|
24
|
+
/** Rows a query must return, in order: each row its column values in order. */
|
|
25
|
+
export interface RowsExpectation {
|
|
26
|
+
kind: "rows";
|
|
27
|
+
sql: string;
|
|
28
|
+
rows: unknown[][];
|
|
29
|
+
}
|
|
30
|
+
/** The first column of the first row a query returns. */
|
|
31
|
+
export interface ValueExpectation {
|
|
32
|
+
kind: "value";
|
|
33
|
+
sql: string;
|
|
34
|
+
value: unknown;
|
|
35
|
+
}
|
|
36
|
+
/** A statement that must fail; with `match`, with a message that contains it (a string) or matches it (a pattern). */
|
|
37
|
+
export interface ErrorExpectation {
|
|
38
|
+
kind: "error";
|
|
39
|
+
sql: string;
|
|
40
|
+
match?: string | RegExp;
|
|
41
|
+
}
|
|
42
|
+
export type Expectation = RowsExpectation | ValueExpectation | ErrorExpectation;
|
|
43
|
+
export interface TestCase {
|
|
44
|
+
/** Names the case in the output, in the JUnit file and for `--run`. */
|
|
45
|
+
name: string;
|
|
46
|
+
/** The migration the seed runs after. Default: the newest, so nothing runs after the seed. */
|
|
47
|
+
at?: string;
|
|
48
|
+
/** Statements run once the database is at `at`: the data the migrations after it meet. */
|
|
49
|
+
seed?: string[];
|
|
50
|
+
/** The last migration run after the seed. Default: the newest. */
|
|
51
|
+
through?: string;
|
|
52
|
+
/** Checked in order, after the migrations ran; every one is checked, even after one fails. */
|
|
53
|
+
expect: Expectation[];
|
|
54
|
+
}
|
|
55
|
+
/** Types a test file's cases; returns them as given. */
|
|
56
|
+
export declare const defineTests: (cases: TestCase[]) => TestCase[];
|
|
57
|
+
/** The query returns exactly `rows`, in order. Values compare as text: `1` and `"1"` are the same, a date as its ISO string. */
|
|
58
|
+
export declare const expectRows: (sql: string, rows: unknown[][]) => RowsExpectation;
|
|
59
|
+
/** The query's first row's first column is `value`, compared as text. */
|
|
60
|
+
export declare const expectValue: (sql: string, value: unknown) => ValueExpectation;
|
|
61
|
+
/** The statement fails; with `match`, with a message containing it (or matching it). */
|
|
62
|
+
export declare const expectError: (sql: string, match?: string | RegExp) => ErrorExpectation;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for migrations and schema objects (#54): what a project's
|
|
3
|
+
* `tests/*.test.ts` files declare, and the helpers they declare it with.
|
|
4
|
+
*
|
|
5
|
+
* import { defineTests, expectError, expectRows, expectValue } from "@intentius/sql-yodeler";
|
|
6
|
+
*
|
|
7
|
+
* export default defineTests([
|
|
8
|
+
* {
|
|
9
|
+
* name: "fill-country fills every event's country",
|
|
10
|
+
* at: "20261010T0530-add-source",
|
|
11
|
+
* seed: ["INSERT INTO shop.events (id, kind, at) VALUES (1, 'view', '2026-06-01 10:00:00')"],
|
|
12
|
+
* expect: [expectValue("SELECT count() FROM shop.events WHERE country = ''", 0)],
|
|
13
|
+
* },
|
|
14
|
+
* ]);
|
|
15
|
+
*
|
|
16
|
+
* A case runs on a database of its own, built by `yodel test` from the
|
|
17
|
+
* migrations: every migration up to `at` (all of them when `at` is not
|
|
18
|
+
* given) is replayed into an empty database, the `seed` statements run,
|
|
19
|
+
* the migrations after `at` up to `through` (default: the newest) run with
|
|
20
|
+
* their steps, backfills included, and then each expectation is checked.
|
|
21
|
+
* A case without `at` tests the schema as the newest migration leaves it:
|
|
22
|
+
* a view's rows, a function's result, a constraint that refuses a row.
|
|
23
|
+
*/
|
|
24
|
+
/** Types a test file's cases; returns them as given. */
|
|
25
|
+
export const defineTests = (cases) => cases;
|
|
26
|
+
/** The query returns exactly `rows`, in order. Values compare as text: `1` and `"1"` are the same, a date as its ISO string. */
|
|
27
|
+
export const expectRows = (sql, rows) => ({ kind: "rows", sql, rows });
|
|
28
|
+
/** The query's first row's first column is `value`, compared as text. */
|
|
29
|
+
export const expectValue = (sql, value) => ({ kind: "value", sql, value });
|
|
30
|
+
/** The statement fails; with `match`, with a message containing it (or matching it). */
|
|
31
|
+
export const expectError = (sql, match) => ({ kind: "error", sql, ...(match !== undefined ? { match } : {}) });
|
|
32
|
+
//# sourceMappingURL=define.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.js","sourceRoot":"","sources":["../../src/testing/define.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAsCH,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAiB,EAAc,EAAE,CAAC,KAAK,CAAC;AAEpE,gIAAgI;AAChI,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAiB,EAAmB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AAE7G,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,KAAc,EAAoB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AAE9G,wFAAwF;AACxF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,KAAuB,EAAoB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Where `yodel test` (#54) finds a project's test files: tests/*.test.ts. */
|
|
2
|
+
/** Where test files are, under the project directory. */
|
|
3
|
+
export declare const TESTS_DIR = "tests";
|
|
4
|
+
/** The project's test files, by name; none when there is no tests/ directory. */
|
|
5
|
+
export declare function testFiles(dir: string): string[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Where `yodel test` (#54) finds a project's test files: tests/*.test.ts. */
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/** Where test files are, under the project directory. */
|
|
5
|
+
export const TESTS_DIR = "tests";
|
|
6
|
+
const TEST_FILE = /\.test\.(ts|mts|js|mjs)$/;
|
|
7
|
+
/** The project's test files, by name; none when there is no tests/ directory. */
|
|
8
|
+
export function testFiles(dir) {
|
|
9
|
+
let names;
|
|
10
|
+
try {
|
|
11
|
+
names = readdirSync(join(dir, TESTS_DIR));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return names.filter((n) => TEST_FILE.test(n)).sort().map((n) => join(dir, TESTS_DIR, n));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/testing/files.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,yDAAyD;AACzD,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC;AACjC,MAAM,SAAS,GAAG,0BAA0B,CAAC;AAE7C,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel test`'s outcome for people (text), for tools (`--json`,
|
|
3
|
+
* schemas/test.schema.json) and for CI (`--junit <file>`, JUnit XML: one
|
|
4
|
+
* testsuite per test file, one testcase per case).
|
|
5
|
+
*/
|
|
6
|
+
import type { TestRun } from "./run.js";
|
|
7
|
+
/** Exit code: a case failed, or could not run. */
|
|
8
|
+
export declare const TESTS_FAILED = 3;
|
|
9
|
+
export declare const testExitCode: (run: TestRun) => number;
|
|
10
|
+
export declare function renderTests(run: TestRun): string;
|
|
11
|
+
/** `--json`: schemas/test.schema.json. */
|
|
12
|
+
export declare function testJson(run: TestRun): Record<string, unknown>;
|
|
13
|
+
/** `--junit <file>`: JUnit XML, one testsuite per test file. */
|
|
14
|
+
export declare function testJunit(run: TestRun): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel test`'s outcome for people (text), for tools (`--json`,
|
|
3
|
+
* schemas/test.schema.json) and for CI (`--junit <file>`, JUnit XML: one
|
|
4
|
+
* testsuite per test file, one testcase per case).
|
|
5
|
+
*/
|
|
6
|
+
/** Exit code: a case failed, or could not run. */
|
|
7
|
+
export const TESTS_FAILED = 3;
|
|
8
|
+
export const testExitCode = (run) => (run.cases.some((c) => c.status !== "passed") ? TESTS_FAILED : 0);
|
|
9
|
+
const count = (run, status) => run.cases.filter((c) => c.status === status).length;
|
|
10
|
+
export function renderTests(run) {
|
|
11
|
+
const lines = [`yodel test on ${run.environment} (${run.where})${run.reset ? `, ${run.scope.join(", ")} emptied first` : ""}:`];
|
|
12
|
+
for (const c of run.cases) {
|
|
13
|
+
lines.push(` ${c.status === "passed" ? "pass" : c.status === "failed" ? "FAIL" : "ERROR"} ${c.file} > ${c.name} (${String(c.ms)} ms)`);
|
|
14
|
+
if (c.error)
|
|
15
|
+
lines.push(` ${c.error}`);
|
|
16
|
+
for (const f of c.failures)
|
|
17
|
+
lines.push(` ${f}`);
|
|
18
|
+
}
|
|
19
|
+
if (run.cases.length === 0)
|
|
20
|
+
lines.push(" no case ran");
|
|
21
|
+
lines.push(`${String(count(run, "passed"))} passed, ${String(count(run, "failed"))} failed, ${String(count(run, "error"))} errors`);
|
|
22
|
+
return `${lines.join("\n")}\n`;
|
|
23
|
+
}
|
|
24
|
+
/** `--json`: schemas/test.schema.json. */
|
|
25
|
+
export function testJson(run) {
|
|
26
|
+
return {
|
|
27
|
+
environment: run.environment,
|
|
28
|
+
dialect: run.dialect,
|
|
29
|
+
where: run.where,
|
|
30
|
+
scope: run.scope,
|
|
31
|
+
reset: run.reset,
|
|
32
|
+
files: run.files,
|
|
33
|
+
summary: { passed: count(run, "passed"), failed: count(run, "failed"), errors: count(run, "error") },
|
|
34
|
+
cases: run.cases.map((c) => ({
|
|
35
|
+
file: c.file,
|
|
36
|
+
name: c.name,
|
|
37
|
+
status: c.status,
|
|
38
|
+
ms: c.ms,
|
|
39
|
+
failures: c.failures,
|
|
40
|
+
...(c.error !== undefined ? { error: c.error } : {}),
|
|
41
|
+
before: c.before,
|
|
42
|
+
after: c.after,
|
|
43
|
+
})),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const xml = (s) => s
|
|
47
|
+
.replace(/&/g, "&")
|
|
48
|
+
.replace(/</g, "<")
|
|
49
|
+
.replace(/>/g, ">")
|
|
50
|
+
.replace(/"/g, """)
|
|
51
|
+
// XML 1.0 has no place for most control characters.
|
|
52
|
+
// eslint-disable-next-line no-control-regex
|
|
53
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, "");
|
|
54
|
+
const secs = (ms) => (ms / 1000).toFixed(3);
|
|
55
|
+
/** `--junit <file>`: JUnit XML, one testsuite per test file. */
|
|
56
|
+
export function testJunit(run) {
|
|
57
|
+
const out = ['<?xml version="1.0" encoding="UTF-8"?>'];
|
|
58
|
+
const total = run.cases.reduce((n, c) => n + c.ms, 0);
|
|
59
|
+
out.push(`<testsuites name="yodel test" tests="${String(run.cases.length)}" failures="${String(count(run, "failed"))}" errors="${String(count(run, "error"))}" time="${secs(total)}">`);
|
|
60
|
+
for (const file of run.files) {
|
|
61
|
+
const cases = run.cases.filter((c) => c.file === file);
|
|
62
|
+
const failures = cases.filter((c) => c.status === "failed").length;
|
|
63
|
+
const errors = cases.filter((c) => c.status === "error").length;
|
|
64
|
+
out.push(` <testsuite name="${xml(file)}" tests="${String(cases.length)}" failures="${String(failures)}" errors="${String(errors)}" time="${secs(cases.reduce((n, c) => n + c.ms, 0))}">`);
|
|
65
|
+
for (const c of cases) {
|
|
66
|
+
const open = ` <testcase classname="${xml(file)}" name="${xml(c.name)}" time="${secs(c.ms)}"`;
|
|
67
|
+
if (c.status === "passed") {
|
|
68
|
+
out.push(`${open}/>`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
out.push(`${open}>`);
|
|
72
|
+
if (c.status === "failed")
|
|
73
|
+
out.push(` <failure message="${xml(c.failures[0] ?? "")}">${xml(c.failures.join("\n"))}</failure>`);
|
|
74
|
+
else
|
|
75
|
+
out.push(` <error message="${xml(c.error ?? "")}">${xml(c.error ?? "")}</error>`);
|
|
76
|
+
out.push(" </testcase>");
|
|
77
|
+
}
|
|
78
|
+
out.push(" </testsuite>");
|
|
79
|
+
}
|
|
80
|
+
out.push("</testsuites>");
|
|
81
|
+
return `${out.join("\n")}\n`;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/testing/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel test` (#54): the cases of a project's `tests/*.test.ts`
|
|
3
|
+
* (`./define.ts`), each run on a database built by replaying the
|
|
4
|
+
* migrations on a throwaway server.
|
|
5
|
+
*
|
|
6
|
+
* Per case, on the server the environment's profile binds:
|
|
7
|
+
*
|
|
8
|
+
* 1. the databases (ClickHouse) or schemas (Postgres) the history names are
|
|
9
|
+
* emptied, so every case starts from nothing;
|
|
10
|
+
* 2. the migrations up to the case's `at` are replayed as `yodel lint
|
|
11
|
+
* --replay` replays them: statements, and the rebuild and
|
|
12
|
+
* PostgresMigrationOp steps apply runs, but not backfills, which would
|
|
13
|
+
* find no rows;
|
|
14
|
+
* 3. the `seed` statements run;
|
|
15
|
+
* 4. the migrations after `at`, up to `through`, run with every step,
|
|
16
|
+
* backfills included.
|
|
17
|
+
*
|
|
18
|
+
* A case's receipts, a backfill's batches and a ClickHouse rebuild's
|
|
19
|
+
* partitions, are kept in memory for that case alone (#112): nothing is
|
|
20
|
+
* written to the server's receipts table, and no case skips work because
|
|
21
|
+
* another case did it, or a run before this one.
|
|
22
|
+
* 5. each expectation is checked, every one even after one fails.
|
|
23
|
+
*
|
|
24
|
+
* A checkpoint (#63) is left out of the chain, as the replay leaves it out.
|
|
25
|
+
*
|
|
26
|
+
* It never runs against an environment yodel applies to: an environment
|
|
27
|
+
* whose history table holds a row is refused, `--reset` or not, and so is a
|
|
28
|
+
* target that already holds what the history names (unless `--reset`), and
|
|
29
|
+
* one another profile on the same server names (unless `--reset-shared`),
|
|
30
|
+
* as the replay refuses them. Nothing is written to a history. The
|
|
31
|
+
* databases are emptied again at the end unless `keep`.
|
|
32
|
+
*/
|
|
33
|
+
import { type Environment, type Project } from "../project.js";
|
|
34
|
+
import { type RebuildReceiptStore, type StepRunner } from "../steps/index.js";
|
|
35
|
+
import type { Expectation, TestCase } from "./define.js";
|
|
36
|
+
export { TESTS_DIR, testFiles } from "./files.js";
|
|
37
|
+
/** One test file and its cases. */
|
|
38
|
+
export interface TestFile {
|
|
39
|
+
/** Relative to the project directory. */
|
|
40
|
+
file: string;
|
|
41
|
+
cases: TestCase[];
|
|
42
|
+
}
|
|
43
|
+
/** What a case's run came to. */
|
|
44
|
+
export interface CaseResult {
|
|
45
|
+
file: string;
|
|
46
|
+
name: string;
|
|
47
|
+
/** passed: every expectation held. failed: one did not. error: the case could not run (a migration or a seed statement failed, an unknown migration id). */
|
|
48
|
+
status: "passed" | "failed" | "error";
|
|
49
|
+
ms: number;
|
|
50
|
+
/** The expectations that did not hold, one line each. */
|
|
51
|
+
failures: string[];
|
|
52
|
+
error?: string;
|
|
53
|
+
/** The migrations replayed before the seed, and those run after it. */
|
|
54
|
+
before: string[];
|
|
55
|
+
after: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface TestRun {
|
|
58
|
+
environment: string;
|
|
59
|
+
dialect: Project["dialect"];
|
|
60
|
+
where: string;
|
|
61
|
+
scope: string[];
|
|
62
|
+
/** The target held what the history names, and was emptied first (`reset`). */
|
|
63
|
+
reset: boolean;
|
|
64
|
+
files: string[];
|
|
65
|
+
cases: CaseResult[];
|
|
66
|
+
}
|
|
67
|
+
/** Imports each test file and returns its cases: the default export, or `tests`. Throws naming what is wrong. */
|
|
68
|
+
export declare function loadTests(projectDir: string, files?: string[]): Promise<TestFile[]>;
|
|
69
|
+
/** A value as text, for comparing what a query returned with what a case expects. */
|
|
70
|
+
export declare function asText(v: unknown): string;
|
|
71
|
+
/** What running an expectation's SQL gave: its rows, or the server's error. */
|
|
72
|
+
export type Outcome = {
|
|
73
|
+
rows: Array<Record<string, unknown>>;
|
|
74
|
+
} | {
|
|
75
|
+
error: string;
|
|
76
|
+
};
|
|
77
|
+
/** Why `e` does not hold for `outcome`, or undefined when it holds. */
|
|
78
|
+
export declare function expectationFailure(e: Expectation, outcome: Outcome): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* A receipt store for one case: a backfill's batches and a rebuild's
|
|
81
|
+
* partitions each run once in it, and nothing outlives the case.
|
|
82
|
+
*
|
|
83
|
+
* It keeps each receipt under its effect. A rebuild reads its receipts with
|
|
84
|
+
* `readAll(prefix)` and looks each up by address, the effect after the
|
|
85
|
+
* store's identity (`<stack>/<env>/`); the identity is the part of the
|
|
86
|
+
* prefix before the effects it asks for.
|
|
87
|
+
*/
|
|
88
|
+
export declare function memoryReceipts(): RebuildReceiptStore;
|
|
89
|
+
export interface RunTestsOptions {
|
|
90
|
+
/** Only the cases whose name matches. */
|
|
91
|
+
run?: RegExp;
|
|
92
|
+
/** Empty the target first when it holds what the history names. */
|
|
93
|
+
reset?: boolean;
|
|
94
|
+
/** Other profiles on the same server whose databases or schemas the run may empty. */
|
|
95
|
+
resetShared?: readonly string[];
|
|
96
|
+
/** Leave the last case's database in place. */
|
|
97
|
+
keep?: boolean;
|
|
98
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
99
|
+
log?: (line: string) => void;
|
|
100
|
+
/** Runs a backfill step (and every other Op step the replay driver does not). Default: as apply runs it. */
|
|
101
|
+
runStep?: StepRunner;
|
|
102
|
+
}
|
|
103
|
+
/** Runs the project's test cases on `env`'s server; see the module comment. */
|
|
104
|
+
export declare function runTests(project: Project, env: Environment, files: readonly TestFile[], options?: RunTestsOptions): Promise<TestRun>;
|