@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.
Files changed (324) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +112 -2
  3. package/dist/bin.d.ts +2 -0
  4. package/dist/bin.js +29 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chant/apply.d.ts +59 -0
  7. package/dist/chant/apply.js +116 -0
  8. package/dist/chant/apply.js.map +1 -0
  9. package/dist/chant/build.d.ts +34 -0
  10. package/dist/chant/build.js +48 -0
  11. package/dist/chant/build.js.map +1 -0
  12. package/dist/chant/drift.d.ts +112 -0
  13. package/dist/chant/drift.js +238 -0
  14. package/dist/chant/drift.js.map +1 -0
  15. package/dist/chant/plan.d.ts +45 -0
  16. package/dist/chant/plan.js +61 -0
  17. package/dist/chant/plan.js.map +1 -0
  18. package/dist/chant/process.d.ts +41 -0
  19. package/dist/chant/process.js +87 -0
  20. package/dist/chant/process.js.map +1 -0
  21. package/dist/chant/runnable.d.ts +19 -0
  22. package/dist/chant/runnable.js +47 -0
  23. package/dist/chant/runnable.js.map +1 -0
  24. package/dist/cli.d.ts +8 -0
  25. package/dist/cli.js +99 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/command.d.ts +23 -0
  28. package/dist/command.js +13 -0
  29. package/dist/command.js.map +1 -0
  30. package/dist/commands/apply.d.ts +3 -0
  31. package/dist/commands/apply.js +426 -0
  32. package/dist/commands/apply.js.map +1 -0
  33. package/dist/commands/approve.d.ts +5 -0
  34. package/dist/commands/approve.js +111 -0
  35. package/dist/commands/approve.js.map +1 -0
  36. package/dist/commands/drift.d.ts +14 -0
  37. package/dist/commands/drift.js +267 -0
  38. package/dist/commands/drift.js.map +1 -0
  39. package/dist/commands/index.d.ts +3 -0
  40. package/dist/commands/index.js +28 -0
  41. package/dist/commands/index.js.map +1 -0
  42. package/dist/commands/init.d.ts +6 -0
  43. package/dist/commands/init.js +139 -0
  44. package/dist/commands/init.js.map +1 -0
  45. package/dist/commands/lint.d.ts +3 -0
  46. package/dist/commands/lint.js +202 -0
  47. package/dist/commands/lint.js.map +1 -0
  48. package/dist/commands/mcp.d.ts +3 -0
  49. package/dist/commands/mcp.js +50 -0
  50. package/dist/commands/mcp.js.map +1 -0
  51. package/dist/commands/new.d.ts +5 -0
  52. package/dist/commands/new.js +292 -0
  53. package/dist/commands/new.js.map +1 -0
  54. package/dist/commands/override.d.ts +3 -0
  55. package/dist/commands/override.js +118 -0
  56. package/dist/commands/override.js.map +1 -0
  57. package/dist/commands/plan.d.ts +3 -0
  58. package/dist/commands/plan.js +183 -0
  59. package/dist/commands/plan.js.map +1 -0
  60. package/dist/commands/rebase.d.ts +3 -0
  61. package/dist/commands/rebase.js +167 -0
  62. package/dist/commands/rebase.js.map +1 -0
  63. package/dist/commands/repair.d.ts +14 -0
  64. package/dist/commands/repair.js +125 -0
  65. package/dist/commands/repair.js.map +1 -0
  66. package/dist/commands/status.d.ts +7 -0
  67. package/dist/commands/status.js +54 -0
  68. package/dist/commands/status.js.map +1 -0
  69. package/dist/config/define.d.ts +148 -0
  70. package/dist/config/define.js +20 -0
  71. package/dist/config/define.js.map +1 -0
  72. package/dist/config/index.d.ts +11 -0
  73. package/dist/config/index.js +12 -0
  74. package/dist/config/index.js.map +1 -0
  75. package/dist/config/load.d.ts +17 -0
  76. package/dist/config/load.js +162 -0
  77. package/dist/config/load.js.map +1 -0
  78. package/dist/config/settings.d.ts +53 -0
  79. package/dist/config/settings.js +116 -0
  80. package/dist/config/settings.js.map +1 -0
  81. package/dist/env-args.d.ts +24 -0
  82. package/dist/env-args.js +92 -0
  83. package/dist/env-args.js.map +1 -0
  84. package/dist/exit.d.ts +20 -0
  85. package/dist/exit.js +28 -0
  86. package/dist/exit.js.map +1 -0
  87. package/dist/history/client.d.ts +25 -0
  88. package/dist/history/client.js +42 -0
  89. package/dist/history/client.js.map +1 -0
  90. package/dist/history/ddl.d.ts +58 -0
  91. package/dist/history/ddl.js +100 -0
  92. package/dist/history/ddl.js.map +1 -0
  93. package/dist/history/index.d.ts +25 -0
  94. package/dist/history/index.js +26 -0
  95. package/dist/history/index.js.map +1 -0
  96. package/dist/history/lock.d.ts +104 -0
  97. package/dist/history/lock.js +230 -0
  98. package/dist/history/lock.js.map +1 -0
  99. package/dist/history/postgres/ddl.d.ts +22 -0
  100. package/dist/history/postgres/ddl.js +47 -0
  101. package/dist/history/postgres/ddl.js.map +1 -0
  102. package/dist/history/postgres/index.d.ts +12 -0
  103. package/dist/history/postgres/index.js +13 -0
  104. package/dist/history/postgres/index.js.map +1 -0
  105. package/dist/history/postgres/lock.d.ts +48 -0
  106. package/dist/history/postgres/lock.js +95 -0
  107. package/dist/history/postgres/lock.js.map +1 -0
  108. package/dist/history/postgres/store.d.ts +39 -0
  109. package/dist/history/postgres/store.js +68 -0
  110. package/dist/history/postgres/store.js.map +1 -0
  111. package/dist/history/rows.d.ts +96 -0
  112. package/dist/history/rows.js +73 -0
  113. package/dist/history/rows.js.map +1 -0
  114. package/dist/history/store.d.ts +72 -0
  115. package/dist/history/store.js +134 -0
  116. package/dist/history/store.js.map +1 -0
  117. package/dist/history/topology.d.ts +41 -0
  118. package/dist/history/topology.js +54 -0
  119. package/dist/history/topology.js.map +1 -0
  120. package/dist/index.d.ts +10 -0
  121. package/dist/index.js +10 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/init/adopt.d.ts +70 -0
  124. package/dist/init/adopt.js +143 -0
  125. package/dist/init/adopt.js.map +1 -0
  126. package/dist/init/baseline.d.ts +61 -0
  127. package/dist/init/baseline.js +88 -0
  128. package/dist/init/baseline.js.map +1 -0
  129. package/dist/init/files.d.ts +23 -0
  130. package/dist/init/files.js +77 -0
  131. package/dist/init/files.js.map +1 -0
  132. package/dist/init/index.d.ts +16 -0
  133. package/dist/init/index.js +17 -0
  134. package/dist/init/index.js.map +1 -0
  135. package/dist/init/live.d.ts +39 -0
  136. package/dist/init/live.js +68 -0
  137. package/dist/init/live.js.map +1 -0
  138. package/dist/lint/check.d.ts +88 -0
  139. package/dist/lint/check.js +227 -0
  140. package/dist/lint/check.js.map +1 -0
  141. package/dist/lint/config.d.ts +21 -0
  142. package/dist/lint/config.js +43 -0
  143. package/dist/lint/config.js.map +1 -0
  144. package/dist/lint/index.d.ts +31 -0
  145. package/dist/lint/index.js +43 -0
  146. package/dist/lint/index.js.map +1 -0
  147. package/dist/lint/project-rules.d.ts +19 -0
  148. package/dist/lint/project-rules.js +32 -0
  149. package/dist/lint/project-rules.js.map +1 -0
  150. package/dist/lint/report.d.ts +7 -0
  151. package/dist/lint/report.js +48 -0
  152. package/dist/lint/report.js.map +1 -0
  153. package/dist/lint/rules.d.ts +53 -0
  154. package/dist/lint/rules.js +42 -0
  155. package/dist/lint/rules.js.map +1 -0
  156. package/dist/lint/silences.d.ts +49 -0
  157. package/dist/lint/silences.js +91 -0
  158. package/dist/lint/silences.js.map +1 -0
  159. package/dist/lint/statements.d.ts +38 -0
  160. package/dist/lint/statements.js +120 -0
  161. package/dist/lint/statements.js.map +1 -0
  162. package/dist/mcp/server.d.ts +40 -0
  163. package/dist/mcp/server.js +106 -0
  164. package/dist/mcp/server.js.map +1 -0
  165. package/dist/mcp/tools.d.ts +72 -0
  166. package/dist/mcp/tools.js +178 -0
  167. package/dist/mcp/tools.js.map +1 -0
  168. package/dist/migrations/checks.d.ts +28 -0
  169. package/dist/migrations/checks.js +97 -0
  170. package/dist/migrations/checks.js.map +1 -0
  171. package/dist/migrations/checksum.d.ts +56 -0
  172. package/dist/migrations/checksum.js +72 -0
  173. package/dist/migrations/checksum.js.map +1 -0
  174. package/dist/migrations/create.d.ts +75 -0
  175. package/dist/migrations/create.js +120 -0
  176. package/dist/migrations/create.js.map +1 -0
  177. package/dist/migrations/directory.d.ts +119 -0
  178. package/dist/migrations/directory.js +229 -0
  179. package/dist/migrations/directory.js.map +1 -0
  180. package/dist/migrations/document.d.ts +106 -0
  181. package/dist/migrations/document.js +83 -0
  182. package/dist/migrations/document.js.map +1 -0
  183. package/dist/migrations/index.d.ts +21 -0
  184. package/dist/migrations/index.js +22 -0
  185. package/dist/migrations/index.js.map +1 -0
  186. package/dist/pr/comment.d.ts +32 -0
  187. package/dist/pr/comment.js +99 -0
  188. package/dist/pr/comment.js.map +1 -0
  189. package/dist/pr/forge.d.ts +54 -0
  190. package/dist/pr/forge.js +119 -0
  191. package/dist/pr/forge.js.map +1 -0
  192. package/dist/pr/issue.d.ts +68 -0
  193. package/dist/pr/issue.js +107 -0
  194. package/dist/pr/issue.js.map +1 -0
  195. package/dist/pr/model.d.ts +99 -0
  196. package/dist/pr/model.js +137 -0
  197. package/dist/pr/model.js.map +1 -0
  198. package/dist/pr/render.d.ts +29 -0
  199. package/dist/pr/render.js +279 -0
  200. package/dist/pr/render.js.map +1 -0
  201. package/dist/project.d.ts +62 -0
  202. package/dist/project.js +92 -0
  203. package/dist/project.js.map +1 -0
  204. package/dist/rebase/index.d.ts +121 -0
  205. package/dist/rebase/index.js +390 -0
  206. package/dist/rebase/index.js.map +1 -0
  207. package/dist/rebase/repair.d.ts +41 -0
  208. package/dist/rebase/repair.js +68 -0
  209. package/dist/rebase/repair.js.map +1 -0
  210. package/dist/replay/clickhouse.d.ts +33 -0
  211. package/dist/replay/clickhouse.js +106 -0
  212. package/dist/replay/clickhouse.js.map +1 -0
  213. package/dist/replay/index.d.ts +39 -0
  214. package/dist/replay/index.js +57 -0
  215. package/dist/replay/index.js.map +1 -0
  216. package/dist/replay/postgres.d.ts +31 -0
  217. package/dist/replay/postgres.js +140 -0
  218. package/dist/replay/postgres.js.map +1 -0
  219. package/dist/replay/replay.d.ts +114 -0
  220. package/dist/replay/replay.js +98 -0
  221. package/dist/replay/replay.js.map +1 -0
  222. package/dist/replay/report.d.ts +18 -0
  223. package/dist/replay/report.js +72 -0
  224. package/dist/replay/report.js.map +1 -0
  225. package/dist/replay/shared.d.ts +28 -0
  226. package/dist/replay/shared.js +77 -0
  227. package/dist/replay/shared.js.map +1 -0
  228. package/dist/schemas.d.ts +34 -0
  229. package/dist/schemas.js +38 -0
  230. package/dist/schemas.js.map +1 -0
  231. package/dist/server.d.ts +40 -0
  232. package/dist/server.js +87 -0
  233. package/dist/server.js.map +1 -0
  234. package/dist/steps/backfill.d.ts +132 -0
  235. package/dist/steps/backfill.js +307 -0
  236. package/dist/steps/backfill.js.map +1 -0
  237. package/dist/steps/index.d.ts +52 -0
  238. package/dist/steps/index.js +52 -0
  239. package/dist/steps/index.js.map +1 -0
  240. package/dist/steps/kinds.d.ts +101 -0
  241. package/dist/steps/kinds.js +143 -0
  242. package/dist/steps/kinds.js.map +1 -0
  243. package/dist/steps/postgres-migration.d.ts +95 -0
  244. package/dist/steps/postgres-migration.js +149 -0
  245. package/dist/steps/postgres-migration.js.map +1 -0
  246. package/dist/steps/rebuild.d.ts +97 -0
  247. package/dist/steps/rebuild.js +155 -0
  248. package/dist/steps/rebuild.js.map +1 -0
  249. package/dist/steps/run.d.ts +45 -0
  250. package/dist/steps/run.js +72 -0
  251. package/dist/steps/run.js.map +1 -0
  252. package/dist/topology/render.d.ts +17 -0
  253. package/dist/topology/render.js +17 -0
  254. package/dist/topology/render.js.map +1 -0
  255. package/dist/version.d.ts +2 -0
  256. package/dist/version.js +11 -0
  257. package/dist/version.js.map +1 -0
  258. package/dist/versioned/approval.d.ts +35 -0
  259. package/dist/versioned/approval.js +53 -0
  260. package/dist/versioned/approval.js.map +1 -0
  261. package/dist/versioned/checks.d.ts +29 -0
  262. package/dist/versioned/checks.js +54 -0
  263. package/dist/versioned/checks.js.map +1 -0
  264. package/dist/versioned/clickhouse.d.ts +24 -0
  265. package/dist/versioned/clickhouse.js +42 -0
  266. package/dist/versioned/clickhouse.js.map +1 -0
  267. package/dist/versioned/digest.d.ts +77 -0
  268. package/dist/versioned/digest.js +69 -0
  269. package/dist/versioned/digest.js.map +1 -0
  270. package/dist/versioned/driver.d.ts +51 -0
  271. package/dist/versioned/driver.js +13 -0
  272. package/dist/versioned/driver.js.map +1 -0
  273. package/dist/versioned/engine.d.ts +206 -0
  274. package/dist/versioned/engine.js +370 -0
  275. package/dist/versioned/engine.js.map +1 -0
  276. package/dist/versioned/explain.d.ts +123 -0
  277. package/dist/versioned/explain.js +294 -0
  278. package/dist/versioned/explain.js.map +1 -0
  279. package/dist/versioned/index.d.ts +46 -0
  280. package/dist/versioned/index.js +77 -0
  281. package/dist/versioned/index.js.map +1 -0
  282. package/dist/versioned/op.d.ts +72 -0
  283. package/dist/versioned/op.js +185 -0
  284. package/dist/versioned/op.js.map +1 -0
  285. package/dist/versioned/policy.d.ts +67 -0
  286. package/dist/versioned/policy.js +161 -0
  287. package/dist/versioned/policy.js.map +1 -0
  288. package/dist/versioned/postgres/catalog.d.ts +33 -0
  289. package/dist/versioned/postgres/catalog.js +34 -0
  290. package/dist/versioned/postgres/catalog.js.map +1 -0
  291. package/dist/versioned/postgres/driver.d.ts +71 -0
  292. package/dist/versioned/postgres/driver.js +140 -0
  293. package/dist/versioned/postgres/driver.js.map +1 -0
  294. package/dist/versioned/postgres/executor.d.ts +104 -0
  295. package/dist/versioned/postgres/executor.js +172 -0
  296. package/dist/versioned/postgres/executor.js.map +1 -0
  297. package/dist/versioned/postgres/index.d.ts +11 -0
  298. package/dist/versioned/postgres/index.js +12 -0
  299. package/dist/versioned/postgres/index.js.map +1 -0
  300. package/dist/versioned/postgres/waits.d.ts +46 -0
  301. package/dist/versioned/postgres/waits.js +78 -0
  302. package/dist/versioned/postgres/waits.js.map +1 -0
  303. package/dist/versioned/report.d.ts +25 -0
  304. package/dist/versioned/report.js +84 -0
  305. package/dist/versioned/report.js.map +1 -0
  306. package/dist/versioned/stand-down.d.ts +30 -0
  307. package/dist/versioned/stand-down.js +56 -0
  308. package/dist/versioned/stand-down.js.map +1 -0
  309. package/dist/versioned/state.d.ts +108 -0
  310. package/dist/versioned/state.js +153 -0
  311. package/dist/versioned/state.js.map +1 -0
  312. package/package.json +61 -4
  313. package/schemas/apply.schema.json +269 -0
  314. package/schemas/common.schema.json +213 -0
  315. package/schemas/drift.schema.json +36 -0
  316. package/schemas/init.schema.json +31 -0
  317. package/schemas/lint-rules.schema.json +54 -0
  318. package/schemas/lint.schema.json +290 -0
  319. package/schemas/mcp.schema.json +70 -0
  320. package/schemas/new.schema.json +71 -0
  321. package/schemas/plan.schema.json +371 -0
  322. package/schemas/rebase.schema.json +54 -0
  323. package/schemas/repair.schema.json +20 -0
  324. 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,3 @@
1
+ import type { Command } from "../command.js";
2
+ /** `yodel override <env> --rule <id> --reason <text>`; the exit codes are in `help`. */
3
+ export declare const override: Command;
@@ -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,3 @@
1
+ import type { Command } from "../command.js";
2
+ /** `yodel plan <env>`; the exit codes are in `help`. */
3
+ export declare const plan: Command;
@@ -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