@ultimat3/cli 23.0.0 → 24.0.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 +15 -7
- package/README.md +3 -3
- package/package.json +28 -28
- package/src/affected.ts +27 -35
- package/src/api-registration.ts +89 -0
- package/src/app-artifacts.ts +1 -1
- package/src/app-auth.ts +4 -27
- package/src/app-config-load.ts +51 -0
- package/src/app-env.ts +5 -11
- package/src/app-manifest.ts +2 -8
- package/src/app-mcp.ts +10 -23
- package/src/app-permissions-borrowed.ts +129 -0
- package/src/app-permissions-site.ts +24 -0
- package/src/app-permissions.ts +7 -17
- package/src/budgets.ts +4 -35
- package/src/cdp-shot-driver.ts +3 -1
- package/src/cmd-auth-spec.ts +16 -0
- package/src/cmd-auth.ts +65 -0
- package/src/cmd-build.ts +64 -15
- package/src/cmd-db-backfill.ts +25 -12
- package/src/cmd-db.ts +6 -5
- package/src/cmd-deploy-helm.ts +7 -6
- package/src/cmd-deploy.ts +4 -4
- package/src/cmd-dev.ts +9 -8
- package/src/cmd-doctor-spec.ts +2 -9
- package/src/cmd-doctor.ts +41 -14
- package/src/cmd-env.ts +26 -2
- package/src/cmd-generate.ts +79 -32
- package/src/cmd-help.ts +19 -6
- package/src/cmd-i18n.ts +10 -9
- package/src/cmd-jobs.ts +18 -6
- package/src/cmd-manifest-spec.ts +1 -1
- package/src/cmd-manifest.ts +6 -25
- package/src/cmd-mcp.ts +46 -15
- package/src/cmd-planned.ts +15 -8
- package/src/cmd-routes.ts +12 -2
- package/src/cmd-secrets.ts +47 -6
- package/src/cmd-shot-matrix.ts +1 -3
- package/src/cmd-shot.ts +12 -3
- package/src/cmd-tasks.ts +2 -2
- package/src/cmd-test-spec.ts +1 -7
- package/src/cmd-test.ts +13 -29
- package/src/cmd-verify.ts +4 -4
- package/src/db-subscribes.ts +12 -4
- package/src/dev-dashboard-guard.ts +76 -0
- package/src/dev-dashboard.ts +51 -29
- package/src/dev-lock.ts +4 -7
- package/src/dispatch.ts +3 -6
- package/src/doctor-app-url.ts +39 -0
- package/src/doctor-auth.ts +95 -0
- package/src/drift-replica-identity.ts +42 -0
- package/src/error-catalog.ts +6 -9
- package/src/error-codes.ts +23 -3
- package/src/error-unthrown.ts +29 -11
- package/src/errors.ts +54 -83
- package/src/framework-schema-apply.ts +78 -0
- package/src/framework-schema-stamp.ts +96 -0
- package/src/framework-schema.ts +13 -2
- package/src/generate-files.ts +4 -3
- package/src/generate-kinds.ts +33 -1
- package/src/generate-shadow.ts +46 -0
- package/src/generate-write.ts +47 -13
- package/src/gh-target.ts +12 -7
- package/src/i18n-audit.ts +4 -1
- package/src/index.ts +1 -3
- package/src/island-bundle.ts +3 -15
- package/src/jobs-drain.ts +82 -15
- package/src/jobs-report.ts +4 -4
- package/src/loopback-host.ts +13 -0
- package/src/manifest-staleness.ts +62 -0
- package/src/mcp-errors.ts +12 -3
- package/src/mcp-host.ts +16 -9
- package/src/mcp-test-run.ts +38 -0
- package/src/messages.ts +2 -0
- package/src/metrics-endpoint.ts +25 -1
- package/src/output.ts +6 -0
- package/src/page-navigation.ts +7 -23
- package/src/page-speculation.ts +5 -20
- package/src/prerender-out.ts +34 -7
- package/src/prerender.ts +8 -5
- package/src/pwa-artifacts.ts +26 -94
- package/src/registry.ts +2 -0
- package/src/revalidated-response.ts +4 -3
- package/src/role-replicator.ts +16 -0
- package/src/role-start.ts +9 -4
- package/src/role-sync.ts +55 -20
- package/src/runtime-assets.ts +11 -7
- package/src/runtime-bindings.ts +4 -4
- package/src/runtime-cache.ts +38 -47
- package/src/runtime-idempotency-scope.ts +23 -0
- package/src/runtime-isr.ts +36 -0
- package/src/runtime-jobs.ts +8 -33
- package/src/runtime-mfa-warning.ts +29 -0
- package/src/runtime-notify-retention.ts +10 -46
- package/src/runtime-overrides.ts +1 -1
- package/src/runtime-purge.ts +38 -16
- package/src/runtime-queue.ts +31 -10
- package/src/runtime-realtime.ts +9 -45
- package/src/runtime-render.ts +62 -35
- package/src/runtime-services.ts +20 -17
- package/src/runtime-storage.ts +10 -5
- package/src/schema-diff.ts +16 -0
- package/src/schema-drift.ts +45 -15
- package/src/seo-routes.ts +80 -9
- package/src/serve-boot.ts +34 -6
- package/src/serve-drain.ts +9 -33
- package/src/serve-env.ts +9 -6
- package/src/serve-web.ts +12 -7
- package/src/shot-locale.ts +5 -23
- package/src/site-assets.ts +3 -5
- package/src/site-config.ts +9 -48
- package/src/site-seo.ts +1 -1
- package/src/sitemap-lastmod.ts +5 -0
- package/src/solid-loader.ts +12 -3
- package/src/source-files.ts +6 -0
- package/src/stored-object-headers.ts +43 -0
- package/src/sync-url.ts +2 -2
- package/src/tasks-facts.ts +3 -29
- package/src/templates/action.ts +3 -0
- package/src/templates/naming.ts +28 -0
- package/src/templates/route.ts +3 -6
- package/src/templates/scaffold-app.ts +6 -2
- package/src/templates/scaffold-container-compose.ts +10 -1
- package/src/templates/scaffold-container.ts +4 -2
- package/src/templates/scaffold-db-client.ts +4 -2
- package/src/templates/scaffold-helm-fences.ts +76 -0
- package/src/templates/scaffold-helm-templates.ts +32 -7
- package/src/templates/scaffold-helm.ts +31 -1
- package/src/templates/scaffold-repo.ts +9 -1
- package/src/templates/scaffold-shell.ts +39 -36
- package/src/test-counts.ts +2 -6
- package/src/test-ignores.ts +25 -0
- package/src/test-passes.ts +0 -15
- package/src/test-select.ts +5 -14
- package/src/test-shards.ts +41 -93
- package/src/test-workers.ts +26 -4
- package/src/theme-boot.ts +6 -31
- package/src/trusted-hops-error.ts +17 -0
- package/src/unthrown-codes.ts +26 -0
- package/src/verify-checks.ts +17 -46
- package/src/verify-deadline.ts +130 -33
- package/src/verify-merge.ts +60 -27
- package/src/verify-part-step.ts +90 -0
- package/src/verify-run.ts +29 -8
- package/src/verify-shard.ts +13 -2
- package/src/verify-stalled.ts +152 -0
- package/src/verify-tests.ts +5 -2
- package/src/build-errors.ts +0 -54
- package/src/verify-errors.ts +0 -58
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// What a step was doing when its deadline passed, as the `X_VERIFY_STEP_TIMEOUT` finding. A hung
|
|
2
|
+
// test file used to cost eight minutes and report "2 process(es) were killed" — nothing a reader
|
|
3
|
+
// could run. The finding now names the file bun says a killed worker held, and its `fix:` runs it.
|
|
4
|
+
|
|
5
|
+
import { ERROR_DOCS_URL } from '@ultimat3/core';
|
|
6
|
+
import type { Finding } from './output';
|
|
7
|
+
import { quoteArg } from './shell-quote';
|
|
8
|
+
import type { InFlightRun, StepExpiry } from './verify-deadline';
|
|
9
|
+
import { STEP_TIMEOUT_FIELD } from './verify-deadline';
|
|
10
|
+
import type { VerifyStepName } from './verify-step';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One `bun test` the step was waiting on, read against what it printed as it was stopped. A `type`,
|
|
14
|
+
* like the meta that carries it: an interface is not assignable to `Finding.meta`'s JSON record.
|
|
15
|
+
*/
|
|
16
|
+
export type StalledRun = {
|
|
17
|
+
/** The argv without its file list: `bun test --parallel=3`. */
|
|
18
|
+
readonly command: string;
|
|
19
|
+
/** Files the run was handed; 0 for a serial run, which selects by filter. */
|
|
20
|
+
readonly files: number;
|
|
21
|
+
/** `--parallel`'s width, and 1 for a serial run. */
|
|
22
|
+
readonly workers: number;
|
|
23
|
+
/** The files that were still running, as bun named them. Empty when it named none. */
|
|
24
|
+
readonly stuck: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** `X_VERIFY_STEP_TIMEOUT`'s `meta`. A `type`: an interface is not assignable to `Finding.meta`. */
|
|
28
|
+
export type StepTimeoutMeta = {
|
|
29
|
+
readonly step: string;
|
|
30
|
+
readonly deadlineMs: number;
|
|
31
|
+
readonly killed: readonly { readonly pid: number; readonly command: string }[];
|
|
32
|
+
readonly inFlight: readonly StalledRun[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* What a `--parallel` coordinator prints for the file a killed worker was holding. The guard kills
|
|
37
|
+
* the workers first for exactly this line (`verify-deadline.ts`); it is bun's own statement of
|
|
38
|
+
* what was in flight, and it is printed in every reporter mode.
|
|
39
|
+
*/
|
|
40
|
+
const CRASHED = /^\s*✗ (.+) \(worker crashed: SIG[A-Z0-9]+\)$/;
|
|
41
|
+
|
|
42
|
+
/** Bun's per-file header — `::group::`-prefixed on GitHub Actions. A serial run prints it at START. */
|
|
43
|
+
const FILE_LINE = /^(?:::group::)?(\S.*\.test\.[cm]?[jt]sx?):$/;
|
|
44
|
+
|
|
45
|
+
const PARALLEL = '--parallel=';
|
|
46
|
+
|
|
47
|
+
const bare = (path: string): string => (path.startsWith('./') ? path.slice(2) : path);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A parallel run names its stuck files itself, one crash line per killed worker. A serial run has
|
|
51
|
+
* no worker to kill: it prints a file's header when the file STARTS, so the stuck file is the last
|
|
52
|
+
* one printed — unless a `(fail)` follows it, which is also all a failures-only reporter ever
|
|
53
|
+
* prints, and then nothing is claimed.
|
|
54
|
+
*/
|
|
55
|
+
export function stalledRun(run: InFlightRun): StalledRun | undefined {
|
|
56
|
+
const [program, verb, ...rest] = run.command;
|
|
57
|
+
if (program !== 'bun' || verb !== 'test' || run.output === undefined) return undefined;
|
|
58
|
+
const lines = run.output.split('\n').map((line) => line.trimEnd());
|
|
59
|
+
const width = rest.find((arg) => arg.startsWith(PARALLEL));
|
|
60
|
+
const flags = rest.filter((arg) => arg.startsWith('--') && !arg.startsWith('--timings'));
|
|
61
|
+
const command = ['bun', 'test', ...flags].join(' ');
|
|
62
|
+
if (width === undefined) {
|
|
63
|
+
const last = lines.findLastIndex((line) => FILE_LINE.test(line));
|
|
64
|
+
const started = last < 0 ? undefined : FILE_LINE.exec(lines[last] ?? '')?.[1];
|
|
65
|
+
const failed = lines.slice(last + 1).some((line) => line.startsWith('(fail)'));
|
|
66
|
+
return {
|
|
67
|
+
command,
|
|
68
|
+
files: 0,
|
|
69
|
+
workers: 1,
|
|
70
|
+
stuck: started === undefined || failed ? [] : [started],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const files = rest.filter((arg) => !arg.startsWith('--'));
|
|
74
|
+
const crashed = lines.flatMap((line) => {
|
|
75
|
+
const path = CRASHED.exec(line)?.[1];
|
|
76
|
+
// As the caller handed it in, so the `fix:` reruns the very argument the run was given.
|
|
77
|
+
return path === undefined ? [] : [files.find((file) => bare(file) === bare(path)) ?? path];
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
command,
|
|
81
|
+
files: files.length,
|
|
82
|
+
workers: Math.max(1, Number(width.slice(PARALLEL.length)) || 1),
|
|
83
|
+
stuck: [...new Set(crashed)],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** How many paths a `cause:` spells out — the rest are in `meta`. */
|
|
88
|
+
const NAMED_IN_CAUSE = 5;
|
|
89
|
+
|
|
90
|
+
const OUTPUT_TAIL_CHARS = 4_000;
|
|
91
|
+
|
|
92
|
+
/** The last thing each killed run printed: what a reader of a red step looks at first. */
|
|
93
|
+
export function stalledOutput(expiry: StepExpiry): string | undefined {
|
|
94
|
+
const text = expiry.inFlight
|
|
95
|
+
.flatMap((run) => (run.output === undefined || run.output === '' ? [] : [run.output]))
|
|
96
|
+
.join('\n');
|
|
97
|
+
if (text === '') return undefined;
|
|
98
|
+
return text.length > OUTPUT_TAIL_CHARS ? `…${text.slice(-OUTPUT_TAIL_CHARS)}` : text;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const inFlightClause = (runs: readonly StalledRun[], expiry: StepExpiry): string => {
|
|
102
|
+
const files = runs.flatMap((run) => run.stuck);
|
|
103
|
+
const named = runs.find((run) => run.stuck.length > 0);
|
|
104
|
+
if (named !== undefined) {
|
|
105
|
+
const shown = files.slice(0, NAMED_IN_CAUSE).join(', ');
|
|
106
|
+
const more = files.length > NAMED_IN_CAUSE ? `, +${String(files.length - NAMED_IN_CAUSE)}` : '';
|
|
107
|
+
return `; still running when it was stopped: ${shown}${more} (${named.command})`;
|
|
108
|
+
}
|
|
109
|
+
const waiting = expiry.inFlight[0];
|
|
110
|
+
if (waiting === undefined) return '';
|
|
111
|
+
return runs[0] === undefined
|
|
112
|
+
? `; it was waiting on: ${waiting.command.slice(0, 4).join(' ')}`
|
|
113
|
+
: `; its running "${runs[0].command}" named no file still in flight, so the run itself did not exit`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A step that ran past its deadline, on that step's own line. `command` is the gate as it was
|
|
118
|
+
* invoked here (`x verify`, or `bun run verify` at the framework root), so the fix runs where the
|
|
119
|
+
* finding was raised. With a stuck file named, the fix runs THAT file; the step's own rerun and
|
|
120
|
+
* the `stepTimeoutMs` edit ride behind it.
|
|
121
|
+
*/
|
|
122
|
+
export const stepTimeoutFinding = (
|
|
123
|
+
step: VerifyStepName,
|
|
124
|
+
ms: number,
|
|
125
|
+
expiry: StepExpiry,
|
|
126
|
+
command: string,
|
|
127
|
+
): Finding => {
|
|
128
|
+
const runs = expiry.inFlight.flatMap((run) => stalledRun(run) ?? []);
|
|
129
|
+
const stuck = runs.flatMap((run) => run.stuck);
|
|
130
|
+
const rerun = `${command} --only ${step} --json`;
|
|
131
|
+
const longer = `if it legitimately needs longer, edit x.verify.json — add "${STEP_TIMEOUT_FIELD}": { "${step}": <milliseconds> }`;
|
|
132
|
+
// Each path through `quoteArg`, one argv word apiece: a test file's name is data from the disk,
|
|
133
|
+
// and this line is pasted into a shell.
|
|
134
|
+
const alone = ['bun', 'test', ...stuck.map(quoteArg)].join(' ');
|
|
135
|
+
const meta: StepTimeoutMeta = {
|
|
136
|
+
step,
|
|
137
|
+
deadlineMs: ms,
|
|
138
|
+
killed: expiry.killed.map((one) => ({ pid: one.pid, command: one.command })),
|
|
139
|
+
inFlight: runs,
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
code: 'X_VERIFY_STEP_TIMEOUT',
|
|
143
|
+
cause: `step "${step}" did not finish within its ${String(ms)} ms deadline, so it was stopped and ${String(expiry.killed.length)} process(es) it had started were killed${inFlightClause(runs, expiry)}`,
|
|
144
|
+
fix:
|
|
145
|
+
stuck.length > 0
|
|
146
|
+
? `${alone} # what had not finished, alone; the whole step: ${rerun}; ${longer}`
|
|
147
|
+
: `${rerun} # reproduce it alone; ${longer}`,
|
|
148
|
+
docs: ERROR_DOCS_URL,
|
|
149
|
+
...(stuck[0] === undefined ? {} : { at: stuck[0] }),
|
|
150
|
+
meta,
|
|
151
|
+
};
|
|
152
|
+
};
|
package/src/verify-tests.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { judgeCoverage } from './coverage-floor';
|
|
|
18
18
|
import { msg } from './messages';
|
|
19
19
|
import { countsOf } from './test-counts';
|
|
20
20
|
import { testEnvOverrides } from './test-dotenv';
|
|
21
|
+
import { IGNORED_TEST_PATTERNS } from './test-ignores';
|
|
21
22
|
import type { TestFile } from './test-select';
|
|
22
23
|
import { discoverTests } from './test-select';
|
|
23
24
|
import { machineLease } from './test-slots';
|
|
@@ -124,7 +125,9 @@ const isSerial = (type: TestType): boolean => SERIAL_TYPES.includes(type);
|
|
|
124
125
|
* Build output, and nested projects that carry their own `x verify`. `examples/**` and
|
|
125
126
|
* `dummy/**` are the second kind: each holds a whole app gated by its own run of this same step
|
|
126
127
|
* list, so collecting them here would report one app failure on two different gates. The patterns
|
|
127
|
-
* are relative to the run's root, so this excludes nothing when the app itself is the root.
|
|
128
|
+
* are relative to the run's root, so this excludes nothing when the app itself is the root. The
|
|
129
|
+
* policy — which directories, and which are anchored at the root — is `test-ignores.ts`'s, the same
|
|
130
|
+
* one `discoverTests` filters the parallel steps by; this is its glob projection.
|
|
128
131
|
*
|
|
129
132
|
* `dummy/**` was added after the framework gate started running a demo app's tests and disagreeing
|
|
130
133
|
* with `bun run test` — which already excluded it — for no reason a reader could see.
|
|
@@ -133,7 +136,7 @@ const isSerial = (type: TestType): boolean => SERIAL_TYPES.includes(type);
|
|
|
133
136
|
* those apps and blocks on a per-app ratchet. Until 2026-08 that was only true of `examples/**`,
|
|
134
137
|
* and `dummy/social-media-clone` was excluded by this list and gated by nothing.
|
|
135
138
|
*/
|
|
136
|
-
const NEVER_A_TEST =
|
|
139
|
+
const NEVER_A_TEST = IGNORED_TEST_PATTERNS;
|
|
137
140
|
|
|
138
141
|
const ignoreFlags = (patterns: readonly string[]): readonly string[] =>
|
|
139
142
|
patterns.map((pattern) => `--path-ignore-patterns=${pattern}`);
|
package/src/build-errors.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// The CLI's build and bundle refusals: an entry an app does not have, an island that will not
|
|
2
|
-
// compile, a framework browser script that will not bundle. Split from `errors.ts` at its 500-line
|
|
3
|
-
// ceiling; `errors.ts` re-exports every name, so no import site moves.
|
|
4
|
-
|
|
5
|
-
import { UltimateError } from '@ultimat3/core';
|
|
6
|
-
import { quoteArg } from './shell-quote';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A build target names an entry file the app does not have. `x build` refuses before it spawns the
|
|
10
|
-
* builder: `bun build`'s own "module not found" says nothing about which file an Ultimate app is
|
|
11
|
-
* supposed to own, and `docker build`'s says nothing about which target wanted it.
|
|
12
|
-
*/
|
|
13
|
-
export class BuildEntryMissingError extends UltimateError {
|
|
14
|
-
constructor(input: { target: string; entry: string }) {
|
|
15
|
-
super({
|
|
16
|
-
code: 'X_BUILD_ENTRY_MISSING',
|
|
17
|
-
cause: `x build --target ${input.target} builds from ${input.entry}, and the app does not have it`,
|
|
18
|
-
fix: `x new scratch-app --dry-run --json # its file list carries ${input.entry}; copy that file into this app`,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A client entry would not compile. `X_BUILD_FAILED`, not a code of its own: an island is a bundle
|
|
25
|
-
* entry point like any other, and the target's own logs are what says which line. The fix builds
|
|
26
|
-
* exactly that one file, so the next message an author reads is the compiler's and not the CLI's.
|
|
27
|
-
*/
|
|
28
|
-
export class IslandBuildFailedError extends UltimateError {
|
|
29
|
-
constructor(input: { file: string; logs: string }) {
|
|
30
|
-
super({
|
|
31
|
-
code: 'X_BUILD_FAILED',
|
|
32
|
-
cause: `${input.file} is an island entry point and would not bundle: ${input.logs}`,
|
|
33
|
-
fix: `bun build --target browser ${input.file}`,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Which framework script a page ships: the sync worker, the page boot, or the client router. */
|
|
39
|
-
export type FrameworkScriptKind = 'sync worker' | 'page boot' | 'client router';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* One of the page's framework scripts would not bundle. Same code as an island: "a browser entry
|
|
43
|
-
* the framework builds did not build" is one condition, and the entry here is framework code, not
|
|
44
|
-
* the app's — the cause names WHICH script, so the reader knows what the page is now missing.
|
|
45
|
-
*/
|
|
46
|
-
export class FrameworkScriptBuildFailedError extends UltimateError {
|
|
47
|
-
constructor(input: { what: FrameworkScriptKind; entry: string; logs: string }) {
|
|
48
|
-
super({
|
|
49
|
-
code: 'X_BUILD_FAILED',
|
|
50
|
-
cause: `the ${input.what} (${input.entry}) would not bundle for the browser: ${input.logs}`,
|
|
51
|
-
fix: `bun build --target browser --format iife ${quoteArg(input.entry)}`,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/verify-errors.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// The gate's sizing and CI-split refusals: a bad budget env var, an unsplittable `--shard`, and a
|
|
2
|
-
// `x verify merge` part that is not a verify document. Split from errors.ts at its size ceiling.
|
|
3
|
-
|
|
4
|
-
import { UltimateError } from '@ultimat3/core';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* `ULTIMATE_TEST_MEMORY_BUDGET` or `ULTIMATE_TEST_MAX_WORKERS` set to something that does not
|
|
8
|
-
* parse. Refused rather than ignored: a typo read as "the default" is a run sized for a machine
|
|
9
|
-
* the caller said this is not.
|
|
10
|
-
*/
|
|
11
|
-
export class TestBudgetInvalidError extends UltimateError {
|
|
12
|
-
constructor(name: string, value: string) {
|
|
13
|
-
super({
|
|
14
|
-
code: 'X_TEST_BUDGET_INVALID',
|
|
15
|
-
cause: `${name}=${JSON.stringify(value)} does not parse — ${
|
|
16
|
-
name === 'ULTIMATE_TEST_MAX_WORKERS'
|
|
17
|
-
? 'expected a positive integer'
|
|
18
|
-
: 'expected a size such as 3g, 512m or 4GiB'
|
|
19
|
-
}`,
|
|
20
|
-
fix:
|
|
21
|
-
name === 'ULTIMATE_TEST_MAX_WORKERS'
|
|
22
|
-
? 'ULTIMATE_TEST_MAX_WORKERS=4 x verify'
|
|
23
|
-
: 'ULTIMATE_TEST_MEMORY_BUDGET=3g x verify',
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The gate as an app types it. A refusal raised from another entry — `bun run verify` at the
|
|
30
|
-
* framework root, where `x verify` answers `X_NOT_IN_APP` — passes its own spelling, so the
|
|
31
|
-
* `fix:` runs where it was raised.
|
|
32
|
-
*/
|
|
33
|
-
const GATE = 'x verify';
|
|
34
|
-
|
|
35
|
-
/** `x verify --shard` asked for a split the gate cannot make. */
|
|
36
|
-
export class VerifyShardInvalidError extends UltimateError {
|
|
37
|
-
constructor(input: { reason: string; fix?: string; command?: string }) {
|
|
38
|
-
const command = input.command ?? GATE;
|
|
39
|
-
super({
|
|
40
|
-
code: 'X_VERIFY_SHARD_INVALID',
|
|
41
|
-
cause: `${command} --shard: ${input.reason}`,
|
|
42
|
-
fix: input.fix ?? `${command} --only unit --shard 1/4 --json`,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** A part handed to `x verify merge` that is not an `x verify --json` document. */
|
|
48
|
-
export class VerifyMergeInputError extends UltimateError {
|
|
49
|
-
constructor(input: { file: string; reason: string; sourceError?: unknown; command?: string }) {
|
|
50
|
-
const command = input.command ?? GATE;
|
|
51
|
-
super({
|
|
52
|
-
code: 'X_VERIFY_MERGE_INPUT',
|
|
53
|
-
cause: `${command} merge: ${input.file} ${input.reason}`,
|
|
54
|
-
fix: `${command} --only unit --json > part.json # one document per part, the last line of stdout`,
|
|
55
|
-
...(input.sourceError === undefined ? {} : { sourceError: input.sourceError }),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|