@prisma-next/cli 0.16.0-dev.30 → 0.16.0-dev.32
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/dist/cli.mjs +13 -13
- package/dist/{client-CMusjGV5.mjs → client-B1UjQc_x.mjs} +30 -37
- package/dist/client-B1UjQc_x.mjs.map +1 -0
- package/dist/{command-helpers-CVn3U9Uz.mjs → command-helpers-D-jBl0Qt.mjs} +5 -5
- package/dist/command-helpers-D-jBl0Qt.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +6 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +6 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +7 -6
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +6 -6
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.mjs +4 -4
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-C-l4-JEY.mjs → contract-at-errors-DvZ_DzJK.mjs} +2 -2
- package/dist/{contract-at-errors-C-l4-JEY.mjs.map → contract-at-errors-DvZ_DzJK.mjs.map} +1 -1
- package/dist/{contract-emit-PaY3qRdN.mjs → contract-emit-BT46Mu8Q.mjs} +3 -3
- package/dist/{contract-emit-PaY3qRdN.mjs.map → contract-emit-BT46Mu8Q.mjs.map} +1 -1
- package/dist/{contract-emit-CfIbTnsR.mjs → contract-emit-hVdlaF2U.mjs} +3 -3
- package/dist/{contract-emit-CfIbTnsR.mjs.map → contract-emit-hVdlaF2U.mjs.map} +1 -1
- package/dist/{contract-infer-DCju2fTk.mjs → contract-infer-DpqTQECL.mjs} +3 -3
- package/dist/{contract-infer-DCju2fTk.mjs.map → contract-infer-DpqTQECL.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-B6E9LGa6.mjs → contract-space-aggregate-loader-CNd-ZB6C.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-B6E9LGa6.mjs.map → contract-space-aggregate-loader-CNd-ZB6C.mjs.map} +1 -1
- package/dist/{db-verify-B_dKu6Yp.mjs → db-verify-FEN6k1m7.mjs} +9 -7
- package/dist/db-verify-FEN6k1m7.mjs.map +1 -0
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts +2 -1
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +61 -21
- package/dist/exports/index.mjs.map +1 -1
- package/dist/{format-cCyDfmRE.mjs → format-DI5qVr22.mjs} +3 -3
- package/dist/{format-cCyDfmRE.mjs.map → format-DI5qVr22.mjs.map} +1 -1
- package/dist/{framework-components-DUN-TYDg.mjs → framework-components-C4XACYNB.mjs} +2 -2
- package/dist/{framework-components-DUN-TYDg.mjs.map → framework-components-C4XACYNB.mjs.map} +1 -1
- package/dist/{init-C9U3ffHH.mjs → init-ClrRkbwd.mjs} +25 -23
- package/dist/init-ClrRkbwd.mjs.map +1 -0
- package/dist/{inspect-live-schema-DHXE7skX.mjs → inspect-live-schema-C0Rgdtw_.mjs} +3 -3
- package/dist/{inspect-live-schema-DHXE7skX.mjs.map → inspect-live-schema-C0Rgdtw_.mjs.map} +1 -1
- package/dist/{migration-check-W5XEx4d-.mjs → migration-check-zEkHpPDT.mjs} +4 -4
- package/dist/{migration-check-W5XEx4d-.mjs.map → migration-check-zEkHpPDT.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-hI5cnoGe.mjs → migration-command-scaffold-CkNsp9g1.mjs} +3 -3
- package/dist/{migration-command-scaffold-hI5cnoGe.mjs.map → migration-command-scaffold-CkNsp9g1.mjs.map} +1 -1
- package/dist/{migration-graph-command-render-DgQg5App.mjs → migration-graph-command-render-DEwFpx-w.mjs} +3 -2
- package/dist/migration-graph-command-render-DEwFpx-w.mjs.map +1 -0
- package/dist/{migration-list-ClkEaoL8.mjs → migration-list-BkafFVXS.mjs} +4 -4
- package/dist/{migration-list-ClkEaoL8.mjs.map → migration-list-BkafFVXS.mjs.map} +1 -1
- package/dist/{migration-log-CjafbZ2Z.mjs → migration-log-BxhAtbhA.mjs} +4 -4
- package/dist/{migration-log-CjafbZ2Z.mjs.map → migration-log-BxhAtbhA.mjs.map} +1 -1
- package/dist/{migration-path-target-C0TQuV5n.mjs → migration-path-target-B5HkjSTO.mjs} +2 -2
- package/dist/{migration-path-target-C0TQuV5n.mjs.map → migration-path-target-B5HkjSTO.mjs.map} +1 -1
- package/dist/{migration-plan-Pc4hCF1H.mjs → migration-plan-Bk2pbPQH.mjs} +5 -5
- package/dist/{migration-plan-Pc4hCF1H.mjs.map → migration-plan-Bk2pbPQH.mjs.map} +1 -1
- package/dist/{migration-status-CqAdRzs8.mjs → migration-status-BzlARseq.mjs} +6 -6
- package/dist/{migration-status-CqAdRzs8.mjs.map → migration-status-BzlARseq.mjs.map} +1 -1
- package/dist/{telemetry-BEyNMfco.mjs → telemetry-BbniBUbP.mjs} +2 -2
- package/dist/{telemetry-BEyNMfco.mjs.map → telemetry-BbniBUbP.mjs.map} +1 -1
- package/dist/{verify-C4RazDE1.mjs → verify-BSaNeaSL.mjs} +2 -2
- package/dist/{verify-C4RazDE1.mjs.map → verify-BSaNeaSL.mjs.map} +1 -1
- package/package.json +21 -21
- package/src/commands/db-init.ts +4 -3
- package/src/commands/db-sign.ts +2 -2
- package/src/commands/db-update.ts +4 -3
- package/src/commands/db-verify.ts +3 -3
- package/src/commands/init/probe-db.ts +9 -2
- package/src/commands/init/templates/render.ts +2 -1
- package/src/control-api/client.ts +36 -17
- package/src/control-api/operations/db-run.ts +2 -1
- package/src/control-api/operations/migrate.ts +3 -2
- package/src/control-api/operations/run-migration.ts +2 -1
- package/src/load-ts-contract.ts +81 -16
- package/src/utils/combine-verify-results.ts +2 -1
- package/src/utils/command-helpers.ts +15 -5
- package/src/utils/formatters/migration-graph-grid-layout.ts +2 -1
- package/dist/client-CMusjGV5.mjs.map +0 -1
- package/dist/command-helpers-CVn3U9Uz.mjs.map +0 -1
- package/dist/db-verify-B_dKu6Yp.mjs.map +0 -1
- package/dist/init-C9U3ffHH.mjs.map +0 -1
- package/dist/migration-graph-command-render-DgQg5App.mjs.map +0 -1
- package/src/control-api/errors.ts +0 -9
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
LedgerEntryRecord,
|
|
5
5
|
} from '@prisma-next/contract/types';
|
|
6
6
|
import { emit as emitContractArtifacts } from '@prisma-next/emitter';
|
|
7
|
+
import { CliStructuredError } from '@prisma-next/errors/control';
|
|
7
8
|
import type { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';
|
|
8
9
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
9
10
|
import type {
|
|
@@ -27,13 +28,13 @@ import {
|
|
|
27
28
|
hasSchemaView,
|
|
28
29
|
} from '@prisma-next/framework-components/control';
|
|
29
30
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
30
|
-
|
|
31
31
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
32
33
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
34
|
+
import { structuredError } from '@prisma-next/utils/structured-error';
|
|
33
35
|
|
|
34
36
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
35
37
|
import { enrichContract } from './contract-enrichment';
|
|
36
|
-
import { ContractValidationError } from './errors';
|
|
37
38
|
import { executeDbInit } from './operations/db-init';
|
|
38
39
|
import { executeDbUpdate } from './operations/db-update';
|
|
39
40
|
import { type ExecuteDbVerifyResult, executeDbVerify } from './operations/db-verify';
|
|
@@ -128,20 +129,25 @@ class ControlClientImpl implements ControlClient {
|
|
|
128
129
|
this.init();
|
|
129
130
|
|
|
130
131
|
if (this.driver) {
|
|
131
|
-
throw new
|
|
132
|
+
throw new CliStructuredError(
|
|
133
|
+
'DRIVER.ALREADY_CONNECTED',
|
|
134
|
+
'Already connected. Call close() before reconnecting.',
|
|
135
|
+
);
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
// Resolve connection: argument > default from options
|
|
135
139
|
const resolvedConnection = connection ?? this.defaultConnection;
|
|
136
140
|
if (resolvedConnection === undefined) {
|
|
137
|
-
throw new
|
|
141
|
+
throw new CliStructuredError(
|
|
142
|
+
'CONFIG.DB_CONNECTION_REQUIRED',
|
|
138
143
|
'No connection provided. Pass a connection to connect() or provide a default connection when creating the client.',
|
|
139
144
|
);
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
// Check for driver descriptor
|
|
143
148
|
if (!this.stack?.driver) {
|
|
144
|
-
throw new
|
|
149
|
+
throw new CliStructuredError(
|
|
150
|
+
'CONFIG.DRIVER_REQUIRED',
|
|
145
151
|
'Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.',
|
|
146
152
|
);
|
|
147
153
|
}
|
|
@@ -167,7 +173,8 @@ class ControlClientImpl implements ControlClient {
|
|
|
167
173
|
private buildControlAdapter(): ControlAdapterInstance<string, string> {
|
|
168
174
|
this.init();
|
|
169
175
|
if (!this.stack?.adapter) {
|
|
170
|
-
throw new
|
|
176
|
+
throw new CliStructuredError(
|
|
177
|
+
'MIGRATION.TARGET_UNSUPPORTED',
|
|
171
178
|
`Target "${this.options.target.targetId}" requires an adapter for migrations`,
|
|
172
179
|
);
|
|
173
180
|
}
|
|
@@ -188,7 +195,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
if (!this.driver || !this.familyInstance || !this.frameworkComponents) {
|
|
191
|
-
throw new
|
|
198
|
+
throw new CliStructuredError(
|
|
199
|
+
'DRIVER.NOT_CONNECTED',
|
|
200
|
+
'Not connected. Call connect(connection) first.',
|
|
201
|
+
);
|
|
192
202
|
}
|
|
193
203
|
return {
|
|
194
204
|
driver: this.driver,
|
|
@@ -229,7 +239,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
229
239
|
contract = familyInstance.deserializeContract(options.contract);
|
|
230
240
|
} catch (error) {
|
|
231
241
|
const message = error instanceof Error ? error.message : String(error);
|
|
232
|
-
throw
|
|
242
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
// Emit verify span
|
|
@@ -282,7 +292,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
282
292
|
contract = familyInstance.deserializeContract(options.contract);
|
|
283
293
|
} catch (error) {
|
|
284
294
|
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
-
throw
|
|
295
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
286
296
|
}
|
|
287
297
|
|
|
288
298
|
// Emit schemaVerify span
|
|
@@ -336,7 +346,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
336
346
|
contract = familyInstance.deserializeContract(options.contract);
|
|
337
347
|
} catch (error) {
|
|
338
348
|
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
-
throw
|
|
349
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
340
350
|
}
|
|
341
351
|
|
|
342
352
|
// Emit sign span
|
|
@@ -381,7 +391,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
381
391
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
382
392
|
|
|
383
393
|
if (!hasMigrations(this.options.target)) {
|
|
384
|
-
throw new
|
|
394
|
+
throw new CliStructuredError(
|
|
395
|
+
'MIGRATION.TARGET_UNSUPPORTED',
|
|
396
|
+
`Target "${this.options.target.targetId}" does not support migrations`,
|
|
397
|
+
);
|
|
385
398
|
}
|
|
386
399
|
|
|
387
400
|
const adapter = this.buildControlAdapter();
|
|
@@ -391,7 +404,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
391
404
|
contract = familyInstance.deserializeContract(options.contract);
|
|
392
405
|
} catch (error) {
|
|
393
406
|
const message = error instanceof Error ? error.message : String(error);
|
|
394
|
-
throw
|
|
407
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
395
408
|
}
|
|
396
409
|
|
|
397
410
|
return executeDbInit({
|
|
@@ -415,7 +428,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
415
428
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
416
429
|
|
|
417
430
|
if (!hasMigrations(this.options.target)) {
|
|
418
|
-
throw new
|
|
431
|
+
throw new CliStructuredError(
|
|
432
|
+
'MIGRATION.TARGET_UNSUPPORTED',
|
|
433
|
+
`Target "${this.options.target.targetId}" does not support migrations`,
|
|
434
|
+
);
|
|
419
435
|
}
|
|
420
436
|
|
|
421
437
|
const adapter = this.buildControlAdapter();
|
|
@@ -425,7 +441,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
425
441
|
contract = familyInstance.deserializeContract(options.contract);
|
|
426
442
|
} catch (error) {
|
|
427
443
|
const message = error instanceof Error ? error.message : String(error);
|
|
428
|
-
throw
|
|
444
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
429
445
|
}
|
|
430
446
|
|
|
431
447
|
return executeDbUpdate({
|
|
@@ -490,7 +506,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
490
506
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
491
507
|
|
|
492
508
|
if (!hasMigrations(this.options.target)) {
|
|
493
|
-
throw new
|
|
509
|
+
throw new CliStructuredError(
|
|
510
|
+
'MIGRATION.TARGET_UNSUPPORTED',
|
|
511
|
+
`Target "${this.options.target.targetId}" does not support migrations`,
|
|
512
|
+
);
|
|
494
513
|
}
|
|
495
514
|
|
|
496
515
|
let contract: Contract;
|
|
@@ -498,7 +517,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
498
517
|
contract = familyInstance.deserializeContract(options.contract);
|
|
499
518
|
} catch (error) {
|
|
500
519
|
const message = error instanceof Error ? error.message : String(error);
|
|
501
|
-
throw
|
|
520
|
+
throw structuredError('CONTRACT.VALIDATION_FAILED', message, { cause: error });
|
|
502
521
|
}
|
|
503
522
|
|
|
504
523
|
return executeMigrate({
|
|
@@ -593,7 +612,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
593
612
|
this.init();
|
|
594
613
|
|
|
595
614
|
if (!this.familyInstance) {
|
|
596
|
-
throw new
|
|
615
|
+
throw new InternalError('Family instance was not initialized. This is a bug.');
|
|
597
616
|
}
|
|
598
617
|
|
|
599
618
|
let contractRaw: unknown;
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from '@prisma-next/migration-tools/aggregate';
|
|
25
25
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
26
26
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
27
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
27
28
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
28
29
|
import { CliStructuredError } from '../../utils/cli-errors';
|
|
29
30
|
import {
|
|
@@ -200,7 +201,7 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
200
201
|
// `destination` is the storage hash users see in CLI output.
|
|
201
202
|
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
|
|
202
203
|
if (!appResolution) {
|
|
203
|
-
throw new
|
|
204
|
+
throw new InternalError(
|
|
204
205
|
'Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.',
|
|
205
206
|
);
|
|
206
207
|
}
|
|
@@ -24,6 +24,7 @@ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
|
24
24
|
import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
|
|
25
25
|
import { findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
26
26
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
27
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
27
28
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
28
29
|
import {
|
|
29
30
|
type BuildAggregateInputs,
|
|
@@ -247,7 +248,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
247
248
|
.map((spaceId) => {
|
|
248
249
|
const entry = perSpacePlans.get(spaceId) ?? atHeadResolutions.get(spaceId);
|
|
249
250
|
if (entry === undefined) {
|
|
250
|
-
throw new
|
|
251
|
+
throw new InternalError(`Unreachable: missing per-space plan for "${spaceId}"`);
|
|
251
252
|
}
|
|
252
253
|
return { spaceId, entry };
|
|
253
254
|
});
|
|
@@ -306,7 +307,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
306
307
|
}
|
|
307
308
|
const entry = atHeadResolutions.get(spaceId);
|
|
308
309
|
if (entry === undefined) {
|
|
309
|
-
throw new
|
|
310
|
+
throw new InternalError(`Unreachable: missing per-space plan for "${spaceId}"`);
|
|
310
311
|
}
|
|
311
312
|
return { spaceId, entry };
|
|
312
313
|
});
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
} from '@prisma-next/framework-components/control';
|
|
13
13
|
import type { ContractSpaceAggregate, PerSpacePlan } from '@prisma-next/migration-tools/aggregate';
|
|
14
14
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
15
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
15
16
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
16
17
|
import type { OnControlProgress, PerSpaceExecutionEntry } from '../types';
|
|
17
18
|
|
|
@@ -243,7 +244,7 @@ export function collectOrdered(
|
|
|
243
244
|
return applyOrder.map((spaceId) => {
|
|
244
245
|
const entry = perSpace.get(spaceId);
|
|
245
246
|
if (!entry) {
|
|
246
|
-
throw new
|
|
247
|
+
throw new InternalError(`planner output missing per-space plan for "${spaceId}"`);
|
|
247
248
|
}
|
|
248
249
|
return { spaceId, entry };
|
|
249
250
|
});
|
package/src/load-ts-contract.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import type { Contract } from '@prisma-next/contract/types';
|
|
5
|
+
import { CliStructuredError } from '@prisma-next/errors/control';
|
|
6
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
5
7
|
import type { Plugin } from 'esbuild';
|
|
6
8
|
import { build } from 'esbuild';
|
|
7
9
|
import { join, resolve as resolvePath } from 'pathe';
|
|
@@ -10,6 +12,30 @@ export interface LoadTsContractOptions {
|
|
|
10
12
|
readonly allowlist?: ReadonlyArray<string>;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
type ContractSourceErrorCode =
|
|
16
|
+
| 'CONTRACT.EXPORT_INVALID'
|
|
17
|
+
| 'CONTRACT.SOURCE_LOAD_FAILED'
|
|
18
|
+
| 'CONTRACT.SOURCE_IMPORT_DISALLOWED';
|
|
19
|
+
|
|
20
|
+
function contractSourceError(
|
|
21
|
+
code: ContractSourceErrorCode,
|
|
22
|
+
message: string,
|
|
23
|
+
options?: {
|
|
24
|
+
readonly meta?: Record<string, unknown>;
|
|
25
|
+
readonly cause?: unknown;
|
|
26
|
+
},
|
|
27
|
+
): CliStructuredError {
|
|
28
|
+
const error = new CliStructuredError(
|
|
29
|
+
code,
|
|
30
|
+
message,
|
|
31
|
+
options?.meta !== undefined ? { meta: options.meta } : undefined,
|
|
32
|
+
);
|
|
33
|
+
if (options?.cause !== undefined) {
|
|
34
|
+
error.cause = options.cause;
|
|
35
|
+
}
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
const DEFAULT_ALLOWLIST = ['@prisma-next/*', 'node:crypto'];
|
|
14
40
|
|
|
15
41
|
function isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>): boolean {
|
|
@@ -31,7 +57,7 @@ function isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>):
|
|
|
31
57
|
return false;
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
function validatePurity(value: unknown): void {
|
|
60
|
+
function validatePurity(value: unknown, entryPath: string): void {
|
|
35
61
|
if (typeof value !== 'object' || value === null) {
|
|
36
62
|
return;
|
|
37
63
|
}
|
|
@@ -44,7 +70,11 @@ function validatePurity(value: unknown): void {
|
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
if (path.has(value)) {
|
|
47
|
-
throw
|
|
73
|
+
throw contractSourceError(
|
|
74
|
+
'CONTRACT.EXPORT_INVALID',
|
|
75
|
+
'Contract export contains circular references',
|
|
76
|
+
{ meta: { path: entryPath, reason: 'circular' } },
|
|
77
|
+
);
|
|
48
78
|
}
|
|
49
79
|
path.add(value);
|
|
50
80
|
|
|
@@ -52,10 +82,18 @@ function validatePurity(value: unknown): void {
|
|
|
52
82
|
for (const key in value) {
|
|
53
83
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
54
84
|
if (descriptor && (descriptor.get || descriptor.set)) {
|
|
55
|
-
throw
|
|
85
|
+
throw contractSourceError(
|
|
86
|
+
'CONTRACT.EXPORT_INVALID',
|
|
87
|
+
`Contract export contains getter/setter at key "${key}"`,
|
|
88
|
+
{ meta: { path: entryPath, reason: 'getter', key } },
|
|
89
|
+
);
|
|
56
90
|
}
|
|
57
91
|
if (descriptor && typeof descriptor.value === 'function') {
|
|
58
|
-
throw
|
|
92
|
+
throw contractSourceError(
|
|
93
|
+
'CONTRACT.EXPORT_INVALID',
|
|
94
|
+
`Contract export contains function at key "${key}"`,
|
|
95
|
+
{ meta: { path: entryPath, reason: 'function', key } },
|
|
96
|
+
);
|
|
59
97
|
}
|
|
60
98
|
check((value as Record<string, unknown>)[key]);
|
|
61
99
|
}
|
|
@@ -72,9 +110,17 @@ function validatePurity(value: unknown): void {
|
|
|
72
110
|
if (error.message.includes('getter') || error.message.includes('circular')) {
|
|
73
111
|
throw error;
|
|
74
112
|
}
|
|
75
|
-
throw
|
|
113
|
+
throw contractSourceError(
|
|
114
|
+
'CONTRACT.EXPORT_INVALID',
|
|
115
|
+
`Contract export is not JSON-serializable: ${error.message}`,
|
|
116
|
+
{ meta: { path: entryPath, reason: 'not-json-serializable' }, cause: error },
|
|
117
|
+
);
|
|
76
118
|
}
|
|
77
|
-
throw
|
|
119
|
+
throw contractSourceError(
|
|
120
|
+
'CONTRACT.EXPORT_INVALID',
|
|
121
|
+
'Contract export is not JSON-serializable',
|
|
122
|
+
{ meta: { path: entryPath, reason: 'not-json-serializable' } },
|
|
123
|
+
);
|
|
78
124
|
}
|
|
79
125
|
}
|
|
80
126
|
|
|
@@ -129,7 +175,8 @@ function createImportAllowlistPlugin(
|
|
|
129
175
|
* @param entryPath - Path to the TypeScript contract file
|
|
130
176
|
* @param options - Optional configuration (import allowlist)
|
|
131
177
|
* @returns The contract as Contract (should already be normalized)
|
|
132
|
-
* @throws
|
|
178
|
+
* @throws structured errors: `CLI.FILE_NOT_FOUND`, `CONTRACT.SOURCE_LOAD_FAILED`,
|
|
179
|
+
* `CONTRACT.SOURCE_IMPORT_DISALLOWED`, `CONTRACT.MODULE_EXPORT_MISSING`, `CONTRACT.EXPORT_INVALID`
|
|
133
180
|
*/
|
|
134
181
|
export async function loadContractFromTs(
|
|
135
182
|
entryPath: string,
|
|
@@ -138,7 +185,9 @@ export async function loadContractFromTs(
|
|
|
138
185
|
const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;
|
|
139
186
|
|
|
140
187
|
if (!existsSync(entryPath)) {
|
|
141
|
-
throw new
|
|
188
|
+
throw new CliStructuredError('CLI.FILE_NOT_FOUND', `Contract file not found: ${entryPath}`, {
|
|
189
|
+
where: { path: entryPath },
|
|
190
|
+
});
|
|
142
191
|
}
|
|
143
192
|
|
|
144
193
|
const tempFile = join(
|
|
@@ -169,22 +218,28 @@ export async function loadContractFromTs(
|
|
|
169
218
|
|
|
170
219
|
if (result.errors.length > 0) {
|
|
171
220
|
const errorMessages = result.errors.map((e: { text: string }) => e.text).join('\n');
|
|
172
|
-
throw
|
|
221
|
+
throw contractSourceError(
|
|
222
|
+
'CONTRACT.SOURCE_LOAD_FAILED',
|
|
223
|
+
`Failed to bundle contract file: ${errorMessages}`,
|
|
224
|
+
{ meta: { path: entryPath, stage: 'bundle' } },
|
|
225
|
+
);
|
|
173
226
|
}
|
|
174
227
|
|
|
175
228
|
if (!result.outputFiles || result.outputFiles.length === 0) {
|
|
176
|
-
throw new
|
|
229
|
+
throw new InternalError('No output files generated from bundling');
|
|
177
230
|
}
|
|
178
231
|
|
|
179
232
|
if (disallowedFromEntry.size > 0) {
|
|
180
|
-
throw
|
|
233
|
+
throw contractSourceError(
|
|
234
|
+
'CONTRACT.SOURCE_IMPORT_DISALLOWED',
|
|
181
235
|
`Disallowed imports detected. Only imports matching the allowlist are permitted:\n Allowlist: ${allowlist.join(', ')}\n Disallowed imports: ${[...disallowedFromEntry].join(', ')}`,
|
|
236
|
+
{ meta: { allowlist: [...allowlist], disallowed: [...disallowedFromEntry] } },
|
|
182
237
|
);
|
|
183
238
|
}
|
|
184
239
|
|
|
185
240
|
const bundleContent = result.outputFiles[0]?.text;
|
|
186
241
|
if (bundleContent === undefined) {
|
|
187
|
-
throw new
|
|
242
|
+
throw new InternalError('Bundle content is undefined');
|
|
188
243
|
}
|
|
189
244
|
writeFileSync(tempFile, bundleContent, 'utf-8');
|
|
190
245
|
|
|
@@ -201,16 +256,22 @@ export async function loadContractFromTs(
|
|
|
201
256
|
} else if (module.contract !== undefined) {
|
|
202
257
|
contract = module.contract;
|
|
203
258
|
} else {
|
|
204
|
-
throw new
|
|
259
|
+
throw new CliStructuredError(
|
|
260
|
+
'CONTRACT.MODULE_EXPORT_MISSING',
|
|
205
261
|
`Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module as Record<string, unknown>).join(', ') || 'none'}`,
|
|
262
|
+
{ where: { path: entryPath } },
|
|
206
263
|
);
|
|
207
264
|
}
|
|
208
265
|
|
|
209
266
|
if (typeof contract !== 'object' || contract === null) {
|
|
210
|
-
throw
|
|
267
|
+
throw contractSourceError(
|
|
268
|
+
'CONTRACT.EXPORT_INVALID',
|
|
269
|
+
`Contract export must be an object, got ${typeof contract}`,
|
|
270
|
+
{ meta: { path: entryPath, reason: 'not-object' } },
|
|
271
|
+
);
|
|
211
272
|
}
|
|
212
273
|
|
|
213
|
-
validatePurity(contract);
|
|
274
|
+
validatePurity(contract, entryPath);
|
|
214
275
|
|
|
215
276
|
// Blind cast: the loaded module was authored by user code
|
|
216
277
|
// (typically via `defineContract` / a contract builder) and
|
|
@@ -233,6 +294,10 @@ export async function loadContractFromTs(
|
|
|
233
294
|
if (error instanceof Error) {
|
|
234
295
|
throw error;
|
|
235
296
|
}
|
|
236
|
-
throw
|
|
297
|
+
throw contractSourceError(
|
|
298
|
+
'CONTRACT.SOURCE_LOAD_FAILED',
|
|
299
|
+
`Failed to load contract from ${entryPath}: ${String(error)}`,
|
|
300
|
+
{ meta: { path: entryPath, stage: 'import' }, cause: error },
|
|
301
|
+
);
|
|
237
302
|
}
|
|
238
303
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { VerifyDatabaseSchemaResult } from '@prisma-next/framework-components/control';
|
|
2
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* The combined per-space contract-satisfaction result plus the standalone
|
|
@@ -42,7 +43,7 @@ export function combineVerifyResults(
|
|
|
42
43
|
): CombinedVerifyResult {
|
|
43
44
|
const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;
|
|
44
45
|
if (appResult === undefined) {
|
|
45
|
-
throw new
|
|
46
|
+
throw new InternalError(
|
|
46
47
|
'Aggregate verifier returned no per-space verify results — this is a wiring bug.',
|
|
47
48
|
);
|
|
48
49
|
}
|
|
@@ -8,7 +8,7 @@ import { APP_SPACE_ID, spaceMigrationDirectory } from '@prisma-next/migration-to
|
|
|
8
8
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
10
|
import { relative, resolve } from 'pathe';
|
|
11
|
-
import { errorRuntime } from './cli-errors';
|
|
11
|
+
import { CliStructuredError, errorRuntime } from './cli-errors';
|
|
12
12
|
import { formatCommandHelp } from './formatters/help';
|
|
13
13
|
import type { CommonCommandOptions } from './global-flags';
|
|
14
14
|
import { parseGlobalFlags } from './global-flags';
|
|
@@ -263,21 +263,31 @@ export async function readContractEnvelope(config: {
|
|
|
263
263
|
const storageHash = storage?.['storageHash'];
|
|
264
264
|
|
|
265
265
|
if (typeof storageHash !== 'string') {
|
|
266
|
-
throw new
|
|
266
|
+
throw new CliStructuredError(
|
|
267
|
+
'CONTRACT.VALIDATION_FAILED',
|
|
267
268
|
`Contract at ${relative(process.cwd(), contractPath)} is missing a valid storage.storageHash. Run \`prisma-next contract emit\` to regenerate.`,
|
|
269
|
+
{ where: { path: contractPath } },
|
|
268
270
|
);
|
|
269
271
|
}
|
|
270
272
|
if (typeof schemaVersion !== 'string') {
|
|
271
|
-
throw new
|
|
273
|
+
throw new CliStructuredError(
|
|
274
|
+
'CONTRACT.VALIDATION_FAILED',
|
|
272
275
|
`Contract at ${relative(process.cwd(), contractPath)} is missing schemaVersion.`,
|
|
276
|
+
{ where: { path: contractPath } },
|
|
273
277
|
);
|
|
274
278
|
}
|
|
275
279
|
if (typeof target !== 'string') {
|
|
276
|
-
throw new
|
|
280
|
+
throw new CliStructuredError(
|
|
281
|
+
'CONTRACT.VALIDATION_FAILED',
|
|
282
|
+
`Contract at ${relative(process.cwd(), contractPath)} is missing target.`,
|
|
283
|
+
{ where: { path: contractPath } },
|
|
284
|
+
);
|
|
277
285
|
}
|
|
278
286
|
if (typeof targetFamily !== 'string') {
|
|
279
|
-
throw new
|
|
287
|
+
throw new CliStructuredError(
|
|
288
|
+
'CONTRACT.VALIDATION_FAILED',
|
|
280
289
|
`Contract at ${relative(process.cwd(), contractPath)} is missing targetFamily.`,
|
|
290
|
+
{ where: { path: contractPath } },
|
|
281
291
|
);
|
|
282
292
|
}
|
|
283
293
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
10
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
10
11
|
import {
|
|
11
12
|
type Cell,
|
|
12
13
|
type CellLine,
|
|
@@ -1124,7 +1125,7 @@ function assertSingleOwner(row: CellsRow, isFocus: boolean): void {
|
|
|
1124
1125
|
if (isFocus) {
|
|
1125
1126
|
const roles = new Set(top.map((cl) => cl.line.role));
|
|
1126
1127
|
if (roles.size > 1) {
|
|
1127
|
-
throw new
|
|
1128
|
+
throw new InternalError(
|
|
1128
1129
|
'migration-graph layout: single-owner invariant violated — two differently-roled lines share the top plane in one cell',
|
|
1129
1130
|
);
|
|
1130
1131
|
}
|