@ultimat3/cli 22.15.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 +36 -13
- package/README.md +31 -8
- package/package.json +29 -28
- package/src/admin-catalog-keys.ts +16 -0
- package/src/admin-registration.ts +192 -0
- package/src/affected.ts +27 -35
- package/src/api-registration.ts +125 -11
- package/src/app-artifacts.ts +1 -1
- package/src/app-auth.ts +4 -27
- package/src/app-boundaries.ts +6 -20
- package/src/app-config-load.ts +51 -0
- package/src/app-env.ts +5 -11
- package/src/app-load.ts +55 -11
- package/src/app-manifest.ts +16 -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 +0 -0
- package/src/app-reload-graph.ts +7 -30
- package/src/app-transport.ts +219 -0
- package/src/boundary-findings.ts +16 -4
- package/src/browser-transport.ts +160 -0
- package/src/budgets.ts +27 -34
- 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-spec.ts +10 -3
- package/src/cmd-build.ts +107 -11
- package/src/cmd-db-backfill.ts +25 -12
- package/src/cmd-db.ts +66 -16
- 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 +75 -24
- package/src/cmd-env.ts +26 -2
- package/src/cmd-generate.ts +195 -47
- package/src/cmd-help.ts +19 -6
- package/src/cmd-i18n.ts +10 -9
- package/src/cmd-jobs-spec.ts +19 -3
- package/src/cmd-jobs.ts +136 -7
- 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-new.ts +15 -1
- package/src/cmd-planned.ts +15 -8
- package/src/cmd-routes.ts +27 -3
- package/src/cmd-secrets-ring.ts +76 -0
- package/src/cmd-secrets-spec.ts +10 -2
- package/src/cmd-secrets.ts +95 -18
- package/src/cmd-shot-matrix.ts +1 -3
- package/src/cmd-shot.ts +12 -3
- package/src/cmd-tasks.ts +84 -15
- package/src/cmd-test-spec.ts +1 -7
- package/src/cmd-test.ts +13 -29
- package/src/cmd-verify.ts +13 -5
- package/src/comment-safe.ts +16 -0
- package/src/coverage-floor.ts +334 -0
- package/src/coverage-lcov.ts +193 -0
- package/src/coverage-source.ts +136 -0
- package/src/db-object-drift.ts +30 -0
- package/src/db-replay-engine.ts +115 -0
- package/src/db-schema-dump.ts +150 -0
- package/src/db-schema-refresh.ts +122 -0
- 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/dev-route-table.ts +9 -0
- package/src/dispatch.ts +3 -6
- package/src/doctor-app-url.ts +39 -0
- package/src/doctor-auth.ts +95 -0
- package/src/doctor-coverage.ts +39 -0
- package/src/doctor-guards.ts +54 -0
- package/src/doctor-sealed.ts +132 -0
- package/src/document-graph.ts +82 -0
- package/src/drift-replica-identity.ts +42 -0
- package/src/error-catalog.ts +6 -9
- package/src/error-codes.ts +55 -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 +28 -2
- package/src/generate-files.ts +79 -12
- package/src/generate-grant-findings.ts +76 -0
- package/src/generate-grants.ts +77 -3
- package/src/generate-kinds.ts +52 -1
- package/src/generate-shadow.ts +46 -0
- package/src/generate-write.ts +62 -40
- package/src/generated-imports.ts +59 -0
- package/src/gh-target.ts +12 -7
- package/src/guard-sources.ts +104 -0
- package/src/guards.ts +23 -7
- package/src/handle-registration.ts +217 -0
- package/src/i18n-audit.ts +4 -1
- package/src/i18n-registration.ts +36 -0
- package/src/image-prepare.ts +51 -8
- package/src/import-closure.ts +165 -0
- package/src/index.ts +12 -5
- package/src/island-bundle.ts +3 -15
- package/src/island-store.ts +24 -16
- package/src/jobs-drain.ts +95 -21
- package/src/jobs-json.ts +50 -0
- package/src/jobs-report.ts +7 -4
- package/src/json-merge.ts +46 -0
- package/src/loopback-host.ts +13 -0
- package/src/manifest-staleness.ts +62 -0
- package/src/mcp-errors.ts +41 -4
- package/src/mcp-host.ts +16 -9
- package/src/mcp-test-run.ts +38 -0
- package/src/messages.ts +35 -0
- package/src/metrics-endpoint.ts +95 -24
- package/src/migration-extensions.ts +29 -0
- package/src/module-imports.ts +47 -0
- package/src/output.ts +24 -9
- package/src/page-navigation.ts +7 -23
- package/src/page-speculation.ts +5 -20
- package/src/permission-grants.ts +41 -9
- package/src/prerender-out.ts +34 -7
- package/src/prerender.ts +8 -5
- package/src/publish-closure.ts +101 -0
- package/src/pwa-artifacts.ts +26 -94
- package/src/registry.ts +2 -0
- package/src/revalidated-response.ts +4 -3
- package/src/role-load.ts +143 -0
- package/src/role-replicator.ts +16 -0
- package/src/role-start.ts +27 -5
- package/src/role-sync.ts +55 -20
- package/src/role-wake.ts +45 -0
- package/src/runtime-admin.ts +141 -0
- package/src/runtime-assets.ts +11 -7
- package/src/runtime-bindings.ts +4 -4
- package/src/runtime-cache.ts +38 -47
- package/src/runtime-hooks.ts +5 -2
- 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 +39 -11
- package/src/runtime-realtime.ts +9 -45
- package/src/runtime-render.ts +68 -35
- package/src/runtime-services.ts +22 -18
- package/src/runtime-storage.ts +10 -5
- package/src/scaffold-typecheck.ts +1 -0
- package/src/schema-diff.ts +16 -0
- package/src/schema-drift.ts +45 -15
- package/src/schema-dump-drift.ts +64 -0
- package/src/seo-routes.ts +80 -9
- package/src/serve-boot.ts +114 -158
- package/src/serve-drain.ts +9 -33
- package/src/serve-env.ts +9 -6
- package/src/serve-prebuilt-paths.ts +19 -0
- package/src/serve-prebuilt.ts +84 -0
- package/src/serve-web.ts +169 -0
- package/src/serve.ts +6 -9
- package/src/server-barrels.ts +54 -0
- 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-edit.ts +49 -0
- 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 -26
- package/src/templates/action.ts +147 -11
- package/src/templates/admin-catalog.ts +58 -0
- package/src/templates/admin-page.ts +36 -8
- package/src/templates/admin.ts +57 -17
- package/src/templates/backfill.ts +121 -87
- package/src/templates/entity.ts +140 -44
- package/src/templates/guard-animated-layout-property.ts +4 -11
- package/src/templates/guard-bare-error.ts +9 -13
- package/src/templates/guard-focus-visible.ts +4 -11
- package/src/templates/guard-image-dimensions.ts +6 -6
- package/src/templates/guard-island-without-states.ts +2 -4
- package/src/templates/guard-raw-breakpoint.ts +210 -0
- package/src/templates/guard-raw-colour.ts +4 -11
- package/src/templates/guard-raw-length.ts +375 -0
- package/src/templates/guard-raw-motion.ts +213 -0
- package/src/templates/guard-raw-shadow.ts +187 -0
- package/src/templates/guard-raw-z-index.ts +166 -0
- package/src/templates/guard-repo-raw-sql.ts +345 -0
- package/src/templates/guard-semantic-interactive.ts +6 -6
- package/src/templates/guard-undeclared-custom-property.ts +217 -0
- package/src/templates/guard-undefined-style-class.ts +215 -0
- package/src/templates/guard-untranslated-string.ts +6 -6
- package/src/templates/guard-unzoned-date.ts +9 -13
- package/src/templates/index.ts +3 -2
- package/src/templates/island.ts +34 -51
- package/src/templates/job-body-test.ts +121 -0
- package/src/templates/job.ts +23 -8
- package/src/templates/naming.ts +28 -0
- package/src/templates/policy.ts +16 -9
- package/src/templates/query.ts +242 -26
- package/src/templates/resource-create.ts +62 -18
- package/src/templates/resource-form-island.ts +58 -80
- package/src/templates/resource-page.ts +269 -0
- package/src/templates/resource-service.ts +96 -0
- package/src/templates/resource.ts +86 -61
- package/src/templates/route.ts +41 -35
- package/src/templates/scaffold-api.ts +55 -1
- package/src/templates/scaffold-app.ts +177 -53
- package/src/templates/scaffold-auth.ts +83 -19
- package/src/templates/scaffold-browser-client.ts +90 -0
- package/src/templates/scaffold-container-compose.ts +10 -1
- package/src/templates/scaffold-container.ts +20 -2
- package/src/templates/scaffold-dashboard-bare.ts +64 -2
- package/src/templates/scaffold-dashboard-example.ts +124 -31
- package/src/templates/scaffold-dashboard-shared.ts +1 -20
- package/src/templates/scaffold-dashboard.ts +1 -2
- package/src/templates/scaffold-db-client.ts +144 -0
- package/src/templates/scaffold-db-package.ts +90 -8
- package/src/templates/scaffold-docs.ts +50 -5
- package/src/templates/scaffold-guards.ts +54 -11
- 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-http.ts +9 -7
- package/src/templates/scaffold-repo.ts +61 -3
- package/src/templates/scaffold-roles.ts +56 -9
- package/src/templates/scaffold-shell.ts +74 -105
- package/src/templates/scaffold-site.ts +45 -19
- package/src/templates/scaffold-ui-package.ts +18 -0
- package/src/templates/slice-foundation.ts +46 -8
- package/src/test-counts.ts +14 -2
- 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/transport-calls.ts +105 -0
- package/src/trusted-hops-error.ts +17 -0
- package/src/unscanned-admin.ts +54 -0
- package/src/unthrown-codes.ts +26 -0
- package/src/verify-checks.ts +43 -53
- package/src/verify-coverage-run.ts +190 -0
- package/src/verify-deadline.ts +353 -0
- package/src/verify-floor.ts +36 -9
- package/src/verify-merge.ts +171 -45
- package/src/verify-part-step.ts +90 -0
- package/src/verify-progress.ts +25 -0
- package/src/verify-role-load-probe.ts +54 -0
- package/src/verify-role-load.ts +148 -0
- package/src/verify-run.ts +116 -37
- package/src/verify-shard.ts +33 -11
- package/src/verify-stalled.ts +152 -0
- package/src/verify-step.ts +42 -1
- package/src/verify-tests.ts +83 -8
- package/src/workspace-checks.ts +50 -8
- package/src/workspace-dep-edit.ts +127 -0
- package/src/build-errors.ts +0 -54
- package/src/cmd-dev-fixture.ts +0 -204
- package/src/dev-live-fixture.ts +0 -122
- package/src/policy-fixture.ts +0 -78
- package/src/realtime-browser-probe-fixture.ts +0 -9
- package/src/role-start-fixture.ts +0 -78
- package/src/scaffold-fixture.ts +0 -160
- package/src/templates/island-fixture.ts +0 -76
- package/src/verify-errors.ts +0 -49
package/src/dev-live-fixture.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// The `x dev` live tests' process discipline, in one place: spawn the supervisor, read its two
|
|
2
|
-
// streams, and — on every path, a failing one included — reap every process it started and prove
|
|
3
|
-
// none is left. Two children of `cmd-dev-restart.live.test.ts` ran for six hours with ppid 1 and a
|
|
4
|
-
// deleted cwd because the test's `finally` SIGKILLed the supervisor, which cannot forward a signal
|
|
5
|
-
// it never gets, and nothing then looked for what it had started (2026-09-29).
|
|
6
|
-
|
|
7
|
-
import { readdirSync, readlinkSync } from 'node:fs'; // why: /proc is read synchronously; Bun has no readlink.
|
|
8
|
-
|
|
9
|
-
/** One pump per stream — reading a stream twice closes its reader under the first `for await`. */
|
|
10
|
-
export function pump(stream: ReadableStream<Uint8Array>): { seen: () => string } {
|
|
11
|
-
const decoder = new TextDecoder();
|
|
12
|
-
let seen = '';
|
|
13
|
-
void (async () => {
|
|
14
|
-
for await (const chunk of stream) seen += decoder.decode(chunk, { stream: true });
|
|
15
|
-
})();
|
|
16
|
-
return { seen: () => seen };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Poll the buffer until `marker` shows up. The caller's own timeout is the deadline. */
|
|
20
|
-
export async function waitFor(output: { seen: () => string }, marker: string): Promise<string> {
|
|
21
|
-
for (;;) {
|
|
22
|
-
const seen = output.seen();
|
|
23
|
-
if (seen.includes(marker)) return seen;
|
|
24
|
-
await Bun.sleep(25);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Whether `pid` is still a process — signal 0 delivers nothing and only asks. */
|
|
29
|
-
export function alive(pid: number): boolean {
|
|
30
|
-
try {
|
|
31
|
-
process.kill(pid, 0);
|
|
32
|
-
return true;
|
|
33
|
-
} catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Every descendant of `pid`, depth first — asked while `pid` lives, or its children are init's. */
|
|
39
|
-
export function descendantsOf(pid: number): readonly number[] {
|
|
40
|
-
const out = Bun.spawnSync(['pgrep', '-P', String(pid)]).stdout.toString();
|
|
41
|
-
const children = out
|
|
42
|
-
.split('\n')
|
|
43
|
-
.map((line) => Number(line.trim()))
|
|
44
|
-
.filter((child) => Number.isInteger(child) && child > 0);
|
|
45
|
-
return children.flatMap((child) => [child, ...descendantsOf(child)]);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Every process whose cwd is `dir` — a deleted one included, which Linux spells `<dir> (deleted)`.
|
|
50
|
-
* The one question that finds an orphan after its parent is gone, when no process tree reaches it.
|
|
51
|
-
* Empty where there is no `/proc` (macOS): the descendant walk is then the whole answer.
|
|
52
|
-
*/
|
|
53
|
-
export function processesIn(dir: string): readonly number[] {
|
|
54
|
-
let pids: readonly string[];
|
|
55
|
-
try {
|
|
56
|
-
pids = readdirSync('/proc').filter((name) => /^\d+$/.test(name));
|
|
57
|
-
} catch {
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
const found: number[] = [];
|
|
61
|
-
for (const pid of pids) {
|
|
62
|
-
try {
|
|
63
|
-
const cwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
64
|
-
if (cwd === dir || cwd === `${dir} (deleted)`) found.push(Number(pid));
|
|
65
|
-
} catch {
|
|
66
|
-
// Gone between the listing and the read, or not ours to read.
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return found;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Whether anything still answers on `port` at the loopback address `x dev` binds. */
|
|
73
|
-
export async function listening(port: number): Promise<boolean> {
|
|
74
|
-
try {
|
|
75
|
-
const socket = await Bun.connect({
|
|
76
|
-
hostname: '127.0.0.1',
|
|
77
|
-
port,
|
|
78
|
-
socket: { data: () => undefined },
|
|
79
|
-
});
|
|
80
|
-
socket.end();
|
|
81
|
-
return true;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* SIGKILL `pids`, then wait until each is gone. A test's last resort, run in `finally` — by then
|
|
89
|
-
* every graceful stop the test meant to prove has had its chance. `performance.now()`, never
|
|
90
|
-
* `Date.now()`: the test preload freezes the wall clock, and a deadline read off it never arrives.
|
|
91
|
-
*/
|
|
92
|
-
export async function reap(pids: readonly number[], timeoutMs = 10_000): Promise<void> {
|
|
93
|
-
for (const pid of pids) {
|
|
94
|
-
try {
|
|
95
|
-
process.kill(pid, 'SIGKILL');
|
|
96
|
-
} catch {
|
|
97
|
-
// Already gone.
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const until = performance.now() + timeoutMs;
|
|
101
|
-
while (pids.some(alive) && performance.now() < until) await Bun.sleep(25);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** What was left: the pids still in `dir` and whether `port` still answers. Both empty is clean. */
|
|
105
|
-
export async function leftovers(
|
|
106
|
-
dir: string,
|
|
107
|
-
port: number | undefined,
|
|
108
|
-
): Promise<{ readonly pids: readonly number[]; readonly port: boolean }> {
|
|
109
|
-
return {
|
|
110
|
-
pids: processesIn(dir),
|
|
111
|
-
port: port === undefined ? false : await listening(port),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* The net under a TIMED-OUT test: bun abandons its body without running `finally`, so an
|
|
117
|
-
* `afterEach` reaps whatever still runs in the fixture directory — the only handle left on an
|
|
118
|
-
* orphan whose parent is gone.
|
|
119
|
-
*/
|
|
120
|
-
export async function reapIn(dir: string): Promise<void> {
|
|
121
|
-
await reap(processesIn(dir));
|
|
122
|
-
}
|
package/src/policy-fixture.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// The one policy declaration set both `x policy` test files run against. Shared rather than
|
|
2
|
-
// copied, for the reason `thrown-by.ts` gives: every count in both files' assertions is read off
|
|
3
|
-
// THIS set, so a second copy drifts and each file keeps passing while they stop agreeing.
|
|
4
|
-
|
|
5
|
-
import { action, registerActions, t } from '@ultimat3/action';
|
|
6
|
-
import { and, can, definePermissions, defineRoles } from '@ultimat3/policy';
|
|
7
|
-
import { from, query, registerQuery } from '@ultimat3/query';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The row the fixture's two reads return, over an empty source: `x policy` describes a query and
|
|
11
|
-
* evaluates its policy, it never executes one, so the rows are the one fact that may be missing.
|
|
12
|
-
*/
|
|
13
|
-
interface PostRow {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Four permissions, three roles, two actions and two queries — each fact earning its place.
|
|
19
|
-
*
|
|
20
|
-
* `archivePost` is guarded by a COMPOSITE, and that is the point of it: its display capability is
|
|
21
|
-
* the label `and(post:publish, post:read)`, which is not any permission, so a report that matched
|
|
22
|
-
* on the label counted this action as enforcing nothing and printed both of its permissions as
|
|
23
|
-
* dead grants. It enforces two, and the facts say so.
|
|
24
|
-
*
|
|
25
|
-
* `post:delete` is the control: declared, granted to `admin`, enforced by nothing at all. It is
|
|
26
|
-
* what a genuinely dead grant looks like, and it is what keeps `unenforced` a claim worth making
|
|
27
|
-
* now that a composite no longer lands in it by accident.
|
|
28
|
-
*
|
|
29
|
-
* `post:publish` is enforced by two actions AND a query, so the aggregation across declarations
|
|
30
|
-
* has something to aggregate.
|
|
31
|
-
*
|
|
32
|
-
* Registers only; the registries are process-global, so the caller clears them first.
|
|
33
|
-
*/
|
|
34
|
-
export function registerPolicyFixture(): void {
|
|
35
|
-
definePermissions(['post:publish', 'post:read', 'post:delete', 'feed:read'] as const);
|
|
36
|
-
defineRoles({
|
|
37
|
-
admin: { grants: ['post:publish', 'post:read', 'post:delete', 'feed:read'] },
|
|
38
|
-
editor: { grants: ['post:publish', 'post:read'] },
|
|
39
|
-
reader: { grants: ['post:read', 'feed:read'] },
|
|
40
|
-
});
|
|
41
|
-
registerActions({
|
|
42
|
-
publishPost: action({
|
|
43
|
-
input: t.object({}),
|
|
44
|
-
output: t.object({}),
|
|
45
|
-
policy: can('post:publish'),
|
|
46
|
-
async handle() {
|
|
47
|
-
return {};
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
archivePost: action({
|
|
51
|
-
input: t.object({}),
|
|
52
|
-
output: t.object({}),
|
|
53
|
-
policy: and(
|
|
54
|
-
can('post:publish'),
|
|
55
|
-
can('post:read', ({ actor }) => actor?.id === 'admin'),
|
|
56
|
-
),
|
|
57
|
-
async handle() {
|
|
58
|
-
return {};
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
-
registerQuery(
|
|
63
|
-
'postFeed',
|
|
64
|
-
query({
|
|
65
|
-
input: t.object({}),
|
|
66
|
-
policy: can('feed:read'),
|
|
67
|
-
sql: () => from<PostRow>('posts', []).orderBy('id').limit(10),
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
|
-
registerQuery(
|
|
71
|
-
'publishedPosts',
|
|
72
|
-
query({
|
|
73
|
-
input: t.object({}),
|
|
74
|
-
policy: can('post:publish'),
|
|
75
|
-
sql: () => from<PostRow>('posts', []).orderBy('id').limit(10),
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// The browser island `wiki/Realtime.md` promises: one live hook and nothing else. It is a real
|
|
2
|
-
// module rather than a string a test writes to a temp path, because module resolution is the thing
|
|
3
|
-
// under test — `@ultimat3/realtime`'s client entry must reach neither the bus nor the WAL decoder.
|
|
4
|
-
// Bundled AND imported by `realtime-browser-barrel.test.ts` — the import is what gives it an lcov
|
|
5
|
-
// record, since `Bun.build()` reads this file without evaluating it.
|
|
6
|
-
|
|
7
|
-
import { useQuery } from '@ultimat3/realtime';
|
|
8
|
-
|
|
9
|
-
export const probeUseQuery = useQuery;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// The one `RunningServices` every `role-start` test file boots roles against, and the one reset
|
|
2
|
-
// between them. Shared rather than copied, for the reason `policy-fixture.ts` gives: three files
|
|
3
|
-
// start the same roles, and a second copy of the runtime drifts while each file keeps passing.
|
|
4
|
-
//
|
|
5
|
-
// Its own module rather than a `.test.ts` neighbours import, because `tsconfig.json` excludes
|
|
6
|
-
// `*.test.ts` — a fixture written there is one `tsc` never reads.
|
|
7
|
-
|
|
8
|
-
import { noopPurgeDriver } from '@ultimat3/cache';
|
|
9
|
-
import { type RealtimeConfig, resetLifecycle } from '@ultimat3/core';
|
|
10
|
-
import { resetHttpConfig } from '@ultimat3/http';
|
|
11
|
-
import {
|
|
12
|
-
createMemoryDriver,
|
|
13
|
-
createMemoryEventBus,
|
|
14
|
-
createMemoryOutboxStore,
|
|
15
|
-
resetJobs,
|
|
16
|
-
resetJobsFacade,
|
|
17
|
-
resetTasks,
|
|
18
|
-
} from '@ultimat3/jobs';
|
|
19
|
-
import { createMemoryDriver as createMemoryMailDriver } from '@ultimat3/mail';
|
|
20
|
-
import { DEFAULT_PRESENCE_TTL_MS, InProcessTransport } from '@ultimat3/realtime/server';
|
|
21
|
-
import { defineStorage, localDriver } from '@ultimat3/storage';
|
|
22
|
-
import { resolveServices } from './runtime-bindings';
|
|
23
|
-
import type { RunningServices } from './runtime-services';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Every service a role touches, embedded but real — no PGlite boot for a role-wiring test.
|
|
27
|
-
*
|
|
28
|
-
* `root` is a parameter and not a constant: each test file owns its own directory and deletes it,
|
|
29
|
-
* so two files sharing one on-disk storage root cannot leave the other's fixture half-removed.
|
|
30
|
-
*/
|
|
31
|
-
/** Enabled, in-process: the section every role test before 22.0.0 was implicitly booting. */
|
|
32
|
-
const REALTIME_ON: RealtimeConfig = { enabled: true, transport: 'memory', urlEnv: undefined };
|
|
33
|
-
|
|
34
|
-
export function fixtureRuntime(root: string, realtime = REALTIME_ON): RunningServices {
|
|
35
|
-
const services = resolveServices(root, {});
|
|
36
|
-
const transport = new InProcessTransport();
|
|
37
|
-
return {
|
|
38
|
-
services,
|
|
39
|
-
db: { async ping() {}, async close() {} } as unknown as RunningServices['db'],
|
|
40
|
-
jobs: createMemoryDriver(),
|
|
41
|
-
// A real store, not a stub: the `worker` role starts the outbox relay against it, and a relay
|
|
42
|
-
// whose `claim()` rejects on the first 200ms tick is an unhandled rejection in whichever test
|
|
43
|
-
// happens to still be running.
|
|
44
|
-
outbox: createMemoryOutboxStore(),
|
|
45
|
-
events: createMemoryEventBus(),
|
|
46
|
-
transport,
|
|
47
|
-
realtime,
|
|
48
|
-
transportDetail: 'in-process fanout',
|
|
49
|
-
// The sync role reads this to build its `PresenceRegistry`; the default is what a boot with no
|
|
50
|
-
// `NATS_URL` resolves to, so the fixture is the real number rather than a rounder one.
|
|
51
|
-
presenceTtlMs: DEFAULT_PRESENCE_TTL_MS,
|
|
52
|
-
storage: defineStorage({ disks: { local: localDriver({ root: `${root}/storage` }) } }),
|
|
53
|
-
mail: createMemoryMailDriver(),
|
|
54
|
-
mailDetail: 'embedded',
|
|
55
|
-
purge: noopPurgeDriver(),
|
|
56
|
-
purgeDetail: 'none',
|
|
57
|
-
stop: async () => transport.close(),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The process-global state one started role leaves behind. `resetLifecycle` is the load-bearing
|
|
63
|
-
* one: core's lifecycle is process-wide and a stopped server leaves it drained, so without it the
|
|
64
|
-
* SECOND web role in a file answers every request `X_DRAINING` — a suite that only passes when its
|
|
65
|
-
* tests are run one at a time. `@ultimat3/http`'s own server suite resets it for the same reason.
|
|
66
|
-
*/
|
|
67
|
-
export function resetDevRolesState(): void {
|
|
68
|
-
resetJobs();
|
|
69
|
-
resetJobsFacade();
|
|
70
|
-
resetTasks();
|
|
71
|
-
resetLifecycle();
|
|
72
|
-
// `configureHttp()` is a process-global registration made at MODULE scope, so one file that
|
|
73
|
-
// loads an app leaves that app's CORS origins, body limit and buckets standing for every later
|
|
74
|
-
// file in the same `bun test` process — and a scaffolded app now ships such a module
|
|
75
|
-
// (`apps/web/app/http.ts`). Nothing is broken today; this is the same rule the four resets above
|
|
76
|
-
// already follow, applied to the fifth global before it is the one that costs an afternoon.
|
|
77
|
-
resetHttpConfig();
|
|
78
|
-
}
|
package/src/scaffold-fixture.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// What the scaffold drift gate compiles: every documented `x new` invocation, with every `x g`
|
|
2
|
-
// generator run on top of the one that carries an app. Separate from the compiler harness next to
|
|
3
|
-
// it, because "which scaffolds exist" is a fact about the CLI's surface, not about running `tsc`.
|
|
4
|
-
|
|
5
|
-
import type { GenerateOptions } from './cmd-generate';
|
|
6
|
-
import { dedupe, generate } from './cmd-generate';
|
|
7
|
-
import { planNewApp } from './cmd-new';
|
|
8
|
-
import type { GeneratedFile } from './templates';
|
|
9
|
-
|
|
10
|
-
/** The app the fixture scaffolds. Kebab, multi-word: single-word names hide casing bugs. */
|
|
11
|
-
export const FIXTURE_APP = 'ledger-demo';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* An `errors.ts` an author wrote: it declares what the slice throws, and not the generated name.
|
|
15
|
-
* The generator's INPUT only — the sandbox's own `errors.ts` is the one the resource wrote — so it
|
|
16
|
-
* carries no `X_*` code: a literal here would be one shipped source hands a reader, and the
|
|
17
|
-
* registry rule would ask where it was registered.
|
|
18
|
-
*/
|
|
19
|
-
export const HANDWRITTEN_ERRORS = `import { UltimateError } from '@ultimat3/core';
|
|
20
|
-
|
|
21
|
-
export class LedgerClosedError extends UltimateError {}
|
|
22
|
-
`;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* A feature slice whose entity is NOT tenant-scoped — the shape `x g entity`'s own comment
|
|
26
|
-
* describes for a single-tenant app, and never a shape the generator writes itself. The
|
|
27
|
-
* generator's INPUT only, the same way `HANDWRITTEN_ERRORS` above is: `x g job`/`x g task` read
|
|
28
|
-
* this from a real `entity.ts`, and a fixture that never exercised it compiled only the shape
|
|
29
|
-
* that never failed.
|
|
30
|
-
*/
|
|
31
|
-
export const HANDWRITTEN_ENTITY_NO_TENANT = `import { entity, text, uuid } from '@ultimat3/entity';
|
|
32
|
-
|
|
33
|
-
export const shortLink = entity('short_links', {
|
|
34
|
-
columns: { id: uuid().primaryKey(), url: text({ max: 2000 }) },
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export type ShortLink = typeof shortLink.$row;
|
|
38
|
-
`;
|
|
39
|
-
|
|
40
|
-
/** The paired `repo.ts`: no `byId`, no `listByOrg` — nothing this slice's job may call. */
|
|
41
|
-
export const HANDWRITTEN_REPO_NO_TENANT = `import { db, sql } from '@ultimat3/db';
|
|
42
|
-
import type { ShortLink } from './entity';
|
|
43
|
-
|
|
44
|
-
export async function list(limit = 50): Promise<readonly ShortLink[]> {
|
|
45
|
-
return db().query<ShortLink>(sql\`select * from short_links order by url limit \${limit}\`);
|
|
46
|
-
}
|
|
47
|
-
`;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The `invoice` resource's own `entity.ts`, as `x g` would read it off disk after the resource
|
|
51
|
-
* below ran — the generators writing INTO that slice are handed it, because a slice with no entity
|
|
52
|
-
* now gets the neutral body and no invented table.
|
|
53
|
-
*/
|
|
54
|
-
export const INVOICE_ENTITY = String(
|
|
55
|
-
generate({ kind: 'entity', name: 'invoice' }).find((file) => file.path.endsWith('/entity.ts'))
|
|
56
|
-
?.contents,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* One realistic invocation of every generator, on top of `x new --example`. Names differ from
|
|
61
|
-
* their feature on purpose: `x g query invoice --feature invoice` would collide with the entity
|
|
62
|
-
* import, and a fixture that trips over its own naming stops testing the templates.
|
|
63
|
-
*
|
|
64
|
-
* `admin: true` on the resource is not decoration: `apps/web/app/<name>/admin/resource.ts` and its
|
|
65
|
-
* test are templates no other invocation emits, and they cost nothing extra here because they land
|
|
66
|
-
* in a sandbox that is compiled anyway.
|
|
67
|
-
*/
|
|
68
|
-
export const FIXTURE_GENERATORS: readonly GenerateOptions[] = [
|
|
69
|
-
{ kind: 'resource', name: 'invoice', admin: true },
|
|
70
|
-
{ kind: 'entity', name: 'credit-note', feature: 'credit-note' },
|
|
71
|
-
{ kind: 'policy', name: 'credit-note', feature: 'credit-note' },
|
|
72
|
-
{ kind: 'action', name: 'send-invoice', feature: 'invoice', sliceEntity: INVOICE_ENTITY },
|
|
73
|
-
{ kind: 'mutator', name: 'rename-invoice', feature: 'invoice', sliceEntity: INVOICE_ENTITY },
|
|
74
|
-
// The other shape both templates have: a slice whose `errors.ts` is the author's and declares
|
|
75
|
-
// no `InvoiceNotFoundError`. The resource's own `errors.ts` still lands in the sandbox (it does
|
|
76
|
-
// declare one), which is the point — this compiles the file `x g action` writes when it must
|
|
77
|
-
// not import that class, beside the one it writes when it may.
|
|
78
|
-
{
|
|
79
|
-
kind: 'action',
|
|
80
|
-
name: 'ping-invoice',
|
|
81
|
-
feature: 'invoice',
|
|
82
|
-
sliceErrors: HANDWRITTEN_ERRORS,
|
|
83
|
-
sliceEntity: INVOICE_ENTITY,
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
kind: 'mutator',
|
|
87
|
-
name: 'touch-invoice',
|
|
88
|
-
feature: 'invoice',
|
|
89
|
-
sliceErrors: HANDWRITTEN_ERRORS,
|
|
90
|
-
sliceEntity: INVOICE_ENTITY,
|
|
91
|
-
},
|
|
92
|
-
{ kind: 'query', name: 'invoice-search', feature: 'invoice' },
|
|
93
|
-
{ kind: 'query', name: 'invoice-feed', feature: 'invoice', live: true },
|
|
94
|
-
{ kind: 'job', name: 'sweep-invoices', feature: 'invoice', sliceEntity: INVOICE_ENTITY },
|
|
95
|
-
{ kind: 'backfill', name: 'reindex-invoices', feature: 'invoice' },
|
|
96
|
-
{ kind: 'task', name: 'nightly-sweep', feature: 'invoice', sliceEntity: INVOICE_ENTITY },
|
|
97
|
-
// The other shape both templates have: a feature whose entity names no tenant column, so the
|
|
98
|
-
// job/task must not assume one — compiled here beside the tenant-scoped pair above, exactly as
|
|
99
|
-
// `ping-invoice`/`touch-invoice` compile the action's other shape beside `send-invoice`.
|
|
100
|
-
{
|
|
101
|
-
kind: 'job',
|
|
102
|
-
name: 'purge-orphans',
|
|
103
|
-
feature: 'short-link',
|
|
104
|
-
sliceEntity: HANDWRITTEN_ENTITY_NO_TENANT,
|
|
105
|
-
sliceRepo: HANDWRITTEN_REPO_NO_TENANT,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
kind: 'task',
|
|
109
|
-
name: 'nightly-purge',
|
|
110
|
-
feature: 'short-link',
|
|
111
|
-
sliceEntity: HANDWRITTEN_ENTITY_NO_TENANT,
|
|
112
|
-
sliceRepo: HANDWRITTEN_REPO_NO_TENANT,
|
|
113
|
-
},
|
|
114
|
-
{ kind: 'route', name: 'pricing', surface: 'site' },
|
|
115
|
-
{ kind: 'route', name: 'billing', surface: 'app' },
|
|
116
|
-
// `--at`, pointed at the `site/` route above: an island's whole reason to exist is a 0kb page
|
|
117
|
-
// that needs one interactive control, so the fixture places it where that is true.
|
|
118
|
-
{ kind: 'island', name: 'currency-picker', at: 'apps/web/site/pricing' },
|
|
119
|
-
// The one screen the admin derives from nothing — and the one generator that must NOT emit a
|
|
120
|
-
// `defineRoute`, so compiling it is how that stays true.
|
|
121
|
-
{ kind: 'admin:page', name: 'reconcile', permission: 'ledger:reconcile' },
|
|
122
|
-
// The app's own convention. It is the only generated file that imports `@ultimat3/cli` for its
|
|
123
|
-
// types, so compiling it is what proves a scaffolded app can actually write one.
|
|
124
|
-
{ kind: 'guard', name: 'migration-safety' },
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
/** The whole scaffolded surface: a new app, then every generator run inside it. */
|
|
128
|
-
export function scaffoldFixture(): readonly GeneratedFile[] {
|
|
129
|
-
return dedupe([
|
|
130
|
-
...planNewApp({ name: FIXTURE_APP, example: true }),
|
|
131
|
-
...FIXTURE_GENERATORS.flatMap((options) => generate(options)),
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface ScaffoldVariant {
|
|
136
|
-
/** Names the invocation in a failure, so a red gate says which `x new` broke. */
|
|
137
|
-
readonly name: string;
|
|
138
|
-
/** What this variant emits that no other one does — why it earns its own compile. */
|
|
139
|
-
readonly why: string;
|
|
140
|
-
readonly files: readonly GeneratedFile[];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Every scaffold a user can ask for, each compiled on its own. One variant per *file set*, not per
|
|
145
|
-
* flag: `--no-example` writes a different `packages/db` than `--example` does, and compiling only
|
|
146
|
-
* the example app is what let `x new --no-example` ship a `schema.ts` importing a slice that
|
|
147
|
-
* invocation never writes.
|
|
148
|
-
*/
|
|
149
|
-
export const scaffoldVariants = (): readonly ScaffoldVariant[] => [
|
|
150
|
-
{
|
|
151
|
-
name: 'x new',
|
|
152
|
-
why: 'the example slice, plus one run of every generator on top of it',
|
|
153
|
-
files: scaffoldFixture(),
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: 'x new --no-example',
|
|
157
|
-
why: 'an empty app/: no entity, so schema, seed and the initial migration have nothing to name',
|
|
158
|
-
files: planNewApp({ name: FIXTURE_APP, example: false }),
|
|
159
|
-
},
|
|
160
|
-
];
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// TEST-ONLY. An app root on disk holding emitted files, with the packages an island imports
|
|
2
|
-
// resolvable BY SPECIFIER the way a real app resolves them — so a template that emits
|
|
3
|
-
// `import { Button } from '@ultimat3/ui'` is proven by a build that actually resolves it, and not
|
|
4
|
-
// by a string assertion that the import is present.
|
|
5
|
-
|
|
6
|
-
// `node:` by necessity, and SYNC by necessity: `[Symbol.dispose]` cannot await, so the teardown
|
|
7
|
-
// half has to be synchronous — and Bun ships neither a path API nor a `symlink`.
|
|
8
|
-
import { mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
9
|
-
import { dirname, join } from 'node:path';
|
|
10
|
-
import type { GeneratedFile } from './naming';
|
|
11
|
-
|
|
12
|
-
/** `packages/cli/src/templates` → the repo root, four hops up. */
|
|
13
|
-
const REPO_ROOT = join(import.meta.dir, '..', '..', '..', '..');
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* INSIDE the checkout, and measured rather than chosen: the identical fixture under `os.tmpdir()`
|
|
17
|
-
* fails every `@ultimat3/*` and every relative import inside it with `Could not resolve`, because
|
|
18
|
-
* `Bun.build`'s resolver is scoped to the project `bun test` was started in and an app root outside
|
|
19
|
-
* it cannot reach its own `node_modules`. `.prerender-fixture` is the same shape for the same
|
|
20
|
-
* reason. The leading dot keeps it out of every `tsc` wildcard include.
|
|
21
|
-
*/
|
|
22
|
-
const FIXTURE_ROOT = join(REPO_ROOT, 'packages', 'cli', '.island-fixture');
|
|
23
|
-
|
|
24
|
-
/** The package the fixture lives inside. Linking it would aim a symlink at its own ancestor. */
|
|
25
|
-
const SELF = 'cli';
|
|
26
|
-
|
|
27
|
-
export interface FixtureApp extends Disposable {
|
|
28
|
-
/** Absolute path of the app root — what `buildIslands` globs from. */
|
|
29
|
-
readonly path: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Symlinks rather than a `bun install`: the emitted island must resolve THIS working copy of
|
|
34
|
-
* `@ultimat3/ui`, and an install in a fixture directory would fetch the registry's last release
|
|
35
|
-
* and quietly prove nothing about the change under test. Every workspace is linked, not a chosen
|
|
36
|
-
* few — a template that grows an import should build, not fail on a list nobody updated.
|
|
37
|
-
*/
|
|
38
|
-
function linkDependencies(root: string): void {
|
|
39
|
-
const scope = join(root, 'node_modules', '@ultimat3');
|
|
40
|
-
mkdirSync(scope, { recursive: true });
|
|
41
|
-
const packages = join(REPO_ROOT, 'packages');
|
|
42
|
-
for (const entry of new Bun.Glob('*/package.json').scanSync({ cwd: packages })) {
|
|
43
|
-
const name = entry.slice(0, entry.indexOf('/'));
|
|
44
|
-
if (name === SELF) continue;
|
|
45
|
-
symlinkSync(join(packages, name), join(scope, name), 'dir');
|
|
46
|
-
}
|
|
47
|
-
// Resolved, never spelled as a path: the installer's layout is its own business and a hardcoded
|
|
48
|
-
// `node_modules/solid-js` is a fixture that breaks on a linker change rather than on a real one.
|
|
49
|
-
symlinkSync(
|
|
50
|
-
dirname(Bun.resolveSync('solid-js/package.json', REPO_ROOT)),
|
|
51
|
-
join(root, 'node_modules', 'solid-js'),
|
|
52
|
-
'dir',
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* `Disposable`, so the idiom is `using root = await fixtureAppRoot(label, files)`. `label` is the
|
|
58
|
-
* caller's, and is what keeps two test FILES off one directory: the path is fixed rather than
|
|
59
|
-
* random, because a random one cannot be named in `.gitignore` and a crashed run leaves it behind.
|
|
60
|
-
*/
|
|
61
|
-
export async function fixtureAppRoot(
|
|
62
|
-
label: string,
|
|
63
|
-
files: readonly GeneratedFile[],
|
|
64
|
-
): Promise<FixtureApp> {
|
|
65
|
-
const path = join(FIXTURE_ROOT, label);
|
|
66
|
-
rmSync(path, { recursive: true, force: true });
|
|
67
|
-
mkdirSync(path, { recursive: true });
|
|
68
|
-
linkDependencies(path);
|
|
69
|
-
for (const file of files) await Bun.write(join(path, file.path), String(file.contents));
|
|
70
|
-
return {
|
|
71
|
-
path,
|
|
72
|
-
[Symbol.dispose]: (): void => {
|
|
73
|
-
rmSync(path, { recursive: true, force: true });
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
package/src/verify-errors.ts
DELETED
|
@@ -1,49 +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
|
-
/** `x verify --shard` asked for a split the gate cannot make. */
|
|
29
|
-
export class VerifyShardInvalidError extends UltimateError {
|
|
30
|
-
constructor(input: { reason: string; fix?: string }) {
|
|
31
|
-
super({
|
|
32
|
-
code: 'X_VERIFY_SHARD_INVALID',
|
|
33
|
-
cause: `x verify --shard: ${input.reason}`,
|
|
34
|
-
fix: input.fix ?? 'x verify --only unit --shard 1/4 --json',
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A part handed to `x verify merge` that is not an `x verify --json` document. */
|
|
40
|
-
export class VerifyMergeInputError extends UltimateError {
|
|
41
|
-
constructor(input: { file: string; reason: string; sourceError?: unknown }) {
|
|
42
|
-
super({
|
|
43
|
-
code: 'X_VERIFY_MERGE_INPUT',
|
|
44
|
-
cause: `x verify merge: ${input.file} ${input.reason}`,
|
|
45
|
-
fix: 'x verify --only <step> --json > part.json # one document per part, the last line of stdout',
|
|
46
|
-
...(input.sourceError === undefined ? {} : { sourceError: input.sourceError }),
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|