@prisma-next/cli 0.5.0-dev.4 → 0.5.0-dev.41
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 +56 -21
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
- package/dist/cli.mjs +123 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-1JqqkiC7.mjs} +45 -20
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +10 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.mjs +7 -7
- package/dist/commands/db-update.mjs +9 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +9 -9
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -56
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +26 -32
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -48
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +6 -10
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +27 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +3 -3
- package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-LjzCoicC.mjs +4 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
- package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-BS4kIX9c.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
- package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +86 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +5 -5
- package/dist/exports/index.mjs +3 -3
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-C7dE9KOJ.mjs +2062 -0
- package/dist/init-C7dE9KOJ.mjs.map +1 -0
- package/dist/{inspect-live-schema-BsoFVoS1.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -11
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +308 -84
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DOXnheFa.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-DOXnheFa.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-Ry3TnEya.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-fU0xoKjS.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-BJwA7ufw.mjs → result-handler-Ch6hVnOo.mjs} +35 -93
- package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-bl__PkXk.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-bl__PkXk.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +22 -16
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +1 -0
- package/src/commands/db-update.ts +1 -1
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +84 -63
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +194 -58
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +10 -6
- package/src/control-api/operations/db-update.ts +10 -6
- package/src/control-api/operations/migration-apply.ts +30 -9
- package/src/control-api/types.ts +69 -7
- package/src/exports/control-api.ts +2 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/command-helpers.ts +45 -23
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-DHq6GQGu.mjs +0 -5
- package/dist/client-TG7rbCWT.mjs.map +0 -1
- package/dist/config-loader-_W4T21X1.mjs.map +0 -1
- package/dist/contract-emit-CQfj7xJn.mjs +0 -122
- package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
- package/dist/contract-emit-DpPjuFy-.mjs +0 -195
- package/dist/contract-emit-DpPjuFy-.mjs.map +0 -1
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/contract-infer-BS4kIX9c.mjs.map +0 -1
- package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/inspect-live-schema-BsoFVoS1.mjs.map +0 -1
- package/dist/migration-status-Ry3TnEya.mjs.map +0 -1
- package/dist/migrations-fU0xoKjS.mjs.map +0 -1
- package/dist/result-handler-BJwA7ufw.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
- package/dist/validate-contract-deps-esa-VQ0h.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
2
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
3
|
import { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
4
4
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
@@ -7,57 +7,156 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
7
7
|
import { dirname } from 'pathe';
|
|
8
8
|
import { loadConfig } from '../../config-loader';
|
|
9
9
|
import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
|
|
10
|
+
import { queueEmitByOutput } from '../../utils/emit-queue';
|
|
10
11
|
import { assertFrameworkComponentsCompatible } from '../../utils/framework-components';
|
|
12
|
+
import { publishContractArtifactPair } from '../../utils/publish-contract-artifact-pair';
|
|
13
|
+
import { validateContractDeps } from '../../utils/validate-contract-deps';
|
|
11
14
|
import { enrichContract } from '../contract-enrichment';
|
|
12
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ContractEmitOptions,
|
|
17
|
+
ContractEmitResult,
|
|
18
|
+
ControlActionName,
|
|
19
|
+
OnControlProgress,
|
|
20
|
+
} from '../types';
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
readonly summary: string;
|
|
16
|
-
readonly diagnostics: readonly unknown[];
|
|
17
|
-
readonly meta?: unknown;
|
|
18
|
-
}
|
|
22
|
+
const EMIT_ACTION: ControlActionName = 'emit';
|
|
19
23
|
|
|
20
24
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
25
|
return typeof value === 'object' && value !== null;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
function
|
|
25
|
-
|
|
28
|
+
function startSpan(onProgress: OnControlProgress | undefined, spanId: string, label: string): void {
|
|
29
|
+
onProgress?.({ action: EMIT_ACTION, kind: 'spanStart', spanId, label });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function endSpan(
|
|
33
|
+
onProgress: OnControlProgress | undefined,
|
|
34
|
+
spanId: string,
|
|
35
|
+
outcome: 'ok' | 'error',
|
|
36
|
+
): void {
|
|
37
|
+
onProgress?.({ action: EMIT_ACTION, kind: 'spanEnd', spanId, outcome });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function failedToResolveContractSource(why: string, fix: string, meta?: Record<string, unknown>) {
|
|
41
|
+
return errorRuntime('Failed to resolve contract source', {
|
|
42
|
+
why,
|
|
43
|
+
fix,
|
|
44
|
+
...ifDefined('meta', meta),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type ValidatedProviderResult =
|
|
49
|
+
| { readonly ok: true; readonly value: unknown }
|
|
50
|
+
| { readonly ok: false; readonly error: ReturnType<typeof errorRuntime> };
|
|
51
|
+
|
|
52
|
+
function diagnosticLocationSuffix(diagnostic: Record<string, unknown>): string {
|
|
53
|
+
const sourceId = typeof diagnostic['sourceId'] === 'string' ? diagnostic['sourceId'] : undefined;
|
|
54
|
+
const span = isRecord(diagnostic['span']) ? diagnostic['span'] : undefined;
|
|
55
|
+
const start = span && isRecord(span['start']) ? span['start'] : undefined;
|
|
56
|
+
const line = start && typeof start['line'] === 'number' ? start['line'] : undefined;
|
|
57
|
+
const column = start && typeof start['column'] === 'number' ? start['column'] : undefined;
|
|
58
|
+
if (sourceId && line !== undefined && column !== undefined) {
|
|
59
|
+
return ` (${sourceId}:${line}:${column})`;
|
|
60
|
+
}
|
|
61
|
+
if (sourceId) {
|
|
62
|
+
return ` (${sourceId})`;
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
26
65
|
}
|
|
27
66
|
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
67
|
+
function mapDiagnosticsToIssues(
|
|
68
|
+
diagnostics: readonly unknown[],
|
|
69
|
+
): ReadonlyArray<{ readonly kind: string; readonly message: string }> {
|
|
70
|
+
const issues: { readonly kind: string; readonly message: string }[] = [];
|
|
71
|
+
for (const raw of diagnostics) {
|
|
72
|
+
if (!isRecord(raw)) continue;
|
|
73
|
+
const code = typeof raw['code'] === 'string' ? raw['code'] : 'diagnostic';
|
|
74
|
+
const message = typeof raw['message'] === 'string' ? raw['message'] : '';
|
|
75
|
+
issues.push({ kind: code, message: `${message}${diagnosticLocationSuffix(raw)}` });
|
|
76
|
+
}
|
|
77
|
+
return issues;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateProviderResult(providerResult: unknown): ValidatedProviderResult {
|
|
81
|
+
if (!isRecord(providerResult) || typeof providerResult['ok'] !== 'boolean') {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: failedToResolveContractSource(
|
|
85
|
+
'Contract source provider returned malformed result shape.',
|
|
86
|
+
'Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).',
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (providerResult['ok']) {
|
|
92
|
+
if (!('value' in providerResult)) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: failedToResolveContractSource(
|
|
96
|
+
'Contract source provider returned malformed success result: missing value.',
|
|
97
|
+
'Ensure contract.source.load success payload is ok(Contract).',
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, value: providerResult['value'] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const failure = providerResult['failure'];
|
|
105
|
+
if (
|
|
106
|
+
!isRecord(failure) ||
|
|
107
|
+
typeof failure['summary'] !== 'string' ||
|
|
108
|
+
!Array.isArray(failure['diagnostics'])
|
|
109
|
+
) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: failedToResolveContractSource(
|
|
113
|
+
'Contract source provider returned malformed failure result: expected summary and diagnostics.',
|
|
114
|
+
'Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).',
|
|
115
|
+
),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: failedToResolveContractSource(
|
|
121
|
+
String(failure['summary']),
|
|
122
|
+
'Fix contract source diagnostics and return ok(Contract).',
|
|
123
|
+
{
|
|
124
|
+
diagnostics: failure['diagnostics'],
|
|
125
|
+
issues: mapDiagnosticsToIssues(failure['diagnostics']),
|
|
126
|
+
...ifDefined('providerMeta', failure['meta']),
|
|
127
|
+
},
|
|
128
|
+
),
|
|
129
|
+
};
|
|
32
130
|
}
|
|
33
131
|
|
|
34
132
|
/**
|
|
35
|
-
*
|
|
133
|
+
* Canonical contract emit operation.
|
|
36
134
|
*
|
|
37
|
-
* This is
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* 4. Emits contract artifacts (JSON and DTS)
|
|
42
|
-
* 5. Writes files to the paths specified in config
|
|
135
|
+
* This is the SINGLE publication path used by both the CLI command
|
|
136
|
+
* (`prisma-next contract emit`) and the Vite plugin
|
|
137
|
+
* (`@prisma-next/vite-plugin-contract-emit`). New callers must go through this
|
|
138
|
+
* function rather than re-implementing load → emit → publish.
|
|
43
139
|
*
|
|
44
|
-
*
|
|
140
|
+
* The whole flow (load config → resolve source → emit bytes → atomic publish)
|
|
141
|
+
* is serialized per output JSON path via `queueEmitByOutput`. Concurrent calls
|
|
142
|
+
* for the same output line up FIFO; the user-visible outcome is "last
|
|
143
|
+
* submission wins on disk" without any supersession bookkeeping. Within a
|
|
144
|
+
* single emit, `publishContractArtifactPair` stages temp files, renames
|
|
145
|
+
* `contract.d.ts` before `contract.json`, and attempts to restore the previous
|
|
146
|
+
* pair if either rename fails — so type-only consumers never observe a
|
|
147
|
+
* mismatched pair.
|
|
45
148
|
*
|
|
46
|
-
* @
|
|
47
|
-
* @
|
|
48
|
-
* @throws If config loading fails, contract is invalid, or file I/O fails
|
|
49
|
-
* @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')
|
|
149
|
+
* @throws {CliStructuredError} on config/source/validation problems
|
|
150
|
+
* @throws {DOMException} `AbortError` if cancelled via `signal`
|
|
50
151
|
*/
|
|
51
152
|
export async function executeContractEmit(
|
|
52
153
|
options: ContractEmitOptions,
|
|
53
154
|
): Promise<ContractEmitResult> {
|
|
54
|
-
const { configPath, signal = new AbortController().signal } = options;
|
|
155
|
+
const { configPath, signal = new AbortController().signal, onProgress } = options;
|
|
55
156
|
const unlessAborted = abortable(signal);
|
|
56
157
|
|
|
57
|
-
// Load config using the existing config loader
|
|
58
158
|
const config = await unlessAborted(loadConfig(configPath));
|
|
59
159
|
|
|
60
|
-
// Validate contract config is present
|
|
61
160
|
if (!config.contract) {
|
|
62
161
|
throw errorContractConfigMissing({
|
|
63
162
|
why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
|
|
@@ -66,14 +165,12 @@ export async function executeContractEmit(
|
|
|
66
165
|
|
|
67
166
|
const contractConfig = config.contract;
|
|
68
167
|
|
|
69
|
-
// Validate output path is present and ends with .json
|
|
70
168
|
if (!contractConfig.output) {
|
|
71
169
|
throw errorContractConfigMissing({
|
|
72
170
|
why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
|
|
73
171
|
});
|
|
74
172
|
}
|
|
75
173
|
|
|
76
|
-
// Validate source exists and is callable
|
|
77
174
|
if (typeof contractConfig.source?.load !== 'function') {
|
|
78
175
|
throw errorContractConfigMissing({
|
|
79
176
|
why: 'Contract config must include a valid source provider object',
|
|
@@ -90,98 +187,84 @@ export async function executeContractEmit(
|
|
|
90
187
|
}
|
|
91
188
|
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
|
|
92
189
|
|
|
93
|
-
|
|
190
|
+
return queueEmitByOutput(outputJsonPath, async () => {
|
|
191
|
+
const stack = createControlStack(config);
|
|
94
192
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
193
|
+
const sourceContext = {
|
|
194
|
+
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
|
|
195
|
+
scalarTypeDescriptors: stack.scalarTypeDescriptors,
|
|
196
|
+
authoringContributions: stack.authoringContributions,
|
|
197
|
+
codecLookup: stack.codecLookup,
|
|
198
|
+
controlMutationDefaults: stack.controlMutationDefaults,
|
|
199
|
+
resolvedInputs: contractConfig.source.inputs ?? [],
|
|
200
|
+
};
|
|
103
201
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
202
|
+
startSpan(onProgress, 'resolveSource', 'Resolving contract source...');
|
|
203
|
+
let providerResult: Awaited<ReturnType<typeof contractConfig.source.load>>;
|
|
204
|
+
try {
|
|
205
|
+
providerResult = await unlessAborted(contractConfig.source.load(sourceContext));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
endSpan(onProgress, 'resolveSource', 'error');
|
|
208
|
+
if (signal.aborted || (isRecord(error) && error['name'] === 'AbortError')) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
throw failedToResolveContractSource(
|
|
212
|
+
error instanceof Error ? error.message : String(error),
|
|
213
|
+
'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',
|
|
214
|
+
);
|
|
110
215
|
}
|
|
111
|
-
throw errorRuntime('Failed to resolve contract source', {
|
|
112
|
-
why: error instanceof Error ? error.message : String(error),
|
|
113
|
-
fix: 'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
216
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (providerResult.ok && !('value' in providerResult)) {
|
|
125
|
-
throw errorRuntime('Failed to resolve contract source', {
|
|
126
|
-
why: 'Contract source provider returned malformed success result: missing value.',
|
|
127
|
-
fix: 'Ensure contract.source.load success payload is ok(Contract).',
|
|
128
|
-
});
|
|
129
|
-
}
|
|
217
|
+
const validatedContract = validateProviderResult(providerResult);
|
|
218
|
+
if (!validatedContract.ok) {
|
|
219
|
+
endSpan(onProgress, 'resolveSource', 'error');
|
|
220
|
+
throw validatedContract.error;
|
|
221
|
+
}
|
|
222
|
+
endSpan(onProgress, 'resolveSource', 'ok');
|
|
130
223
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
224
|
+
startSpan(onProgress, 'emit', 'Emitting contract...');
|
|
225
|
+
let emitResult: Awaited<ReturnType<typeof emit>>;
|
|
226
|
+
try {
|
|
227
|
+
const familyInstance = config.family.create(stack);
|
|
228
|
+
const rawComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
|
|
229
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
230
|
+
config.family.familyId,
|
|
231
|
+
config.target.targetId,
|
|
232
|
+
rawComponents,
|
|
233
|
+
);
|
|
234
|
+
const enrichedIR = enrichContract(validatedContract.value as Contract, frameworkComponents);
|
|
235
|
+
familyInstance.validateContract(enrichedIR);
|
|
236
|
+
emitResult = await unlessAborted(
|
|
237
|
+
emit(enrichedIR, stack, config.family.emission, { outputJsonPath }),
|
|
238
|
+
);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
endSpan(onProgress, 'emit', 'error');
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
endSpan(onProgress, 'emit', 'ok');
|
|
137
244
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
245
|
+
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
|
|
246
|
+
await publishContractArtifactPair({
|
|
247
|
+
outputJsonPath,
|
|
248
|
+
outputDtsPath,
|
|
249
|
+
contractJson: emitResult.contractJson,
|
|
250
|
+
contractDts: emitResult.contractDts,
|
|
251
|
+
publicationToken: String(process.hrtime.bigint()),
|
|
146
252
|
});
|
|
147
|
-
}
|
|
148
253
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
config.family.familyId,
|
|
154
|
-
config.target.targetId,
|
|
155
|
-
rawComponents,
|
|
156
|
-
);
|
|
157
|
-
const enrichedIR = enrichContract(providerResult.value as Contract, frameworkComponents);
|
|
158
|
-
|
|
159
|
-
familyInstance.validateContract(enrichedIR);
|
|
160
|
-
const emitResult = await unlessAborted(
|
|
161
|
-
emit(enrichedIR, stack, config.family.emission, {
|
|
162
|
-
outputJsonPath: outputJsonPath,
|
|
163
|
-
}),
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// Create directory if needed and write files (both colocated)
|
|
167
|
-
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
|
|
168
|
-
await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));
|
|
169
|
-
await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));
|
|
170
|
-
|
|
171
|
-
// Validate that contract.d.ts type imports are resolvable
|
|
172
|
-
const { validateContractDeps } = await import('../../utils/validate-contract-deps');
|
|
173
|
-
const depsValidation = validateContractDeps(emitResult.contractDts, dirname(outputDtsPath));
|
|
174
|
-
if (depsValidation.warning) {
|
|
175
|
-
process.stderr.write(`\n⚠ ${depsValidation.warning}\n`);
|
|
176
|
-
}
|
|
254
|
+
const validationWarning = validateContractDeps(
|
|
255
|
+
emitResult.contractDts,
|
|
256
|
+
dirname(outputDtsPath),
|
|
257
|
+
).warning;
|
|
177
258
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
259
|
+
return {
|
|
260
|
+
storageHash: emitResult.storageHash,
|
|
261
|
+
...ifDefined('executionHash', emitResult.executionHash),
|
|
262
|
+
profileHash: emitResult.profileHash,
|
|
263
|
+
files: {
|
|
264
|
+
json: outputJsonPath,
|
|
265
|
+
dts: outputDtsPath,
|
|
266
|
+
},
|
|
267
|
+
...ifDefined('validationWarning', validationWarning),
|
|
268
|
+
};
|
|
269
|
+
});
|
|
187
270
|
}
|
|
@@ -8,10 +8,10 @@ import type {
|
|
|
8
8
|
MigrationRunnerResult,
|
|
9
9
|
TargetMigrationsCapability,
|
|
10
10
|
} from '@prisma-next/framework-components/control';
|
|
11
|
+
import { hasOperationPreview } from '@prisma-next/framework-components/control';
|
|
11
12
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
12
13
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
13
14
|
import type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';
|
|
14
|
-
import { extractOperationStatements } from './extract-operation-statements';
|
|
15
15
|
import { createOperationCallbacks, stripOperations } from './migration-helpers';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -83,9 +83,11 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
83
83
|
contract,
|
|
84
84
|
schema: schemaIR,
|
|
85
85
|
policy,
|
|
86
|
-
// `db init`
|
|
87
|
-
//
|
|
88
|
-
|
|
86
|
+
// `db init` reconciles against the live introspected schema; there is no
|
|
87
|
+
// prior contract to derive a "from" identity from. The required
|
|
88
|
+
// `fromContract: null` makes that structural fact visible at the call
|
|
89
|
+
// site (vs. silently letting the planner default to a baseline plan).
|
|
90
|
+
fromContract: null,
|
|
89
91
|
frameworkComponents,
|
|
90
92
|
});
|
|
91
93
|
|
|
@@ -194,12 +196,14 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
194
196
|
|
|
195
197
|
// Plan mode - don't execute
|
|
196
198
|
if (mode === 'plan') {
|
|
197
|
-
const
|
|
199
|
+
const preview = hasOperationPreview(familyInstance)
|
|
200
|
+
? familyInstance.toOperationPreview(migrationPlan.operations)
|
|
201
|
+
: undefined;
|
|
198
202
|
const result: DbInitSuccess = {
|
|
199
203
|
mode: 'plan',
|
|
200
204
|
plan: {
|
|
201
205
|
operations: stripOperations(migrationPlan.operations),
|
|
202
|
-
...ifDefined('
|
|
206
|
+
...ifDefined('preview', preview),
|
|
203
207
|
},
|
|
204
208
|
destination: {
|
|
205
209
|
storageHash: migrationPlan.destination.storageHash,
|
|
@@ -7,10 +7,10 @@ import type {
|
|
|
7
7
|
MigrationRunnerResult,
|
|
8
8
|
TargetMigrationsCapability,
|
|
9
9
|
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import { hasOperationPreview } from '@prisma-next/framework-components/control';
|
|
10
11
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
11
12
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
12
13
|
import type { DbUpdateResult, DbUpdateSuccess, OnControlProgress } from '../types';
|
|
13
|
-
import { extractOperationStatements } from './extract-operation-statements';
|
|
14
14
|
import { createOperationCallbacks, stripOperations } from './migration-helpers';
|
|
15
15
|
|
|
16
16
|
// F12: db update allows additive, widening, and destructive operations.
|
|
@@ -83,9 +83,11 @@ export async function executeDbUpdate<TFamilyId extends string, TTargetId extend
|
|
|
83
83
|
contract,
|
|
84
84
|
schema: schemaIR,
|
|
85
85
|
policy,
|
|
86
|
-
// `db update`
|
|
87
|
-
//
|
|
88
|
-
|
|
86
|
+
// `db update` reconciles against the live introspected schema; there is
|
|
87
|
+
// no prior contract to derive a "from" identity from. The required
|
|
88
|
+
// `fromContract: null` makes that structural fact visible at the call
|
|
89
|
+
// site (vs. silently letting the planner default to a baseline plan).
|
|
90
|
+
fromContract: null,
|
|
89
91
|
frameworkComponents,
|
|
90
92
|
});
|
|
91
93
|
if (plannerResult.kind === 'failure') {
|
|
@@ -113,12 +115,14 @@ export async function executeDbUpdate<TFamilyId extends string, TTargetId extend
|
|
|
113
115
|
const migrationPlan = plannerResult.plan;
|
|
114
116
|
|
|
115
117
|
if (mode === 'plan') {
|
|
116
|
-
const
|
|
118
|
+
const preview = hasOperationPreview(familyInstance)
|
|
119
|
+
? familyInstance.toOperationPreview(migrationPlan.operations)
|
|
120
|
+
: undefined;
|
|
117
121
|
const result: DbUpdateSuccess = {
|
|
118
122
|
mode: 'plan',
|
|
119
123
|
plan: {
|
|
120
124
|
operations: stripOperations(migrationPlan.operations),
|
|
121
|
-
...(
|
|
125
|
+
...ifDefined('preview', preview),
|
|
122
126
|
},
|
|
123
127
|
destination: {
|
|
124
128
|
storageHash: migrationPlan.destination.storageHash,
|
|
@@ -30,6 +30,21 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
|
|
|
30
30
|
readonly onProgress?: OnControlProgress;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Apply a sequence of migration packages against the configured driver.
|
|
35
|
+
*
|
|
36
|
+
* Validates the path's continuity (origin → ... → destination, no gaps),
|
|
37
|
+
* then drives the family/target's migration runner over each package's
|
|
38
|
+
* operations in order, surfacing per-migration progress through `onProgress`.
|
|
39
|
+
*
|
|
40
|
+
* The `pendingMigrations` parameter is trusted input. Callers are responsible
|
|
41
|
+
* for upstream verification of the originating migration packages — typically
|
|
42
|
+
* by loading them via `readMigrationPackage` from
|
|
43
|
+
* `@prisma-next/migration-tools/io`, which performs hash-integrity checks at
|
|
44
|
+
* the load boundary. This operation does not re-verify the packages and
|
|
45
|
+
* assumes the `(metadata, ops)` pairs on disk have not been tampered with
|
|
46
|
+
* since emit.
|
|
47
|
+
*/
|
|
33
48
|
export async function executeMigrationApply<TFamilyId extends string, TTargetId extends string>(
|
|
34
49
|
options: ExecuteMigrationApplyOptions<TFamilyId, TTargetId>,
|
|
35
50
|
): Promise<MigrationApplyResult> {
|
|
@@ -64,15 +79,19 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
64
79
|
|
|
65
80
|
const firstMigration = pendingMigrations[0]!;
|
|
66
81
|
const lastMigration = pendingMigrations[pendingMigrations.length - 1]!;
|
|
67
|
-
|
|
82
|
+
// Manifest `from` is `string | null` (null = baseline). The live-marker
|
|
83
|
+
// layer encodes "no prior state" as EMPTY_CONTRACT_HASH; bridge here so the
|
|
84
|
+
// string comparisons below work uniformly.
|
|
85
|
+
const firstFromMarker = firstMigration.from ?? EMPTY_CONTRACT_HASH;
|
|
86
|
+
if (firstFromMarker !== originHash || lastMigration.to !== destinationHash) {
|
|
68
87
|
return notOk({
|
|
69
88
|
code: 'MIGRATION_PATH_NOT_FOUND' as const,
|
|
70
89
|
summary: 'Migration apply path does not match requested origin and destination',
|
|
71
|
-
why: `Path resolved as ${
|
|
90
|
+
why: `Path resolved as ${firstFromMarker} -> ${lastMigration.to}, but requested ${originHash} -> ${destinationHash}`,
|
|
72
91
|
meta: {
|
|
73
92
|
originHash,
|
|
74
93
|
destinationHash,
|
|
75
|
-
pathOrigin:
|
|
94
|
+
pathOrigin: firstFromMarker,
|
|
76
95
|
pathDestination: lastMigration.to,
|
|
77
96
|
},
|
|
78
97
|
});
|
|
@@ -81,18 +100,19 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
81
100
|
for (let i = 1; i < pendingMigrations.length; i++) {
|
|
82
101
|
const previous = pendingMigrations[i - 1]!;
|
|
83
102
|
const current = pendingMigrations[i]!;
|
|
84
|
-
|
|
103
|
+
const currentFromMarker = current.from ?? EMPTY_CONTRACT_HASH;
|
|
104
|
+
if (previous.to !== currentFromMarker) {
|
|
85
105
|
return notOk({
|
|
86
106
|
code: 'MIGRATION_PATH_NOT_FOUND' as const,
|
|
87
107
|
summary: 'Migration apply path contains a discontinuity between adjacent migrations',
|
|
88
|
-
why: `Migration "${previous.dirName}" ends at ${previous.to}, but next migration "${current.dirName}" starts at ${
|
|
108
|
+
why: `Migration "${previous.dirName}" ends at ${previous.to}, but next migration "${current.dirName}" starts at ${currentFromMarker}`,
|
|
89
109
|
meta: {
|
|
90
110
|
originHash,
|
|
91
111
|
destinationHash,
|
|
92
112
|
previousDirName: previous.dirName,
|
|
93
113
|
previousTo: previous.to,
|
|
94
114
|
currentDirName: current.dirName,
|
|
95
|
-
currentFrom:
|
|
115
|
+
currentFrom: currentFromMarker,
|
|
96
116
|
discontinuityIndex: i,
|
|
97
117
|
},
|
|
98
118
|
});
|
|
@@ -120,13 +140,14 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
120
140
|
allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] as const,
|
|
121
141
|
};
|
|
122
142
|
|
|
123
|
-
//
|
|
124
|
-
// for a fresh database (no marker present).
|
|
143
|
+
// Manifest `from === null` means "no prior state" — the runner expects
|
|
144
|
+
// `origin: null` for a fresh database (no marker present).
|
|
125
145
|
const plan = {
|
|
126
146
|
targetId,
|
|
127
|
-
origin: migration.from ===
|
|
147
|
+
origin: migration.from === null ? null : { storageHash: migration.from },
|
|
128
148
|
destination: { storageHash: migration.to },
|
|
129
149
|
operations,
|
|
150
|
+
providedInvariants: migration.providedInvariants,
|
|
130
151
|
};
|
|
131
152
|
|
|
132
153
|
const destinationContract = familyInstance.validateContract(migration.toContract);
|