@ultimat3/cli 21.0.0 → 22.1.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/CLAUDE.md +84 -1650
- package/README.md +40 -2
- package/package.json +30 -30
- package/src/api-registration.ts +124 -0
- package/src/app-artifacts.ts +39 -0
- package/src/app-boundaries.ts +5 -10
- package/src/app-load.ts +37 -5
- package/src/app-openapi.ts +27 -0
- package/src/app-permissions.ts +0 -0
- package/src/app-root.ts +1 -1
- package/src/async-pages.ts +32 -0
- package/src/bin.ts +7 -1
- package/src/boundary-findings.ts +36 -0
- package/src/browser-launcher-fake-html.ts +80 -0
- package/src/browser-launcher-fake.ts +165 -0
- package/src/browser-launcher-port.ts +159 -0
- package/src/browser-launcher.ts +26 -133
- package/src/budgets.ts +11 -1
- package/src/cdp-shot-a11y.ts +77 -0
- package/src/cdp-shot-clock.ts +14 -0
- package/src/cdp-shot-driver.ts +150 -0
- package/src/cdp-shot-element.ts +147 -0
- package/src/cdp-shot-errors.ts +62 -0
- package/src/cdp-shot-keys.ts +152 -0
- package/src/cdp-shot-page.ts +230 -0
- package/src/cdp-shot-watch.ts +241 -0
- package/src/cmd-affected-spec.ts +24 -0
- package/src/cmd-affected.ts +3 -19
- package/src/cmd-build-spec.ts +16 -0
- package/src/cmd-build.ts +46 -20
- package/src/cmd-ci-spec.ts +24 -0
- package/src/cmd-ci.ts +17 -21
- package/src/cmd-db-branch.ts +2 -2
- package/src/cmd-db-spec.ts +99 -0
- package/src/cmd-db.ts +15 -94
- package/src/cmd-deploy-helm.ts +136 -0
- package/src/cmd-deploy-spec.ts +40 -0
- package/src/cmd-deploy.ts +111 -43
- package/src/cmd-dev-spec.ts +25 -0
- package/src/cmd-dev.ts +55 -36
- package/src/cmd-docs-spec.ts +16 -0
- package/src/cmd-docs.ts +2 -11
- package/src/cmd-doctor-spec.ts +21 -0
- package/src/cmd-doctor.ts +4 -18
- package/src/cmd-env-spec.ts +18 -0
- package/src/cmd-env.ts +4 -13
- package/src/cmd-errors-spec.ts +23 -0
- package/src/cmd-errors.ts +3 -17
- package/src/cmd-fix-spec.ts +18 -0
- package/src/cmd-fix.ts +4 -13
- package/src/cmd-generate-spec.ts +31 -0
- package/src/cmd-generate.ts +46 -42
- package/src/cmd-i18n-spec.ts +16 -0
- package/src/cmd-i18n.ts +7 -14
- package/src/cmd-jobs-spec.ts +56 -0
- package/src/cmd-jobs.ts +3 -50
- package/src/cmd-manifest-spec.ts +15 -0
- package/src/cmd-manifest.ts +19 -20
- package/src/cmd-mcp-spec.ts +21 -0
- package/src/cmd-mcp.ts +2 -15
- package/src/cmd-new-spec.ts +33 -0
- package/src/cmd-new.ts +38 -33
- package/src/cmd-policy-spec.ts +13 -0
- package/src/cmd-policy.ts +2 -9
- package/src/cmd-pr-spec.ts +39 -0
- package/src/cmd-pr.ts +5 -65
- package/src/cmd-registries-spec.ts +32 -0
- package/src/cmd-registries.ts +4 -25
- package/src/cmd-routes-spec.ts +12 -0
- package/src/cmd-routes.ts +2 -7
- package/src/cmd-secrets-spec.ts +19 -0
- package/src/cmd-secrets.ts +22 -34
- package/src/cmd-shot-island.ts +12 -13
- package/src/cmd-shot-spec.ts +53 -0
- package/src/cmd-shot.ts +11 -60
- package/src/cmd-tasks-spec.ts +21 -0
- package/src/cmd-tasks.ts +2 -16
- package/src/cmd-test-spec.ts +54 -0
- package/src/cmd-test.ts +4 -49
- package/src/cmd-verify-spec.ts +28 -0
- package/src/cmd-verify.ts +3 -23
- package/src/db-seed.ts +4 -2
- package/src/dev-dashboard.ts +4 -4
- package/src/dev-lock.ts +18 -2
- package/src/dev-port.ts +21 -0
- package/src/dev-route-table.ts +3 -3
- package/src/dispatch.ts +12 -5
- package/src/document-styles.ts +1 -1
- package/src/drift.ts +4 -18
- package/src/error-catalog.ts +16 -9
- package/src/error-codes.ts +28 -30
- package/src/error-pages.ts +19 -7
- package/src/errors.ts +14 -0
- package/src/favicon.ts +2 -2
- package/src/fix-imports.ts +1 -1
- package/src/fix-scan.ts +2 -9
- package/src/flag-reads.ts +1 -2
- package/src/foreign-text.ts +36 -0
- package/src/framework-schema.ts +5 -6
- package/src/generate-feature.ts +42 -0
- package/src/generate-files.ts +7 -3
- package/src/generate-grants.ts +83 -0
- package/src/generate-kinds.ts +56 -4
- package/src/i18n-index.ts +59 -10
- package/src/icon-assets.ts +1 -1
- package/src/image-prepare.ts +14 -0
- package/src/import-scan.ts +63 -0
- package/src/index.ts +67 -348
- package/src/invocation-flags.ts +26 -0
- package/src/island-bundle.ts +6 -1
- package/src/island-capture.ts +6 -6
- package/src/island-harness-script.ts +6 -2
- package/src/island-realtime.ts +15 -8
- package/src/island-shot.ts +5 -0
- package/src/island-store.ts +131 -0
- package/src/island-verdict.ts +1 -1
- package/src/job-registration.ts +42 -0
- package/src/jobs-driver.ts +2 -2
- package/src/load-findings.ts +51 -0
- package/src/mcp-db-target.ts +1 -1
- package/src/mcp-errors.ts +28 -27
- package/src/mcp-host.ts +27 -12
- package/src/mcp-ui-diff.ts +27 -0
- package/src/mcp-ui-inspect.ts +4 -4
- package/src/mcp-ui-interact.ts +21 -15
- package/src/mcp-ui.ts +23 -17
- package/src/measure-database.ts +73 -0
- package/src/measure-paths.ts +74 -0
- package/src/measure-scope.ts +74 -0
- package/src/messages.ts +1 -3
- package/src/metrics-endpoint.ts +1 -1
- package/src/otlp-export.ts +1 -1
- package/src/output.ts +6 -0
- package/src/permission-grants.ts +86 -0
- package/src/prerender-out.ts +25 -0
- package/src/prerender.ts +121 -102
- package/src/pwa-artifacts.ts +3 -3
- package/src/reexport-manifest.ts +2 -1
- package/src/registry.ts +80 -56
- package/src/role-realtime.ts +36 -0
- package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
- package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
- package/src/role-start-types.ts +112 -0
- package/src/{dev-roles.ts → role-start.ts} +42 -115
- package/src/{dev-sync.ts → role-sync.ts} +22 -8
- package/src/root-env.ts +67 -0
- package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
- package/src/{dev-services.ts → runtime-bindings.ts} +39 -16
- package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
- package/src/runtime-jobs.ts +87 -0
- package/src/{dev-live-feed.ts → runtime-live-feed.ts} +18 -5
- package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
- package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
- package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
- package/src/runtime-realtime.ts +55 -0
- package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
- package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
- package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
- package/src/scaffold-fixture.ts +28 -6
- package/src/scaffold-typecheck.ts +6 -3
- package/src/schema-drift.ts +7 -1
- package/src/script-csp.ts +5 -2
- package/src/secrets-rotation.ts +59 -0
- package/src/serve-boot.ts +192 -0
- package/src/serve-drain.ts +24 -0
- package/src/serve-entry.ts +6 -0
- package/src/serve-env.ts +116 -0
- package/src/serve-types.ts +55 -0
- package/src/serve.ts +44 -347
- package/src/shot-server.ts +2 -2
- package/src/shot-settle.ts +10 -1
- package/src/shot-theme.ts +3 -3
- package/src/shot-verdict.ts +16 -7
- package/src/signal-shred.ts +27 -0
- package/src/solid-loader.ts +26 -2
- package/src/static-report.ts +8 -1
- package/src/sync-url.ts +21 -1
- package/src/templates/action.ts +30 -16
- package/src/templates/entity.ts +12 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/job.ts +10 -7
- package/src/templates/policy.ts +20 -2
- package/src/templates/resource-create.ts +127 -0
- package/src/templates/resource-form-island.ts +49 -12
- package/src/templates/resource.ts +10 -4
- package/src/templates/scaffold-app.ts +4 -1
- package/src/templates/scaffold-auth.ts +3 -1
- package/src/templates/scaffold-container-compose.ts +184 -0
- package/src/templates/scaffold-container.ts +24 -159
- package/src/templates/scaffold-dashboard-example.ts +2 -2
- package/src/templates/scaffold-db-package.ts +16 -3
- package/src/templates/scaffold-demo-org.ts +41 -0
- package/src/templates/scaffold-entries.ts +1 -1
- package/src/templates/scaffold-env.ts +6 -0
- package/src/templates/scaffold-helm-templates.ts +66 -7
- package/src/templates/scaffold-helm.ts +27 -5
- package/src/templates/scaffold-i18n.ts +15 -10
- package/src/templates/scaffold-repo.ts +13 -10
- package/src/templates/scaffold-roles.ts +38 -10
- package/src/templates/slice-foundation.ts +1 -1
- package/src/templates/wrap.ts +4 -1
- package/src/test-passes.ts +2 -1
- package/src/test-workers.ts +26 -0
- package/src/ts-scan.ts +3 -6
- package/src/tsconfig-references.ts +1 -2
- package/src/verify-checks.ts +41 -39
- package/src/verify-e2e.ts +10 -7
- package/src/verify-run.ts +11 -1
- package/src/verify-step.ts +3 -3
- package/src/verify-tests.ts +1 -26
- package/src/verify-typecheck.ts +28 -0
- package/src/web-binding.ts +2 -2
- package/src/workspace-graph.ts +10 -33
- package/src/cdp-browser.ts +0 -94
- package/src/cdp-connection.ts +0 -247
- package/src/cdp-e2e-page.ts +0 -180
- package/src/cdp-e2e-session.ts +0 -199
- package/src/cdp-errors.ts +0 -56
- package/src/cdp-launch.ts +0 -193
- package/src/cdp-offline-script.ts +0 -73
- package/src/cdp-pipe.ts +0 -77
- package/src/e2e-app.ts +0 -103
- package/src/e2e-browser-handle.ts +0 -55
- package/src/e2e-dom-fixture.ts +0 -117
- package/src/e2e-driver.ts +0 -117
- package/src/e2e-errors.ts +0 -117
- package/src/e2e-evaluate.ts +0 -156
- package/src/e2e-locator.ts +0 -86
- package/src/e2e-page.ts +0 -153
- package/src/e2e-preload.ts +0 -64
- package/src/e2e-probe.ts +0 -23
- package/src/e2e-selection.ts +0 -182
- package/src/e2e-spawn.ts +0 -169
- package/src/measurement-actor.ts +0 -26
- /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
- /package/src/{dev-render.ts → runtime-render.ts} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// `x ci`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-ci.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
/** Log lines kept per failed job. Enough to hold a findings block, short enough to read. */
|
|
7
|
+
export const TAIL_LINES = 40;
|
|
8
|
+
|
|
9
|
+
export const ciSpec: CommandSpec = {
|
|
10
|
+
name: 'ci',
|
|
11
|
+
summary: 'the workflow runs for this branch, and the findings inside the failed steps log',
|
|
12
|
+
usage: 'x ci [--branch <name>] [--run <id>] [--repo owner/name] [--tail <n>] [--full] [--json]',
|
|
13
|
+
flags: [
|
|
14
|
+
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
15
|
+
{ name: 'branch', type: 'string', summary: 'branch to read runs for; this one by default' },
|
|
16
|
+
{ name: 'run', type: 'string', summary: 'one run id, instead of this branch latest' },
|
|
17
|
+
{
|
|
18
|
+
name: 'tail',
|
|
19
|
+
type: 'string',
|
|
20
|
+
summary: `log lines kept per failed job (default ${TAIL_LINES})`,
|
|
21
|
+
},
|
|
22
|
+
{ name: 'full', type: 'boolean', summary: 'the whole failed-step log, not the tail' },
|
|
23
|
+
],
|
|
24
|
+
};
|
package/src/cmd-ci.ts
CHANGED
|
@@ -9,14 +9,18 @@ import type { CiLogLine } from './ci-log';
|
|
|
9
9
|
import { findingsFrom, jobsInLog, parseLogLines, tailOf } from './ci-log';
|
|
10
10
|
import type { CiJob, CiRun } from './ci-runs';
|
|
11
11
|
import { failedLog, isFailed, isRunning, latestPerWorkflow, listRuns, viewRun } from './ci-runs';
|
|
12
|
+
import { ciSpec, TAIL_LINES } from './cmd-ci-spec';
|
|
12
13
|
import type { CliCommand, CommandContext } from './command';
|
|
13
14
|
import { parseIntFlag } from './flag-number';
|
|
15
|
+
import { commentBlock } from './foreign-text';
|
|
14
16
|
import type { GhRepo } from './gh-target';
|
|
15
17
|
import { currentBranch, resolveRepo } from './gh-target';
|
|
16
18
|
import { msg } from './messages';
|
|
17
19
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
18
20
|
import { flagBool, flagString } from './parse';
|
|
19
21
|
|
|
22
|
+
export { TAIL_LINES } from './cmd-ci-spec';
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* Every catalog key this command renders, declared — `msg()` answers `⟦key⟧` for a key nobody
|
|
22
26
|
* added, which is loud in a terminal and SILENT to a build. `cmd-ci.test.ts` holds this list
|
|
@@ -34,9 +38,6 @@ export const CI_MESSAGE_KEYS = [
|
|
|
34
38
|
'cli.ci.logs.empty',
|
|
35
39
|
] as const;
|
|
36
40
|
|
|
37
|
-
/** Log lines kept per failed job. Enough to hold a findings block, short enough to read. */
|
|
38
|
-
export const TAIL_LINES = 40;
|
|
39
|
-
|
|
40
41
|
/** How far back a branch's run history is read before "the latest run of each workflow". */
|
|
41
42
|
export const RUN_LOOKBACK = 20;
|
|
42
43
|
|
|
@@ -55,22 +56,7 @@ const RUN_FLAG = { name: 'run', command: 'ci', min: 1, example: 'x ci --run 3248
|
|
|
55
56
|
const TAIL_FLAG = { name: 'tail', command: 'ci', min: 1, example: 'x ci --tail 80 --json' };
|
|
56
57
|
|
|
57
58
|
export const ciCommand: CliCommand = {
|
|
58
|
-
spec:
|
|
59
|
-
name: 'ci',
|
|
60
|
-
summary: 'the workflow runs for this branch, and the findings inside the failed steps log',
|
|
61
|
-
usage: 'x ci [--branch <name>] [--run <id>] [--repo owner/name] [--tail <n>] [--full] [--json]',
|
|
62
|
-
flags: [
|
|
63
|
-
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
64
|
-
{ name: 'branch', type: 'string', summary: 'branch to read runs for; this one by default' },
|
|
65
|
-
{ name: 'run', type: 'string', summary: 'one run id, instead of this branch latest' },
|
|
66
|
-
{
|
|
67
|
-
name: 'tail',
|
|
68
|
-
type: 'string',
|
|
69
|
-
summary: `log lines kept per failed job (default ${TAIL_LINES})`,
|
|
70
|
-
},
|
|
71
|
-
{ name: 'full', type: 'boolean', summary: 'the whole failed-step log, not the tail' },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
59
|
+
spec: ciSpec,
|
|
74
60
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
75
61
|
const repo = await resolveRepo(ctx, 'ci', flagString(ctx.args, 'repo'));
|
|
76
62
|
const rawRun = flagString(ctx.args, 'run');
|
|
@@ -142,7 +128,14 @@ async function inspect(
|
|
|
142
128
|
const pool: readonly CiLogLine[] = own.length > 0 ? own : lines;
|
|
143
129
|
const job = failed.find((candidate) => candidate.name === name);
|
|
144
130
|
for (const finding of findingsFrom(pool)) {
|
|
145
|
-
findings.push(
|
|
131
|
+
findings.push({
|
|
132
|
+
...finding,
|
|
133
|
+
at: finding.at ?? name,
|
|
134
|
+
// Fenced and tagged: the log is foreign text, and a `fix:` reconstructed from it is the
|
|
135
|
+
// log author's instruction, not this CLI's. `commentBlock` is `x pr review`'s fence.
|
|
136
|
+
fix: commentBlock(`ci-log ${name}`, [finding.fix]).join('\n'),
|
|
137
|
+
source: 'ci-log',
|
|
138
|
+
});
|
|
146
139
|
}
|
|
147
140
|
failures.push({
|
|
148
141
|
job: name,
|
|
@@ -229,7 +222,8 @@ function runLines(entry: RunReport): readonly string[] {
|
|
|
229
222
|
continue;
|
|
230
223
|
}
|
|
231
224
|
out.push(msg('cli.ci.tail', { job: failure.job }));
|
|
232
|
-
for (const line of
|
|
225
|
+
for (const line of commentBlock(`ci-log ${failure.job}`, failure.tail))
|
|
226
|
+
out.push(` | ${line}`);
|
|
233
227
|
}
|
|
234
228
|
const jobs = entry.jobs;
|
|
235
229
|
if (jobs !== undefined && jobs.length > failures.length) {
|
|
@@ -266,6 +260,8 @@ function runJson(entry: RunReport): JsonValue {
|
|
|
266
260
|
job: failure.job,
|
|
267
261
|
url: failure.url,
|
|
268
262
|
failedSteps: [...failure.failedSteps],
|
|
263
|
+
// Raw lines for a machine, labelled as foreign: the log's author wrote every one.
|
|
264
|
+
tailSource: 'ci-log',
|
|
269
265
|
tail: [...failure.tail],
|
|
270
266
|
})),
|
|
271
267
|
}),
|
package/src/cmd-db-branch.ts
CHANGED
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
previewUrl,
|
|
24
24
|
} from './db-branch';
|
|
25
25
|
import { stepFinding } from './db-finding';
|
|
26
|
-
import type { DevServices } from './dev-services';
|
|
27
|
-
import { resolveServices } from './dev-services';
|
|
28
26
|
import { MissingPositionalError, UnknownCommandError } from './errors';
|
|
29
27
|
import { msg } from './messages';
|
|
30
28
|
import type { CommandResult, Finding } from './output';
|
|
31
29
|
import { flagString } from './parse';
|
|
30
|
+
import type { DevServices } from './runtime-bindings';
|
|
31
|
+
import { resolveServices } from './runtime-bindings';
|
|
32
32
|
import { portFromEnv } from './serve';
|
|
33
33
|
import { renderTable } from './table';
|
|
34
34
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `x db`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-db.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import { BRANCH_SUBCOMMANDS } from './db-branch';
|
|
5
|
+
import type { CommandSpec } from './parse';
|
|
6
|
+
|
|
7
|
+
export const DB_SUBCOMMANDS = [
|
|
8
|
+
'gen',
|
|
9
|
+
'migrate',
|
|
10
|
+
'reset',
|
|
11
|
+
'seed',
|
|
12
|
+
'studio',
|
|
13
|
+
'branch',
|
|
14
|
+
'backfill',
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export const dbSpec: CommandSpec = {
|
|
18
|
+
name: 'db',
|
|
19
|
+
summary: 'gen, migrate, reset, seed, studio, branch, backfill',
|
|
20
|
+
usage:
|
|
21
|
+
'x db gen "add publish_at" | migrate | reset | seed [<name>] [--tier reference|dev] [--dry-run] | studio | branch ls | branch create <name> | branch drop <name> | backfill [<name>|--all] [--write] [--force] | backfill --pending | backfill --list [--name n] [--status s] [--limit n]',
|
|
22
|
+
requiresApp: true,
|
|
23
|
+
subcommands: DB_SUBCOMMANDS,
|
|
24
|
+
// Declared from the constant `runBranchCommand` validates against, never a second literal: it
|
|
25
|
+
// is what lets the `errors` step resolve `x db branch ls` — a fix line three shipped errors
|
|
26
|
+
// hand out, which read `ls` as a branch name and cloned a database until 1.2.x.
|
|
27
|
+
subcommandPositionals: { branch: BRANCH_SUBCOMMANDS },
|
|
28
|
+
// Each flag whose summary begins `<subcommand>:` declares that scope, and the parser refuses
|
|
29
|
+
// it anywhere else: `x db gen --dry-run` used to parse, reach `runGen` and WRITE the
|
|
30
|
+
// migration. `cmd-db.test.ts` pins summary and scope to the same fact.
|
|
31
|
+
flags: [
|
|
32
|
+
{
|
|
33
|
+
name: 'name',
|
|
34
|
+
type: 'string',
|
|
35
|
+
summary: 'migration, branch or seed name, or backfill to filter',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'tier',
|
|
39
|
+
type: 'string',
|
|
40
|
+
summary: 'seed: which tier to run — reference or dev; also ULTIMATE_SEED_TIER',
|
|
41
|
+
subcommands: ['seed'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'dry-run',
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
summary: 'seed: report what each seed would write, and write nothing',
|
|
47
|
+
subcommands: ['seed'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'list',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
summary: 'backfill: print the x_backfills ledger',
|
|
53
|
+
subcommands: ['backfill'],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'pending',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
summary: 'backfill: declared minus completed; non-zero exit when anything is unswept',
|
|
59
|
+
subcommands: ['backfill'],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'all',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
summary: 'backfill: every pending sweep, isolated per name',
|
|
65
|
+
subcommands: ['backfill'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'write',
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
summary: 'backfill: enqueue the pass; dry run without it',
|
|
71
|
+
subcommands: ['backfill'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'force',
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
summary: 'backfill: sweep a name the ledger records as completed, as a NEW ledger row',
|
|
77
|
+
subcommands: ['backfill'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'status',
|
|
81
|
+
type: 'string',
|
|
82
|
+
summary: 'backfill: filter by running, completed or failed',
|
|
83
|
+
subcommands: ['backfill'],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'limit',
|
|
87
|
+
type: 'string',
|
|
88
|
+
summary: 'backfill: max ledger rows to return',
|
|
89
|
+
subcommands: ['backfill'],
|
|
90
|
+
},
|
|
91
|
+
// Declared because `X_MIGRATION_IRREVERSIBLE`'s own fix line names it. A `fix:` is copied
|
|
92
|
+
// and run verbatim, so a flag the parser refuses would make the error unfollowable.
|
|
93
|
+
{
|
|
94
|
+
name: 'allow-destructive',
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
summary: 'let x db gen emit a drop whose down cannot restore the rows',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
package/src/cmd-db.ts
CHANGED
|
@@ -17,9 +17,9 @@ import { postgresDriver } from '@ultimat3/entity';
|
|
|
17
17
|
import { requireAppRoot } from './app-root';
|
|
18
18
|
import { runBackfillCommand } from './cmd-db-backfill';
|
|
19
19
|
import { runBranchCommand } from './cmd-db-branch';
|
|
20
|
+
import { DB_SUBCOMMANDS, dbSpec } from './cmd-db-spec';
|
|
20
21
|
import { plannedSubcommand } from './cmd-planned';
|
|
21
22
|
import type { CliCommand, CommandContext } from './command';
|
|
22
|
-
import { BRANCH_SUBCOMMANDS } from './db-branch';
|
|
23
23
|
import { stepFinding } from './db-finding';
|
|
24
24
|
import { generateAppMigration, unrenderedJson, unrenderedLines } from './db-generate';
|
|
25
25
|
import type { SeedPassRow } from './db-seed';
|
|
@@ -32,109 +32,20 @@ import {
|
|
|
32
32
|
seedTotals,
|
|
33
33
|
selectSeeds,
|
|
34
34
|
} from './db-seed';
|
|
35
|
-
import {
|
|
35
|
+
import { DevAlreadyRunningError, liveDevLock } from './dev-lock';
|
|
36
36
|
import { CliNotImplementedError, MissingSubcommandError, UnknownCommandError } from './errors';
|
|
37
37
|
import { withJobDriver } from './jobs-driver';
|
|
38
38
|
import { msg } from './messages';
|
|
39
39
|
import type { CommandResult, Finding } from './output';
|
|
40
40
|
import { findingFrom } from './output';
|
|
41
41
|
import { flagBool, flagString } from './parse';
|
|
42
|
+
import { resolveServices } from './runtime-bindings';
|
|
42
43
|
import { runMigrations } from './serve';
|
|
43
44
|
|
|
44
|
-
export
|
|
45
|
-
'gen',
|
|
46
|
-
'migrate',
|
|
47
|
-
'reset',
|
|
48
|
-
'seed',
|
|
49
|
-
'studio',
|
|
50
|
-
'branch',
|
|
51
|
-
'backfill',
|
|
52
|
-
] as const;
|
|
45
|
+
export { DB_SUBCOMMANDS } from './cmd-db-spec';
|
|
53
46
|
|
|
54
47
|
export const dbCommand: CliCommand = {
|
|
55
|
-
spec:
|
|
56
|
-
name: 'db',
|
|
57
|
-
summary: 'gen, migrate, reset, seed, studio, branch, backfill',
|
|
58
|
-
usage:
|
|
59
|
-
'x db gen "add publish_at" | migrate | reset | seed [<name>] [--tier reference|dev] [--dry-run] | studio | branch ls | branch create <name> | branch drop <name> | backfill [<name>|--all] [--write] [--force] | backfill --pending | backfill --list [--name n] [--status s] [--limit n]',
|
|
60
|
-
requiresApp: true,
|
|
61
|
-
subcommands: DB_SUBCOMMANDS,
|
|
62
|
-
// Declared from the constant `runBranchCommand` validates against, never a second literal: it
|
|
63
|
-
// is what lets the `errors` step resolve `x db branch ls` — a fix line three shipped errors
|
|
64
|
-
// hand out, which read `ls` as a branch name and cloned a database until 1.2.x.
|
|
65
|
-
subcommandPositionals: { branch: BRANCH_SUBCOMMANDS },
|
|
66
|
-
// Each flag whose summary begins `<subcommand>:` declares that scope, and the parser refuses
|
|
67
|
-
// it anywhere else: `x db gen --dry-run` used to parse, reach `runGen` and WRITE the
|
|
68
|
-
// migration. `cmd-db.test.ts` pins summary and scope to the same fact.
|
|
69
|
-
flags: [
|
|
70
|
-
{
|
|
71
|
-
name: 'name',
|
|
72
|
-
type: 'string',
|
|
73
|
-
summary: 'migration, branch or seed name, or backfill to filter',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: 'tier',
|
|
77
|
-
type: 'string',
|
|
78
|
-
summary: 'seed: which tier to run — reference or dev; also ULTIMATE_SEED_TIER',
|
|
79
|
-
subcommands: ['seed'],
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'dry-run',
|
|
83
|
-
type: 'boolean',
|
|
84
|
-
summary: 'seed: report what each seed would write, and write nothing',
|
|
85
|
-
subcommands: ['seed'],
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: 'list',
|
|
89
|
-
type: 'boolean',
|
|
90
|
-
summary: 'backfill: print the x_backfills ledger',
|
|
91
|
-
subcommands: ['backfill'],
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'pending',
|
|
95
|
-
type: 'boolean',
|
|
96
|
-
summary: 'backfill: declared minus completed; non-zero exit when anything is unswept',
|
|
97
|
-
subcommands: ['backfill'],
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'all',
|
|
101
|
-
type: 'boolean',
|
|
102
|
-
summary: 'backfill: every pending sweep, isolated per name',
|
|
103
|
-
subcommands: ['backfill'],
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'write',
|
|
107
|
-
type: 'boolean',
|
|
108
|
-
summary: 'backfill: enqueue the pass; dry run without it',
|
|
109
|
-
subcommands: ['backfill'],
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: 'force',
|
|
113
|
-
type: 'boolean',
|
|
114
|
-
summary: 'backfill: sweep a name the ledger records as completed, as a NEW ledger row',
|
|
115
|
-
subcommands: ['backfill'],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: 'status',
|
|
119
|
-
type: 'string',
|
|
120
|
-
summary: 'backfill: filter by running, completed or failed',
|
|
121
|
-
subcommands: ['backfill'],
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: 'limit',
|
|
125
|
-
type: 'string',
|
|
126
|
-
summary: 'backfill: max ledger rows to return',
|
|
127
|
-
subcommands: ['backfill'],
|
|
128
|
-
},
|
|
129
|
-
// Declared because `X_MIGRATION_IRREVERSIBLE`'s own fix line names it. A `fix:` is copied
|
|
130
|
-
// and run verbatim, so a flag the parser refuses would make the error unfollowable.
|
|
131
|
-
{
|
|
132
|
-
name: 'allow-destructive',
|
|
133
|
-
type: 'boolean',
|
|
134
|
-
summary: 'let x db gen emit a drop whose down cannot restore the rows',
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
},
|
|
48
|
+
spec: dbSpec,
|
|
138
49
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
139
50
|
const root = requireAppRoot('db', ctx.cwd).dir;
|
|
140
51
|
// No default, and no `?? 'migrate'` here either: `gen` writes a migration file and `reset`
|
|
@@ -299,6 +210,16 @@ async function runReset(ctx: CommandContext, root: string): Promise<CommandResul
|
|
|
299
210
|
fix: 'drop and recreate the database yourself, then run: x db migrate',
|
|
300
211
|
});
|
|
301
212
|
}
|
|
213
|
+
// A running `x dev` has this directory open: deleting it under a live embedded Postgres is data
|
|
214
|
+
// loss mid-write, not a reset. The dev lock is the one fact that says someone holds it.
|
|
215
|
+
const holder = liveDevLock(services.stateDir);
|
|
216
|
+
if (holder !== undefined) {
|
|
217
|
+
throw new DevAlreadyRunningError({
|
|
218
|
+
lock: holder,
|
|
219
|
+
stateDir: services.stateDir,
|
|
220
|
+
embeddedDb: true,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
302
223
|
await rm(join(services.stateDir, 'pgdata'), { recursive: true, force: true });
|
|
303
224
|
return runMigrate(ctx, root, msg('cli.db.reset.done'));
|
|
304
225
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// The helm half of `x deploy`: which release, in which namespace, waited on for how long, and what
|
|
2
|
+
// the rollout reported. Split from cmd-deploy.ts, which owns the plan and the compose method.
|
|
3
|
+
//
|
|
4
|
+
// `helm upgrade --install` with no `--wait` exits 0 the moment the API server ACCEPTS the objects,
|
|
5
|
+
// so a deploy whose pods never became ready reported success; and helm's default 5m timeout failed
|
|
6
|
+
// an upgrade whose migrate hook was still applying a long migration, leaving the Job running.
|
|
7
|
+
|
|
8
|
+
// why: Bun exposes no path-join primitive; the chart path is handed to helm as one joined string.
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { UltimateError } from '@ultimat3/core';
|
|
11
|
+
import { APP_CONFIG_EXPORT } from './app-auth';
|
|
12
|
+
import { APP_CONFIG_FILE } from './app-root';
|
|
13
|
+
import { BadFlagError } from './errors';
|
|
14
|
+
|
|
15
|
+
/** Long enough for a migration a pre-upgrade hook must finish; helm's own 5m default is not. */
|
|
16
|
+
export const HELM_DEFAULT_TIMEOUT = '15m';
|
|
17
|
+
|
|
18
|
+
/** What a helm deploy is aimed at, resolved once from the flags and `app.config.ts`. */
|
|
19
|
+
export interface HelmTarget {
|
|
20
|
+
readonly release: string;
|
|
21
|
+
readonly namespace: string | undefined;
|
|
22
|
+
readonly timeout: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Helm's own `time.ParseDuration` grammar, positive units only: `15m`, `900s`, `1h30m`. */
|
|
26
|
+
const DURATION = /^(?:\d+(?:h|m|s|ms))+$/;
|
|
27
|
+
/** A namespace and a release are both DNS-1123 labels; helm caps a release at 53 characters. */
|
|
28
|
+
const LABEL = /^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?$/;
|
|
29
|
+
const RELEASE_MAX = 53;
|
|
30
|
+
|
|
31
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
32
|
+
typeof value === 'object' && value !== null;
|
|
33
|
+
|
|
34
|
+
/** `--timeout`, screened: a value helm would reject is refused before anything is spawned. */
|
|
35
|
+
export function readHelmTimeout(raw: string | undefined): string {
|
|
36
|
+
if (raw === undefined) return HELM_DEFAULT_TIMEOUT;
|
|
37
|
+
if (DURATION.test(raw) && /[1-9]/.test(raw)) return raw;
|
|
38
|
+
throw new BadFlagError({
|
|
39
|
+
flag: 'timeout',
|
|
40
|
+
command: 'deploy',
|
|
41
|
+
reason: 'it is not a positive helm duration such as 15m, 900s or 1h30m',
|
|
42
|
+
fix: 'x deploy --method helm --timeout 15m --json',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `--namespace` and `--release`, each a DNS-1123 label or a refusal naming the flag. */
|
|
47
|
+
export function readLabel(flag: 'namespace' | 'release', raw: string): string {
|
|
48
|
+
const max = flag === 'release' ? RELEASE_MAX : 63;
|
|
49
|
+
if (raw.length <= max && LABEL.test(raw)) return raw;
|
|
50
|
+
throw new BadFlagError({
|
|
51
|
+
flag,
|
|
52
|
+
command: 'deploy',
|
|
53
|
+
reason: `it must be a lowercase DNS-1123 label of at most ${max} characters`,
|
|
54
|
+
fix:
|
|
55
|
+
flag === 'release'
|
|
56
|
+
? 'x deploy --method helm --release my-app --json'
|
|
57
|
+
: 'x deploy --method helm --namespace my-namespace --json',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The release name: `--release`, else `app.config.ts`'s `name`. It was the literal `app` for every
|
|
63
|
+
* app, so two apps deployed to one namespace were one release and the second upgrade replaced the
|
|
64
|
+
* first. `--release` exists for a release that already carries another name — `app`, for every
|
|
65
|
+
* cluster an earlier version deployed to — because a release name is cluster state the framework
|
|
66
|
+
* does not own and cannot rename.
|
|
67
|
+
*/
|
|
68
|
+
export async function readReleaseName(root: string, flag: string | undefined): Promise<string> {
|
|
69
|
+
if (flag !== undefined) return readLabel('release', flag);
|
|
70
|
+
const module = (await import(join(root, APP_CONFIG_FILE))) as Record<string, unknown>;
|
|
71
|
+
const config = module[APP_CONFIG_EXPORT];
|
|
72
|
+
const name = isRecord(config) ? config['name'] : undefined;
|
|
73
|
+
if (typeof name === 'string' && name.length <= RELEASE_MAX && LABEL.test(name)) return name;
|
|
74
|
+
throw new UltimateError({
|
|
75
|
+
code: 'X_CONFIG_INVALID',
|
|
76
|
+
cause:
|
|
77
|
+
typeof name === 'string'
|
|
78
|
+
? `app.config.ts names the app "${name.slice(0, 80)}", which is not a helm release name (a DNS-1123 label of at most ${RELEASE_MAX} characters)`
|
|
79
|
+
: 'app.config.ts exports no config.name, and x deploy --method helm names the release after it',
|
|
80
|
+
fix: 'name the release explicitly: x deploy --method helm --release my-app --json',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The one `helm upgrade`, waited on: `--wait` for the rollout, `--output json` for the verdict. */
|
|
85
|
+
export function helmUpgradeArgs(
|
|
86
|
+
root: string,
|
|
87
|
+
target: HelmTarget,
|
|
88
|
+
imageOverrides: readonly string[],
|
|
89
|
+
): readonly string[] {
|
|
90
|
+
return [
|
|
91
|
+
'helm',
|
|
92
|
+
'upgrade',
|
|
93
|
+
'--install',
|
|
94
|
+
target.release,
|
|
95
|
+
join(root, 'docker', 'helm'),
|
|
96
|
+
...(target.namespace === undefined ? [] : ['--namespace', target.namespace]),
|
|
97
|
+
'--wait',
|
|
98
|
+
'--timeout',
|
|
99
|
+
target.timeout,
|
|
100
|
+
'--output',
|
|
101
|
+
'json',
|
|
102
|
+
...imageOverrides,
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** What `--json` reports about the rollout helm waited on. */
|
|
107
|
+
export interface Rollout {
|
|
108
|
+
readonly release: string;
|
|
109
|
+
readonly namespace: string | null;
|
|
110
|
+
readonly revision: number | null;
|
|
111
|
+
readonly status: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* helm's `--output json` release record, read structurally. `status` is helm's own word —
|
|
116
|
+
* `deployed`, `failed`, `pending-upgrade` — and `unknown` only when helm printed no record at all,
|
|
117
|
+
* never a guess dressed as one.
|
|
118
|
+
*/
|
|
119
|
+
export function readRollout(target: HelmTarget, stdout: string): Rollout {
|
|
120
|
+
let parsed: unknown;
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(stdout);
|
|
123
|
+
} catch {
|
|
124
|
+
parsed = undefined;
|
|
125
|
+
}
|
|
126
|
+
const info = isRecord(parsed) ? parsed['info'] : undefined;
|
|
127
|
+
const status = isRecord(info) ? info['status'] : undefined;
|
|
128
|
+
const revision = isRecord(parsed) ? parsed['version'] : undefined;
|
|
129
|
+
const namespace = isRecord(parsed) ? parsed['namespace'] : undefined;
|
|
130
|
+
return {
|
|
131
|
+
release: target.release,
|
|
132
|
+
namespace: typeof namespace === 'string' ? namespace : (target.namespace ?? null),
|
|
133
|
+
revision: typeof revision === 'number' ? revision : null,
|
|
134
|
+
status: typeof status === 'string' ? status : 'unknown',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// `x deploy`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-deploy.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const deploySpec: CommandSpec = {
|
|
7
|
+
name: 'deploy',
|
|
8
|
+
summary: 'run the container deploy plan: migrate first, then the serving roles',
|
|
9
|
+
usage:
|
|
10
|
+
'x deploy --image repo/app:tag [--method compose|helm] [--release name] [--namespace ns] [--timeout 15m] [--dry-run] [--json]',
|
|
11
|
+
requiresApp: true,
|
|
12
|
+
flags: [
|
|
13
|
+
{ name: 'image', type: 'string', summary: 'image reference to deploy' },
|
|
14
|
+
{ name: 'method', type: 'string', summary: 'compose | helm', default: 'compose' },
|
|
15
|
+
{ name: 'dry-run', type: 'boolean', summary: 'print the plan, run nothing' },
|
|
16
|
+
{
|
|
17
|
+
name: 'release',
|
|
18
|
+
type: 'string',
|
|
19
|
+
summary: 'helm release name (default: app.config.ts name)',
|
|
20
|
+
},
|
|
21
|
+
{ name: 'namespace', type: 'string', summary: 'helm namespace (default: the kube context)' },
|
|
22
|
+
{
|
|
23
|
+
name: 'timeout',
|
|
24
|
+
type: 'string',
|
|
25
|
+
// No `default:` — the parser would then fill it on every compose deploy too, and the refusal
|
|
26
|
+
// of a helm-only flag there could not tell an operator's value from its own.
|
|
27
|
+
summary: 'how long helm waits for the migrate hook and the rollout (default 15m)',
|
|
28
|
+
},
|
|
29
|
+
// `--critical` was here and is gone. It parsed, it was echoed into the plan JSON as
|
|
30
|
+
// `critical: <bool>`, and no file in `packages/` read that field — so the flag changed
|
|
31
|
+
// nothing about what `x deploy` did, on either method. `flag-reads.ts`'s
|
|
32
|
+
// `X_CLI_FLAG_UNREAD` passed it, because that gate proves a flag is READ and this one was:
|
|
33
|
+
// into a field with no reader. It is not coming back: `@ultimat3/pwa`'s
|
|
34
|
+
// `updateSignal({ reason: 'security' })`, the call it was to have triggered, is **deleted**
|
|
35
|
+
// as of 9.0.0 for having had no runtime caller of its own, and nothing in the framework
|
|
36
|
+
// force-navigates a client. A deploy also has no channel to one — the plan is
|
|
37
|
+
// `docker compose up` / `helm upgrade`, and the client's build id is read by `http` (tier 2)
|
|
38
|
+
// and `sync` (tier 3), neither of which may import a tier-4 package to act on it.
|
|
39
|
+
],
|
|
40
|
+
};
|