@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
package/dist/commands/status.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { guarded, parseEnvArgs, usage } from "../env-args.js";
|
|
2
2
|
import { loadProject, resolveEnvironment } from "../project.js";
|
|
3
|
+
import { listTenants, tenantTarget } from "../tenants/index.js";
|
|
3
4
|
import { closeVersioned, openVersioned, readChain, renderStatus, statusJson, statusReport } from "../versioned/index.js";
|
|
4
5
|
/** Exit code: migrations are pending, and nothing is wrong. */
|
|
5
6
|
export const STATUS_PENDING = 3;
|
|
@@ -16,6 +17,10 @@ whose files changed, or that is gone from the directory). When yodel apply
|
|
|
16
17
|
would run, prints the plan digest it would ask approval for. Reads only;
|
|
17
18
|
takes no lock.
|
|
18
19
|
|
|
20
|
+
An environment with a tenant set (yodel.config.ts environments.<env>.tenants)
|
|
21
|
+
is listed tenant by tenant, each against its own history, then a line of how
|
|
22
|
+
many tenants are up to date; <env>/<tenant> names one tenant.
|
|
23
|
+
|
|
19
24
|
Options:
|
|
20
25
|
--json print the status as JSON
|
|
21
26
|
-C, --dir <dir> the project directory (default: the working directory)
|
|
@@ -39,16 +44,38 @@ export const status = {
|
|
|
39
44
|
const project = await loadProject(dir);
|
|
40
45
|
const env = resolveEnvironment(project, name);
|
|
41
46
|
const ordered = readChain(project);
|
|
42
|
-
const
|
|
43
|
-
|
|
47
|
+
const tenants = env.tenant ? undefined : await listTenants(project, env.name);
|
|
48
|
+
if (tenants)
|
|
49
|
+
return tenantsStatus(project, env.name, tenants, ordered, json, io);
|
|
50
|
+
const report = await readStatus(project, env.name, ordered);
|
|
44
51
|
if (json)
|
|
45
52
|
io.out(`${JSON.stringify(statusJson(report), null, 2)}\n`);
|
|
46
53
|
// The sections already say what is wrong; the refusal's wording is apply's.
|
|
47
54
|
else
|
|
48
55
|
io.out(renderStatus({ ...report, refusal: undefined }));
|
|
49
|
-
|
|
50
|
-
return report.refusal.exitCode;
|
|
51
|
-
return report.status.pending.length > 0 ? STATUS_PENDING : 0;
|
|
56
|
+
return exitOf(report);
|
|
52
57
|
}),
|
|
53
58
|
};
|
|
59
|
+
async function readStatus(project, name, ordered) {
|
|
60
|
+
const venv = await openVersioned(project, resolveEnvironment(project, name));
|
|
61
|
+
return statusReport(venv, ordered, { sync: false }).finally(() => closeVersioned(venv));
|
|
62
|
+
}
|
|
63
|
+
const exitOf = (report) => report.refusal?.exitCode ?? (report.status.pending.length > 0 ? STATUS_PENDING : 0);
|
|
64
|
+
/** A tenant set's status (#56): each tenant's, then how many are up to date. The exit code is the worst tenant's: a refusal, then pending. */
|
|
65
|
+
async function tenantsStatus(project, env, tenants, ordered, json, io) {
|
|
66
|
+
const reports = [];
|
|
67
|
+
for (const t of tenants) {
|
|
68
|
+
const report = await readStatus(project, tenantTarget(env, t), ordered);
|
|
69
|
+
reports.push(report);
|
|
70
|
+
if (!json)
|
|
71
|
+
io.out(`${renderStatus({ ...report, refusal: undefined })}\n`);
|
|
72
|
+
}
|
|
73
|
+
const upToDate = reports.filter((r) => !r.refusal && r.status.pending.length === 0).length;
|
|
74
|
+
if (json)
|
|
75
|
+
io.out(`${JSON.stringify({ environment: env, tenants: reports.map((r) => statusJson(r)), upToDate, total: reports.length }, null, 2)}\n`);
|
|
76
|
+
else
|
|
77
|
+
io.out(`${env}: ${String(upToDate)} of ${String(reports.length)} tenants up to date${upToDate < reports.length ? `; behind: ${reports.filter((r) => r.refusal || r.status.pending.length > 0).map((r) => r.environment).join(", ")}` : ""}\n`);
|
|
78
|
+
const codes = reports.map(exitOf);
|
|
79
|
+
return codes.find((c) => c !== 0 && c !== STATUS_PENDING) ?? (codes.includes(STATUS_PENDING) ? STATUS_PENDING : 0);
|
|
80
|
+
}
|
|
54
81
|
//# sourceMappingURL=status.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { guarded } from "../env-args.js";
|
|
5
|
+
import { loadProject, resolveEnvironment, YodelError } from "../project.js";
|
|
6
|
+
import { readChain } from "../versioned/index.js";
|
|
7
|
+
import { loadTests, runTests, testFiles, TESTS_DIR } from "../testing/run.js";
|
|
8
|
+
import { renderTests, testExitCode, testJson, testJunit } from "../testing/report.js";
|
|
9
|
+
const usage = "usage: yodel test [--env <env>] [--run <regexp>] [--reset] [--reset-shared <profile>]... [--keep] [--json] [--junit <file>] [--dir <project>]";
|
|
10
|
+
const help = `${usage}
|
|
11
|
+
|
|
12
|
+
Runs the cases in tests/*.test.ts against databases built from the
|
|
13
|
+
migrations on a throwaway server: the profile --env names (default: test,
|
|
14
|
+
else replay). A case seeds data at a migration and checks it after the
|
|
15
|
+
migrations that follow (backfills included), or checks the schema the newest
|
|
16
|
+
migration leaves (a view's rows, a function's result, a constraint that
|
|
17
|
+
refuses a row):
|
|
18
|
+
|
|
19
|
+
import { defineTests, expectError, expectRows, expectValue } from "@intentius/sql-yodeler";
|
|
20
|
+
|
|
21
|
+
export default defineTests([
|
|
22
|
+
{
|
|
23
|
+
name: "fill-country fills every country",
|
|
24
|
+
at: "20261010T0530-add-source", // seed after this migration
|
|
25
|
+
seed: ["INSERT INTO shop.events (id, kind, at) VALUES (1, 'view', now())"],
|
|
26
|
+
expect: [expectValue("SELECT count() FROM shop.events WHERE country = ''", 0)],
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
Per case, the databases (or schemas) the history names are emptied, the
|
|
31
|
+
migrations up to at (default: the newest) are replayed as yodel lint
|
|
32
|
+
--replay replays them (backfills skipped: there are no rows yet), the seed
|
|
33
|
+
runs, the migrations after at up to through (default: the newest) run with
|
|
34
|
+
every step, backfills included, and each expectation is checked. Values
|
|
35
|
+
compare as text. Nothing is written to a history.
|
|
36
|
+
|
|
37
|
+
It never runs against an environment yodel applies to: a target whose
|
|
38
|
+
history table holds a row is refused, and so is one that already holds what
|
|
39
|
+
the history names (unless --reset), or one another profile on the same
|
|
40
|
+
server names (unless --reset-shared <profile>).
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--env <env> the throwaway server's profile (default: test, else
|
|
44
|
+
replay)
|
|
45
|
+
--run <regexp> only the cases whose name matches
|
|
46
|
+
--reset drop what the history names first when it is there.
|
|
47
|
+
Destructive: never against a server whose data
|
|
48
|
+
matters
|
|
49
|
+
--reset-shared <profile>
|
|
50
|
+
run even though <profile> binds the same server and
|
|
51
|
+
names one of those databases or schemas (repeatable)
|
|
52
|
+
--keep leave the last case's database in place
|
|
53
|
+
--json print the outcome as JSON
|
|
54
|
+
--junit <file> also write the outcome as JUnit XML to <file>
|
|
55
|
+
-C, --dir <dir> the project directory (default: the working directory)
|
|
56
|
+
|
|
57
|
+
Exit codes:
|
|
58
|
+
0 every case passed (or there is no case)
|
|
59
|
+
3 a case failed, or could not run (a migration or seed statement failed)
|
|
60
|
+
4 refused, nothing run: the target has a migration history, already holds
|
|
61
|
+
what the history names (no --reset), or another profile names it
|
|
62
|
+
1 anything else: a test file that does not load, a bad config, a server
|
|
63
|
+
that does not answer
|
|
64
|
+
`;
|
|
65
|
+
function parseTestArgs(args) {
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = parseArgs({
|
|
69
|
+
args,
|
|
70
|
+
allowPositionals: false,
|
|
71
|
+
strict: true,
|
|
72
|
+
options: {
|
|
73
|
+
env: { type: "string" },
|
|
74
|
+
run: { type: "string" },
|
|
75
|
+
reset: { type: "boolean" },
|
|
76
|
+
"reset-shared": { type: "string", multiple: true },
|
|
77
|
+
keep: { type: "boolean" },
|
|
78
|
+
json: { type: "boolean" },
|
|
79
|
+
junit: { type: "string" },
|
|
80
|
+
dir: { type: "string", short: "C" },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
throw new YodelError(`${e instanceof Error ? e.message : String(e)}\n${usage}`);
|
|
86
|
+
}
|
|
87
|
+
const v = parsed.values;
|
|
88
|
+
let run;
|
|
89
|
+
if (typeof v.run === "string") {
|
|
90
|
+
try {
|
|
91
|
+
run = new RegExp(v.run);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
throw new YodelError(`--run ${v.run}: ${e instanceof Error ? e.message : String(e)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
env: typeof v.env === "string" ? v.env : undefined,
|
|
99
|
+
run,
|
|
100
|
+
reset: v.reset === true,
|
|
101
|
+
resetShared: Array.isArray(v["reset-shared"]) ? v["reset-shared"].filter((r) => typeof r === "string") : [],
|
|
102
|
+
keep: v.keep === true,
|
|
103
|
+
json: v.json === true,
|
|
104
|
+
junit: typeof v.junit === "string" ? v.junit : undefined,
|
|
105
|
+
dir: typeof v.dir === "string" ? v.dir : process.cwd(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** The profile a run uses when --env is not given: test, else replay. */
|
|
109
|
+
function defaultEnv(project) {
|
|
110
|
+
const profiles = Object.keys(project.config.sql?.profiles ?? {});
|
|
111
|
+
const found = ["test", "replay"].find((p) => profiles.includes(p));
|
|
112
|
+
if (!found)
|
|
113
|
+
throw new YodelError(`yodel test runs on a throwaway server: name its profile with --env (chant.config.ts declares no sql.profiles.test or sql.profiles.replay)`);
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
/** `yodel test`; the exit codes are in `help`. */
|
|
117
|
+
export const test = {
|
|
118
|
+
name: "test",
|
|
119
|
+
summary: "run tests/*.test.ts on databases built from the migrations",
|
|
120
|
+
implemented: true,
|
|
121
|
+
run: (args, io) => guarded("test", io, args, help, async () => {
|
|
122
|
+
const a = parseTestArgs(args);
|
|
123
|
+
const project = await loadProject(a.dir);
|
|
124
|
+
const files = testFiles(project.dir);
|
|
125
|
+
if (files.length === 0) {
|
|
126
|
+
if (!a.json)
|
|
127
|
+
io.out(`No test files: ${TESTS_DIR}/*.test.ts holds none.\n`);
|
|
128
|
+
else
|
|
129
|
+
io.out(`${JSON.stringify({ files: [], summary: { passed: 0, failed: 0, errors: 0 }, cases: [] }, null, 2)}\n`);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
const tests = await loadTests(project.dir, files);
|
|
133
|
+
// A directory that is not one chain is refused (exit 4) before anything runs.
|
|
134
|
+
readChain(project);
|
|
135
|
+
const env = resolveEnvironment(project, a.env ?? defaultEnv(project));
|
|
136
|
+
// A rebuild step's executor prints its SQL to stdout: while the cases run, stdout goes to stderr, so --json stays one document.
|
|
137
|
+
const write = process.stdout.write.bind(process.stdout);
|
|
138
|
+
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
139
|
+
let run;
|
|
140
|
+
try {
|
|
141
|
+
run = await runTests(project, env, tests, {
|
|
142
|
+
...(a.run ? { run: a.run } : {}),
|
|
143
|
+
reset: a.reset,
|
|
144
|
+
resetShared: a.resetShared,
|
|
145
|
+
keep: a.keep,
|
|
146
|
+
log: (l) => { io.err(`test: ${l}\n`); },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
process.stdout.write = write;
|
|
151
|
+
}
|
|
152
|
+
if (a.junit !== undefined) {
|
|
153
|
+
const path = resolve(a.junit);
|
|
154
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
155
|
+
writeFileSync(path, testJunit(run));
|
|
156
|
+
}
|
|
157
|
+
if (a.json)
|
|
158
|
+
io.out(`${JSON.stringify(testJson(run), null, 2)}\n`);
|
|
159
|
+
else
|
|
160
|
+
io.out(renderTests(run));
|
|
161
|
+
return testExitCode(run);
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/commands/test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the project's config as it is at the base commit: the commit a
|
|
3
|
+
* change merges onto. A setting that decides how a change is gated (a
|
|
4
|
+
* wave's gate policy) is read there, never from the change itself, so a
|
|
5
|
+
* pull request cannot loosen the rule that gates its own merge.
|
|
6
|
+
*
|
|
7
|
+
* The base is, first one that applies:
|
|
8
|
+
*
|
|
9
|
+
* 1. `YODEL_BASE`, a commit-ish;
|
|
10
|
+
* 2. on a pull request, the merge base of `HEAD` and the target branch
|
|
11
|
+
* (`GITHUB_BASE_REF` on GitHub and Forgejo,
|
|
12
|
+
* `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` on GitLab), on `origin`;
|
|
13
|
+
* 3. `HEAD^1`, the first parent: after a merge or a squash onto the
|
|
14
|
+
* branch, the branch as it was before the change.
|
|
15
|
+
*
|
|
16
|
+
* chant's wave runner reads its spec file at the first parent too
|
|
17
|
+
* (`chant run wave`), so the two agree on a push to the branch.
|
|
18
|
+
*/
|
|
19
|
+
import type { PolicyConfig, YodelConfig } from "./define.js";
|
|
20
|
+
export declare const BASE_ENV = "YODEL_BASE";
|
|
21
|
+
/** Where the base was taken from, for messages. */
|
|
22
|
+
export interface BaseRef {
|
|
23
|
+
/** What `git rev-parse` resolves. */
|
|
24
|
+
ref: string;
|
|
25
|
+
/** How it was chosen. */
|
|
26
|
+
source: string;
|
|
27
|
+
}
|
|
28
|
+
/** The base commit to read at, by the rule at the top. */
|
|
29
|
+
export declare function baseRef(cwd: string, env?: NodeJS.ProcessEnv): BaseRef;
|
|
30
|
+
/** The file at `path` (relative to `cwd`) at commit `ref`; text null when that commit has no such file. */
|
|
31
|
+
export declare function fileAtBase(cwd: string, path: string, ref: string): {
|
|
32
|
+
sha: string;
|
|
33
|
+
text: string | null;
|
|
34
|
+
};
|
|
35
|
+
/** yodel.config.ts as the base commit has it. */
|
|
36
|
+
export interface ConfigAtBase {
|
|
37
|
+
base: BaseRef;
|
|
38
|
+
sha: string;
|
|
39
|
+
/** Undefined when the base has no yodel.config.ts. */
|
|
40
|
+
config: YodelConfig | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Loads `<dir>/yodel.config.ts` as it is at the base. The file is
|
|
44
|
+
* evaluated from a copy beside the project's own, so its imports resolve
|
|
45
|
+
* as they do there, and the copy is removed after.
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadYodelConfigAtBase(dir: string, env?: NodeJS.ProcessEnv): Promise<ConfigAtBase>;
|
|
48
|
+
/** The plan policy (#53) as the base commit has it (#73). */
|
|
49
|
+
export interface PolicyAtBase {
|
|
50
|
+
policy: PolicyConfig | undefined;
|
|
51
|
+
/** The base commit read, when one could be. */
|
|
52
|
+
sha?: string;
|
|
53
|
+
/** Why the working tree's policy is not the one that applies, when it differs; for the plan and apply to say. */
|
|
54
|
+
note?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `policy` from yodel.config.ts at the base commit, never the working
|
|
58
|
+
* tree's: a pull request that weakens or deletes a rule is judged by the
|
|
59
|
+
* rule the branch had. A base with no policy (or no yodel.config.ts) applies
|
|
60
|
+
* none, whatever the working tree adds, and the note says so. A checkout
|
|
61
|
+
* with no base to read (no git, a repository's first commit) applies none
|
|
62
|
+
* either.
|
|
63
|
+
*/
|
|
64
|
+
export declare function policyAtBase(project: {
|
|
65
|
+
dir: string;
|
|
66
|
+
yodel?: {
|
|
67
|
+
config: YodelConfig;
|
|
68
|
+
} | undefined;
|
|
69
|
+
}, env?: NodeJS.ProcessEnv): Promise<PolicyAtBase>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the project's config as it is at the base commit: the commit a
|
|
3
|
+
* change merges onto. A setting that decides how a change is gated (a
|
|
4
|
+
* wave's gate policy) is read there, never from the change itself, so a
|
|
5
|
+
* pull request cannot loosen the rule that gates its own merge.
|
|
6
|
+
*
|
|
7
|
+
* The base is, first one that applies:
|
|
8
|
+
*
|
|
9
|
+
* 1. `YODEL_BASE`, a commit-ish;
|
|
10
|
+
* 2. on a pull request, the merge base of `HEAD` and the target branch
|
|
11
|
+
* (`GITHUB_BASE_REF` on GitHub and Forgejo,
|
|
12
|
+
* `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` on GitLab), on `origin`;
|
|
13
|
+
* 3. `HEAD^1`, the first parent: after a merge or a squash onto the
|
|
14
|
+
* branch, the branch as it was before the change.
|
|
15
|
+
*
|
|
16
|
+
* chant's wave runner reads its spec file at the first parent too
|
|
17
|
+
* (`chant run wave`), so the two agree on a push to the branch.
|
|
18
|
+
*/
|
|
19
|
+
import { execFileSync } from "node:child_process";
|
|
20
|
+
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { YodelError } from "../project.js";
|
|
24
|
+
import { checkYodelConfig, CONFIG_FILE } from "./load.js";
|
|
25
|
+
export const BASE_ENV = "YODEL_BASE";
|
|
26
|
+
const git = (cwd, args) => execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
27
|
+
/** The base commit to read at, by the rule at the top. */
|
|
28
|
+
export function baseRef(cwd, env = process.env) {
|
|
29
|
+
const set = env[BASE_ENV];
|
|
30
|
+
if (set)
|
|
31
|
+
return { ref: set, source: BASE_ENV };
|
|
32
|
+
const target = env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME || env.GITHUB_BASE_REF;
|
|
33
|
+
if (target) {
|
|
34
|
+
try {
|
|
35
|
+
return { ref: git(cwd, ["merge-base", "HEAD", `origin/${target}`]), source: `the merge base with origin/${target}` };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A shallow or partial checkout: fall through to the first parent.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ref: "HEAD^1", source: "HEAD^1, the commit the change merged onto" };
|
|
42
|
+
}
|
|
43
|
+
/** The file at `path` (relative to `cwd`) at commit `ref`; text null when that commit has no such file. */
|
|
44
|
+
export function fileAtBase(cwd, path, ref) {
|
|
45
|
+
let sha;
|
|
46
|
+
try {
|
|
47
|
+
sha = git(cwd, ["rev-parse", "--verify", `${ref}^{commit}`]);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new YodelError(`the config is read at the base commit ${ref}, which this checkout does not have. ` +
|
|
51
|
+
`Check out the full history (fetch-depth: 0 on GitHub and Forgejo, GIT_DEPTH: "0" on GitLab), or set ${BASE_ENV}.`);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return { sha, text: execFileSync("git", ["show", `${sha}:./${path.split("\\").join("/")}`], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }) };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { sha, text: null };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Loads `<dir>/yodel.config.ts` as it is at the base. The file is
|
|
62
|
+
* evaluated from a copy beside the project's own, so its imports resolve
|
|
63
|
+
* as they do there, and the copy is removed after.
|
|
64
|
+
*/
|
|
65
|
+
export async function loadYodelConfigAtBase(dir, env = process.env) {
|
|
66
|
+
const base = baseRef(dir, env);
|
|
67
|
+
const { sha, text } = fileAtBase(dir, CONFIG_FILE, base.ref);
|
|
68
|
+
if (text === null)
|
|
69
|
+
return { base, sha, config: undefined };
|
|
70
|
+
const copy = join(dir, `.yodel.config.base-${sha.slice(0, 12)}-${String(process.pid)}.ts`);
|
|
71
|
+
writeFileSync(copy, text);
|
|
72
|
+
try {
|
|
73
|
+
let mod;
|
|
74
|
+
try {
|
|
75
|
+
mod = (await import(`${pathToFileURL(copy).href}?base=${sha}`));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw new YodelError(`${CONFIG_FILE} at ${sha.slice(0, 12)} (${base.source}): ${e instanceof Error ? e.message : String(e)}`);
|
|
79
|
+
}
|
|
80
|
+
// Not checked against the profiles: the base may name an environment the change removes.
|
|
81
|
+
return { base, sha, config: checkYodelConfig(mod.default, `${CONFIG_FILE} at ${sha.slice(0, 12)}`) };
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (existsSync(copy))
|
|
85
|
+
unlinkSync(copy);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `policy` from yodel.config.ts at the base commit, never the working
|
|
90
|
+
* tree's: a pull request that weakens or deletes a rule is judged by the
|
|
91
|
+
* rule the branch had. A base with no policy (or no yodel.config.ts) applies
|
|
92
|
+
* none, whatever the working tree adds, and the note says so. A checkout
|
|
93
|
+
* with no base to read (no git, a repository's first commit) applies none
|
|
94
|
+
* either.
|
|
95
|
+
*/
|
|
96
|
+
export async function policyAtBase(project, env = process.env) {
|
|
97
|
+
const tree = project.yodel?.config.policy;
|
|
98
|
+
let at;
|
|
99
|
+
try {
|
|
100
|
+
at = await loadYodelConfigAtBase(project.dir, env);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
const why = e instanceof Error ? (e.message.split("\n")[0] ?? "") : String(e);
|
|
104
|
+
return { policy: undefined, ...(tree ? { note: `The policy is read at the base commit, and there is none to read (${why}), so no policy applies.` } : {}) };
|
|
105
|
+
}
|
|
106
|
+
const policy = at.config?.policy;
|
|
107
|
+
const short = at.sha.slice(0, 12);
|
|
108
|
+
if (tree && !policy) {
|
|
109
|
+
return { policy: undefined, sha: at.sha, note: `yodel.config.ts at ${short} (${at.base.source}) declares no policy, so none applies; the working tree's applies once it is on the branch.` };
|
|
110
|
+
}
|
|
111
|
+
if (policy && !samePolicy(tree, policy)) {
|
|
112
|
+
return { policy, sha: at.sha, note: `The policy applied is yodel.config.ts's at ${short} (${at.base.source}), not the working tree's, which applies once it is on the branch.` };
|
|
113
|
+
}
|
|
114
|
+
return { policy, sha: at.sha };
|
|
115
|
+
}
|
|
116
|
+
/** Whether two policies say the same: their rules by id and what each declares (a check by its source), and their overriders. */
|
|
117
|
+
function samePolicy(a, b) {
|
|
118
|
+
const canon = (p) => JSON.stringify(p, (_k, v) => (v instanceof RegExp ? v.toString() : typeof v === "function" ? v.toString() : v));
|
|
119
|
+
return canon(a) === canon(b);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/config/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AAUrC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,IAAc,EAAU,EAAE,CAClD,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAEjG,0DAA0D;AAC1D,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IACvE,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1B,IAAI,GAAG;QAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,mCAAmC,IAAI,GAAG,CAAC,eAAe,CAAC;IAC9E,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,8BAA8B,MAAM,EAAE,EAAE,CAAC;QACvH,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,2CAA2C,EAAE,CAAC;AAChF,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAY,EAAE,GAAW;IAC/D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,UAAU,CAClB,yCAAyC,GAAG,uCAAuC;YACjF,uGAAuG,QAAQ,GAAG,CACrH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9J,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAUD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC3F,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3F,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC;QACH,IAAI,GAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,GAAG,EAAE,CAAC,CAA0B,CAAC;QAC3F,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,UAAU,CAAC,GAAG,WAAW,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChI,CAAC;QACD,yFAAyF;QACzF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,WAAW,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IACvG,CAAC;YAAS,CAAC;QACT,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAqE,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC5I,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;IAC1C,IAAI,EAAgB,CAAC;IACrB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9E,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,qEAAqE,GAAG,0BAA0B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9J,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACjC,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,sBAAsB,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,6FAA6F,EAAE,CAAC;IAC/L,CAAC;IACD,IAAI,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,8CAA8C,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,oEAAoE,EAAE,CAAC;IACnL,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;AACjC,CAAC;AAED,iIAAiI;AACjI,SAAS,UAAU,CAAC,CAA2B,EAAE,CAA2B;IAC1E,MAAM,KAAK,GAAG,CAAC,CAA2B,EAAE,EAAE,CAC5C,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3H,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
|