@ultimat3/cli 12.0.0 → 13.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/package.json +29 -28
- package/src/dev-queue.ts +10 -36
- package/src/error-catalog.ts +1 -0
- package/src/error-codes.ts +5 -0
- package/src/framework-schema.ts +152 -0
- package/src/index.ts +10 -0
- package/src/mcp-errors.ts +3 -0
- package/src/schema-errors.ts +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,33 +37,34 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@babel/core": "^7.28.4",
|
|
40
|
-
"@ultimat3/action": "
|
|
41
|
-
"@ultimat3/admin": "
|
|
42
|
-
"@ultimat3/ai": "
|
|
43
|
-
"@ultimat3/auth": "
|
|
44
|
-
"@ultimat3/cache": "
|
|
45
|
-
"@ultimat3/core": "
|
|
46
|
-
"@ultimat3/db": "
|
|
47
|
-
"@ultimat3/entity": "
|
|
48
|
-
"@ultimat3/flags": "
|
|
49
|
-
"@ultimat3/http": "
|
|
50
|
-
"@ultimat3/i18n": "
|
|
51
|
-
"@ultimat3/jobs": "
|
|
52
|
-
"@ultimat3/mail": "
|
|
53
|
-
"@ultimat3/manifest": "
|
|
54
|
-
"@ultimat3/mcp": "
|
|
55
|
-
"@ultimat3/money": "
|
|
56
|
-
"@ultimat3/
|
|
57
|
-
"@ultimat3/
|
|
58
|
-
"@ultimat3/
|
|
59
|
-
"@ultimat3/
|
|
60
|
-
"@ultimat3/
|
|
61
|
-
"@ultimat3/
|
|
62
|
-
"@ultimat3/
|
|
63
|
-
"@ultimat3/
|
|
64
|
-
"@ultimat3/
|
|
65
|
-
"@ultimat3/
|
|
66
|
-
"@ultimat3/
|
|
40
|
+
"@ultimat3/action": "13.0.0",
|
|
41
|
+
"@ultimat3/admin": "13.0.0",
|
|
42
|
+
"@ultimat3/ai": "13.0.0",
|
|
43
|
+
"@ultimat3/auth": "13.0.0",
|
|
44
|
+
"@ultimat3/cache": "13.0.0",
|
|
45
|
+
"@ultimat3/core": "13.0.0",
|
|
46
|
+
"@ultimat3/db": "13.0.0",
|
|
47
|
+
"@ultimat3/entity": "13.0.0",
|
|
48
|
+
"@ultimat3/flags": "13.0.0",
|
|
49
|
+
"@ultimat3/http": "13.0.0",
|
|
50
|
+
"@ultimat3/i18n": "13.0.0",
|
|
51
|
+
"@ultimat3/jobs": "13.0.0",
|
|
52
|
+
"@ultimat3/mail": "13.0.0",
|
|
53
|
+
"@ultimat3/manifest": "13.0.0",
|
|
54
|
+
"@ultimat3/mcp": "13.0.0",
|
|
55
|
+
"@ultimat3/money": "13.0.0",
|
|
56
|
+
"@ultimat3/notify": "13.0.0",
|
|
57
|
+
"@ultimat3/policy": "13.0.0",
|
|
58
|
+
"@ultimat3/pwa": "13.0.0",
|
|
59
|
+
"@ultimat3/query": "13.0.0",
|
|
60
|
+
"@ultimat3/realtime": "13.0.0",
|
|
61
|
+
"@ultimat3/render": "13.0.0",
|
|
62
|
+
"@ultimat3/schema": "13.0.0",
|
|
63
|
+
"@ultimat3/scraping": "13.0.0",
|
|
64
|
+
"@ultimat3/seo": "13.0.0",
|
|
65
|
+
"@ultimat3/storage": "13.0.0",
|
|
66
|
+
"@ultimat3/testing": "13.0.0",
|
|
67
|
+
"@ultimat3/time": "13.0.0",
|
|
67
68
|
"babel-preset-solid": "^1.9.15"
|
|
68
69
|
}
|
|
69
70
|
}
|
package/src/dev-queue.ts
CHANGED
|
@@ -8,11 +8,8 @@ import {
|
|
|
8
8
|
type PostgresIdempotencyStore,
|
|
9
9
|
postgresIdempotencyStore,
|
|
10
10
|
resetIdempotency,
|
|
11
|
-
SQL_AUDIT_TABLE,
|
|
12
|
-
SQL_IDEMPOTENCY_TABLE,
|
|
13
11
|
setIdempotencyStore,
|
|
14
12
|
} from '@ultimat3/action';
|
|
15
|
-
import { SQL_AUTH_LIMIT_TABLES } from '@ultimat3/auth';
|
|
16
13
|
import type { DbClient, PgliteClient, PostgresClient, SqlFragment } from '@ultimat3/db';
|
|
17
14
|
import {
|
|
18
15
|
createPgliteClient,
|
|
@@ -23,7 +20,6 @@ import {
|
|
|
23
20
|
setDbClient,
|
|
24
21
|
} from '@ultimat3/db';
|
|
25
22
|
import type { Tx } from '@ultimat3/entity';
|
|
26
|
-
import { SQL_RATE_LIMIT_TABLE } from '@ultimat3/http';
|
|
27
23
|
import type { EventBus, JobDriver, OutboxStore, PgExecutor } from '@ultimat3/jobs';
|
|
28
24
|
import {
|
|
29
25
|
createJobsFacade,
|
|
@@ -32,13 +28,13 @@ import {
|
|
|
32
28
|
createPgOutboxStore,
|
|
33
29
|
resetJobDriver,
|
|
34
30
|
resetJobsFacade,
|
|
35
|
-
SQL_JOBS_TABLE,
|
|
36
31
|
setEventBus,
|
|
37
32
|
setJobDriver,
|
|
38
33
|
setJobsFacade,
|
|
39
34
|
} from '@ultimat3/jobs';
|
|
40
35
|
import { attachReplica, type ReplicaEnv, replicaUrlFor } from './dev-replica';
|
|
41
36
|
import type { DevServices } from './dev-services';
|
|
37
|
+
import { applyFrameworkSchema } from './framework-schema';
|
|
42
38
|
import type { RuntimeOverrides } from './runtime-overrides';
|
|
43
39
|
|
|
44
40
|
/** Both embedded and external clients boot lazily and close explicitly. */
|
|
@@ -113,40 +109,18 @@ export function pgExecutorFor(client: DbClient): PgExecutor {
|
|
|
113
109
|
/**
|
|
114
110
|
* Every table this process's framework packages own, applied before anything reads one.
|
|
115
111
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
112
|
+
* The LIST is `FRAMEWORK_SCHEMA` and lives in `framework-schema.ts`, not here: this function is on
|
|
113
|
+
* every boot path the framework has — `x dev`, each served role, `x jobs`, `x db backfill`,
|
|
114
|
+
* `x mcp serve` and `ROLE=migrate` all reach it through `startQueue` — so the list it reads is the
|
|
115
|
+
* one place a framework table can be forgotten, and it is worth being a table somebody can read
|
|
116
|
+
* rather than an array literal inside a boot function.
|
|
119
117
|
*
|
|
120
|
-
*
|
|
121
|
-
* `@ultimat3/
|
|
122
|
-
*
|
|
123
|
-
* — the same reason `SQL_JOBS_TABLE` is applied here. Each one absent is the same failure at a
|
|
124
|
-
* different door: a retried `POST /api/payments/charge` charges the card twice, and the FIRST
|
|
125
|
-
* request a `rateLimitStore` deployment serves dies on a missing `x_rate_limit` relation. The
|
|
126
|
-
* table is installed whether or not this boot passes `runtime.rateLimitStore` — `create table if
|
|
127
|
-
* not exists` on an unused table costs one round trip at boot, and a store installed later must
|
|
128
|
-
* not be the thing that discovers the schema was never applied. The auth pair is the strongest
|
|
129
|
-
* case for that rule: `defineAuth` builds its limiter when the APP's modules import, which is
|
|
130
|
-
* after this, so the first failed sign-in would otherwise be what discovers the missing relation.
|
|
118
|
+
* Each package's DDL is here and not in `@ultimat3/action`, `@ultimat3/http`, `@ultimat3/auth` or
|
|
119
|
+
* `@ultimat3/notify` because a package that holds no database dependency cannot apply its own
|
|
120
|
+
* schema — the same reason `SQL_JOBS_TABLE` is applied by the boot.
|
|
131
121
|
*/
|
|
132
122
|
async function applySchema(client: DevDbClient): Promise<void> {
|
|
133
|
-
|
|
134
|
-
SQL_JOBS_TABLE,
|
|
135
|
-
SQL_IDEMPOTENCY_TABLE,
|
|
136
|
-
// The DDL only, and deliberately NO `setAuditSink` beside `setIdempotencyStore` below: there
|
|
137
|
-
// is no default audit sink on purpose, so `X_AUDIT_SINK_MISSING` keeps firing at boot for an
|
|
138
|
-
// app that declares `audit: true` and installs none. Applying the table without installing a
|
|
139
|
-
// sink is the same call `SQL_RATE_LIMIT_TABLE` already makes — one round trip at boot on a
|
|
140
|
-
// possibly-unused table, against `postgresAuditSink` failing its first write with
|
|
141
|
-
// `relation "x_audit" does not exist`.
|
|
142
|
-
SQL_AUDIT_TABLE,
|
|
143
|
-
SQL_RATE_LIMIT_TABLE,
|
|
144
|
-
SQL_AUTH_LIMIT_TABLES,
|
|
145
|
-
]) {
|
|
146
|
-
for (const statement of ddl.split(';')) {
|
|
147
|
-
if (statement.trim().length > 0) await client.execute(raw(statement));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
123
|
+
await applyFrameworkSchema((statement) => client.execute(raw(statement)));
|
|
150
124
|
}
|
|
151
125
|
|
|
152
126
|
/**
|
package/src/error-catalog.ts
CHANGED
package/src/error-codes.ts
CHANGED
|
@@ -49,6 +49,10 @@ export const CLI_OWNED_ERROR_CODES = [
|
|
|
49
49
|
// reading — the hole that let `scripts/` hold seven type errors under a green gate.
|
|
50
50
|
'X_PACKAGE_UNREFERENCED',
|
|
51
51
|
'X_RELEASE_VERSION_SKEW',
|
|
52
|
+
// The framework's own tables, refused by name rather than by the driver's rejection: a raw
|
|
53
|
+
// `permission denied for schema public` says which statement failed and neither which framework
|
|
54
|
+
// table it was creating nor which package wants it.
|
|
55
|
+
'X_FRAMEWORK_SCHEMA_FAILED',
|
|
52
56
|
'X_STORAGE_UNWRITABLE',
|
|
53
57
|
'X_STORAGE_SECRET_DEV',
|
|
54
58
|
'X_MANIFEST_STALE',
|
|
@@ -198,6 +202,7 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
|
|
|
198
202
|
X_ERROR_CODE_UNDOCUMENTED: 'a shipped error code has no row in the error reference',
|
|
199
203
|
X_ERROR_CODE_UNREGISTERED: 'the error reference documents a code no package registers',
|
|
200
204
|
X_ERROR_CODE_UNRESOLVED: 'an error code is written as a name this repository cannot resolve',
|
|
205
|
+
X_FRAMEWORK_SCHEMA_FAILED: 'a framework table could not be created at boot',
|
|
201
206
|
X_STORAGE_UNWRITABLE: 'the storage disk this process needs cannot be written to',
|
|
202
207
|
X_STORAGE_SECRET_DEV: 'upload grants would be signed with the shipped development key',
|
|
203
208
|
X_CLI_UNEXPECTED: 'the CLI itself failed',
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// The framework's OWN tables, as one table: which package declares each, which relations it
|
|
2
|
+
// creates, and the DDL. One list, read by the one applier, so "installed in dev and not in
|
|
3
|
+
// production" is not a state this framework can be in — `startQueue` is on every boot path there
|
|
4
|
+
// is, `ROLE=migrate` included.
|
|
5
|
+
//
|
|
6
|
+
// A framework table an app has to install by hand is a table that will be missing in production on
|
|
7
|
+
// the one code path that needs it, and it surfaces as a Postgres `42P01` from inside a worker.
|
|
8
|
+
|
|
9
|
+
import { SQL_AUDIT_TABLE, SQL_IDEMPOTENCY_TABLE } from '@ultimat3/action';
|
|
10
|
+
import { AUTH_TABLE_NAMES, AUTH_TABLES, SQL_AUTH_LIMIT_TABLES } from '@ultimat3/auth';
|
|
11
|
+
import { SQL_RATE_LIMIT_TABLE } from '@ultimat3/http';
|
|
12
|
+
import { SQL_JOBS_TABLE } from '@ultimat3/jobs';
|
|
13
|
+
import { SQL_NOTIFY_DELIVERIES_TABLE, SQL_NOTIFY_INBOX_TABLE } from '@ultimat3/notify';
|
|
14
|
+
import { FrameworkSchemaFailedError } from './schema-errors';
|
|
15
|
+
|
|
16
|
+
export interface FrameworkSchema {
|
|
17
|
+
/** The package whose source declares the DDL — where to look when a column is wrong. */
|
|
18
|
+
readonly pkg: string;
|
|
19
|
+
/**
|
|
20
|
+
* Every relation this entry creates. Read by the refusal below, so an operator learns which
|
|
21
|
+
* tables were being installed rather than only which statement failed — and pinned against the
|
|
22
|
+
* DDL text by `framework-schema.test.ts`, so a row cannot claim a table its SQL never creates.
|
|
23
|
+
*/
|
|
24
|
+
readonly tables: readonly string[];
|
|
25
|
+
/** One or more statements each; `;` separates. */
|
|
26
|
+
readonly ddl: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Applied unconditionally, whether or not this boot installs a store behind it.
|
|
31
|
+
*
|
|
32
|
+
* `create table if not exists` on an unused table costs one round trip at boot. The alternative
|
|
33
|
+
* costs a request: a store installed later must never be the thing that discovers the schema was
|
|
34
|
+
* never applied, and several of these are installed AFTER this runs — `defineAuth` builds its
|
|
35
|
+
* limiter when the app's modules import, and `setNotifyStores` is an app's boot line.
|
|
36
|
+
*
|
|
37
|
+
* Ordered so a foreign key never precedes its target. Only `AUTH_TABLES` has any, and they are
|
|
38
|
+
* internal to that entry, which is why it ships as an ordered list rather than one string.
|
|
39
|
+
*/
|
|
40
|
+
export const FRAMEWORK_SCHEMA: readonly FrameworkSchema[] = Object.freeze([
|
|
41
|
+
Object.freeze({
|
|
42
|
+
pkg: '@ultimat3/jobs',
|
|
43
|
+
tables: Object.freeze([
|
|
44
|
+
'x_jobs',
|
|
45
|
+
'x_job_steps',
|
|
46
|
+
'x_backfills',
|
|
47
|
+
'x_outbox',
|
|
48
|
+
'x_scheduler_state',
|
|
49
|
+
'x_scheduler_leader',
|
|
50
|
+
'x_job_leases',
|
|
51
|
+
'x_job_events',
|
|
52
|
+
]),
|
|
53
|
+
ddl: Object.freeze([SQL_JOBS_TABLE]),
|
|
54
|
+
}),
|
|
55
|
+
Object.freeze({
|
|
56
|
+
pkg: '@ultimat3/action',
|
|
57
|
+
tables: Object.freeze(['x_idempotency']),
|
|
58
|
+
ddl: Object.freeze([SQL_IDEMPOTENCY_TABLE]),
|
|
59
|
+
}),
|
|
60
|
+
// The DDL only, and deliberately NO `setAuditSink`: there is no default audit sink on purpose,
|
|
61
|
+
// so `X_AUDIT_SINK_MISSING` keeps firing at boot for an app that declares `audit: true` and
|
|
62
|
+
// installs none.
|
|
63
|
+
Object.freeze({
|
|
64
|
+
pkg: '@ultimat3/action',
|
|
65
|
+
tables: Object.freeze(['x_audit']),
|
|
66
|
+
ddl: Object.freeze([SQL_AUDIT_TABLE]),
|
|
67
|
+
}),
|
|
68
|
+
Object.freeze({
|
|
69
|
+
pkg: '@ultimat3/http',
|
|
70
|
+
tables: Object.freeze(['x_rate_limit']),
|
|
71
|
+
ddl: Object.freeze([SQL_RATE_LIMIT_TABLE]),
|
|
72
|
+
}),
|
|
73
|
+
Object.freeze({
|
|
74
|
+
pkg: '@ultimat3/auth',
|
|
75
|
+
tables: Object.freeze(['x_auth_failures', 'x_auth_lockouts']),
|
|
76
|
+
ddl: Object.freeze([SQL_AUTH_LIMIT_TABLES]),
|
|
77
|
+
}),
|
|
78
|
+
/**
|
|
79
|
+
* The five tables `BuiltinAdapter` reads, and the oldest hole in this list.
|
|
80
|
+
*
|
|
81
|
+
* `packages/auth/src/tables.ts` exports them "so an app can paste them into a migration", and
|
|
82
|
+
* nothing in the framework has ever applied them — while `x db gen` diffs `describeEntities()`
|
|
83
|
+
* and these are not `entity()` declarations, so neither half was a file anybody could
|
|
84
|
+
* hand-write. `examples/dummy/CLAUDE.md` records the consequence in its own words: nobody can
|
|
85
|
+
* hold a session in the reference app. Applied here on exactly the rule the rate-limit and audit
|
|
86
|
+
* rows already follow.
|
|
87
|
+
*
|
|
88
|
+
* `AUTH_TABLE_NAMES` rather than five literals: @ultimat3/auth already publishes the list, and a
|
|
89
|
+
* second copy is a second thing to keep right when a table is added.
|
|
90
|
+
*/
|
|
91
|
+
Object.freeze({
|
|
92
|
+
pkg: '@ultimat3/auth',
|
|
93
|
+
tables: AUTH_TABLE_NAMES,
|
|
94
|
+
ddl: AUTH_TABLES,
|
|
95
|
+
}),
|
|
96
|
+
/**
|
|
97
|
+
* The delivery ledger is what stops a replayed notifier job sending twice, and it is the entry
|
|
98
|
+
* whose absence is least visible: without the table the ledger's first `claim` raises `42P01`
|
|
99
|
+
* from inside a worker, which reads as a dead-lettered notification rather than as a missing
|
|
100
|
+
* schema. Installed whether or not this boot calls `setNotifyStores`, for the same reason as
|
|
101
|
+
* every row above it — that call is an APP's boot line and runs after this one.
|
|
102
|
+
*/
|
|
103
|
+
Object.freeze({
|
|
104
|
+
pkg: '@ultimat3/notify',
|
|
105
|
+
tables: Object.freeze(['x_notify_deliveries']),
|
|
106
|
+
ddl: Object.freeze([SQL_NOTIFY_DELIVERIES_TABLE]),
|
|
107
|
+
}),
|
|
108
|
+
Object.freeze({
|
|
109
|
+
pkg: '@ultimat3/notify',
|
|
110
|
+
tables: Object.freeze(['x_notify_inbox']),
|
|
111
|
+
ddl: Object.freeze([SQL_NOTIFY_INBOX_TABLE]),
|
|
112
|
+
}),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/** Every relation this boot creates, flattened. */
|
|
116
|
+
export const frameworkTableNames = (): readonly string[] =>
|
|
117
|
+
FRAMEWORK_SCHEMA.flatMap((entry) => [...entry.tables]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* PGlite speaks the extended protocol, which carries one statement per round trip, so the DDL is
|
|
121
|
+
* applied statement by statement. Safe to split on `;`: every constant is fixed, with no semicolon
|
|
122
|
+
* inside a literal, and each package's own SQL test is where that stays true.
|
|
123
|
+
*/
|
|
124
|
+
export const schemaStatements = (ddl: readonly string[]): readonly string[] =>
|
|
125
|
+
ddl.flatMap((text) => text.split(';')).filter((statement) => statement.trim().length > 0);
|
|
126
|
+
|
|
127
|
+
/** One statement, executed. The caller owns the connection; this file owns no database import. */
|
|
128
|
+
export type SchemaExecutor = (statement: string) => Promise<unknown>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Apply every entry, in order, and answer what was created.
|
|
132
|
+
*
|
|
133
|
+
* The refusal is the point of the `pkg`/`tables` columns: a raw `permission denied for schema
|
|
134
|
+
* public` names neither the framework table it was creating nor the package that wants it, and a
|
|
135
|
+
* boot failure is read by an operator who has no source tree open.
|
|
136
|
+
*/
|
|
137
|
+
export async function applyFrameworkSchema(execute: SchemaExecutor): Promise<readonly string[]> {
|
|
138
|
+
for (const entry of FRAMEWORK_SCHEMA) {
|
|
139
|
+
for (const statement of schemaStatements(entry.ddl)) {
|
|
140
|
+
try {
|
|
141
|
+
await execute(statement);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new FrameworkSchemaFailedError({
|
|
144
|
+
pkg: entry.pkg,
|
|
145
|
+
tables: entry.tables,
|
|
146
|
+
cause: error,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return frameworkTableNames();
|
|
152
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -196,6 +196,15 @@ export type { FixHelper, FixScan } from './fix-scan';
|
|
|
196
196
|
export { scanFixes, scanFixHelpers, scanFixSites } from './fix-scan';
|
|
197
197
|
export type { DeclaredFlag } from './flag-reads';
|
|
198
198
|
export { checkFlagReads, declaredFlags, readsFlag } from './flag-reads';
|
|
199
|
+
export type { FrameworkSchema, SchemaExecutor } from './framework-schema';
|
|
200
|
+
// The framework's own tables, as data. Exported so `scripts/` can read the applier's list without
|
|
201
|
+
// re-deriving it — the shape a ratchet over declared-but-never-applied DDL needs.
|
|
202
|
+
export {
|
|
203
|
+
applyFrameworkSchema,
|
|
204
|
+
FRAMEWORK_SCHEMA,
|
|
205
|
+
frameworkTableNames,
|
|
206
|
+
schemaStatements,
|
|
207
|
+
} from './framework-schema';
|
|
199
208
|
export type { Guard } from './guards';
|
|
200
209
|
export { findingProblem, GUARD_DIR, guardFindings, guardPaths } from './guards';
|
|
201
210
|
// The island bundler, and only its entry point. An island is the one module Ultimate ships to a
|
|
@@ -239,6 +248,7 @@ export { flagBool, flagList, flagString, GLOBAL_FLAGS, nearest, parseArgs } from
|
|
|
239
248
|
export type { PrerenderedPage, PrerenderOptions, PrerenderReport } from './prerender';
|
|
240
249
|
export { DEFAULT_ORIGIN, isPrerenderable, prerenderSite } from './prerender';
|
|
241
250
|
export { COMMANDS, cliVersion, commandFor, SPECS } from './registry';
|
|
251
|
+
export { FrameworkSchemaFailedError } from './schema-errors';
|
|
242
252
|
export type { MigratedApp, ServedApp, ServeOptions, StartedApp } from './serve';
|
|
243
253
|
export {
|
|
244
254
|
CONTAINER_BINDING,
|
package/src/mcp-errors.ts
CHANGED
|
@@ -96,6 +96,9 @@ const CLI_FIXES: Readonly<Record<CliErrorCode, string>> = {
|
|
|
96
96
|
X_RELEASE_VERSION_SKEW: 'bun run scripts/release.ts --bump patch --dry-run --json',
|
|
97
97
|
// Two real remedies and the command cannot know which one this deployment wants, so it names
|
|
98
98
|
// the one that inspects the binding rather than guessing between a volume and a bucket.
|
|
99
|
+
// `x db migrate --json` and not `x doctor`: this fires from inside `startQueue`, so the command
|
|
100
|
+
// that re-runs exactly the failing step is the migrate role, and it reports what it applied.
|
|
101
|
+
X_FRAMEWORK_SCHEMA_FAILED: 'x db migrate --json',
|
|
99
102
|
X_STORAGE_UNWRITABLE: 'x doctor --json',
|
|
100
103
|
X_STORAGE_SECRET_DEV: 'export STORAGE_SIGNING_SECRET="$(openssl rand -hex 32)"',
|
|
101
104
|
X_MANIFEST_STALE: 'x manifest --json',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// The one code raised while installing the framework's own tables. Apart from `errors.ts` for the
|
|
2
|
+
// reason `packages/jobs/src/backfill-errors.ts` is apart from that package's: one file, one job,
|
|
3
|
+
// and `errors.ts` is at 486 lines against the 500 the `filesize` step enforces. The code, its
|
|
4
|
+
// title and its registration stay in `error-codes.ts`, where every other CLI code lives.
|
|
5
|
+
|
|
6
|
+
import { renderThrowable, UltimateError } from '@ultimat3/core';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A statement in `FRAMEWORK_SCHEMA` did not apply. Raised in place of the driver's own rejection,
|
|
10
|
+
* which names neither the framework table being created nor the package that wants it — and a boot
|
|
11
|
+
* failure is read by an operator with no source tree open.
|
|
12
|
+
*
|
|
13
|
+
* `renderThrowable` and never `${cause}`: a `catch` binding is annotated by nobody, and a pool
|
|
14
|
+
* rejection is routinely an object whose `toString` throws.
|
|
15
|
+
*/
|
|
16
|
+
export class FrameworkSchemaFailedError extends UltimateError {
|
|
17
|
+
constructor(input: { pkg: string; tables: readonly string[]; cause: unknown }) {
|
|
18
|
+
super({
|
|
19
|
+
code: 'X_FRAMEWORK_SCHEMA_FAILED',
|
|
20
|
+
cause: `${input.pkg} could not create ${input.tables.join(', ')}: ${renderThrowable(input.cause)}`,
|
|
21
|
+
// An EDIT plus the command that CONFIRMS it, which is the house shape for a repair the gate
|
|
22
|
+
// cannot perform: the two real causes are a role without `create`, and a relation of that
|
|
23
|
+
// name already present with an incompatible shape.
|
|
24
|
+
fix: `grant create on schema public to the role in DATABASE_URL, then re-run: x db migrate`,
|
|
25
|
+
meta: { pkg: input.pkg, tables: [...input.tables] },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|