@intentius/sql-yodeler 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/chant/build.js +7 -0
- package/dist/chant/build.js.map +1 -1
- package/dist/ci/render.d.ts +289 -0
- package/dist/ci/render.js +688 -0
- package/dist/ci/render.js.map +1 -0
- package/dist/ci/replay.d.ts +17 -0
- package/dist/ci/replay.js +69 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/commands/apply.d.ts +113 -0
- package/dist/commands/apply.js +228 -21
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/approve.js +44 -0
- package/dist/commands/approve.js.map +1 -1
- package/dist/commands/checkpoint.d.ts +3 -0
- package/dist/commands/checkpoint.js +78 -0
- package/dist/commands/checkpoint.js.map +1 -0
- package/dist/commands/ci.d.ts +3 -0
- package/dist/commands/ci.js +118 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/docs.d.ts +11 -0
- package/dist/commands/docs.js +108 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/drift.d.ts +13 -1
- package/dist/commands/drift.js +18 -2
- package/dist/commands/drift.js.map +1 -1
- package/dist/commands/index.js +10 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.js +11 -3
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/override.js +48 -16
- package/dist/commands/override.js.map +1 -1
- package/dist/commands/plan.js +5 -1
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/report.d.ts +13 -0
- package/dist/commands/report.js +121 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/revert.d.ts +20 -0
- package/dist/commands/revert.js +246 -0
- package/dist/commands/revert.js.map +1 -0
- package/dist/commands/status.js +32 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/config/base.d.ts +69 -0
- package/dist/config/base.js +121 -0
- package/dist/config/base.js.map +1 -0
- package/dist/config/define.d.ts +123 -1
- package/dist/config/define.js +2 -0
- package/dist/config/define.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/load.d.ts +1 -1
- package/dist/config/load.js +90 -5
- package/dist/config/load.js.map +1 -1
- package/dist/config/settings.d.ts +4 -1
- package/dist/config/settings.js +8 -2
- package/dist/config/settings.js.map +1 -1
- package/dist/exit.js +1 -1
- package/dist/exit.js.map +1 -1
- package/dist/history/postgres/ddl.d.ts +18 -0
- package/dist/history/postgres/ddl.js +27 -2
- package/dist/history/postgres/ddl.js.map +1 -1
- package/dist/history/postgres/store.d.ts +2 -0
- package/dist/history/postgres/store.js +7 -1
- package/dist/history/postgres/store.js.map +1 -1
- package/dist/history/rows.d.ts +16 -3
- package/dist/history/rows.js +30 -1
- package/dist/history/rows.js.map +1 -1
- package/dist/history/store.d.ts +2 -0
- package/dist/history/store.js +2 -2
- package/dist/history/store.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lint/check.js +17 -2
- package/dist/lint/check.js.map +1 -1
- package/dist/lint/report.js +1 -1
- package/dist/lint/report.js.map +1 -1
- package/dist/lint/routines.d.ts +26 -0
- package/dist/lint/routines.js +65 -0
- package/dist/lint/routines.js.map +1 -0
- package/dist/lint/rules.d.ts +16 -4
- package/dist/lint/rules.js +9 -4
- package/dist/lint/rules.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/migrations/checkpoint.d.ts +45 -0
- package/dist/migrations/checkpoint.js +110 -0
- package/dist/migrations/checkpoint.js.map +1 -0
- package/dist/migrations/create.js +2 -0
- package/dist/migrations/create.js.map +1 -1
- package/dist/migrations/document.d.ts +10 -0
- package/dist/migrations/document.js +1 -0
- package/dist/migrations/document.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.js +2 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/routines.d.ts +31 -0
- package/dist/migrations/routines.js +55 -0
- package/dist/migrations/routines.js.map +1 -0
- package/dist/notify/index.d.ts +124 -0
- package/dist/notify/index.js +210 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/verify.d.ts +53 -0
- package/dist/notify/verify.js +71 -0
- package/dist/notify/verify.js.map +1 -0
- package/dist/pr/model.d.ts +3 -0
- package/dist/pr/model.js +16 -2
- package/dist/pr/model.js.map +1 -1
- package/dist/pr/render.js +27 -1
- package/dist/pr/render.js.map +1 -1
- package/dist/project.d.ts +19 -2
- package/dist/project.js +33 -1
- package/dist/project.js.map +1 -1
- package/dist/replay/replay.d.ts +9 -1
- package/dist/replay/replay.js +62 -31
- package/dist/replay/replay.js.map +1 -1
- package/dist/replay/report.js +1 -1
- package/dist/replay/report.js.map +1 -1
- package/dist/report/audit.d.ts +138 -0
- package/dist/report/audit.js +202 -0
- package/dist/report/audit.js.map +1 -0
- package/dist/report/jsonl.d.ts +21 -0
- package/dist/report/jsonl.js +52 -0
- package/dist/report/jsonl.js.map +1 -0
- package/dist/report/model.d.ts +68 -0
- package/dist/report/model.js +127 -0
- package/dist/report/model.js.map +1 -0
- package/dist/report/render.d.ts +18 -0
- package/dist/report/render.js +127 -0
- package/dist/report/render.js.map +1 -0
- package/dist/schema-docs/render.d.ts +49 -0
- package/dist/schema-docs/render.js +359 -0
- package/dist/schema-docs/render.js.map +1 -0
- package/dist/schemas.d.ts +5 -2
- package/dist/schemas.js +5 -2
- package/dist/schemas.js.map +1 -1
- package/dist/sources/ddl.d.ts +53 -0
- package/dist/sources/ddl.js +347 -0
- package/dist/sources/ddl.js.map +1 -0
- package/dist/sources/index.d.ts +69 -0
- package/dist/sources/index.js +170 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/steps/postgres-migration.d.ts +35 -25
- package/dist/steps/postgres-migration.js +42 -29
- package/dist/steps/postgres-migration.js.map +1 -1
- package/dist/tenants/index.d.ts +40 -0
- package/dist/tenants/index.js +122 -0
- package/dist/tenants/index.js.map +1 -0
- package/dist/tenants/target.d.ts +23 -0
- package/dist/tenants/target.js +29 -0
- package/dist/tenants/target.js.map +1 -0
- package/dist/versioned/engine.d.ts +6 -0
- package/dist/versioned/engine.js +12 -2
- package/dist/versioned/engine.js.map +1 -1
- package/dist/versioned/index.d.ts +1 -0
- package/dist/versioned/index.js +1 -0
- package/dist/versioned/index.js.map +1 -1
- package/dist/versioned/op.d.ts +6 -1
- package/dist/versioned/op.js +7 -2
- package/dist/versioned/op.js.map +1 -1
- package/dist/versioned/policy.d.ts +33 -2
- package/dist/versioned/policy.js +59 -8
- package/dist/versioned/policy.js.map +1 -1
- package/dist/versioned/report.js +9 -0
- package/dist/versioned/report.js.map +1 -1
- package/dist/versioned/revert.d.ts +128 -0
- package/dist/versioned/revert.js +251 -0
- package/dist/versioned/revert.js.map +1 -0
- package/dist/versioned/state.d.ts +25 -0
- package/dist/versioned/state.js +36 -3
- package/dist/versioned/state.js.map +1 -1
- package/dist/waves/index.d.ts +179 -0
- package/dist/waves/index.js +268 -0
- package/dist/waves/index.js.map +1 -0
- package/dist/waves/promotion.d.ts +56 -0
- package/dist/waves/promotion.js +108 -0
- package/dist/waves/promotion.js.map +1 -0
- package/dist/waves/record.d.ts +24 -0
- package/dist/waves/record.js +20 -0
- package/dist/waves/record.js.map +1 -0
- package/package.json +11 -7
- package/schemas/apply.schema.json +162 -3
- package/schemas/checkpoint.schema.json +19 -0
- package/schemas/drift.schema.json +26 -1
- package/schemas/event.schema.json +36 -0
- package/schemas/lint.schema.json +4 -0
- package/schemas/plan.schema.json +118 -0
- package/schemas/report.schema.json +114 -0
- package/schemas/revert.schema.json +69 -0
- package/schemas/status.schema.json +64 -3
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ npx yodel apply dev
|
|
|
22
22
|
|
|
23
23
|
For Postgres, use `templates/postgres` with `--param name=app-schema --param schema=app`.
|
|
24
24
|
|
|
25
|
-
Each template gives `chant.config.ts` with a profile per environment (`dev`, `prod`), `yodel.config.ts`, a migrate Op per environment (`yodel apply` behind a gate bound to the plan digest), a `WatchOp` per environment for drift on a schedule, and pipelines for GitHub Actions, GitLab CI and Forgejo Actions: `yodel lint` and the plan comment on pull requests with read-only credentials, `yodel apply` on a push to main with the writer, and the drift watch on its cron with the reader. `
|
|
25
|
+
Each template gives `chant.config.ts` with a profile per environment (`dev`, `prod`), `yodel.config.ts`, a migrate Op per environment (`yodel apply` behind a gate bound to the plan digest), a `WatchOp` per environment for drift on a schedule, and pipelines for GitHub Actions, GitLab CI and Forgejo Actions: `yodel lint` and the plan comment on pull requests with read-only credentials, `yodel apply` on a push to main with the writer, and the drift watch on its cron with the reader. `yodel ci` (`npm run ci`) renders the pipelines from the installed package, so upgrading yodel and rendering again brings its pipeline fixes, and the project's own jobs, declared in a module `yodel.config.ts` `ci.jobs` names, are kept. The parameters are in each template's `chant.template.json`; the template's README covers the credentials and each forge's setup.
|
|
26
26
|
|
|
27
27
|
## Documentation
|
|
28
28
|
|
|
@@ -92,8 +92,8 @@ export const op = Op({
|
|
|
92
92
|
`yodel lint` checks the migrations directory offline, with no server, so it runs in CI on every pull request. Every rule is free. Exit 0 when there is no error (warnings and silenced findings do not fail), 3 when there is at least one, 1 when lint cannot run; `--json` prints the findings.
|
|
93
93
|
|
|
94
94
|
- Integrity, always errors: `fork` (two migrations follow the same parent; `yodel rebase`), `directory` (a bad name or file, a missing parent, a cycle, another dialect), `checksum` (a migration's files changed since it was written), `unreadable-object` (an object in a recorded schema, or a step, the sql lexicon cannot read or classify; never skipped).
|
|
95
|
-
- From the classifier's classes: `destructive` (a drop), `pg-rewrite` (a rewrite or full scan under ACCESS EXCLUSIVE), `pg-lock` (writes blocked for a whole index build or scan: no `CONCURRENTLY`, a constraint validated in place), `ch-mutation` (a mutation rewriting parts), `ch-rebuild` (a `ClickHouseRebuildOp` step).
|
|
96
|
-
- `refused-step` (a step `yodel apply` will not run), `migration-sql` (migration.sql does not hold a statement migration.json records), `silence` (a bad or unused `yodel:allow` line).
|
|
95
|
+
- From the classifier's classes: `destructive` (a drop), `pg-rewrite` (a rewrite or full scan under ACCESS EXCLUSIVE), `pg-lock` (writes blocked for a whole index build or scan: no `CONCURRENTLY`, a constraint validated in place), `pg-signature` (a function or procedure dropped and created with another signature), `ch-mutation` (a mutation rewriting parts), `ch-rebuild` (a `ClickHouseRebuildOp` step).
|
|
96
|
+
- `refused-step` (a step `yodel apply` will not run), `pg-routine-in-use` (a function dropped while a declared trigger still executes it), `migration-sql` (migration.sql does not hold a statement migration.json records), `silence` (a bad or unused `yodel:allow` line).
|
|
97
97
|
|
|
98
98
|
A line above a statement in its `migration.sql` silences one of the classifier rules for that statement:
|
|
99
99
|
|
package/dist/chant/build.js
CHANGED
|
@@ -7,6 +7,7 @@ import { tmpdir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { YodelError } from "../project.js";
|
|
9
9
|
import { runChant } from "./process.js";
|
|
10
|
+
import { checkOwnership, duplicateExport, syncSources } from "../sources/index.js";
|
|
10
11
|
/**
|
|
11
12
|
* Builds the project's declared schema. Writes to `out` when given, else to
|
|
12
13
|
* a temporary directory that {@link Build.dispose} removes. A build that
|
|
@@ -20,12 +21,17 @@ export async function buildSchema(project, options = {}) {
|
|
|
20
21
|
rmSync(tmp, { recursive: true, force: true });
|
|
21
22
|
};
|
|
22
23
|
try {
|
|
24
|
+
// The DDL sources (#67) first: their declarations are part of the build.
|
|
25
|
+
const synced = await syncSources(project, options.env ? { env: options.env } : {});
|
|
23
26
|
const result = await runChant(["build", project.sourceDir, "--lexicon", "sql", "-o", path], {
|
|
24
27
|
cwd: project.dir,
|
|
25
28
|
...(options.env ? { env: options.env } : {}),
|
|
26
29
|
});
|
|
27
30
|
if (result.code !== 0) {
|
|
28
31
|
const detail = (result.stderr || result.stdout).trim();
|
|
32
|
+
const duplicate = duplicateExport(detail, synced);
|
|
33
|
+
if (duplicate)
|
|
34
|
+
throw duplicate;
|
|
29
35
|
throw new YodelError(`chant build failed in ${project.dir}${detail ? `:\n${detail}` : ""}`);
|
|
30
36
|
}
|
|
31
37
|
let output;
|
|
@@ -38,6 +44,7 @@ export async function buildSchema(project, options = {}) {
|
|
|
38
44
|
if (output.dialect !== project.dialect) {
|
|
39
45
|
throw new YodelError(`the build holds ${output.dialect} objects, but the project's dialect is ${project.dialect}`);
|
|
40
46
|
}
|
|
47
|
+
await checkOwnership(output, synced);
|
|
41
48
|
return { path, output, dispose };
|
|
42
49
|
}
|
|
43
50
|
catch (e) {
|
package/dist/chant/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/chant/build.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAiC,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/chant/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel ci` (#65): a project's CI for GitHub Actions, GitLab CI and Forgejo
|
|
3
|
+
* Actions, rendered by the installed package, so a fix to the pipelines
|
|
4
|
+
* reaches a project when it upgrades yodel and renders again. Until #65 the
|
|
5
|
+
* templates carried this as scripts/ci.ts.
|
|
6
|
+
*
|
|
7
|
+
* yodel ci write the files
|
|
8
|
+
* yodel ci --check exit 1 when a file differs from what this renders
|
|
9
|
+
* yodel ci --replay the lint job's replay check on this machine (./replay.ts)
|
|
10
|
+
*
|
|
11
|
+
* yodel.config.ts `ci` picks the forges (default all three) and names the
|
|
12
|
+
* module that declares the project's own jobs, which are merged after
|
|
13
|
+
* yodel's into the pull request pipeline and kept on every render
|
|
14
|
+
* ({@link ProjectJobs}).
|
|
15
|
+
*
|
|
16
|
+
* Environments are the keys of `sql.profiles` in chant.config.ts, in order,
|
|
17
|
+
* but `replay`, which is the lint job's throwaway server.
|
|
18
|
+
* Each one needs ops/migrate-<env>.op.ts and ops/watch-<env>.op.ts.
|
|
19
|
+
* The apply pipeline's waves are yodel.config.ts `waves`, in order; without
|
|
20
|
+
* it, one wave per environment in profile order, each with gate `always`.
|
|
21
|
+
*
|
|
22
|
+
* Three pipelines per forge, each declared with chant: yodel-pr with the
|
|
23
|
+
* github lexicon's Workflow, Job and Step (Forgejo renders the same
|
|
24
|
+
* declaration through the forgejo lexicon's dialect) and the gitlab
|
|
25
|
+
* lexicon's Workflow and Job; yodel-apply with chant's
|
|
26
|
+
* generateOpWavesPipeline, from the waves spec this writes
|
|
27
|
+
* (yodel-waves.json); the watch with generateOpsPipeline.
|
|
28
|
+
*
|
|
29
|
+
* - yodel-pr: on a pull (merge) request, `yodel ci --check` (these files are
|
|
30
|
+
* what yodel ci renders), `yodel lint`, and the replay check into a
|
|
31
|
+
* throwaway server the job starts (the `replay` profile), then for each
|
|
32
|
+
* environment `yodel plan <env> --comment`, which posts the pending
|
|
33
|
+
* migrations and the digest to approve. One job per environment, holding
|
|
34
|
+
* that environment's reader credentials and nothing else.
|
|
35
|
+
* - yodel-apply: on a push to main, one job per wave, each needing the one
|
|
36
|
+
* before, running `chant run wave`: it plans the environment (`yodel apply
|
|
37
|
+
* <env> --wave-plan`), reads the wave's gate policy from yodel-waves.json
|
|
38
|
+
* as the commit before the push has it, waits for an approval when that
|
|
39
|
+
* policy asks for one (exit 3, and the waves after it do not run), and
|
|
40
|
+
* applies (`yodel apply <env> --wave-apply`). The only jobs that hold a
|
|
41
|
+
* writer's credentials, one environment's each, with
|
|
42
|
+
* YODEL_CREDENTIALS=writer. Two merges in quick succession do not fail the
|
|
43
|
+
* second apply: it waits for the first's lock (YODEL_LOCK_WAIT), and an
|
|
44
|
+
* apply whose commit is no longer main's tip stands down
|
|
45
|
+
* (YODEL_STAND_DOWN), since the newer commit's waves apply the same
|
|
46
|
+
* environments.
|
|
47
|
+
* - watch-<env>: the environment's WatchOp on its own schedule, rendered by
|
|
48
|
+
* chant's generateOpsPipeline, with the reader's credentials. After the
|
|
49
|
+
* Op, `yodel drift <env> --issue` compares the server with the schema the
|
|
50
|
+
* newest applied migration records (so a migration waiting at its gate is
|
|
51
|
+
* not drift), keeps one tracking issue for the environment (opened on
|
|
52
|
+
* drift, updated while it changes, closed when it is gone), and fails the
|
|
53
|
+
* job on drift. The issue is written with the job's token on GitHub
|
|
54
|
+
* (issues: write) and Forgejo, and with GITLAB_TOKEN on GitLab.
|
|
55
|
+
*
|
|
56
|
+
* The scheduled watch never pushes. Its first phase (chant lifecycle
|
|
57
|
+
* snapshot) commits a snapshot to chant/lifecycle and tries to push it; the
|
|
58
|
+
* drift it reports comes from `chant lifecycle diff --live`, declared against
|
|
59
|
+
* live, which needs no snapshot on the remote. So the job sets a push URL
|
|
60
|
+
* that cannot be pushed to: the push fails, chant carries on, and the job
|
|
61
|
+
* works the same with a read-only token (GitHub `contents: read`, GitLab's
|
|
62
|
+
* CI_JOB_TOKEN) as with Forgejo's token, which can write. A push that could
|
|
63
|
+
* land would race the approvals people push to chant/lifecycle.
|
|
64
|
+
*
|
|
65
|
+
* Every job installs @intentius/sql-yodeler from npm with `npm ci`, which
|
|
66
|
+
* needs no token, in IMAGE. With yodel.config.ts `ci.image` (#66), the CI
|
|
67
|
+
* image ghcr.io/intentius/sql-yodeler pinned by its digest, every job runs
|
|
68
|
+
* in that image instead, which carries node, yodel, chant and the lexicons
|
|
69
|
+
* on PATH, and no job runs `npm ci` ({@link Toolchain}).
|
|
70
|
+
*/
|
|
71
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
72
|
+
export type Forge = "github" | "gitlab" | "forgejo";
|
|
73
|
+
export declare const FORGES: readonly Forge[];
|
|
74
|
+
export interface RenderedFile {
|
|
75
|
+
/** Relative to the project. */
|
|
76
|
+
path: string;
|
|
77
|
+
content: string;
|
|
78
|
+
}
|
|
79
|
+
/** The image every job runs in, where the forge runs jobs in a container. It needs git, so not -slim. */
|
|
80
|
+
export declare const IMAGE = "node:22-bookworm";
|
|
81
|
+
/** The CI image (#66): yodel, chant and the sql lexicon, published for each release. */
|
|
82
|
+
export declare const YODEL_IMAGE = "ghcr.io/intentius/sql-yodeler";
|
|
83
|
+
/**
|
|
84
|
+
* What the jobs run in. By default IMAGE, and each job installs the
|
|
85
|
+
* project's packages with `npm ci` and puts node_modules/.bin on PATH. With
|
|
86
|
+
* `ci.image` the yodel image (pinned by digest): its yodel, chant and tsx
|
|
87
|
+
* are on PATH, and a checkout resolves chant and the lexicons from the
|
|
88
|
+
* image's /node_modules, so nothing is installed.
|
|
89
|
+
*/
|
|
90
|
+
export interface Toolchain {
|
|
91
|
+
image: string;
|
|
92
|
+
/** The yodel image: no `npm ci`, and every job in the container. */
|
|
93
|
+
yodelImage: boolean;
|
|
94
|
+
}
|
|
95
|
+
export declare const toolchainOf: (envs: Pick<Environments, "image">) => Toolchain;
|
|
96
|
+
export declare const MAIN = "main";
|
|
97
|
+
/** DEV for dev: the prefix of an environment's variables. */
|
|
98
|
+
export declare const prefixOf: (env: string) => string;
|
|
99
|
+
export type Dialect = "clickhouse" | "postgres";
|
|
100
|
+
/** The environments to render for: chant.config.ts's sql.dialect and its profiles, in order. */
|
|
101
|
+
export interface Environments {
|
|
102
|
+
dialect: Dialect;
|
|
103
|
+
names: readonly string[];
|
|
104
|
+
/** The forges to render for: yodel.config.ts `ci.forges`, default every one. */
|
|
105
|
+
forges?: readonly Forge[];
|
|
106
|
+
/** The project's own jobs (yodel.config.ts `ci.jobs`), merged after yodel's. */
|
|
107
|
+
jobs?: ProjectJobs;
|
|
108
|
+
/** yodel.config.ts `ci.image`: the yodel image every job runs in, by digest. */
|
|
109
|
+
image?: string;
|
|
110
|
+
/** The apply pipeline's waves, in order. */
|
|
111
|
+
waves: readonly Wave[];
|
|
112
|
+
/** chant.config.ts declares the `replay` profile, so the lint job runs the replay check. */
|
|
113
|
+
replay: boolean;
|
|
114
|
+
}
|
|
115
|
+
/** DEV_CLICKHOUSE for dev on ClickHouse: what an environment's variables start with. */
|
|
116
|
+
export declare const variablePrefix: (env: string, dialect: Dialect) => string;
|
|
117
|
+
/** The variables a job reading `env` holds: the URL and the reader. */
|
|
118
|
+
export declare const readerVariables: (env: string, dialect: Dialect) => string[];
|
|
119
|
+
/** The variables the apply job for `env` holds: the URL and the writer. */
|
|
120
|
+
export declare const writerVariables: (env: string, dialect: Dialect) => string[];
|
|
121
|
+
/**
|
|
122
|
+
* The approvals: `chant approve` records them on the chant/lifecycle branch,
|
|
123
|
+
* and approvers push it. A CI checkout fetches one ref, so fetch this one too.
|
|
124
|
+
* The remote-tracking ref is what chant's push lease reads.
|
|
125
|
+
*/
|
|
126
|
+
export declare const FETCH_APPROVALS = "git fetch --no-tags origin '+refs/heads/chant/lifecycle:refs/remotes/origin/chant/lifecycle' && git branch -f chant/lifecycle origin/chant/lifecycle || echo 'no chant/lifecycle branch yet, so nothing is approved'";
|
|
127
|
+
/** The apply jobs' lock settings: wait for another apply's lock, and stand down for a newer commit on main. */
|
|
128
|
+
export declare const APPLY_LOCK: Record<string, string>;
|
|
129
|
+
/** The watch reads; it never pushes. See the comment at the top. */
|
|
130
|
+
export declare const NO_PUSH = "git remote set-url --push origin no-push://the-drift-watch-only-reads";
|
|
131
|
+
/** After the watch's `chant run`: drift against the history, the environment's tracking issue, and exit 2 on drift. */
|
|
132
|
+
export declare const watchDrift: (env: string) => string;
|
|
133
|
+
/**
|
|
134
|
+
* The replay check: `yodel lint --replay replay --reset` replays every
|
|
135
|
+
* migration, in order, into a fresh database on the `replay` profile's
|
|
136
|
+
* server and fails when one does not give the schema it recorded. That
|
|
137
|
+
* server is a throwaway container the lint job starts as a service, the
|
|
138
|
+
* image chant's emulator runs, which the job (in IMAGE, on every forge)
|
|
139
|
+
* reaches by the service's name; never a server whose data matters, since
|
|
140
|
+
* --reset drops what the history names there first.
|
|
141
|
+
*/
|
|
142
|
+
export declare const REPLAY_PROFILE = "replay";
|
|
143
|
+
interface ReplayService {
|
|
144
|
+
/**
|
|
145
|
+
* The service's name, and its host where the job runs in a container.
|
|
146
|
+
* Prefixed, so it cannot meet another container's alias on a runner's
|
|
147
|
+
* shared network: a host that answers for two servers would split the replay.
|
|
148
|
+
*/
|
|
149
|
+
name: string;
|
|
150
|
+
image: string;
|
|
151
|
+
port: number;
|
|
152
|
+
/** The service container's environment. */
|
|
153
|
+
env: Record<string, string>;
|
|
154
|
+
health: string;
|
|
155
|
+
/** The server's URL at `host`, on `port` (default: the service's own). */
|
|
156
|
+
url: (host: string, port?: number) => string;
|
|
157
|
+
/** What the lint job sets besides the URL: the throwaway server's credentials. */
|
|
158
|
+
jobEnv: Record<string, string>;
|
|
159
|
+
}
|
|
160
|
+
export declare const REPLAY_SERVICE: Record<Dialect, ReplayService>;
|
|
161
|
+
/** Waits up to 90 s for the replay server's port; a shell-free one-liner, so every forge runs it the same. */
|
|
162
|
+
export declare const waitFor: (host: string, port: number) => string;
|
|
163
|
+
export declare const LINT = "yodel lint";
|
|
164
|
+
export declare const REPLAY = "yodel lint --replay replay --reset";
|
|
165
|
+
/** Fails a pull request whose pipelines differ from what yodel ci renders. */
|
|
166
|
+
export declare const CI_CHECK = "yodel ci --check";
|
|
167
|
+
/**
|
|
168
|
+
* Webhook events (yodel.config.ts `notify`): the variables the apply, plan
|
|
169
|
+
* and watch jobs pass from the forge's secrets. yodel sends nothing while
|
|
170
|
+
* they are unset (empty), so an unconfigured repository needs neither.
|
|
171
|
+
* GitLab jobs have the project's variables already.
|
|
172
|
+
*/
|
|
173
|
+
export declare const WEBHOOK_VARIABLES: string[];
|
|
174
|
+
/** Where the plan and apply jobs write `yodel report`, kept as the job's artifact. */
|
|
175
|
+
export declare const REPORT_DIR = "yodel-report";
|
|
176
|
+
/**
|
|
177
|
+
* `yodel report <env>` as JSON and as the HTML run view, into REPORT_DIR.
|
|
178
|
+
* It runs whatever the job's outcome, and a gap it reports (exit 4) does not
|
|
179
|
+
* fail the job: the report is for reading. `yodel` is the project's, named
|
|
180
|
+
* by path, since GitLab's after_script does not keep before_script's PATH;
|
|
181
|
+
* in the yodel image, the image's, which is on PATH.
|
|
182
|
+
*/
|
|
183
|
+
export declare const reportCommand: (env: string, tc?: Toolchain) => string;
|
|
184
|
+
/** The artifact a job keeps its report in. */
|
|
185
|
+
export declare const reportArtifact: (job: string) => string;
|
|
186
|
+
type Actions = "github" | "forgejo";
|
|
187
|
+
export declare function actionsReview(forge: Actions, envs_: Environments): RenderedFile;
|
|
188
|
+
export declare const GITLAB_WATCH_FILE = ".gitlab/yodel-watch.gitlab-ci.yml";
|
|
189
|
+
export declare const GITLAB_APPLY_FILE = ".gitlab/yodel-apply.gitlab-ci.yml";
|
|
190
|
+
export declare function gitlabPipeline(envs_: Environments): RenderedFile;
|
|
191
|
+
/** When a wave waits for an approval; yodel.config.ts `waves[].gate`. */
|
|
192
|
+
export type WaveGate = "always" | "on-destructive" | "never";
|
|
193
|
+
export interface Wave {
|
|
194
|
+
env: string;
|
|
195
|
+
gate: WaveGate;
|
|
196
|
+
/**
|
|
197
|
+
* The environment whose history must record a migration applied before
|
|
198
|
+
* this one applies it (yodel.config.ts `waves[].requires`, default the
|
|
199
|
+
* wave before): this wave's jobs, and its plan job, read it with its reader.
|
|
200
|
+
*/
|
|
201
|
+
requires?: string;
|
|
202
|
+
/**
|
|
203
|
+
* A tenant set's wave (#56): a run per tenant, `<env>/<tenant>`, in place
|
|
204
|
+
* of the environment's one, all behind the wave's one gate.
|
|
205
|
+
*/
|
|
206
|
+
targets?: readonly string[];
|
|
207
|
+
/** Split the wave into a deciding job and this many share jobs (yodel.config.ts `waves[].shares`). */
|
|
208
|
+
shares?: number;
|
|
209
|
+
}
|
|
210
|
+
/** The waves spec: committed, and read by every wave job (its gate policy at the commit before the push). */
|
|
211
|
+
export declare const WAVES_SPEC = "yodel-waves.json";
|
|
212
|
+
export declare const APPLY_PIPELINE = "yodel-apply";
|
|
213
|
+
interface WavesSpec {
|
|
214
|
+
name: string;
|
|
215
|
+
op: string;
|
|
216
|
+
plan: string[];
|
|
217
|
+
apply: string[];
|
|
218
|
+
branches: string[];
|
|
219
|
+
waves: Array<{
|
|
220
|
+
name: string;
|
|
221
|
+
runs: Array<{
|
|
222
|
+
target: string;
|
|
223
|
+
op: string;
|
|
224
|
+
}>;
|
|
225
|
+
gate: WaveGate;
|
|
226
|
+
shares?: number;
|
|
227
|
+
environment?: {
|
|
228
|
+
name: string;
|
|
229
|
+
};
|
|
230
|
+
variables?: Record<string, string>;
|
|
231
|
+
}>;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The waves spec. What the file holds is the same for every forge; each
|
|
235
|
+
* forge's jobs get their own variables on top (`variables`).
|
|
236
|
+
*/
|
|
237
|
+
export declare function wavesSpec(waves: readonly Wave[], variables?: (env: string) => Record<string, string>): WavesSpec;
|
|
238
|
+
/** The waves spec file and the apply pipeline on each forge. */
|
|
239
|
+
export declare function applyPipelines(dir: string, envs: Environments): Promise<RenderedFile[]>;
|
|
240
|
+
/** yodel.config.ts `waves`, checked against the environments; one wave per environment, gate `always`, without it. */
|
|
241
|
+
export declare function readWaves(dir: string, envs: readonly string[]): Promise<Wave[]>;
|
|
242
|
+
/**
|
|
243
|
+
* Each wave of an environment with a tenant set (#56), with a run per
|
|
244
|
+
* tenant. A set given by a query is read from the server now, with the
|
|
245
|
+
* environment's profile: run yodel ci again when the tenants change.
|
|
246
|
+
*/
|
|
247
|
+
export declare function withTenants(dir: string, waves: readonly Wave[]): Promise<Wave[]>;
|
|
248
|
+
/**
|
|
249
|
+
* The watch's spec for one environment: the Op, and on GitHub and Forgejo the
|
|
250
|
+
* reader's secrets. A GitLab job already has the project's variables (a
|
|
251
|
+
* writer's only under its environment, which the watch does not declare), and
|
|
252
|
+
* `NAME: $NAME` there would refer to itself.
|
|
253
|
+
*/
|
|
254
|
+
export declare function watchSpec(env: string, dialect: Dialect, forge: Forge): {
|
|
255
|
+
name: string;
|
|
256
|
+
variables?: Record<string, string>;
|
|
257
|
+
permissions?: Record<string, string>;
|
|
258
|
+
};
|
|
259
|
+
export declare function watchPipelines(dir: string, envs_: Environments): Promise<RenderedFile[]>;
|
|
260
|
+
/** The dialect and the environments chant.config.ts declares, in order. */
|
|
261
|
+
export declare function environments(dir: string): Promise<Environments>;
|
|
262
|
+
/**
|
|
263
|
+
* The project's own jobs (#65): the module yodel.config.ts `ci.jobs` names,
|
|
264
|
+
* relative to the project, declaring jobs with chant's lexicons. `actions`
|
|
265
|
+
* (the github lexicon's Job) goes into yodel-pr on GitHub and Forgejo, after
|
|
266
|
+
* yodel's jobs; `gitlab` (the gitlab lexicon's Job) into .gitlab-ci.yml,
|
|
267
|
+
* after yodel's, its stages after `review`. They are declared here, not
|
|
268
|
+
* edited into the rendered files, so every `yodel ci` keeps them:
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* // ci/jobs.ts
|
|
272
|
+
* import { Job, Step } from "@intentius/chant-lexicon-github";
|
|
273
|
+
* import { Job as GitLabJob, Image } from "@intentius/chant-lexicon-gitlab";
|
|
274
|
+
* export const actions = { test: new Job({ "runs-on": "ubuntu-latest", steps: [new Step({ uses: "actions/checkout@v7" }), new Step({ run: "npm test" })] }) };
|
|
275
|
+
* export const gitlab = { test: new GitLabJob({ stage: "test", image: new Image({ name: "node:22" }), script: ["npm test"] }) };
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export interface ProjectJobs {
|
|
279
|
+
actions?: Record<string, Declarable>;
|
|
280
|
+
gitlab?: Record<string, Declarable>;
|
|
281
|
+
}
|
|
282
|
+
/** Loads the module `ci.jobs` names. */
|
|
283
|
+
export declare function loadProjectJobs(dir: string, file: string): Promise<ProjectJobs>;
|
|
284
|
+
/** The pipelines that need no Op discovery: pull requests, and GitLab's main file. */
|
|
285
|
+
export declare function review(envs: Environments): RenderedFile[];
|
|
286
|
+
/** The forge a rendered file is for; undefined for the waves spec, which every forge's apply reads. */
|
|
287
|
+
export declare function forgeOf(path: string): Forge | undefined;
|
|
288
|
+
export declare function render(dir: string): Promise<RenderedFile[]>;
|
|
289
|
+
export {};
|