@intentius/sql-yodeler 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +112 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +29 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +59 -0
- package/dist/chant/apply.js +116 -0
- package/dist/chant/apply.js.map +1 -0
- package/dist/chant/build.d.ts +34 -0
- package/dist/chant/build.js +48 -0
- package/dist/chant/build.js.map +1 -0
- package/dist/chant/drift.d.ts +112 -0
- package/dist/chant/drift.js +238 -0
- package/dist/chant/drift.js.map +1 -0
- package/dist/chant/plan.d.ts +45 -0
- package/dist/chant/plan.js +61 -0
- package/dist/chant/plan.js.map +1 -0
- package/dist/chant/process.d.ts +41 -0
- package/dist/chant/process.js +87 -0
- package/dist/chant/process.js.map +1 -0
- package/dist/chant/runnable.d.ts +19 -0
- package/dist/chant/runnable.js +47 -0
- package/dist/chant/runnable.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +23 -0
- package/dist/command.js +13 -0
- package/dist/command.js.map +1 -0
- package/dist/commands/apply.d.ts +3 -0
- package/dist/commands/apply.js +426 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/approve.d.ts +5 -0
- package/dist/commands/approve.js +111 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/drift.d.ts +14 -0
- package/dist/commands/drift.js +267 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +28 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +139 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +202 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +50 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.js +292 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/override.d.ts +3 -0
- package/dist/commands/override.js +118 -0
- package/dist/commands/override.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +183 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/rebase.d.ts +3 -0
- package/dist/commands/rebase.js +167 -0
- package/dist/commands/rebase.js.map +1 -0
- package/dist/commands/repair.d.ts +14 -0
- package/dist/commands/repair.js +125 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/define.d.ts +148 -0
- package/dist/config/define.js +20 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +162 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +53 -0
- package/dist/config/settings.js +116 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +24 -0
- package/dist/env-args.js +92 -0
- package/dist/env-args.js.map +1 -0
- package/dist/exit.d.ts +20 -0
- package/dist/exit.js +28 -0
- package/dist/exit.js.map +1 -0
- package/dist/history/client.d.ts +25 -0
- package/dist/history/client.js +42 -0
- package/dist/history/client.js.map +1 -0
- package/dist/history/ddl.d.ts +58 -0
- package/dist/history/ddl.js +100 -0
- package/dist/history/ddl.js.map +1 -0
- package/dist/history/index.d.ts +25 -0
- package/dist/history/index.js +26 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/lock.d.ts +104 -0
- package/dist/history/lock.js +230 -0
- package/dist/history/lock.js.map +1 -0
- package/dist/history/postgres/ddl.d.ts +22 -0
- package/dist/history/postgres/ddl.js +47 -0
- package/dist/history/postgres/ddl.js.map +1 -0
- package/dist/history/postgres/index.d.ts +12 -0
- package/dist/history/postgres/index.js +13 -0
- package/dist/history/postgres/index.js.map +1 -0
- package/dist/history/postgres/lock.d.ts +48 -0
- package/dist/history/postgres/lock.js +95 -0
- package/dist/history/postgres/lock.js.map +1 -0
- package/dist/history/postgres/store.d.ts +39 -0
- package/dist/history/postgres/store.js +68 -0
- package/dist/history/postgres/store.js.map +1 -0
- package/dist/history/rows.d.ts +96 -0
- package/dist/history/rows.js +73 -0
- package/dist/history/rows.js.map +1 -0
- package/dist/history/store.d.ts +72 -0
- package/dist/history/store.js +134 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/topology.d.ts +41 -0
- package/dist/history/topology.js +54 -0
- package/dist/history/topology.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/init/adopt.d.ts +70 -0
- package/dist/init/adopt.js +143 -0
- package/dist/init/adopt.js.map +1 -0
- package/dist/init/baseline.d.ts +61 -0
- package/dist/init/baseline.js +88 -0
- package/dist/init/baseline.js.map +1 -0
- package/dist/init/files.d.ts +23 -0
- package/dist/init/files.js +77 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/index.d.ts +16 -0
- package/dist/init/index.js +17 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/live.d.ts +39 -0
- package/dist/init/live.js +68 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +88 -0
- package/dist/lint/check.js +227 -0
- package/dist/lint/check.js.map +1 -0
- package/dist/lint/config.d.ts +21 -0
- package/dist/lint/config.js +43 -0
- package/dist/lint/config.js.map +1 -0
- package/dist/lint/index.d.ts +31 -0
- package/dist/lint/index.js +43 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/project-rules.d.ts +19 -0
- package/dist/lint/project-rules.js +32 -0
- package/dist/lint/project-rules.js.map +1 -0
- package/dist/lint/report.d.ts +7 -0
- package/dist/lint/report.js +48 -0
- package/dist/lint/report.js.map +1 -0
- package/dist/lint/rules.d.ts +53 -0
- package/dist/lint/rules.js +42 -0
- package/dist/lint/rules.js.map +1 -0
- package/dist/lint/silences.d.ts +49 -0
- package/dist/lint/silences.js +91 -0
- package/dist/lint/silences.js.map +1 -0
- package/dist/lint/statements.d.ts +38 -0
- package/dist/lint/statements.js +120 -0
- package/dist/lint/statements.js.map +1 -0
- package/dist/mcp/server.d.ts +40 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +72 -0
- package/dist/mcp/tools.js +178 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/migrations/checks.d.ts +28 -0
- package/dist/migrations/checks.js +97 -0
- package/dist/migrations/checks.js.map +1 -0
- package/dist/migrations/checksum.d.ts +56 -0
- package/dist/migrations/checksum.js +72 -0
- package/dist/migrations/checksum.js.map +1 -0
- package/dist/migrations/create.d.ts +75 -0
- package/dist/migrations/create.js +120 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +229 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +106 -0
- package/dist/migrations/document.js +83 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +21 -0
- package/dist/migrations/index.js +22 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/pr/comment.d.ts +32 -0
- package/dist/pr/comment.js +99 -0
- package/dist/pr/comment.js.map +1 -0
- package/dist/pr/forge.d.ts +54 -0
- package/dist/pr/forge.js +119 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/issue.d.ts +68 -0
- package/dist/pr/issue.js +107 -0
- package/dist/pr/issue.js.map +1 -0
- package/dist/pr/model.d.ts +99 -0
- package/dist/pr/model.js +137 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +29 -0
- package/dist/pr/render.js +279 -0
- package/dist/pr/render.js.map +1 -0
- package/dist/project.d.ts +62 -0
- package/dist/project.js +92 -0
- package/dist/project.js.map +1 -0
- package/dist/rebase/index.d.ts +121 -0
- package/dist/rebase/index.js +390 -0
- package/dist/rebase/index.js.map +1 -0
- package/dist/rebase/repair.d.ts +41 -0
- package/dist/rebase/repair.js +68 -0
- package/dist/rebase/repair.js.map +1 -0
- package/dist/replay/clickhouse.d.ts +33 -0
- package/dist/replay/clickhouse.js +106 -0
- package/dist/replay/clickhouse.js.map +1 -0
- package/dist/replay/index.d.ts +39 -0
- package/dist/replay/index.js +57 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay/postgres.d.ts +31 -0
- package/dist/replay/postgres.js +140 -0
- package/dist/replay/postgres.js.map +1 -0
- package/dist/replay/replay.d.ts +114 -0
- package/dist/replay/replay.js +98 -0
- package/dist/replay/replay.js.map +1 -0
- package/dist/replay/report.d.ts +18 -0
- package/dist/replay/report.js +72 -0
- package/dist/replay/report.js.map +1 -0
- package/dist/replay/shared.d.ts +28 -0
- package/dist/replay/shared.js +77 -0
- package/dist/replay/shared.js.map +1 -0
- package/dist/schemas.d.ts +34 -0
- package/dist/schemas.js +38 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.js +87 -0
- package/dist/server.js.map +1 -0
- package/dist/steps/backfill.d.ts +132 -0
- package/dist/steps/backfill.js +307 -0
- package/dist/steps/backfill.js.map +1 -0
- package/dist/steps/index.d.ts +52 -0
- package/dist/steps/index.js +52 -0
- package/dist/steps/index.js.map +1 -0
- package/dist/steps/kinds.d.ts +101 -0
- package/dist/steps/kinds.js +143 -0
- package/dist/steps/kinds.js.map +1 -0
- package/dist/steps/postgres-migration.d.ts +95 -0
- package/dist/steps/postgres-migration.js +149 -0
- package/dist/steps/postgres-migration.js.map +1 -0
- package/dist/steps/rebuild.d.ts +97 -0
- package/dist/steps/rebuild.js +155 -0
- package/dist/steps/rebuild.js.map +1 -0
- package/dist/steps/run.d.ts +45 -0
- package/dist/steps/run.js +72 -0
- package/dist/steps/run.js.map +1 -0
- package/dist/topology/render.d.ts +17 -0
- package/dist/topology/render.js +17 -0
- package/dist/topology/render.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/versioned/approval.d.ts +35 -0
- package/dist/versioned/approval.js +53 -0
- package/dist/versioned/approval.js.map +1 -0
- package/dist/versioned/checks.d.ts +29 -0
- package/dist/versioned/checks.js +54 -0
- package/dist/versioned/checks.js.map +1 -0
- package/dist/versioned/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +42 -0
- package/dist/versioned/clickhouse.js.map +1 -0
- package/dist/versioned/digest.d.ts +77 -0
- package/dist/versioned/digest.js +69 -0
- package/dist/versioned/digest.js.map +1 -0
- package/dist/versioned/driver.d.ts +51 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +206 -0
- package/dist/versioned/engine.js +370 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +294 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +46 -0
- package/dist/versioned/index.js +77 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +72 -0
- package/dist/versioned/op.js +185 -0
- package/dist/versioned/op.js.map +1 -0
- package/dist/versioned/policy.d.ts +67 -0
- package/dist/versioned/policy.js +161 -0
- package/dist/versioned/policy.js.map +1 -0
- package/dist/versioned/postgres/catalog.d.ts +33 -0
- package/dist/versioned/postgres/catalog.js +34 -0
- package/dist/versioned/postgres/catalog.js.map +1 -0
- package/dist/versioned/postgres/driver.d.ts +71 -0
- package/dist/versioned/postgres/driver.js +140 -0
- package/dist/versioned/postgres/driver.js.map +1 -0
- package/dist/versioned/postgres/executor.d.ts +104 -0
- package/dist/versioned/postgres/executor.js +172 -0
- package/dist/versioned/postgres/executor.js.map +1 -0
- package/dist/versioned/postgres/index.d.ts +11 -0
- package/dist/versioned/postgres/index.js +12 -0
- package/dist/versioned/postgres/index.js.map +1 -0
- package/dist/versioned/postgres/waits.d.ts +46 -0
- package/dist/versioned/postgres/waits.js +78 -0
- package/dist/versioned/postgres/waits.js.map +1 -0
- package/dist/versioned/report.d.ts +25 -0
- package/dist/versioned/report.js +84 -0
- package/dist/versioned/report.js.map +1 -0
- package/dist/versioned/stand-down.d.ts +30 -0
- package/dist/versioned/stand-down.js +56 -0
- package/dist/versioned/stand-down.js.map +1 -0
- package/dist/versioned/state.d.ts +108 -0
- package/dist/versioned/state.js +153 -0
- package/dist/versioned/state.js.map +1 -0
- package/package.json +61 -4
- package/schemas/apply.schema.json +269 -0
- package/schemas/common.schema.json +213 -0
- package/schemas/drift.schema.json +36 -0
- package/schemas/init.schema.json +31 -0
- package/schemas/lint-rules.schema.json +54 -0
- package/schemas/lint.schema.json +290 -0
- package/schemas/mcp.schema.json +70 -0
- package/schemas/new.schema.json +71 -0
- package/schemas/plan.schema.json +371 -0
- package/schemas/rebase.schema.json +54 -0
- package/schemas/repair.schema.json +20 -0
- package/schemas/status.schema.json +310 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { guarded } from "../env-args.js";
|
|
6
|
+
import { loadProject, resolveEnvironment, YodelError } from "../project.js";
|
|
7
|
+
import { closeVersioned, inspect, openVersioned } from "../versioned/index.js";
|
|
8
|
+
import { buildSchema } from "../chant/build.js";
|
|
9
|
+
import { checksProblem, createMigration, describeExpect, parseCheckArg, migrationId, MIGRATIONS_DIR, newestMigration, readMigrations, refusedSteps, verifyChecksum, writeMigration, } from "../migrations/index.js";
|
|
10
|
+
import { asBackfill, BACKFILL_FILE, backfillTemplate, loadBackfill, TEMPLATE_MARK } from "../steps/index.js";
|
|
11
|
+
const usage = "usage: yodel new <name> [--backfill <file>] [--check <sql>]... [--replace [--env <env>]...] [--json] [--dir <project>]";
|
|
12
|
+
const help = `${usage}
|
|
13
|
+
|
|
14
|
+
Writes the next migration, migrations/<YYYYMMDDTHHMM>-<name>/, by diffing
|
|
15
|
+
the newest migration's recorded schema (an empty schema for the first)
|
|
16
|
+
against the current build of the declared schema. Offline: no database,
|
|
17
|
+
dev or otherwise, is read. The name is lowercase letters and digits, words
|
|
18
|
+
joined by - or _. The timestamp is UTC.
|
|
19
|
+
|
|
20
|
+
The migration holds migration.sql (each statement after a comment naming its
|
|
21
|
+
object, rule and class) and migration.json (id, parent, checksum, the
|
|
22
|
+
recorded schema, the steps). A change no statement makes in place, such as a
|
|
23
|
+
ClickHouse sort-key change or a Postgres column rename, is an Op step:
|
|
24
|
+
written to migration.json with its declaration, and to migration.sql as a
|
|
25
|
+
comment only.
|
|
26
|
+
|
|
27
|
+
A data backfill is a step too. --backfill <file> ends the migration with a
|
|
28
|
+
backfill step: <file> (relative to the project directory) is a module
|
|
29
|
+
exporting a chant Op whose every step is an effect() batch with its own
|
|
30
|
+
receipt, running SQL with activity("yodelSql", { sql }). It is copied into
|
|
31
|
+
the migration's directory as ${BACKFILL_FILE} and counts in its checksum.
|
|
32
|
+
yodel apply runs it after the statements before it, and a run that fails or
|
|
33
|
+
is stopped part way resumes at the first batch without a receipt. When
|
|
34
|
+
<file> does not exist yet, a template is written there and no migration:
|
|
35
|
+
write the batches, then run the same command again. With --backfill a
|
|
36
|
+
migration is written even when the declared schema has not changed.
|
|
37
|
+
|
|
38
|
+
--check <sql> adds a pre-migration check to migration.json: a read-only
|
|
39
|
+
query that must return no rows. yodel apply runs it against the environment
|
|
40
|
+
before the gate and again under the lock just before the migration's first
|
|
41
|
+
statement; a check that fails refuses the run (exit 4) and names it.
|
|
42
|
+
Written "<name>: <sql>" it is named <name>, else check-1, check-2 and so on.
|
|
43
|
+
For "returns true" or a count within bounds, edit its "expect" in
|
|
44
|
+
migration.json (docs/migrations.md); the checks count in the checksum and
|
|
45
|
+
the plan digest.
|
|
46
|
+
|
|
47
|
+
--replace redoes the newest migration when it is named <name>: it is written
|
|
48
|
+
again from its parent's recorded schema and the current build, under a new
|
|
49
|
+
id, and the old one is removed. It is for a migration you just wrote and
|
|
50
|
+
have not applied: refused when the migration is applied in an environment
|
|
51
|
+
--env names (each one is read), and it says so when git already tracks it.
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
--backfill <file> end the migration with a backfill step running <file>
|
|
55
|
+
--check <sql> a pre-migration check that must return no rows
|
|
56
|
+
(repeatable; "<name>: <sql>" names it)
|
|
57
|
+
--replace redo the newest migration, named <name>
|
|
58
|
+
--env <env> with --replace: refuse if it is applied in <env>
|
|
59
|
+
(repeatable)
|
|
60
|
+
--json print what was written as JSON
|
|
61
|
+
-C, --dir <dir> the project directory (default: the working directory)
|
|
62
|
+
|
|
63
|
+
Exit codes:
|
|
64
|
+
0 a migration was written, or the declared schema has no change from the
|
|
65
|
+
newest recorded schema (then nothing is written)
|
|
66
|
+
1 no migration could be made: a bad config, a declaration chant cannot
|
|
67
|
+
build, a migrations directory that is not one chain (a fork, a missing
|
|
68
|
+
parent), a migration of that id already there
|
|
69
|
+
4 --replace refused, nothing written: the newest migration is not named
|
|
70
|
+
<name>, or it is applied in an environment --env names
|
|
71
|
+
`;
|
|
72
|
+
function parseNewArgs(args) {
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = parseArgs({
|
|
76
|
+
args,
|
|
77
|
+
allowPositionals: true,
|
|
78
|
+
strict: true,
|
|
79
|
+
options: { json: { type: "boolean" }, dir: { type: "string", short: "C" }, backfill: { type: "string" }, replace: { type: "boolean" }, env: { type: "string", multiple: true }, check: { type: "string", multiple: true } },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
throw new YodelError(`${e instanceof Error ? e.message : String(e)}\n${usage}`);
|
|
84
|
+
}
|
|
85
|
+
const [name, ...rest] = parsed.positionals;
|
|
86
|
+
if (name === undefined || rest.length > 0)
|
|
87
|
+
throw new YodelError(usage);
|
|
88
|
+
if (parsed.values.env !== undefined && parsed.values.replace !== true)
|
|
89
|
+
throw new YodelError(`--env goes with --replace\n${usage}`);
|
|
90
|
+
migrationId(name, new Date()); // a bad name fails before anything is built
|
|
91
|
+
return {
|
|
92
|
+
name,
|
|
93
|
+
json: parsed.values.json === true,
|
|
94
|
+
dir: typeof parsed.values.dir === "string" ? parsed.values.dir : process.cwd(),
|
|
95
|
+
backfill: typeof parsed.values.backfill === "string" ? parsed.values.backfill : undefined,
|
|
96
|
+
replace: parsed.values.replace === true,
|
|
97
|
+
envs: Array.isArray(parsed.values.env) ? parsed.values.env.filter((e) => typeof e === "string") : [],
|
|
98
|
+
checks: parseChecks(parsed.values.check),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function parseChecks(value) {
|
|
102
|
+
const args = Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
103
|
+
const checks = args.map((a, i) => parseCheckArg(a, i + 1));
|
|
104
|
+
const problem = checksProblem(checks);
|
|
105
|
+
if (problem)
|
|
106
|
+
throw new YodelError(`--check: ${problem}\n${usage}`);
|
|
107
|
+
return checks;
|
|
108
|
+
}
|
|
109
|
+
function report(io, json, made, path, projectDir) {
|
|
110
|
+
const steps = made.statements.steps;
|
|
111
|
+
const refused = refusedSteps(steps);
|
|
112
|
+
const statementCount = steps.length - refused.length;
|
|
113
|
+
if (json) {
|
|
114
|
+
io.out(`${JSON.stringify({
|
|
115
|
+
written: true,
|
|
116
|
+
id: made.id,
|
|
117
|
+
path,
|
|
118
|
+
parent: made.document.parent,
|
|
119
|
+
checksum: made.document.checksum,
|
|
120
|
+
dialect: made.document.dialect,
|
|
121
|
+
statements: statementCount,
|
|
122
|
+
ops: refused.filter((s) => s.kind === "op").map((s) => ({ op: s.op, object: s.object, name: s.name, rules: s.changes.map((c) => c.rule) })),
|
|
123
|
+
manual: refused.filter((s) => s.kind === "manual").map((s) => ({ object: s.object, name: s.name, rules: s.changes.map((c) => c.rule), detail: s.detail })),
|
|
124
|
+
hints: made.document.hints,
|
|
125
|
+
checks: made.document.checks ?? [],
|
|
126
|
+
}, null, 2)}\n`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const where = relative(projectDir, path) || path;
|
|
130
|
+
const parent = made.document.parent ? `follows ${made.document.parent}` : "the first migration";
|
|
131
|
+
const lines = [`Wrote ${where}/ (${String(statementCount)} statement${statementCount === 1 ? "" : "s"}; ${parent})`];
|
|
132
|
+
for (const s of refused) {
|
|
133
|
+
const rules = s.changes.map((c) => c.rule).join(", ");
|
|
134
|
+
const backfill = asBackfill(s);
|
|
135
|
+
if (backfill) {
|
|
136
|
+
lines.push(` This migration ends with a backfill step: the Op ${backfill.options.file} exports, copied into the migration's directory.`);
|
|
137
|
+
lines.push(` yodel apply runs its effect() batches after the statements before it, resuming from their receipts after a failure.`);
|
|
138
|
+
}
|
|
139
|
+
else if (s.kind === "op") {
|
|
140
|
+
lines.push(` This migration contains an Op step: ${s.op} for ${s.object} (${s.name}), ${rules}.`);
|
|
141
|
+
lines.push(` It is not SQL. migration.json holds the Op's declaration:`);
|
|
142
|
+
lines.push(` ${s.declaration}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
lines.push(` This migration contains a manual step: ${s.object} (${s.name}), ${rules}. No statement and no Op makes it:`);
|
|
146
|
+
lines.push(` ${s.detail}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const c of made.document.checks ?? [])
|
|
150
|
+
lines.push(` check ${c.name}: ${c.sql} (${describeExpect(c)}, before the first statement)`);
|
|
151
|
+
for (const h of made.document.hints)
|
|
152
|
+
lines.push(` hint: ${h}`);
|
|
153
|
+
io.out(`${lines.join("\n")}\n`);
|
|
154
|
+
}
|
|
155
|
+
/** `yodel new <name>`; the exit codes are in `help`. */
|
|
156
|
+
export const newCommand = {
|
|
157
|
+
name: "new",
|
|
158
|
+
summary: "write the next migration from the declared schema",
|
|
159
|
+
implemented: true,
|
|
160
|
+
run: (args, io) => guarded("new", io, args, help, async () => {
|
|
161
|
+
const { name, json, dir, backfill: backfillArg, replace, envs, checks } = parseNewArgs(args);
|
|
162
|
+
const project = await loadProject(dir);
|
|
163
|
+
let backfill;
|
|
164
|
+
if (backfillArg !== undefined) {
|
|
165
|
+
const path = resolve(project.dir, backfillArg);
|
|
166
|
+
if (!existsSync(path)) {
|
|
167
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
168
|
+
writeFileSync(path, backfillTemplate(name, project.dialect));
|
|
169
|
+
const where = relative(project.dir, path) || path;
|
|
170
|
+
if (json)
|
|
171
|
+
io.out(`${JSON.stringify({ written: false, template: path }, null, 2)}\n`);
|
|
172
|
+
else
|
|
173
|
+
io.out(`Wrote a backfill template at ${where}. Write its batches, then run yodel new ${name} --backfill ${backfillArg} again. No migration written.\n`);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
const text = readFileSync(path, "utf8");
|
|
177
|
+
if (text.includes(TEMPLATE_MARK))
|
|
178
|
+
throw new YodelError(`${path} is still the template; write its batches' SQL first`);
|
|
179
|
+
await loadBackfill(path, text);
|
|
180
|
+
backfill = { text };
|
|
181
|
+
}
|
|
182
|
+
const migrationsDir = join(project.dir, MIGRATIONS_DIR);
|
|
183
|
+
const ordered = readMigrations(migrationsDir);
|
|
184
|
+
const replaced = replace ? await replaceable(project, ordered, name, envs, io) : undefined;
|
|
185
|
+
const previous = replaced ? ordered[ordered.length - 2] : newestMigration(ordered);
|
|
186
|
+
if (previous && !verifyChecksum(previous).ok) {
|
|
187
|
+
io.err(`yodel new: warning: ${previous.id}'s files no longer match its recorded checksum; the new migration follows it as recorded\n`);
|
|
188
|
+
}
|
|
189
|
+
const build = await buildSchema(project);
|
|
190
|
+
let made;
|
|
191
|
+
try {
|
|
192
|
+
made = createMigration({ name, build: build.output, previous, ...(backfill ? { backfill } : {}), ...(checks.length > 0 ? { checks } : {}) });
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
build.dispose();
|
|
196
|
+
}
|
|
197
|
+
if (made.id === undefined) {
|
|
198
|
+
if (replaced) {
|
|
199
|
+
const msg = `No changes from ${previous ? `${previous.id}'s recorded schema` : "an empty schema"}: the declared schema is what ${replaced.id} started from. Nothing written; ${replaced.id} is kept (delete it to drop it).`;
|
|
200
|
+
if (json)
|
|
201
|
+
io.out(`${JSON.stringify({ written: false, replaced: null, kept: replaced.id }, null, 2)}\n`);
|
|
202
|
+
else
|
|
203
|
+
io.out(`${msg}\n`);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
const from = previous ? `the newest migration's recorded schema (${previous.id})` : "an empty schema (the declared schema declares nothing)";
|
|
207
|
+
if (json)
|
|
208
|
+
io.out(`${JSON.stringify({ written: false, parent: previous?.id ?? null, dialect: project.dialect, hints: made.statements.hints }, null, 2)}\n`);
|
|
209
|
+
else
|
|
210
|
+
io.out(`No changes: the declared schema matches ${from}. Nothing written.\n`);
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
if (replaced) {
|
|
214
|
+
// Moved aside first and removed only once the new one is written, so a failure leaves the old one in place.
|
|
215
|
+
const aside = join(migrationsDir, `.replaced-${replaced.id}`);
|
|
216
|
+
renameSync(replaced.dir, aside);
|
|
217
|
+
let path;
|
|
218
|
+
try {
|
|
219
|
+
path = writeMigration(migrationsDir, made);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
renameSync(aside, replaced.dir);
|
|
223
|
+
throw e;
|
|
224
|
+
}
|
|
225
|
+
rmSync(aside, { recursive: true, force: true });
|
|
226
|
+
if (!json)
|
|
227
|
+
io.out(`Replaced ${replaced.id}.\n`);
|
|
228
|
+
report(io, json, made, path, project.dir);
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
const path = writeMigration(migrationsDir, made);
|
|
232
|
+
report(io, json, made, path, project.dir);
|
|
233
|
+
return 0;
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
236
|
+
/** Exit code: --replace refused; nothing written. */
|
|
237
|
+
export const REPLACE_REFUSED = 4;
|
|
238
|
+
/** The newest migration, when --replace may redo it: named `name`, and applied in none of `envs`. */
|
|
239
|
+
async function replaceable(project, ordered, name, envs, io) {
|
|
240
|
+
const newest = newestMigration(ordered);
|
|
241
|
+
if (!newest)
|
|
242
|
+
throw new YodelError(`--check <sql> adds a pre-migration check to migration.json: a read-only
|
|
243
|
+
query that must return no rows. yodel apply runs it against the environment
|
|
244
|
+
before the gate and again under the lock just before the migration's first
|
|
245
|
+
statement; a check that fails refuses the run (exit 4) and names it.
|
|
246
|
+
Written "<name>: <sql>" it is named <name>, else check-1, check-2 and so on.
|
|
247
|
+
For "returns true" or a count within bounds, edit its "expect" in
|
|
248
|
+
migration.json (docs/migrations.md); the checks count in the checksum and
|
|
249
|
+
the plan digest.
|
|
250
|
+
|
|
251
|
+
--replace redoes the newest migration, and there is none yet; run yodel new ${name}`, REPLACE_REFUSED);
|
|
252
|
+
if (newest.id.slice(newest.id.indexOf("-") + 1) !== name) {
|
|
253
|
+
throw new YodelError(`--check <sql> adds a pre-migration check to migration.json: a read-only
|
|
254
|
+
query that must return no rows. yodel apply runs it against the environment
|
|
255
|
+
before the gate and again under the lock just before the migration's first
|
|
256
|
+
statement; a check that fails refuses the run (exit 4) and names it.
|
|
257
|
+
Written "<name>: <sql>" it is named <name>, else check-1, check-2 and so on.
|
|
258
|
+
For "returns true" or a count within bounds, edit its "expect" in
|
|
259
|
+
migration.json (docs/migrations.md); the checks count in the checksum and
|
|
260
|
+
the plan digest.
|
|
261
|
+
|
|
262
|
+
--replace redoes the newest migration, ${newest.id}, which is not named ${name}; nothing written`, REPLACE_REFUSED);
|
|
263
|
+
}
|
|
264
|
+
for (const envName of envs) {
|
|
265
|
+
const env = resolveEnvironment(project, envName);
|
|
266
|
+
const venv = await openVersioned(project, env);
|
|
267
|
+
try {
|
|
268
|
+
const { status } = await inspect(venv, ordered, { sync: false });
|
|
269
|
+
if (status.applied.some((a) => a.id === newest.id)) {
|
|
270
|
+
throw new YodelError(`refused: ${newest.id} is applied in ${env.name}, so it is not redone; write the change as a new migration (yodel new <name>). Nothing written.`, REPLACE_REFUSED);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await closeVersioned(venv);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (envs.length === 0)
|
|
278
|
+
io.err(`yodel new: --replace checked no environment; with --env <env>, it refuses a migration applied there\n`);
|
|
279
|
+
if (tracked(project.dir, newest.dir))
|
|
280
|
+
io.err(`yodel new: warning: git already tracks ${newest.id}; anyone who has that commit has the old one\n`);
|
|
281
|
+
return newest;
|
|
282
|
+
}
|
|
283
|
+
/** Whether git tracks anything under `path`. */
|
|
284
|
+
function tracked(cwd, path) {
|
|
285
|
+
try {
|
|
286
|
+
return execFileSync("git", ["ls-files", "--", path], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() !== "";
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=new.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,UAAU,EAAgB,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,cAAc,EACd,cAAc,GAGf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE7G,MAAM,KAAK,GAAG,wHAAwH,CAAC;AAEvI,MAAM,IAAI,GAAG,GAAG,KAAK;;;;;;;;;;;;;;;;;;;+BAmBU,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwC3C,CAAC;AAYF,SAAS,YAAY,CAAC,IAAc;IAClC,IAAI,MAAoC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC;YACjB,IAAI;YACJ,gBAAgB,EAAE,IAAI;YACtB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;SAC5N,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IAC3C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI;QAAE,MAAM,IAAI,UAAU,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;IACnI,WAAW,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,4CAA4C;IAC3E,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI;QACjC,GAAG,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9E,QAAQ,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACzF,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI;QACvC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QACjH,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;KACzC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,OAAO;QAAE,MAAM,IAAI,UAAU,CAAC,YAAY,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,MAAM,CAAC,EAAM,EAAE,IAAa,EAAE,IAAsB,EAAE,IAAY,EAAE,UAAkB;IAC7F,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;IACpC,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACrD,IAAI,IAAI,EAAE,CAAC;QACT,EAAE,CAAC,GAAG,CACJ,GAAG,IAAI,CAAC,SAAS,CACf;YACE,OAAO,EAAE,IAAI;YACb,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,IAAI;YACJ,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;YAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ;YAChC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;YAC9B,UAAU,EAAE,cAAc;YAC1B,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3I,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1J,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;YAC1B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE;SACnC,EACD,IAAI,EACJ,CAAC,CACF,IAAI,CACN,CAAC;QACF,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAChG,MAAM,KAAK,GAAG,CAAC,SAAS,KAAK,MAAM,MAAM,CAAC,cAAc,CAAC,aAAa,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,GAAG,CAAC,CAAC;IACrH,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,sDAAsD,QAAQ,CAAC,OAAO,CAAC,IAAI,kDAAkD,CAAC,CAAC;YAC1I,KAAK,CAAC,IAAI,CAAC,yHAAyH,CAAC,CAAC;QACxI,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC;YACnG,KAAK,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;YAC5E,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,MAAM,KAAK,oCAAoC,CAAC,CAAC;YAC3H,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,KAAK,cAAc,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC;IACzI,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAChE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,MAAM,UAAU,GAAY;IACjC,IAAI,EAAE,KAAK;IACX,OAAO,EAAE,mDAAmD;IAC5D,WAAW,EAAE,IAAI;IACjB,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAChB,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7F,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,QAAsC,CAAC;QAC3C,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9C,aAAa,CAAC,IAAI,EAAE,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;gBAClD,IAAI,IAAI;oBAAE,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;;oBAChF,EAAE,CAAC,GAAG,CAAC,gCAAgC,KAAK,2CAA2C,IAAI,eAAe,WAAW,iCAAiC,CAAC,CAAC;gBAC7J,OAAO,CAAC,CAAC;YACX,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAAE,MAAM,IAAI,UAAU,CAAC,GAAG,IAAI,sDAAsD,CAAC,CAAC;YACtH,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC/B,QAAQ,GAAG,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3F,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACnF,IAAI,QAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;YAC7C,EAAE,CAAC,GAAG,CAAC,uBAAuB,QAAQ,CAAC,EAAE,4FAA4F,CAAC,CAAC;QACzI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,IAAwC,CAAC;QAC7C,IAAI,CAAC;YACH,IAAI,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/I,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,mBAAmB,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC,iBAAiB,iCAAiC,QAAQ,CAAC,EAAE,mCAAmC,QAAQ,CAAC,EAAE,kCAAkC,CAAC;gBAC7N,IAAI,IAAI;oBAAE,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;;oBACnG,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;gBACxB,OAAO,CAAC,CAAC;YACX,CAAC;YACD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,2CAA2C,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,wDAAwD,CAAC;YAC7I,IAAI,IAAI;gBAAE,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,IAAI,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;;gBACtJ,EAAE,CAAC,GAAG,CAAC,2CAA2C,IAAI,sBAAsB,CAAC,CAAC;YACnF,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,4GAA4G;YAC5G,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9D,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAChC,IAAI,IAAY,CAAC;YACjB,IAAI,CAAC;gBACH,IAAI,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAChC,MAAM,CAAC,CAAC;YACV,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI;gBAAE,EAAE,CAAC,GAAG,CAAC,YAAY,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;YAChD,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1C,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,IAAI,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;CACL,CAAC;AAEF,qDAAqD;AACrD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC,qGAAqG;AACrG,KAAK,UAAU,WAAW,CAAC,OAAgB,EAAE,OAA6B,EAAE,IAAY,EAAE,IAAuB,EAAE,EAAM;IACvH,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,UAAU,CAAC;;;;;;;;;8EAS0C,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC;IACrG,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACzD,MAAM,IAAI,UAAU,CAAC;;;;;;;;;yCASgB,MAAM,CAAC,EAAE,wBAAwB,IAAI,mBAAmB,EAAE,eAAe,CAAC,CAAC;IAClH,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnD,MAAM,IAAI,UAAU,CAAC,YAAY,MAAM,CAAC,EAAE,kBAAkB,GAAG,CAAC,IAAI,iGAAiG,EAAE,eAAe,CAAC,CAAC;YAC1L,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,EAAE,CAAC,GAAG,CAAC,uGAAuG,CAAC,CAAC;IACvI,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;QAAE,EAAE,CAAC,GAAG,CAAC,0CAA0C,MAAM,CAAC,EAAE,gDAAgD,CAAC,CAAC;IAClJ,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gDAAgD;AAChD,SAAS,OAAO,CAAC,GAAW,EAAE,IAAY;IACxC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IACrI,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { guarded } from "../env-args.js";
|
|
3
|
+
import { loadProject, resolveEnvironment, YodelError } from "../project.js";
|
|
4
|
+
import { EXIT_REFUSED } from "../exit.js";
|
|
5
|
+
import { runChant } from "../chant/process.js";
|
|
6
|
+
import { runnable } from "../chant/runnable.js";
|
|
7
|
+
import { closeVersioned, findMigrateOp, hasMigrations, judgePlan, openVersioned, overrideGate, readChain, statusReport, ALLOW_OUT_OF_ORDER_ENV } from "../versioned/index.js";
|
|
8
|
+
const usage = "usage: yodel override <env> --rule <id> --reason <text> [--by <name>] [--dir <project>] [--op <name>] [--allow-out-of-order]";
|
|
9
|
+
const help = `${usage}
|
|
10
|
+
|
|
11
|
+
Records an override of a policy rule (yodel.config.ts policy.rules) for the
|
|
12
|
+
plan yodel apply <env> would run now: a person's decision, with a reason, to
|
|
13
|
+
apply a plan the rule denies. It is written to chant's gate ledger (the
|
|
14
|
+
chant/lifecycle branch) with chant approve <op> override-<rule> --plan
|
|
15
|
+
<digest> --note <reason>, bound to the environment's migrations Op and the
|
|
16
|
+
plan digest. It counts only for exactly that digest: once the plan moves
|
|
17
|
+
(a migration added or edited, the live schema changed), the rule denies
|
|
18
|
+
again and needs a new override. It counts only when policy.overriders
|
|
19
|
+
lists the name it is recorded under (--by, else chant's: the CI actor or
|
|
20
|
+
your user). yodel apply records the overrides it relied on in the history.
|
|
21
|
+
|
|
22
|
+
Refused when the rule does not deny the current plan: an override answers
|
|
23
|
+
one denial, it is not a standing exception.
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--rule <id> the rule to override (repeatable)
|
|
27
|
+
--reason <text> why; required
|
|
28
|
+
--by <name> who is overriding (chant approve --approver)
|
|
29
|
+
--op <name> the migrations Op, when more than one applies <env>
|
|
30
|
+
--allow-out-of-order the plan yodel apply --allow-out-of-order runs
|
|
31
|
+
-C, --dir <dir> the project directory (default: the working directory)
|
|
32
|
+
|
|
33
|
+
Exit codes:
|
|
34
|
+
0 recorded
|
|
35
|
+
4 nothing recorded: the plan has nothing to apply or apply would refuse
|
|
36
|
+
it, a rule that does not deny it, a name policy.overriders does not list
|
|
37
|
+
1 anything else: a bad config, a server that does not answer, chant
|
|
38
|
+
approve failed
|
|
39
|
+
`;
|
|
40
|
+
/** `yodel override <env> --rule <id> --reason <text>`; the exit codes are in `help`. */
|
|
41
|
+
export const override = {
|
|
42
|
+
name: "override",
|
|
43
|
+
summary: "override a policy rule for the current plan, with a reason",
|
|
44
|
+
implemented: true,
|
|
45
|
+
run: (args, io) => guarded("override", io, args, help, async () => {
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = parseArgs({
|
|
49
|
+
args,
|
|
50
|
+
allowPositionals: true,
|
|
51
|
+
strict: true,
|
|
52
|
+
options: {
|
|
53
|
+
rule: { type: "string", multiple: true },
|
|
54
|
+
reason: { type: "string" },
|
|
55
|
+
by: { type: "string" },
|
|
56
|
+
dir: { type: "string", short: "C" },
|
|
57
|
+
op: { type: "string" },
|
|
58
|
+
"allow-out-of-order": { type: "boolean" },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
throw new YodelError(`${e instanceof Error ? e.message : String(e)}\n${usage}`);
|
|
64
|
+
}
|
|
65
|
+
const { values, positionals } = parsed;
|
|
66
|
+
const [name, ...rest] = positionals;
|
|
67
|
+
if (name === undefined || rest.length > 0)
|
|
68
|
+
throw new YodelError(usage);
|
|
69
|
+
const rules = Array.isArray(values.rule) ? values.rule.filter((r) => typeof r === "string") : [];
|
|
70
|
+
const reason = typeof values.reason === "string" ? values.reason.trim() : "";
|
|
71
|
+
const by = typeof values.by === "string" ? values.by : undefined;
|
|
72
|
+
const allowOutOfOrder = values["allow-out-of-order"] === true || process.env[ALLOW_OUT_OF_ORDER_ENV] === "1";
|
|
73
|
+
const project = await loadProject(typeof values.dir === "string" ? values.dir : process.cwd());
|
|
74
|
+
const env = resolveEnvironment(project, name);
|
|
75
|
+
if (rules.length === 0 || reason === "")
|
|
76
|
+
throw new YodelError(`--rule and --reason are required: an override names the rule and says why\n${usage}`);
|
|
77
|
+
if (!hasMigrations(project))
|
|
78
|
+
throw new YodelError(`${project.dir} has no migrations/ directory with a migration; policy is for versioned migrations`);
|
|
79
|
+
const policy = project.yodel?.config.policy;
|
|
80
|
+
if (!policy)
|
|
81
|
+
throw new YodelError(`yodel.config.ts declares no policy, so there is nothing to override`, EXIT_REFUSED);
|
|
82
|
+
const unknown = rules.filter((r) => !policy.rules.some((x) => x.id === r));
|
|
83
|
+
if (unknown.length > 0)
|
|
84
|
+
throw new YodelError(`no policy rule ${unknown.join(", ")} (policy.rules: ${policy.rules.map((r) => r.id).join(", ")}); nothing recorded`, EXIT_REFUSED);
|
|
85
|
+
if (by !== undefined && !(policy.overriders ?? []).includes(by)) {
|
|
86
|
+
throw new YodelError(`${by} is not in policy.overriders (${(policy.overriders ?? []).join(", ") || "none"}), so an override by them would not count; nothing recorded`, EXIT_REFUSED);
|
|
87
|
+
}
|
|
88
|
+
const ordered = readChain(project);
|
|
89
|
+
const venv = await openVersioned(project, env);
|
|
90
|
+
try {
|
|
91
|
+
const report = await statusReport(venv, ordered, { allowOutOfOrder });
|
|
92
|
+
if (report.refusal)
|
|
93
|
+
throw new YodelError(`yodel apply would refuse this plan before any policy: ${report.refusal.message}\nNothing recorded.`, EXIT_REFUSED);
|
|
94
|
+
if (!report.digest)
|
|
95
|
+
throw new YodelError(`${env.name} has nothing pending: there is no plan to override; nothing recorded`, EXIT_REFUSED);
|
|
96
|
+
const results = await judgePlan({ policy, environment: env.name, dialect: project.dialect, toApply: report.status.pending, digest: undefined, ledger: () => Promise.resolve(undefined) });
|
|
97
|
+
const notDenying = rules.filter((r) => results.find((x) => x.id === r)?.outcome !== "denied");
|
|
98
|
+
if (notDenying.length > 0) {
|
|
99
|
+
throw new YodelError(`${notDenying.join(", ")} ${notDenying.length === 1 ? "does" : "do"} not deny the plan for ${env.name} (${report.digest}); an override answers a denial. Nothing recorded.`, EXIT_REFUSED);
|
|
100
|
+
}
|
|
101
|
+
const op = await findMigrateOp(project, env, typeof values.op === "string" ? values.op : undefined);
|
|
102
|
+
for (const rule of rules) {
|
|
103
|
+
const denial = results.find((x) => x.id === rule);
|
|
104
|
+
io.out(`${rule} denies ${report.digest}:\n${(denial?.reasons ?? []).map((x) => ` ${x}`).join("\n")}\n`);
|
|
105
|
+
const ran = await runChant(["approve", op.name, overrideGate(rule), "--plan", report.digest, "--note", reason, ...(by ? ["--approver", by] : [])], { cwd: project.dir, onStdout: io.err, onStderr: io.err });
|
|
106
|
+
if (ran.code !== 0)
|
|
107
|
+
throw new YodelError(`chant approve ${op.name} ${overrideGate(rule)} failed (exit ${String(ran.code)}); the override of ${rule} was not recorded`);
|
|
108
|
+
io.out(`Recorded an override of ${rule} for ${report.digest}${by ? ` by ${by}` : ""}: ${reason}\n`);
|
|
109
|
+
}
|
|
110
|
+
io.out(`It counts for this plan only. Apply it with:\n ${runnable(`yodel apply ${env.name}`, project.dir)}\n`);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await closeVersioned(venv);
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=override.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override.js","sourceRoot":"","sources":["../../src/commands/override.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { guarded } from "../env-args.js";
|
|
3
|
+
import { loadProject, resolveEnvironment, YodelError } from "../project.js";
|
|
4
|
+
import { declarativeTopologyRefusal, describeSettingsTopology, environmentSettings } from "../config/settings.js";
|
|
5
|
+
import { buildSchema } from "../chant/build.js";
|
|
6
|
+
import { planEnvironment, planExitCode, planJson, renderPlan } from "../chant/plan.js";
|
|
7
|
+
import { hasMigrations } from "../versioned/index.js";
|
|
8
|
+
import { describeServer, probeServer } from "../server.js";
|
|
9
|
+
import { runnable } from "../chant/runnable.js";
|
|
10
|
+
import { decodeSubject } from "../versioned/explain.js";
|
|
11
|
+
import { collectVersionedPlan } from "../pr/model.js";
|
|
12
|
+
import { commentMarker, planJsonVersioned, renderPlanMarkdown, renderPlanText, subjectIn } from "../pr/render.js";
|
|
13
|
+
import { resolveForgeTarget } from "../pr/forge.js";
|
|
14
|
+
import { commentClient, findComment, upsertComment } from "../pr/comment.js";
|
|
15
|
+
const usage = "usage: yodel plan <env> [--json] [--dir <project>] [--format text|markdown|json]\n" +
|
|
16
|
+
" [--allow-out-of-order] [--op <name>]\n" +
|
|
17
|
+
" [--comment [--forge github|gitlab|forgejo]\n" +
|
|
18
|
+
" [--repo <repo>] [--pr <n>] [--api-url <url>] [--token-env <VAR>]]";
|
|
19
|
+
const help = `${usage}
|
|
20
|
+
|
|
21
|
+
Declarative project (no migrations/ directory): shows the classified change
|
|
22
|
+
from the environment's live database (sql.profiles.<env>) to the declared
|
|
23
|
+
schema, as chant sql plan reports it.
|
|
24
|
+
|
|
25
|
+
Versioned project (a migrations/ directory): shows the pending migrations,
|
|
26
|
+
each step with its rule and class (an Op step with the Op that makes it, a
|
|
27
|
+
manual step with what it needs), integrity problems (directory problems,
|
|
28
|
+
checksum mismatches, out-of-order migrations), and the plan digest yodel
|
|
29
|
+
apply asks approval for, with the chant approve command that approves it.
|
|
30
|
+
When chant's gate ledger holds an approval of the migrations Op's gate for
|
|
31
|
+
another digest, it says what moved since. Reads only.
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--json print the plan as JSON (the same as --format json)
|
|
35
|
+
--format <f> text (default), markdown (the pull request comment),
|
|
36
|
+
json
|
|
37
|
+
--allow-out-of-order versioned: the plan yodel apply --allow-out-of-order
|
|
38
|
+
would run
|
|
39
|
+
--op <name> versioned: the migrations Op, when more than one
|
|
40
|
+
applies <env>
|
|
41
|
+
--comment versioned: post the markdown as one comment on the
|
|
42
|
+
pull (merge) request, updating the one posted before;
|
|
43
|
+
prints the text plan. The forge, repository, request
|
|
44
|
+
and API come from the CI job's environment
|
|
45
|
+
(GitHub Actions, GitLab CI, Forgejo Actions) unless
|
|
46
|
+
given:
|
|
47
|
+
--forge <f> github, gitlab or forgejo
|
|
48
|
+
--repo <repo> owner/name (GitHub, Forgejo) or the project id (GitLab)
|
|
49
|
+
--pr <n> the pull request number or merge request iid
|
|
50
|
+
--api-url <url> the REST API base (https://api.github.com,
|
|
51
|
+
https://gitlab.example/api/v4,
|
|
52
|
+
https://forgejo.example/api/v1)
|
|
53
|
+
--token-env <VAR> the variable holding the token (default GITHUB_TOKEN;
|
|
54
|
+
GITLAB_TOKEN; FORGEJO_TOKEN, else GITHUB_TOKEN)
|
|
55
|
+
-C, --dir <dir> the project directory (default: the working directory)
|
|
56
|
+
|
|
57
|
+
Exit codes:
|
|
58
|
+
0 every change can be made in place, or there is none; versioned: apply
|
|
59
|
+
would run (or has nothing to do). Pending migrations still exit 0: plan
|
|
60
|
+
reports, and a CI job that posts it should not fail because a pull
|
|
61
|
+
request adds a migration; yodel status exits 3 for them
|
|
62
|
+
2 a change needs its own Op: a ClickHouse rebuild, a Postgres expand and
|
|
63
|
+
contract (declarative); versioned: a pending migration has an Op or
|
|
64
|
+
manual step apply does not run yet
|
|
65
|
+
4 versioned: apply would refuse: a checksum mismatch, an applied migration
|
|
66
|
+
gone from the directory, an out-of-order migration, a fork or another
|
|
67
|
+
problem in the migrations directory
|
|
68
|
+
1 no plan could be made: a bad config, an unknown environment, a declaration
|
|
69
|
+
chant cannot build, a server it cannot read, a comment the forge refused
|
|
70
|
+
`;
|
|
71
|
+
function parsePlanArgs(args) {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = parseArgs({
|
|
75
|
+
args,
|
|
76
|
+
allowPositionals: true,
|
|
77
|
+
strict: true,
|
|
78
|
+
options: {
|
|
79
|
+
json: { type: "boolean" },
|
|
80
|
+
dir: { type: "string", short: "C" },
|
|
81
|
+
format: { type: "string" },
|
|
82
|
+
comment: { type: "boolean" },
|
|
83
|
+
"allow-out-of-order": { type: "boolean" },
|
|
84
|
+
op: { type: "string" },
|
|
85
|
+
forge: { type: "string" },
|
|
86
|
+
repo: { type: "string" },
|
|
87
|
+
pr: { type: "string" },
|
|
88
|
+
"api-url": { type: "string" },
|
|
89
|
+
"token-env": { type: "string" },
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
throw new YodelError(`${e instanceof Error ? e.message : String(e)}\n${usage}`);
|
|
95
|
+
}
|
|
96
|
+
const { values: v, positionals } = parsed;
|
|
97
|
+
const [env, ...rest] = positionals;
|
|
98
|
+
if (env === undefined || rest.length > 0)
|
|
99
|
+
throw new YodelError(usage);
|
|
100
|
+
const str = (k) => (typeof v[k] === "string" ? v[k] : undefined);
|
|
101
|
+
const format = str("format") ?? (v.json === true ? "json" : "text");
|
|
102
|
+
if (format !== "text" && format !== "markdown" && format !== "json")
|
|
103
|
+
throw new YodelError(`--format must be text, markdown or json, got ${JSON.stringify(format)}\n${usage}`);
|
|
104
|
+
if (v.json === true && format !== "json")
|
|
105
|
+
throw new YodelError(`--json and --format ${format} cannot be combined\n${usage}`);
|
|
106
|
+
const forgeFlags = ["forge", "repo", "pr", "api-url", "token-env"].filter((k) => str(k) !== undefined);
|
|
107
|
+
if (forgeFlags.length > 0 && v.comment !== true)
|
|
108
|
+
throw new YodelError(`--${forgeFlags.join(", --")} ${forgeFlags.length === 1 ? "goes" : "go"} with --comment\n${usage}`);
|
|
109
|
+
const forge = { forge: str("forge"), repo: str("repo"), pr: str("pr"), apiUrl: str("api-url"), tokenEnv: str("token-env") };
|
|
110
|
+
return {
|
|
111
|
+
env,
|
|
112
|
+
dir: str("dir") ?? process.cwd(),
|
|
113
|
+
format,
|
|
114
|
+
comment: v.comment === true,
|
|
115
|
+
allowOutOfOrder: v["allow-out-of-order"] === true,
|
|
116
|
+
op: str("op"),
|
|
117
|
+
forge,
|
|
118
|
+
versionedOnly: [
|
|
119
|
+
...(format === "markdown" ? ["--format markdown"] : []),
|
|
120
|
+
...(v.comment === true ? ["--comment"] : []),
|
|
121
|
+
...(v["allow-out-of-order"] === true ? ["--allow-out-of-order"] : []),
|
|
122
|
+
...(str("op") !== undefined ? ["--op"] : []),
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** `yodel plan <env>`; the exit codes are in `help`. */
|
|
127
|
+
export const plan = {
|
|
128
|
+
name: "plan",
|
|
129
|
+
summary: "show what apply would change in an environment",
|
|
130
|
+
implemented: true,
|
|
131
|
+
run: (args, io) => guarded("plan", io, args, help, async () => {
|
|
132
|
+
const a = parsePlanArgs(args);
|
|
133
|
+
const project = await loadProject(a.dir);
|
|
134
|
+
const env = resolveEnvironment(project, a.env);
|
|
135
|
+
if (hasMigrations(project))
|
|
136
|
+
return versionedPlan(project, env, a, io);
|
|
137
|
+
if (a.versionedOnly.length > 0)
|
|
138
|
+
throw new YodelError(`${a.versionedOnly.join(", ")} ${a.versionedOnly.length === 1 ? "is" : "are"} for versioned migrations, and ${project.dir} has no migrations/ directory with a migration`);
|
|
139
|
+
return declarativePlan(project, env, a.format === "json", io);
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
142
|
+
async function declarativePlan(project, env, json, io) {
|
|
143
|
+
const refusal = project.dialect === "clickhouse" ? declarativeTopologyRefusal(project, env.name) : undefined;
|
|
144
|
+
if (refusal)
|
|
145
|
+
throw refusal;
|
|
146
|
+
const topology = project.dialect === "clickhouse" ? `, topology ${describeSettingsTopology(environmentSettings(project, env.name))}` : "";
|
|
147
|
+
const build = await buildSchema(project);
|
|
148
|
+
try {
|
|
149
|
+
const result = await planEnvironment(project, env, build);
|
|
150
|
+
const server = await probeServer(project, env);
|
|
151
|
+
if (json)
|
|
152
|
+
io.out(`${JSON.stringify({ ...planJson(result), ...(server ? { server } : {}) }, null, 2)}\n`);
|
|
153
|
+
else
|
|
154
|
+
io.out(`${await renderPlan(result, `Plan for ${env.name} (${server ? describeServer(server) : project.dialect}, sql.profiles.${env.name}${topology})`)}\n`);
|
|
155
|
+
return planExitCode(result);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
build.dispose();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function versionedPlan(project, env, a, io) {
|
|
162
|
+
const marker = commentMarker(env.name);
|
|
163
|
+
// Resolve the forge first: a job missing its token fails before it reads the server.
|
|
164
|
+
const target = a.comment ? resolveForgeTarget(a.forge, process.env) : undefined;
|
|
165
|
+
const client = target ? commentClient(target) : undefined;
|
|
166
|
+
const existing = client ? await findComment(client, marker) : undefined;
|
|
167
|
+
const carried = existing ? subjectIn(existing.body) : undefined;
|
|
168
|
+
const previous = carried ? decodeSubject(carried) : undefined;
|
|
169
|
+
const result = await collectVersionedPlan(project, env, { allowOutOfOrder: a.allowOutOfOrder, opName: a.op, previous });
|
|
170
|
+
const code = result.refusal?.exitCode ?? 0;
|
|
171
|
+
if (client && target) {
|
|
172
|
+
const posted = await upsertComment(client, marker, renderPlanMarkdown(result), existing);
|
|
173
|
+
io.err(`yodel plan: ${posted.action} the plan comment on ${target.forge} ${target.repo}#${String(target.pr)}${posted.comment.url ? ` (${posted.comment.url})` : ` (comment ${String(posted.comment.id)})`}\n`);
|
|
174
|
+
}
|
|
175
|
+
if (a.format === "json")
|
|
176
|
+
io.out(`${JSON.stringify(planJsonVersioned(result), null, 2)}\n`);
|
|
177
|
+
else if (a.format === "markdown")
|
|
178
|
+
io.out(renderPlanMarkdown(result));
|
|
179
|
+
else
|
|
180
|
+
io.out(renderPlanText(result, { runnable: (c) => runnable(c, project.dir) }));
|
|
181
|
+
return code;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=plan.js.map
|