@starci/hfs 4.12.2 → 4.13.2
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/CHANGELOG.md +50 -0
- package/README.md +8 -9
- package/cache/app-families.d.mts +30 -0
- package/cache/app-families.mjs +94 -0
- package/cache/content-index.d.mts +13 -0
- package/cache/content-index.mjs +37 -0
- package/cache/db-family.d.mts +13 -0
- package/cache/db-family.mjs +37 -0
- package/cache/fake-session.d.mts +42 -0
- package/cache/fake-session.mjs +57 -0
- package/cache/fingerprint.d.mts +28 -0
- package/cache/fingerprint.mjs +120 -0
- package/cache/format-family.d.mts +13 -0
- package/cache/format-family.mjs +49 -0
- package/cache/git.d.mts +11 -0
- package/cache/git.mjs +13 -0
- package/cache/hash.d.mts +15 -0
- package/cache/hash.mjs +58 -0
- package/cache/hfs-family.d.mts +35 -0
- package/cache/hfs-family.mjs +164 -0
- package/cache/import-graph.d.mts +75 -0
- package/cache/import-graph.mjs +204 -0
- package/cache/jsonl.d.mts +31 -0
- package/cache/jsonl.mjs +115 -0
- package/cache/lint-family.d.mts +162 -0
- package/cache/lint-family.mjs +433 -0
- package/cache/order.d.mts +1 -0
- package/cache/order.mjs +2 -0
- package/cache/progress.d.mts +19 -0
- package/cache/progress.mjs +68 -0
- package/cache/quality-baselines.d.mts +109 -0
- package/cache/quality-baselines.mjs +235 -0
- package/cache/records.d.mts +33 -0
- package/cache/records.mjs +54 -0
- package/cache/report.d.mts +19 -0
- package/cache/report.mjs +32 -0
- package/cache/run-log.d.mts +17 -0
- package/cache/run-log.mjs +37 -0
- package/cache/run.d.mts +33 -0
- package/cache/run.mjs +65 -0
- package/cache/session.d.mts +29 -0
- package/cache/session.mjs +197 -0
- package/cache/side-key.d.mts +10 -0
- package/cache/side-key.mjs +47 -0
- package/cache/test-family.d.mts +130 -0
- package/cache/test-family.mjs +258 -0
- package/cache/trust.d.mts +53 -0
- package/cache/trust.mjs +157 -0
- package/cache/types-family.d.mts +74 -0
- package/cache/types-family.mjs +154 -0
- package/cache/types.d.mts +99 -0
- package/cache/types.mjs +8 -0
- package/cache/wiring-family.d.mts +14 -0
- package/cache/wiring-family.mjs +43 -0
- package/coverage/cli.d.mts +4 -2
- package/coverage/cli.mjs +44 -5
- package/db/migrate.d.mts +16 -0
- package/db/migrate.mjs +21 -4
- package/db/postgres.d.mts +23 -0
- package/db/postgres.mjs +32 -1
- package/lint/run.d.mts +47 -1
- package/lint/run.mjs +157 -5
- package/package.json +2 -1
- package/release/gate.d.mts +2 -0
- package/release/gate.mjs +4 -2
- package/release/records.d.mts +15 -0
- package/release/records.mjs +25 -0
- package/release/urgent.mjs +23 -4
- package/runtime/knowledge/grammars/core/playbook.yaml +12 -18
- package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
- package/runtime/knowledge/hfs/ci-standard.yaml +29 -6
- package/runtime/knowledge/hfs/rules.yaml +97 -7
- package/runtime/knowledge/hfs/slots.yaml +20 -8
- package/runtime/modules/kernel/failure-codes.yaml +46 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +48 -0
- package/runtime/scripts/hfs/architecture/hfs.d.mts +6 -6
- package/runtime/scripts/hfs/check.mjs +1 -1
- package/runtime/scripts/hfs/gate-threshold.mjs +2 -1
- package/runtime/scripts/hfs/rules/boot-proof.d.mts +5 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +15 -1
- package/runtime/scripts/hfs/rules/ci-custody.mjs +3 -1
- package/runtime/scripts/hfs/rules/ci-standard.mjs +32 -3
- package/runtime/scripts/hfs/rules/database-migrations.d.mts +14 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +55 -0
- package/runtime/scripts/hfs/rules/database.mjs +2 -1
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +53 -6
- package/runtime/scripts/hfs/rules/release-provenance.mjs +32 -4
- package/runtime/scripts/hfs/rules/stacks.mjs +5 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +4 -1
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +98 -7
- package/runtime/scripts/hfs/rules/unused-symbol.d.mts +9 -2
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +15 -2
- package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +21 -5
- package/runtime/scripts/hfs/stack-deploy-config.d.mts +18 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +53 -1
- package/runtime/scripts/hfs/stacks-gate.mjs +2 -0
- package/runtime/scripts/hfs/tree-findings.mjs +3 -1
- package/scaffold/add-inbox-lite.mjs +2 -4
- package/scaffold/add-queue-lite.mjs +2 -3
- package/scaffold/add-table.mjs +2 -4
- package/scaffold/migration-stamp.d.mts +11 -0
- package/scaffold/migration-stamp.mjs +32 -0
- package/src/helpers/hfs-arch-fixture.d.mts +1 -1
- package/src/main.d.mts +4 -1
- package/src/main.mjs +189 -74
- package/src/stack-records-double.d.mts +33 -0
- package/stack/ci-key.d.mts +11 -0
- package/stack/ci-key.mjs +44 -0
- package/stack/cli.d.mts +7 -1
- package/stack/cli.mjs +22 -13
- package/stack/config.d.mts +15 -0
- package/stack/converge.d.mts +2 -0
- package/stack/converge.mjs +1 -1
- package/stack/db-phases.d.mts +185 -0
- package/stack/db-phases.mjs +346 -0
- package/stack/deploy.d.mts +22 -5
- package/stack/deploy.mjs +242 -72
- package/stack/journal.d.mts +146 -0
- package/stack/journal.mjs +191 -0
- package/stack/manager-backup.d.mts +54 -0
- package/stack/manager-backup.mjs +136 -0
- package/stack/notify.d.mts +9 -0
- package/stack/notify.mjs +32 -0
- package/stack/release-store.d.mts +49 -1
- package/stack/release-store.mjs +102 -16
- package/stack/remote.d.mts +2 -0
- package/stack/remote.mjs +32 -11
- package/stack/rollback-guard.d.mts +10 -0
- package/stack/rollback-guard.mjs +41 -0
- package/stack/server-key.d.mts +2 -0
- package/stack/server-key.mjs +1 -1
- package/stack/ssh-gate.d.mts +35 -0
- package/stack/ssh-gate.mjs +145 -0
- package/stack/status.d.mts +51 -0
- package/stack/status.mjs +115 -0
- package/stack/urgent-floor.d.mts +10 -61
- package/stack/urgent-floor.mjs +28 -123
- package/stack/verify.mjs +26 -0
- package/sync/ci-commands.mjs +10 -5
- package/sync/format-worker.d.mts +1 -0
- package/sync/format-worker.mjs +17 -0
- package/sync/format.d.mts +21 -3
- package/sync/format.mjs +99 -7
- package/sync/index.d.mts +3 -6
- package/sync/index.mjs +12 -12
- package/sync/stack-images.d.mts +25 -10
- package/sync/stack-images.mjs +215 -54
- package/templates/app/ci-workflows/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/gitignore +1 -0
- package/templates/app/gitignore-lite +1 -0
- package/templates/app/package-scripts/db.json +1 -1
- package/templates/app/skeleton/.gitattributes +2 -0
- package/templates/app/skeleton-lite/.gitattributes +2 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +2 -1
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +0 -63
package/stack/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
// cli.mjs - `starci app stack <action>` for the Swarm deploy tooling: flags, dry run versus --execute, dispatch, text and --json output.
|
|
10
10
|
// starci app stack <action> --env <env> [the action's FLAGS] (only the READ_ONLY actions default --env to production)
|
|
11
11
|
// Each action takes only its own flags: a flag of another action is a refusal, never ignored.
|
|
12
|
-
// (
|
|
12
|
+
// (`starci app world <up|down|status>` is the test world's, dispatched by packages/hfs/src/main.mjs; `stack status` below is the deploy's drift report.)
|
|
13
13
|
// Every action module exports one async function (ctx, config, options) -> { ok, lines, data }; a refusal throws StackError.
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { stackContext } from './context.mjs';
|
|
@@ -19,12 +19,12 @@ import { StackError, EXIT, refusal } from './errors.mjs';
|
|
|
19
19
|
import { parseTargetBinding, bindStackTarget, nativeToolEnvironment } from './target-binding.mjs';
|
|
20
20
|
import { oneCommand, prepareUrgentRelease } from './urgent-release.mjs';
|
|
21
21
|
/** The actions that drive the docker CLI of the operator machine (pointed at the manager by docker-transport.mjs when the key is sealed). */
|
|
22
|
-
const DOCKER_ACTIONS = new Set(['deploy', 'accept', 'converge', 'verify', 'rollback', 'prune']);
|
|
23
|
-
export const ACTIONS = Object.freeze(['prepare', 'connect', 'disconnect', 'build-args', 'render', 'deploy', 'converge', 'verify', 'rollback', 'prune', 'backup', 'jobs', 'task', 'ingress', 'retire', 'build', 'accept']);
|
|
22
|
+
const DOCKER_ACTIONS = new Set(['deploy', 'accept', 'converge', 'verify', 'rollback', 'prune', 'status']);
|
|
23
|
+
export const ACTIONS = Object.freeze(['prepare', 'connect', 'disconnect', 'build-args', 'render', 'deploy', 'converge', 'verify', 'rollback', 'prune', 'backup', 'jobs', 'task', 'ingress', 'retire', 'build', 'accept', 'status', 'ci-key']);
|
|
24
24
|
/** True when `value` names one action of `starci app stack`. */
|
|
25
25
|
export const isAction = (value) => ACTIONS.includes(value);
|
|
26
|
-
export const BOOLEAN = new Set(['--execute', '--json', '--swarm-state', '--init-swarm', '--owner-approved', '--fix-live-restore', '--skip-public', '--skip-internal', '--allow-unpinned', '--push', '--urgent', '--first-start']);
|
|
27
|
-
export const VALUE = new Set(['--env', '--image', '--sha', '--previous', '--out', '--release', '--since', '--timeout', '--retry', '--keep', '--app', '--ssh-cmd', '--extra-compose', '--project', '--target-binding', '--db-url-env', '--backup-out', '--db-service', '--reason', '--remote']);
|
|
26
|
+
export const BOOLEAN = new Set(['--execute', '--json', '--swarm-state', '--init-swarm', '--owner-approved', '--fix-live-restore', '--skip-public', '--skip-internal', '--allow-unpinned', '--push', '--urgent', '--first-start', '--redeploy', '--data-acknowledged', '--print-authorized-keys', '--print-gate']);
|
|
27
|
+
export const VALUE = new Set(['--env', '--image', '--sha', '--previous', '--out', '--release', '--since', '--timeout', '--retry', '--keep', '--app', '--ssh-cmd', '--extra-compose', '--project', '--target-binding', '--db-url-env', '--backup-out', '--db-service', '--reason', '--remote', '--ci-run', '--public-key']);
|
|
28
28
|
export const REPEAT = new Set(['--image', '--keep', '--extra-compose', '--project']);
|
|
29
29
|
/** `--ssh-cmd` replaces the configured alias for an action whose module (or the Docker transport) reaches the manager over SSH. */
|
|
30
30
|
const SSH = ['--ssh-cmd'];
|
|
@@ -38,10 +38,10 @@ export const FLAGS = Object.freeze({
|
|
|
38
38
|
disconnect: ['--execute'],
|
|
39
39
|
'build-args': ['--app'],
|
|
40
40
|
render: ['--image', '--sha', '--previous', '--out', '--allow-unpinned', '--extra-compose'],
|
|
41
|
-
deploy: ['--image', '--sha', '--execute', '--out', '--timeout', '--retry', '--skip-public', '--target-binding', '--urgent', '--reason', '--remote', '--db-url-env', '--backup-out', '--first-start', '--db-service', ...SSH],
|
|
41
|
+
deploy: ['--image', '--sha', '--execute', '--out', '--timeout', '--retry', '--skip-public', '--target-binding', '--urgent', '--reason', '--remote', '--db-url-env', '--backup-out', '--first-start', '--db-service', '--ci-run', '--redeploy', ...SSH],
|
|
42
42
|
converge: ['--since', '--timeout', ...SSH],
|
|
43
43
|
verify: ['--retry', '--skip-public', '--skip-internal', '--target-binding', ...SSH],
|
|
44
|
-
rollback: ['--release', '--execute', '--timeout', ...SSH],
|
|
44
|
+
rollback: ['--release', '--execute', '--timeout', '--data-acknowledged', ...SSH],
|
|
45
45
|
prune: ['--keep', '--execute', ...SSH],
|
|
46
46
|
backup: ['--swarm-state', '--execute', ...SSH],
|
|
47
47
|
jobs: ['--execute', ...SSH],
|
|
@@ -49,12 +49,14 @@ export const FLAGS = Object.freeze({
|
|
|
49
49
|
ingress: ['--execute', ...SSH],
|
|
50
50
|
retire: ['--project', '--execute', ...SSH],
|
|
51
51
|
build: ['--push', '--execute'],
|
|
52
|
-
accept: ['--release', '--execute', '--retry', ...SSH],
|
|
52
|
+
accept: ['--release', '--execute', '--retry', '--db-url-env', ...SSH],
|
|
53
|
+
status: [...SSH],
|
|
54
|
+
'ci-key': ['--public-key', '--print-authorized-keys', '--print-gate'],
|
|
53
55
|
});
|
|
54
56
|
/** The operands of the actions that take any, as the usage prints them. */
|
|
55
57
|
export const OPERANDS = Object.freeze({ jobs: '<action> [<arg>...]', task: '<name> [<arg>...]', ingress: '<action> [<arg>...]' });
|
|
56
58
|
/** The read-only actions, whose `--env` defaults to production. Every action with an effect names its environment, so none reaches production by omission. */
|
|
57
|
-
export const READ_ONLY = Object.freeze(['build-args', 'render', 'verify']);
|
|
59
|
+
export const READ_ONLY = Object.freeze(['build-args', 'render', 'verify', 'status', 'ci-key']);
|
|
58
60
|
/** Splits argv into { flags, positional }; an unknown flag, a flag of another action or a flag without its value is a refusal. */
|
|
59
61
|
function parseFlags(action, argv) {
|
|
60
62
|
const own = new Set(['--env', '--json', ...FLAGS[action]]);
|
|
@@ -91,8 +93,10 @@ function checkDeployForm(flags) {
|
|
|
91
93
|
}
|
|
92
94
|
if (!flags.urgent && (flags['first-start'] || flags['db-service']))
|
|
93
95
|
throw refusal('STACK_USAGE', '--first-start and --db-service are the first start of an empty database under the urgent floor: add --urgent');
|
|
94
|
-
if (
|
|
95
|
-
throw refusal('STACK_USAGE', '--
|
|
96
|
+
if (flags.urgent && flags['ci-run'])
|
|
97
|
+
throw refusal('STACK_USAGE', '--ci-run names the CI run that deploys; an urgent deploy runs no CI (the CI run of its tag is the back-fill)');
|
|
98
|
+
if (flags['ci-run'] !== undefined && !/^[0-9]{1,20}$/u.test(flags['ci-run']))
|
|
99
|
+
throw refusal('STACK_USAGE', '--ci-run takes the numeric id of the CI run');
|
|
96
100
|
}
|
|
97
101
|
/** `--image api=ghcr.io/o/x@sha256:...` (repeatable or comma separated) -> { api: 'ghcr.io/o/x@sha256:...' }. */
|
|
98
102
|
function parseImages(values = []) {
|
|
@@ -130,15 +134,18 @@ const OPTIONS = {
|
|
|
130
134
|
out: flags.out ? path.resolve(cwd, flags.out) : null, timeoutSeconds: number(flags, 'timeout'), retrySeconds: number(flags, 'retry'), skipPublic: flags['skip-public'] === true,
|
|
131
135
|
urgent: flags.urgent === true, dbUrlEnv: flags['db-url-env'], backupOut: flags['backup-out'] ? path.resolve(cwd, flags['backup-out']) : undefined,
|
|
132
136
|
firstStart: flags['first-start'] === true, dbService: flags['db-service'], reason: flags.reason, remote: flags.remote,
|
|
137
|
+
ciRun: flags['ci-run'], redeploy: flags.redeploy === true,
|
|
133
138
|
}),
|
|
134
139
|
converge: ({ flags }) => ({ since: flags.since, timeoutSeconds: number(flags, 'timeout') }),
|
|
135
140
|
verify: ({ flags }) => ({ retrySeconds: number(flags, 'retry'), skipPublic: flags['skip-public'] === true, skipInternal: flags['skip-internal'] === true }),
|
|
136
|
-
rollback: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, timeoutSeconds: number(flags, 'timeout') }),
|
|
141
|
+
rollback: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, timeoutSeconds: number(flags, 'timeout'), dataAcknowledged: flags['data-acknowledged'] === true }),
|
|
137
142
|
prune: ({ flags, cwd }) => ({ keepFiles: (flags.keep ?? []).map((file) => path.resolve(cwd, file)), execute: flags.execute === true }),
|
|
138
143
|
backup: ({ flags }) => ({ swarmState: flags['swarm-state'] === true, execute: flags.execute === true }),
|
|
139
144
|
ingress: ({ flags, positional }) => ({ action: positional[0], execute: flags.execute === true, args: positional.slice(1) }),
|
|
140
145
|
build: ({ flags }) => ({ push: flags.push === true, execute: flags.execute === true }),
|
|
141
|
-
accept: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, retrySeconds: number(flags, 'retry') }),
|
|
146
|
+
accept: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, retrySeconds: number(flags, 'retry'), dbUrlEnv: flags['db-url-env'] }),
|
|
147
|
+
status: () => ({}),
|
|
148
|
+
'ci-key': ({ flags, cwd }) => ({ publicKey: flags['public-key'] ? path.resolve(cwd, flags['public-key']) : null, printAuthorizedKeys: flags['print-authorized-keys'] === true, printGate: flags['print-gate'] === true }),
|
|
142
149
|
retire: ({ flags }) => ({ projects: flags.project ?? [], execute: flags.execute === true }),
|
|
143
150
|
jobs: ({ flags, positional }) => ({ action: positional[0], args: positional.slice(1), execute: flags.execute === true }),
|
|
144
151
|
task: ({ flags, positional }) => ({ name: positional[0], args: positional.slice(1), execute: flags.execute === true }),
|
|
@@ -154,6 +161,8 @@ const MODULES = {
|
|
|
154
161
|
retire: ['./retire.mjs', 'retireCompose'],
|
|
155
162
|
build: ['./build.mjs', 'buildImages'],
|
|
156
163
|
accept: ['./deploy.mjs', 'acceptRelease'],
|
|
164
|
+
status: ['./status.mjs', 'statusStack'],
|
|
165
|
+
'ci-key': ['./ci-key.mjs', 'ciKey'],
|
|
157
166
|
};
|
|
158
167
|
async function handlerOf(action, importer) {
|
|
159
168
|
const [file, name] = MODULES[action];
|
package/stack/config.d.mts
CHANGED
|
@@ -84,6 +84,14 @@ export interface BackupBlock {
|
|
|
84
84
|
dir: string | null;
|
|
85
85
|
volumes: BackupVolume[];
|
|
86
86
|
}
|
|
87
|
+
/** The normalised `deploy.notify` block: the sealed webhook URL and the outcomes announced. */
|
|
88
|
+
export interface NotifyBlock {
|
|
89
|
+
webhook: {
|
|
90
|
+
member: string;
|
|
91
|
+
key: string;
|
|
92
|
+
};
|
|
93
|
+
on: string[];
|
|
94
|
+
}
|
|
87
95
|
export interface RegistryBlock {
|
|
88
96
|
host: string;
|
|
89
97
|
visibility: string;
|
|
@@ -158,6 +166,12 @@ export interface DeployBlock {
|
|
|
158
166
|
environment: Record<string, ServiceEnvironment>;
|
|
159
167
|
configs: ConfigEntry[];
|
|
160
168
|
passEnv: string[];
|
|
169
|
+
/** deploy.auto: a green release tag deploys here by itself (the CI workflow reads it; the verbs do not). */
|
|
170
|
+
auto: boolean;
|
|
171
|
+
/** deploy.approval: none, or reviewers set on the GitHub Environment. */
|
|
172
|
+
approval: 'none' | 'reviewers';
|
|
173
|
+
/** deploy.platforms: the platforms the images are built for (default linux/amd64). */
|
|
174
|
+
platforms: string[];
|
|
161
175
|
verify: {
|
|
162
176
|
http: HttpCheck[];
|
|
163
177
|
exec: ExecCheck[];
|
|
@@ -166,6 +180,7 @@ export interface DeployBlock {
|
|
|
166
180
|
tasks: Record<string, TaskEntry>;
|
|
167
181
|
postDeploy: string[];
|
|
168
182
|
backup: BackupBlock | null;
|
|
183
|
+
notify: NotifyBlock | null;
|
|
169
184
|
ingress: IngressConfig | null;
|
|
170
185
|
registry: RegistryBlock;
|
|
171
186
|
}
|
package/stack/converge.d.mts
CHANGED
|
@@ -2,6 +2,8 @@ import type { DeployConfig } from './config.mjs';
|
|
|
2
2
|
import type { StackContext } from './context.mjs';
|
|
3
3
|
import type { ActionResult } from './remote.mjs';
|
|
4
4
|
type Ctx = Pick<StackContext, 'spawn' | 'now' | 'sleep'>;
|
|
5
|
+
/** The JSON text with object keys sorted at every depth, so two equal specs compare equal whatever the key order. */
|
|
6
|
+
export declare function canonical(value: unknown): string;
|
|
5
7
|
/**
|
|
6
8
|
* Waits until the stack converged. `since` is the ISO instant the deploy began (required). Returns { ok, lines, data: { services, failed, pending } };
|
|
7
9
|
* on failure the lines name the service and the Swarm task error text.
|
package/stack/converge.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const TAB = String.fromCodePoint(9);
|
|
|
13
13
|
const TASK_FORMAT = `{{.DesiredState}}|{{.Status.State}}|{{.Status.Timestamp}}|{{.Status.Err}}${TAB}{{json .Spec}}`;
|
|
14
14
|
const docker = (ctx, args) => toolOutput(ctx, 'docker', args);
|
|
15
15
|
/** The JSON text with object keys sorted at every depth, so two equal specs compare equal whatever the key order. */
|
|
16
|
-
function canonical(value) {
|
|
16
|
+
export function canonical(value) {
|
|
17
17
|
if (Array.isArray(value))
|
|
18
18
|
return `[${value.map(canonical).join(',')}]`;
|
|
19
19
|
if (value !== null && typeof value === 'object')
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { DbTarget as PostgresTarget } from '../db/postgres.mjs';
|
|
2
|
+
import type { DatabaseDecl, DatabaseTarget, MigrationPhase } from '../db/migrate.mjs';
|
|
3
|
+
import type { DeployConfig } from './config.mjs';
|
|
4
|
+
import type { StackContext } from './context.mjs';
|
|
5
|
+
import type { ArchiveFacts, ManagerBackup } from './manager-backup.mjs';
|
|
6
|
+
import type { ActionResult, Runner } from './remote.mjs';
|
|
7
|
+
/** The receipt of a database backup (db/backup-receipt.mjs). */
|
|
8
|
+
export interface BackupReceipt {
|
|
9
|
+
schemaOnly: boolean;
|
|
10
|
+
at: string;
|
|
11
|
+
file: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
sha256: string;
|
|
14
|
+
parts: string[];
|
|
15
|
+
}
|
|
16
|
+
/** What a backup hands back: where the receipt is and what it says. */
|
|
17
|
+
export interface BackupData {
|
|
18
|
+
receiptPath: string;
|
|
19
|
+
receipt?: BackupReceipt | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** The database a floor step reaches (db/postgres.mjs): `{ urlEnv }`, `{}` or `{ target }`, spread into the migrate options. */
|
|
22
|
+
export type DbTarget = Record<string, unknown>;
|
|
23
|
+
/** The verdict of one migrate pass; `data` is migrate's (`done`, `push`, `pushFiles`, `pushed`). */
|
|
24
|
+
export interface MigrateResult {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
lines?: string[] | undefined;
|
|
27
|
+
data?: Record<string, unknown> | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Whether the database is empty, and why not. */
|
|
30
|
+
export interface Emptiness {
|
|
31
|
+
empty: boolean;
|
|
32
|
+
history: boolean;
|
|
33
|
+
publicTables: number;
|
|
34
|
+
}
|
|
35
|
+
/** The outcome of the `first-start` step. */
|
|
36
|
+
export interface FirstStartStepResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
code?: string;
|
|
39
|
+
lines: string[];
|
|
40
|
+
}
|
|
41
|
+
/** The collaborators of the database steps, each defaulting to the app's own db verbs; a spec injects stubs. */
|
|
42
|
+
export interface FloorDeps {
|
|
43
|
+
backup: (ctx: StackContext, args: {
|
|
44
|
+
target: DbTarget;
|
|
45
|
+
out: string;
|
|
46
|
+
}) => Promise<BackupData>;
|
|
47
|
+
decrypt: (ctx: StackContext, args: {
|
|
48
|
+
archive: string;
|
|
49
|
+
identity: string | null;
|
|
50
|
+
parts: string[];
|
|
51
|
+
}) => Promise<{
|
|
52
|
+
parts: string[];
|
|
53
|
+
}>;
|
|
54
|
+
migrate: (ctx: StackContext, args: {
|
|
55
|
+
target: DbTarget;
|
|
56
|
+
execute: boolean;
|
|
57
|
+
phase?: MigrationPhase | undefined;
|
|
58
|
+
}) => Promise<MigrateResult>;
|
|
59
|
+
dbContainer: (ctx: StackContext, config: DeployConfig, args: {
|
|
60
|
+
service: string;
|
|
61
|
+
seconds: number;
|
|
62
|
+
}) => Promise<string | undefined>;
|
|
63
|
+
emptiness: (ctx: StackContext, target: PostgresTarget) => Promise<Emptiness>;
|
|
64
|
+
/** The database of a running container, reached with the credentials that container holds. */
|
|
65
|
+
containerTarget: (ctx: StackContext, container: string) => PostgresTarget;
|
|
66
|
+
/** The encrypted dump of a `stack` database, made and kept on the manager. */
|
|
67
|
+
managerBackup: (ctx: StackContext, config: DeployConfig, args: {
|
|
68
|
+
run: Runner;
|
|
69
|
+
container: string;
|
|
70
|
+
url: string;
|
|
71
|
+
recipients: string[];
|
|
72
|
+
at: string;
|
|
73
|
+
}) => Promise<ManagerBackup>;
|
|
74
|
+
/** The test decrypt of that dump on the manager with its own identity: the parts it lists. */
|
|
75
|
+
managerDecrypt: (ctx: StackContext, args: {
|
|
76
|
+
run: Runner;
|
|
77
|
+
archive: string;
|
|
78
|
+
}) => Promise<string[]>;
|
|
79
|
+
}
|
|
80
|
+
/** The code or message of a thrown value, for a refusal line. */
|
|
81
|
+
export declare const reasonOf: (error: unknown) => string;
|
|
82
|
+
/** The message of a thrown value. */
|
|
83
|
+
export declare const messageOf: (error: unknown) => string;
|
|
84
|
+
/** A refused step: the lines so far and the code; the CLI prints both and exits 2. */
|
|
85
|
+
export declare const refused: (lines: string[], code: string, message: string, data?: Record<string, unknown>) => ActionResult;
|
|
86
|
+
export declare const AGE_HEADER = "age-encryption.org/v1\n";
|
|
87
|
+
/** Problems of a backup receipt against the facts measured on its archive: a full backup written by this run, size, age header and sha256 equal. */
|
|
88
|
+
export declare function backupProblems(receipt: BackupReceipt | undefined, facts: ArchiveFacts, startedMs: number): string[];
|
|
89
|
+
/** How long a first start waits for the database service's container to accept connections. */
|
|
90
|
+
export declare const FIRST_START_WAIT_SECONDS = 180;
|
|
91
|
+
/** How long a deploy of a running stack waits for the database service's container to accept connections. */
|
|
92
|
+
export declare const DB_WAIT_SECONDS = 60;
|
|
93
|
+
export declare const FLOOR_DEPS: Readonly<FloorDeps>;
|
|
94
|
+
/**
|
|
95
|
+
* The `first-start` step of a first start's deploy (between the hold deploy and the real one): the database container, the proof it is empty,
|
|
96
|
+
* migrate preview then execute (every migration, contract ones included: no release runs yet) inside it. `targetFor` reaches the container's
|
|
97
|
+
* database; `hint` ends the not-empty message with what the operator does next. Returns { ok, lines, code } where code names the failure.
|
|
98
|
+
*/
|
|
99
|
+
export declare function firstStartStep(ctx: StackContext, config: DeployConfig, input: {
|
|
100
|
+
service: string;
|
|
101
|
+
targetFor: (container: string) => PostgresTarget;
|
|
102
|
+
hint: string;
|
|
103
|
+
}, f: FloorDeps): Promise<FirstStartStepResult>;
|
|
104
|
+
/** The database a deploy migrates: the `database:` block of application-stacks.yaml. */
|
|
105
|
+
export interface DeployDatabase {
|
|
106
|
+
decl: DatabaseDecl;
|
|
107
|
+
target: DatabaseTarget;
|
|
108
|
+
service: string;
|
|
109
|
+
}
|
|
110
|
+
/** The `database:` block of the app, or null when it declares none (then a deploy has no database phase at all). */
|
|
111
|
+
export declare function declaredDatabase(ctx: Pick<StackContext, 'cwd'>, config: Pick<DeployConfig, 'repoRoot'>): Promise<DeployDatabase | null>;
|
|
112
|
+
/** What the phases of one deploy are given. */
|
|
113
|
+
export interface PhaseInput {
|
|
114
|
+
ctx: StackContext;
|
|
115
|
+
config: DeployConfig;
|
|
116
|
+
database: DeployDatabase;
|
|
117
|
+
f: FloorDeps;
|
|
118
|
+
/** The manager's runner (a `stack` database's backup is made and kept there). */
|
|
119
|
+
run: Runner | null;
|
|
120
|
+
dbUrlEnv?: string | undefined;
|
|
121
|
+
backupOut?: string | undefined;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A `stack` database must be a service of the stack the deploy renders: an app whose `database:` block exists but whose database is not one of its
|
|
125
|
+
* services declares `target: external`. Returns the refusal, or null when the service is there (or the stack's services are not known).
|
|
126
|
+
*/
|
|
127
|
+
export declare function unknownDatabaseService(config: Pick<DeployConfig, 'stack' | 'services'>, database: DeployDatabase): {
|
|
128
|
+
code: string;
|
|
129
|
+
message: string;
|
|
130
|
+
} | null;
|
|
131
|
+
/** One recorded step of a deploy. */
|
|
132
|
+
export interface PhaseStep {
|
|
133
|
+
step: string;
|
|
134
|
+
ok: boolean;
|
|
135
|
+
}
|
|
136
|
+
/** What the deploy keeps of its migrations (the deploy result's `data.migrations`; WP1's journal records it). */
|
|
137
|
+
export interface MigrationRecord {
|
|
138
|
+
/** Something was pending before the deploy. */
|
|
139
|
+
pending: boolean;
|
|
140
|
+
/** The file names of the expand and of the contract migrations that were pending. */
|
|
141
|
+
expand: string[];
|
|
142
|
+
contract: string[];
|
|
143
|
+
/** The verified backup taken because something was pending, else null (nothing pending: no backup). */
|
|
144
|
+
backup: {
|
|
145
|
+
receiptPath: string;
|
|
146
|
+
file: string;
|
|
147
|
+
bytes: number;
|
|
148
|
+
sha256: string;
|
|
149
|
+
where: 'manager' | 'local';
|
|
150
|
+
} | null;
|
|
151
|
+
/** The versions the expand pass and the contract pass applied. */
|
|
152
|
+
applied: string[];
|
|
153
|
+
contractApplied: string[];
|
|
154
|
+
}
|
|
155
|
+
/** The result of a phase: ok, or the refusal that stopped it; the steps it ran and the lines it printed either way. */
|
|
156
|
+
export interface PhaseResult {
|
|
157
|
+
ok: boolean;
|
|
158
|
+
refused?: {
|
|
159
|
+
code: string;
|
|
160
|
+
message: string;
|
|
161
|
+
} | undefined;
|
|
162
|
+
lines: string[];
|
|
163
|
+
steps: PhaseStep[];
|
|
164
|
+
record: MigrationRecord;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Before the stack deploy: preview, backup only when something is pending, then the expand migrations. Any refusal leaves the services untouched
|
|
168
|
+
* and carries its code; the migrations an execute applied before failing stay (the backup is their way back).
|
|
169
|
+
*/
|
|
170
|
+
export declare function expandPhase(p: PhaseInput): Promise<PhaseResult>;
|
|
171
|
+
/**
|
|
172
|
+
* After the accept: the `*_contract_*` migrations. A red contract leaves the release accepted (it is serving and verified); the caller reports a
|
|
173
|
+
* red job and the owner decides. Nothing pending is a green no-op without a database call beyond the preview.
|
|
174
|
+
*/
|
|
175
|
+
export declare function contractPhase(p: PhaseInput): Promise<{
|
|
176
|
+
ok: boolean;
|
|
177
|
+
lines: string[];
|
|
178
|
+
applied: string[];
|
|
179
|
+
skipped: boolean;
|
|
180
|
+
}>;
|
|
181
|
+
/** The first-start hooks of a `stack` database on an environment with no accepted release (the deploy's `deploy-hold` and `first-start` steps). */
|
|
182
|
+
export declare function firstStartHooks(p: PhaseInput): {
|
|
183
|
+
step: () => Promise<FirstStartStepResult>;
|
|
184
|
+
service: string;
|
|
185
|
+
};
|