@prisma-next/cli 0.16.0-dev.3 → 0.16.0-dev.31
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/README.md +44 -45
- package/dist/cli.mjs +13 -13
- package/dist/{client-KuBQftxz.mjs → client-B1UjQc_x.mjs} +50 -58
- package/dist/client-B1UjQc_x.mjs.map +1 -0
- package/dist/{command-helpers-Cpq44aVa.mjs → command-helpers-D-jBl0Qt.mjs} +16 -8
- 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 +8 -6
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +11 -12
- 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 +14 -15
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +13 -13
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +4 -2
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +4 -4
- package/dist/commands/migration-list.d.mts +1 -1
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +16 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +3 -4
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +5 -5
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +13 -18
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/contract-at-errors-DvZ_DzJK.mjs +36 -0
- package/dist/contract-at-errors-DvZ_DzJK.mjs.map +1 -0
- package/dist/{contract-emit-CgnXSANN.mjs → contract-emit-BT46Mu8Q.mjs} +3 -3
- package/dist/{contract-emit-CgnXSANN.mjs.map → contract-emit-BT46Mu8Q.mjs.map} +1 -1
- package/dist/{contract-emit-cJQBlvGb.mjs → contract-emit-hVdlaF2U.mjs} +6 -7
- package/dist/{contract-emit-cJQBlvGb.mjs.map → contract-emit-hVdlaF2U.mjs.map} +1 -1
- package/dist/{contract-enrichment-gn9sWbPw.mjs → contract-enrichment-JUhLv-BX.mjs} +8 -8
- package/dist/{contract-enrichment-gn9sWbPw.mjs.map → contract-enrichment-JUhLv-BX.mjs.map} +1 -1
- package/dist/{contract-infer-Cg8evbeg.mjs → contract-infer-DpqTQECL.mjs} +3 -3
- package/dist/{contract-infer-Cg8evbeg.mjs.map → contract-infer-DpqTQECL.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-D_3VeHVb.mjs → contract-space-aggregate-loader-CNd-ZB6C.mjs} +14 -14
- package/dist/contract-space-aggregate-loader-CNd-ZB6C.mjs.map +1 -0
- package/dist/{db-verify-BEclUzki.mjs → db-verify-FEN6k1m7.mjs} +10 -8
- package/dist/db-verify-FEN6k1m7.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +4 -4
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -3
- 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/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{format-DAUAZqPt.mjs → format-DI5qVr22.mjs} +8 -7
- package/dist/format-DI5qVr22.mjs.map +1 -0
- package/dist/{framework-components-CnbUbiJw.mjs → framework-components-C4XACYNB.mjs} +3 -3
- package/dist/framework-components-C4XACYNB.mjs.map +1 -0
- package/dist/{init-C3JhkHg-.mjs → init-ClrRkbwd.mjs} +77 -67
- package/dist/init-ClrRkbwd.mjs.map +1 -0
- package/dist/{inspect-live-schema-CX7EA5C3.mjs → inspect-live-schema-C0Rgdtw_.mjs} +4 -4
- package/dist/{inspect-live-schema-CX7EA5C3.mjs.map → inspect-live-schema-C0Rgdtw_.mjs.map} +1 -1
- package/dist/{migration-check-MHT3w7_v.mjs → migration-check-zEkHpPDT.mjs} +58 -30
- package/dist/migration-check-zEkHpPDT.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CRyYrHZ9.mjs → migration-command-scaffold-CkNsp9g1.mjs} +4 -4
- package/dist/{migration-command-scaffold-CRyYrHZ9.mjs.map → migration-command-scaffold-CkNsp9g1.mjs.map} +1 -1
- package/dist/{migration-graph-command-render-B83xrnNy.mjs → migration-graph-command-render-DEwFpx-w.mjs} +5 -4
- package/dist/migration-graph-command-render-DEwFpx-w.mjs.map +1 -0
- package/dist/{migration-list-BAKujUjh.mjs → migration-list-BkafFVXS.mjs} +6 -6
- package/dist/migration-list-BkafFVXS.mjs.map +1 -0
- package/dist/{migration-log-C_xam2HZ.mjs → migration-log-BxhAtbhA.mjs} +5 -5
- package/dist/{migration-log-C_xam2HZ.mjs.map → migration-log-BxhAtbhA.mjs.map} +1 -1
- package/dist/{migration-path-target-DpPTsRCg.mjs → migration-path-target-B5HkjSTO.mjs} +2 -2
- package/dist/{migration-path-target-DpPTsRCg.mjs.map → migration-path-target-B5HkjSTO.mjs.map} +1 -1
- package/dist/{migration-plan-CreSVtWZ.mjs → migration-plan-Bk2pbPQH.mjs} +64 -74
- package/dist/migration-plan-Bk2pbPQH.mjs.map +1 -0
- package/dist/{migration-status-YgEvXf-u.mjs → migration-status-BzlARseq.mjs} +13 -12
- package/dist/migration-status-BzlARseq.mjs.map +1 -0
- package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
- package/dist/output-DWNYqZGc.mjs.map +1 -0
- package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
- package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
- package/dist/{telemetry-DVr6RFnq.mjs → telemetry-BbniBUbP.mjs} +2 -2
- package/dist/{telemetry-DVr6RFnq.mjs.map → telemetry-BbniBUbP.mjs.map} +1 -1
- package/dist/{types-D4r_HI9-.d.mts → types-DgXprE3w.d.mts} +3 -3
- package/dist/types-DgXprE3w.d.mts.map +1 -0
- package/dist/{verify-CgDVZjhc.mjs → verify-BSaNeaSL.mjs} +2 -2
- package/dist/{verify-CgDVZjhc.mjs.map → verify-BSaNeaSL.mjs.map} +1 -1
- package/package.json +21 -21
- package/src/commands/db-init.ts +5 -3
- package/src/commands/db-sign.ts +9 -7
- package/src/commands/db-update.ts +18 -9
- package/src/commands/db-verify.ts +4 -4
- package/src/commands/format.ts +1 -1
- package/src/commands/init/hygiene-gitattributes.ts +37 -21
- package/src/commands/init/init.ts +6 -6
- package/src/commands/init/output.ts +4 -5
- package/src/commands/init/probe-db.ts +9 -2
- package/src/commands/init/reinit-cleanup.ts +16 -17
- package/src/commands/init/templates/render.ts +2 -1
- package/src/commands/inspect-live-schema.ts +1 -1
- package/src/commands/migrate.ts +6 -5
- package/src/commands/migration-check.ts +72 -34
- package/src/commands/migration-list.ts +1 -5
- package/src/commands/migration-log.ts +1 -1
- package/src/commands/migration-new.ts +17 -41
- package/src/commands/migration-plan.ts +64 -88
- package/src/commands/migration-show.ts +2 -2
- package/src/commands/migration-status.ts +6 -7
- package/src/commands/ref.ts +19 -14
- package/src/control-api/client.ts +43 -25
- package/src/control-api/contract-enrichment.ts +7 -7
- package/src/control-api/operations/contract-emit.ts +2 -3
- package/src/control-api/operations/db-init.ts +3 -3
- package/src/control-api/operations/db-run.ts +8 -7
- package/src/control-api/operations/db-update.ts +2 -2
- package/src/control-api/operations/db-verify.ts +4 -4
- package/src/control-api/operations/format.ts +5 -4
- package/src/control-api/operations/migrate.ts +6 -5
- package/src/control-api/operations/run-migration.ts +2 -1
- package/src/control-api/types.ts +1 -1
- package/src/load-ts-contract.ts +81 -16
- package/src/utils/cli-errors.ts +15 -7
- package/src/utils/combine-verify-results.ts +2 -1
- package/src/utils/command-helpers.ts +15 -5
- package/src/utils/contract-at-errors.ts +13 -22
- package/src/utils/contract-space-aggregate-loader.ts +8 -8
- package/src/utils/contract-space-seed-phase.ts +11 -9
- package/src/utils/extension-pack-inputs.ts +8 -8
- package/src/utils/formatters/migration-graph-grid-layout.ts +2 -1
- package/src/utils/formatters/migration-graph-labels.ts +1 -2
- package/src/utils/formatters/migration-list-data-column.ts +1 -2
- package/src/utils/framework-components.ts +9 -9
- package/src/utils/integrity-violation-to-check-failure.ts +3 -3
- package/src/utils/migration-command-scaffold.ts +1 -1
- package/src/utils/plan-resolution.ts +7 -10
- package/src/utils/ref-advancement.ts +22 -3
- package/dist/client-KuBQftxz.mjs.map +0 -1
- package/dist/command-helpers-Cpq44aVa.mjs.map +0 -1
- package/dist/contract-at-errors-D9gEjYtg.mjs +0 -42
- package/dist/contract-at-errors-D9gEjYtg.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-D_3VeHVb.mjs.map +0 -1
- package/dist/db-verify-BEclUzki.mjs.map +0 -1
- package/dist/format-DAUAZqPt.mjs.map +0 -1
- package/dist/framework-components-CnbUbiJw.mjs.map +0 -1
- package/dist/init-C3JhkHg-.mjs.map +0 -1
- package/dist/migration-check-MHT3w7_v.mjs.map +0 -1
- package/dist/migration-graph-command-render-B83xrnNy.mjs.map +0 -1
- package/dist/migration-list-BAKujUjh.mjs.map +0 -1
- package/dist/migration-plan-CreSVtWZ.mjs.map +0 -1
- package/dist/migration-status-YgEvXf-u.mjs.map +0 -1
- package/dist/output-mEQ74_nd.mjs.map +0 -1
- package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
- package/dist/types-D4r_HI9-.d.mts.map +0 -1
- package/src/control-api/errors.ts +0 -9
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
|
}
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
errorConfigFileNotFound,
|
|
11
11
|
errorConfigValidation,
|
|
12
12
|
errorContractConfigMissing,
|
|
13
|
-
|
|
13
|
+
errorContractMissingExtensions,
|
|
14
14
|
errorContractValidationFailed,
|
|
15
15
|
errorDatabaseConnectionRequired,
|
|
16
16
|
errorDriverRequired,
|
|
@@ -54,7 +54,7 @@ export {
|
|
|
54
54
|
errorConfigFileNotFound,
|
|
55
55
|
errorConfigValidation,
|
|
56
56
|
errorContractConfigMissing,
|
|
57
|
-
|
|
57
|
+
errorContractMissingExtensions,
|
|
58
58
|
errorContractValidationFailed,
|
|
59
59
|
errorDatabaseConnectionRequired,
|
|
60
60
|
errorDriverRequired,
|
|
@@ -215,22 +215,30 @@ export function errorPlanForgotTheFlag(
|
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* `viaRef: true` (the default) mirrors migration-tools' `errorRefNotResolvable`:
|
|
220
|
+
* a ref name with no pointer file, where the fallback hash isn't a graph
|
|
221
|
+
* node either — there's nothing to materialize a contract from.
|
|
222
|
+
* `viaRef: false` is a distinct, ref-independent case: an explicit `--from
|
|
223
|
+
* <hash>` that doesn't name a ref, on an empty migration graph, so there is
|
|
224
|
+
* no graph node and no ref to resolve a contract through.
|
|
225
|
+
*/
|
|
218
226
|
export function errorSnapshotMissing(
|
|
219
227
|
identifier: string,
|
|
220
228
|
options?: { readonly viaRef?: boolean },
|
|
221
229
|
): CliStructuredError {
|
|
222
230
|
const viaRef = options?.viaRef !== false;
|
|
223
231
|
const fix = viaRef
|
|
224
|
-
? `
|
|
225
|
-
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name
|
|
232
|
+
? `Create the ref with "prisma-next ref set ${identifier} <hash>" (or advance it via "prisma-next db update --advance-ref ${identifier}"), or pass a hash that is a node in the migration graph.`
|
|
233
|
+
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name (its contract resolves through the snapshot store), or run db update first.`;
|
|
226
234
|
return errorRuntime(
|
|
227
235
|
viaRef
|
|
228
|
-
? `Ref "${identifier}"
|
|
236
|
+
? `Ref "${identifier}" is not resolvable`
|
|
229
237
|
: `No contract source for from-hash "${identifier}"`,
|
|
230
238
|
{
|
|
231
239
|
why: viaRef
|
|
232
|
-
? `Ref "${identifier}"
|
|
233
|
-
: `Hash "${identifier}" is not a graph
|
|
240
|
+
? `Ref "${identifier}" has no pointer file, and the hash being resolved is not a node in the migration graph either.`
|
|
241
|
+
: `Hash "${identifier}" is not a node in the migration graph (the graph is empty), and it does not name a ref either.`,
|
|
234
242
|
fix,
|
|
235
243
|
meta: {
|
|
236
244
|
code: 'MIGRATION.SNAPSHOT_MISSING',
|
|
@@ -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
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
2
2
|
import { notOk, type Result } from '@prisma-next/utils/result';
|
|
3
|
-
import { join } from 'pathe';
|
|
4
3
|
import {
|
|
5
4
|
CliStructuredError,
|
|
6
5
|
errorContractValidationFailed,
|
|
@@ -16,7 +15,7 @@ export function mapContractAtError(
|
|
|
16
15
|
): Result<never, CliStructuredError> {
|
|
17
16
|
if (MigrationToolsError.is(error)) {
|
|
18
17
|
switch (error.code) {
|
|
19
|
-
case 'MIGRATION.
|
|
18
|
+
case 'MIGRATION.REF_NOT_RESOLVABLE': {
|
|
20
19
|
const refName =
|
|
21
20
|
typeof error.details?.['refName'] === 'string'
|
|
22
21
|
? error.details['refName']
|
|
@@ -27,18 +26,13 @@ export function mapContractAtError(
|
|
|
27
26
|
}
|
|
28
27
|
case 'MIGRATION.CONTRACT_DESERIALIZATION_FAILED': {
|
|
29
28
|
const filePath =
|
|
30
|
-
typeof error.details?.['filePath'] === 'string'
|
|
31
|
-
? error.details['filePath']
|
|
32
|
-
: 'ref-snapshot';
|
|
29
|
+
typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';
|
|
33
30
|
const message =
|
|
34
31
|
typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;
|
|
35
|
-
const isRefSnapshot = filePath.endsWith('.contract.json');
|
|
36
32
|
return notOk(
|
|
37
33
|
errorContractValidationFailed(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
: `Predecessor contract at ${filePath} failed to deserialize: ${message}`,
|
|
41
|
-
{ where: { path: isRefSnapshot ? 'ref-snapshot' : filePath } },
|
|
34
|
+
`Predecessor contract at ${filePath} failed to deserialize: ${message}`,
|
|
35
|
+
{ where: { path: filePath } },
|
|
42
36
|
),
|
|
43
37
|
);
|
|
44
38
|
}
|
|
@@ -66,22 +60,19 @@ export function mapContractAtError(
|
|
|
66
60
|
fix: error.fix,
|
|
67
61
|
}),
|
|
68
62
|
);
|
|
69
|
-
case 'MIGRATION.
|
|
70
|
-
const
|
|
71
|
-
typeof error.details?.['
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
case 'MIGRATION.CONTRACT_SNAPSHOT_MISSING': {
|
|
64
|
+
const expectedPath =
|
|
65
|
+
typeof error.details?.['expectedPath'] === 'string'
|
|
66
|
+
? error.details['expectedPath']
|
|
67
|
+
: 'migrations/snapshots/';
|
|
74
68
|
const role = options?.artifactRole ?? 'from';
|
|
75
69
|
return notOk(
|
|
76
|
-
errorFileNotFound(
|
|
70
|
+
errorFileNotFound(expectedPath, {
|
|
77
71
|
why:
|
|
78
72
|
role === 'to'
|
|
79
|
-
? `Target migration is missing its
|
|
80
|
-
: `Predecessor migration is missing its
|
|
81
|
-
fix:
|
|
82
|
-
role === 'to'
|
|
83
|
-
? 'Re-emit the target migration so its sibling `end-contract.json` / `end-contract.d.ts` are restored, then re-run this command.'
|
|
84
|
-
: 'Re-emit the predecessor migration (`prisma-next migration plan` from its source) so its sibling `end-contract.json` is restored, then re-run this command.',
|
|
73
|
+
? `Target migration is missing its contract snapshot at ${expectedPath}`
|
|
74
|
+
: `Predecessor migration is missing its contract snapshot at ${expectedPath}`,
|
|
75
|
+
fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
|
|
85
76
|
}),
|
|
86
77
|
);
|
|
87
78
|
}
|
|
@@ -114,8 +114,8 @@ export function mapIntegrityViolations(
|
|
|
114
114
|
? 'Contract-space layout violation detected'
|
|
115
115
|
: `Contract-space layout violations detected (${layout.length})`;
|
|
116
116
|
return new CliStructuredError('MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION', summary, {
|
|
117
|
-
why: `The on-disk \`migrations/\` directory and your \`
|
|
118
|
-
fix: 'Declare the extension in `
|
|
117
|
+
why: `The on-disk \`migrations/\` directory and your \`extensions\` declaration are not in agreement.\n${lines.join('\n')}`,
|
|
118
|
+
fix: 'Declare the extension in `extensions` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.',
|
|
119
119
|
docsUrl: CONTRACT_SPACES_DOCS_URL,
|
|
120
120
|
meta: { violations: layout },
|
|
121
121
|
});
|
|
@@ -177,14 +177,14 @@ export interface BuildAggregateInputs<TFamilyId extends string, TTargetId extend
|
|
|
177
177
|
readonly targetId: TTargetId;
|
|
178
178
|
readonly migrationsDir: string;
|
|
179
179
|
readonly appContract: Contract;
|
|
180
|
-
readonly
|
|
180
|
+
readonly extensions: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
181
181
|
readonly deserializeContract: (contractJson: unknown) => Contract;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
function declaredExtensionsFromInputs(
|
|
185
|
-
|
|
185
|
+
extensions: BuildAggregateInputs<string, string>['extensions'],
|
|
186
186
|
): readonly DeclaredExtensionEntry[] {
|
|
187
|
-
return toDeclaredExtensionsFromRaw(
|
|
187
|
+
return toDeclaredExtensionsFromRaw(extensions as ReadonlyArray<unknown>);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/**
|
|
@@ -195,7 +195,7 @@ export function refuseDeclaredExtensionTargetMismatch<
|
|
|
195
195
|
TFamilyId extends string,
|
|
196
196
|
TTargetId extends string,
|
|
197
197
|
>(inputs: BuildAggregateInputs<TFamilyId, TTargetId>): CliStructuredError | null {
|
|
198
|
-
for (const declared of declaredExtensionsFromInputs(inputs.
|
|
198
|
+
for (const declared of declaredExtensionsFromInputs(inputs.extensions)) {
|
|
199
199
|
if (declared.targetId !== inputs.targetId) {
|
|
200
200
|
return targetMismatchError(declared.id, inputs.targetId, declared.targetId);
|
|
201
201
|
}
|
|
@@ -271,7 +271,7 @@ export async function buildContractSpaceAggregate<
|
|
|
271
271
|
>(
|
|
272
272
|
inputs: BuildAggregateInputs<TFamilyId, TTargetId>,
|
|
273
273
|
): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {
|
|
274
|
-
const declaredExtensions = declaredExtensionsFromInputs(inputs.
|
|
274
|
+
const declaredExtensions = declaredExtensionsFromInputs(inputs.extensions);
|
|
275
275
|
const loaded = await loadContractSpaceAggregateForCli(inputs);
|
|
276
276
|
if (!loaded.ok) {
|
|
277
277
|
return loaded;
|
|
@@ -375,7 +375,7 @@ export async function buildReadAggregate(
|
|
|
375
375
|
targetId: config.target.id,
|
|
376
376
|
migrationsDir: options.migrationsDir,
|
|
377
377
|
appContract: appContractForLoad,
|
|
378
|
-
|
|
378
|
+
extensions: config.extensions ?? [],
|
|
379
379
|
deserializeContract,
|
|
380
380
|
});
|
|
381
381
|
if (!loaded.ok) {
|
|
@@ -2,7 +2,7 @@ import { materialiseExtensionMigrationPackageIfMissing } from '@prisma-next/migr
|
|
|
2
2
|
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
3
3
|
import type { MigrationOps } from '@prisma-next/migration-tools/package';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
emitContractSpaceArtifacts,
|
|
6
6
|
planAllSpaces,
|
|
7
7
|
readContractSpaceHeadRef,
|
|
8
8
|
type SpacePlanOutput,
|
|
@@ -39,7 +39,7 @@ export interface SeedPhaseExtensionInput {
|
|
|
39
39
|
|
|
40
40
|
export interface ContractSpaceSeedPhaseInputs {
|
|
41
41
|
readonly migrationsDir: string;
|
|
42
|
-
readonly
|
|
42
|
+
readonly extensions: ReadonlyArray<SeedPhaseExtensionInput>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -53,7 +53,7 @@ export interface ContractSpaceSeedPhaseInputs {
|
|
|
53
53
|
* - `action: 'unchanged'` — the on-disk head already matched the
|
|
54
54
|
* descriptor and no new migration packages needed to be written.
|
|
55
55
|
*
|
|
56
|
-
* Either way, the
|
|
56
|
+
* Either way, the artifacts (`contract.json`, `contract.d.ts`,
|
|
57
57
|
* `refs/head.json`) are re-emitted: the framework owns those files and
|
|
58
58
|
* makes the re-emit observably idempotent at the byte level.
|
|
59
59
|
*/
|
|
@@ -75,9 +75,11 @@ export interface ContractSpaceSeedPhaseResult {
|
|
|
75
75
|
* For every extension that exposes a `contractSpace`:
|
|
76
76
|
*
|
|
77
77
|
* 1. Read the on-disk head ref (returns `null` on first emit).
|
|
78
|
-
* 2.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* 2. Write the head contract into the migrations-root snapshot store
|
|
79
|
+
* (write-if-absent, keyed by hash) and unconditionally re-emit
|
|
80
|
+
* `refs/head.json`, via {@link emitContractSpaceArtifacts}. The
|
|
81
|
+
* framework owns `refs/head.json`; re-emit is the contract for that
|
|
82
|
+
* file. The snapshot itself is only written once per distinct hash.
|
|
81
83
|
* 3. Materialise any descriptor-shipped migration packages not yet on
|
|
82
84
|
* disk via {@link materialiseExtensionMigrationPackageIfMissing}.
|
|
83
85
|
* Existing packages are left untouched (by-existence skip).
|
|
@@ -96,7 +98,7 @@ export interface ContractSpaceSeedPhaseResult {
|
|
|
96
98
|
export async function runContractSpaceSeedPhase(
|
|
97
99
|
inputs: ContractSpaceSeedPhaseInputs,
|
|
98
100
|
): Promise<ContractSpaceSeedPhaseResult> {
|
|
99
|
-
const planInputs = inputs.
|
|
101
|
+
const planInputs = inputs.extensions
|
|
100
102
|
.filter(
|
|
101
103
|
(
|
|
102
104
|
pack,
|
|
@@ -132,7 +134,7 @@ export async function runContractSpaceSeedPhase(
|
|
|
132
134
|
string,
|
|
133
135
|
NonNullable<SeedPhaseExtensionInput['contractSpace']>
|
|
134
136
|
>();
|
|
135
|
-
for (const pack of inputs.
|
|
137
|
+
for (const pack of inputs.extensions) {
|
|
136
138
|
if (pack.contractSpace !== undefined) descriptorBySpace.set(pack.id, pack.contractSpace);
|
|
137
139
|
}
|
|
138
140
|
|
|
@@ -144,7 +146,7 @@ export async function runContractSpaceSeedPhase(
|
|
|
144
146
|
const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, space.spaceId);
|
|
145
147
|
const priorHash = onDiskHeadRef?.hash ?? null;
|
|
146
148
|
|
|
147
|
-
await
|
|
149
|
+
await emitContractSpaceArtifacts(inputs.migrationsDir, space.spaceId, {
|
|
148
150
|
contract: descriptor.contractJson,
|
|
149
151
|
contractDts: buildPlaceholderContractDts(space.spaceId),
|
|
150
152
|
headRef: { hash: descriptor.headRef.hash, invariants: descriptor.headRef.invariants },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Single descriptor-import boundary for CLI consumers of `Config.
|
|
2
|
+
* Single descriptor-import boundary for CLI consumers of `Config.extensions`.
|
|
3
3
|
*
|
|
4
4
|
* Every CLI command / utility that reads an extension descriptor's
|
|
5
5
|
* `contractSpace` projection (loader, migrate-pass, extension-migrations
|
|
@@ -54,9 +54,9 @@ export interface ExtensionPackInput {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* Structural shape we read off each `Config.
|
|
57
|
+
* Structural shape we read off each `Config.extensions` entry.
|
|
58
58
|
*
|
|
59
|
-
* The CLI is the descriptor-import boundary; `
|
|
59
|
+
* The CLI is the descriptor-import boundary; `extensions` is the only
|
|
60
60
|
* surface where the SQL-family-typed `ControlExtensionDescriptor` flows
|
|
61
61
|
* into framework-neutral helpers. The structural cast lives here, and
|
|
62
62
|
* here alone — every other CLI consumer reads the canonical
|
|
@@ -76,14 +76,14 @@ type ExtensionPackLike = {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Project the CLI's `Config.
|
|
79
|
+
* Project the CLI's `Config.extensions` array into the canonical
|
|
80
80
|
* {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`
|
|
81
81
|
* structural cast in the CLI lives inside this function.
|
|
82
82
|
*/
|
|
83
83
|
export function toExtensionInputs(
|
|
84
|
-
|
|
84
|
+
extensions: ReadonlyArray<unknown>,
|
|
85
85
|
): readonly ExtensionPackInput[] {
|
|
86
|
-
return
|
|
86
|
+
return extensions.map((raw) => {
|
|
87
87
|
const pack = raw as ExtensionPackLike;
|
|
88
88
|
if (pack.contractSpace === undefined) {
|
|
89
89
|
return { id: pack.id, targetId: pack.targetId };
|
|
@@ -147,10 +147,10 @@ export function toDeclaredExtensions(
|
|
|
147
147
|
* always reads the contract from on-disk artefacts instead.
|
|
148
148
|
*/
|
|
149
149
|
export function toDeclaredExtensionsFromRaw(
|
|
150
|
-
|
|
150
|
+
extensions: ReadonlyArray<unknown>,
|
|
151
151
|
): readonly DeclaredExtensionEntry[] {
|
|
152
152
|
const entries: DeclaredExtensionEntry[] = [];
|
|
153
|
-
for (const raw of
|
|
153
|
+
for (const raw of extensions) {
|
|
154
154
|
if (typeof raw !== 'object' || raw === null) continue;
|
|
155
155
|
const descriptor = Object.getOwnPropertyDescriptor(raw, 'contractSpace');
|
|
156
156
|
if (descriptor === undefined) continue;
|
|
@@ -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
|
}
|
|
@@ -104,8 +104,7 @@ function abbreviateHash(hash: string, hashLength: number, emptySource: string):
|
|
|
104
104
|
if (hash === EMPTY_CONTRACT_HASH) {
|
|
105
105
|
return emptySource;
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
return stripped.slice(0, hashLength);
|
|
107
|
+
return hash.slice(0, hashLength);
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
interface ContractOverlayNames {
|
|
@@ -18,8 +18,7 @@ export function migrationListEmptySource(glyphMode: GlyphMode): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function abbreviateContractHash(hash: string): string {
|
|
21
|
-
|
|
22
|
-
return stripped.slice(0, MIGRATION_LIST_HASH_WIDTH);
|
|
21
|
+
return hash.slice(0, MIGRATION_LIST_HASH_WIDTH);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export function padFromHashColumn(text: string, width: number): string {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type TargetBoundComponentDescriptor,
|
|
5
5
|
} from '@prisma-next/framework-components/components';
|
|
6
6
|
import type { ControlStack } from '@prisma-next/framework-components/control';
|
|
7
|
-
import { errorConfigValidation,
|
|
7
|
+
import { errorConfigValidation, errorContractMissingExtensions } from './cli-errors';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Asserts that all framework components are compatible with the expected family and target.
|
|
@@ -27,7 +27,7 @@ import { errorConfigValidation, errorContractMissingExtensionPacks } from './cli
|
|
|
27
27
|
* @example
|
|
28
28
|
* ```ts
|
|
29
29
|
* const config = await loadConfig();
|
|
30
|
-
* const frameworkComponents = [config.target, config.adapter, ...(config.
|
|
30
|
+
* const frameworkComponents = [config.target, config.adapter, ...(config.extensions ?? [])];
|
|
31
31
|
*
|
|
32
32
|
* // Validate and type-narrow components before passing to planner
|
|
33
33
|
* const typedComponents = assertFrameworkComponentsCompatible(
|
|
@@ -114,12 +114,12 @@ export function assertFrameworkComponentsCompatible<
|
|
|
114
114
|
* This check ensures the emitted contract matches the CLI config before running
|
|
115
115
|
* commands that depend on the contract (e.g., db verify, db sign).
|
|
116
116
|
*
|
|
117
|
-
* @param contract - The contract to validate (must include targetFamily, target,
|
|
118
|
-
* @param stack - The control plane stack (target, adapter, driver,
|
|
117
|
+
* @param contract - The contract to validate (must include targetFamily, target, extensions).
|
|
118
|
+
* @param stack - The control plane stack (target, adapter, driver, extensions).
|
|
119
119
|
*
|
|
120
120
|
* @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target
|
|
121
121
|
* doesn't match the configured family/target.
|
|
122
|
-
* @throws {CliStructuredError}
|
|
122
|
+
* @throws {CliStructuredError} errorContractMissingExtensions when the contract requires
|
|
123
123
|
* extension packs that are not provided in the config (includes all missing packs in error.meta).
|
|
124
124
|
*
|
|
125
125
|
* @example
|
|
@@ -141,14 +141,14 @@ export function assertContractRequirementsSatisfied<
|
|
|
141
141
|
contract,
|
|
142
142
|
stack,
|
|
143
143
|
}: {
|
|
144
|
-
readonly contract: Pick<Contract, 'targetFamily' | 'target' | '
|
|
144
|
+
readonly contract: Pick<Contract, 'targetFamily' | 'target' | 'extensions'>;
|
|
145
145
|
readonly stack: ControlStack<TFamilyId, TTargetId>;
|
|
146
146
|
}): void {
|
|
147
147
|
const providedComponentIds = new Set<string>([
|
|
148
148
|
stack.target.id,
|
|
149
149
|
...(stack.adapter ? [stack.adapter.id] : []),
|
|
150
150
|
]);
|
|
151
|
-
for (const extension of stack.
|
|
151
|
+
for (const extension of stack.extensions) {
|
|
152
152
|
providedComponentIds.add(extension.id);
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -172,8 +172,8 @@ export function assertContractRequirementsSatisfied<
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
if (result.missingExtensionPackIds.length > 0) {
|
|
175
|
-
throw
|
|
176
|
-
|
|
175
|
+
throw errorContractMissingExtensions({
|
|
176
|
+
missingExtensions: result.missingExtensionPackIds,
|
|
177
177
|
providedComponentIds: [...providedComponentIds],
|
|
178
178
|
});
|
|
179
179
|
}
|