@prisma-next/cli 0.3.0-pr.99.6 → 0.4.0-dev.1
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/LICENSE +201 -0
- package/README.md +381 -128
- package/dist/agent-skill-mongo.md +106 -0
- package/dist/agent-skill-postgres.md +106 -0
- package/dist/cli-errors-BDCYR5ap.mjs +4 -0
- package/dist/cli-errors-DStABy9d.d.mts +3 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.js +1 -2910
- package/dist/cli.mjs +254 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-DiUkJAeN.mjs +987 -0
- package/dist/client-DiUkJAeN.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts +7 -0
- package/dist/commands/contract-emit.d.mts.map +1 -0
- package/dist/commands/contract-emit.mjs +4 -0
- package/dist/commands/contract-infer.d.mts +7 -0
- package/dist/commands/contract-infer.d.mts.map +1 -0
- package/dist/commands/contract-infer.mjs +4 -0
- package/dist/commands/db-init.d.mts +7 -0
- package/dist/commands/db-init.d.mts.map +1 -0
- package/dist/commands/db-init.mjs +125 -0
- package/dist/commands/db-init.mjs.map +1 -0
- package/dist/commands/db-schema.d.mts +7 -0
- package/dist/commands/db-schema.d.mts.map +1 -0
- package/dist/commands/db-schema.mjs +53 -0
- package/dist/commands/db-schema.mjs.map +1 -0
- package/dist/commands/db-sign.d.mts +7 -0
- package/dist/commands/db-sign.d.mts.map +1 -0
- package/dist/commands/db-sign.mjs +136 -0
- package/dist/commands/db-sign.mjs.map +1 -0
- package/dist/commands/db-update.d.mts +7 -0
- package/dist/commands/db-update.d.mts.map +1 -0
- package/dist/commands/db-update.mjs +122 -0
- package/dist/commands/db-update.mjs.map +1 -0
- package/dist/commands/db-verify.d.mts +7 -0
- package/dist/commands/db-verify.d.mts.map +1 -0
- package/dist/commands/db-verify.mjs +322 -0
- package/dist/commands/db-verify.mjs.map +1 -0
- package/dist/commands/migration-apply.d.mts +36 -0
- package/dist/commands/migration-apply.d.mts.map +1 -0
- package/dist/commands/migration-apply.mjs +244 -0
- package/dist/commands/migration-apply.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts +8 -0
- package/dist/commands/migration-new.d.mts.map +1 -0
- package/dist/commands/migration-new.mjs +152 -0
- package/dist/commands/migration-new.mjs.map +1 -0
- package/dist/commands/migration-plan.d.mts +47 -0
- package/dist/commands/migration-plan.d.mts.map +1 -0
- package/dist/commands/migration-plan.mjs +313 -0
- package/dist/commands/migration-plan.mjs.map +1 -0
- package/dist/commands/migration-ref.d.mts +43 -0
- package/dist/commands/migration-ref.d.mts.map +1 -0
- package/dist/commands/migration-ref.mjs +195 -0
- package/dist/commands/migration-ref.mjs.map +1 -0
- package/dist/commands/migration-show.d.mts +28 -0
- package/dist/commands/migration-show.d.mts.map +1 -0
- package/dist/commands/migration-show.mjs +140 -0
- package/dist/commands/migration-show.mjs.map +1 -0
- package/dist/commands/migration-status.d.mts +86 -0
- package/dist/commands/migration-status.d.mts.map +1 -0
- package/dist/commands/migration-status.mjs +4 -0
- package/dist/commands/migration-verify.d.mts +16 -0
- package/dist/commands/migration-verify.d.mts.map +1 -0
- package/dist/commands/migration-verify.mjs +110 -0
- package/dist/commands/migration-verify.mjs.map +1 -0
- package/dist/config-loader-C4VXKl8f.mjs +43 -0
- package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
- package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
- package/dist/config-loader.d.mts.map +1 -0
- package/dist/config-loader.mjs +3 -0
- package/dist/contract-emit-D2wDXfyo.mjs +191 -0
- package/dist/contract-emit-D2wDXfyo.mjs.map +1 -0
- package/dist/contract-emit-D9WOShFz.mjs +4 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
- package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
- package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
- package/dist/contract-infer-DozZT511.mjs +90 -0
- package/dist/contract-infer-DozZT511.mjs.map +1 -0
- package/dist/exports/config-types.d.mts +2 -0
- package/dist/exports/config-types.mjs +3 -0
- package/dist/exports/control-api.d.mts +624 -0
- package/dist/exports/control-api.d.mts.map +1 -0
- package/dist/exports/control-api.mjs +6 -0
- package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +137 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/extract-operation-statements-DZUJNmL3.mjs +13 -0
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +1 -0
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +26 -0
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +1 -0
- package/dist/framework-components-BAsliT4V.mjs +59 -0
- package/dist/framework-components-BAsliT4V.mjs.map +1 -0
- package/dist/init-DQ8auNB4.mjs +430 -0
- package/dist/init-DQ8auNB4.mjs.map +1 -0
- package/dist/inspect-live-schema-BYnhztxZ.mjs +91 -0
- package/dist/inspect-live-schema-BYnhztxZ.mjs.map +1 -0
- package/dist/migration-command-scaffold-CntCcntR.mjs +105 -0
- package/dist/migration-command-scaffold-CntCcntR.mjs.map +1 -0
- package/dist/migration-status-CJANY4yr.mjs +1583 -0
- package/dist/migration-status-CJANY4yr.mjs.map +1 -0
- package/dist/migrations-DTZBYXm1.mjs +173 -0
- package/dist/migrations-DTZBYXm1.mjs.map +1 -0
- package/dist/progress-adapter-B-YvmcDu.mjs +43 -0
- package/dist/progress-adapter-B-YvmcDu.mjs.map +1 -0
- package/dist/quick-reference-mongo.md +93 -0
- package/dist/quick-reference-postgres.md +91 -0
- package/dist/result-handler-oK_vA-Fn.mjs +697 -0
- package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
- package/dist/terminal-ui-C5k88MmW.mjs +274 -0
- package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
- package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
- package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
- package/dist/verify-DlFQ2FOw.mjs +385 -0
- package/dist/verify-DlFQ2FOw.mjs.map +1 -0
- package/package.json +87 -40
- package/src/cli.ts +118 -58
- package/src/commands/contract-emit.ts +101 -78
- package/src/commands/contract-infer-paths.ts +32 -0
- package/src/commands/contract-infer.ts +143 -0
- package/src/commands/db-init.ts +97 -219
- package/src/commands/db-schema.ts +77 -0
- package/src/commands/db-sign.ts +46 -73
- package/src/commands/db-update.ts +236 -0
- package/src/commands/db-verify.ts +409 -119
- package/src/commands/init/detect-package-manager.ts +47 -0
- package/src/commands/init/index.ts +21 -0
- package/src/commands/init/init.ts +203 -0
- package/src/commands/init/templates/agent-skill-mongo.md +106 -0
- package/src/commands/init/templates/agent-skill-postgres.md +106 -0
- package/src/commands/init/templates/agent-skill.ts +19 -0
- package/src/commands/init/templates/code-templates.ts +168 -0
- package/src/commands/init/templates/quick-reference-mongo.md +93 -0
- package/src/commands/init/templates/quick-reference-postgres.md +91 -0
- package/src/commands/init/templates/quick-reference.ts +19 -0
- package/src/commands/init/templates/render.ts +20 -0
- package/src/commands/init/templates/tsconfig.ts +35 -0
- package/src/commands/inspect-live-schema.ts +170 -0
- package/src/commands/migration-apply.ts +427 -0
- package/src/commands/migration-new.ts +260 -0
- package/src/commands/migration-plan.ts +519 -0
- package/src/commands/migration-ref.ts +305 -0
- package/src/commands/migration-show.ts +246 -0
- package/src/commands/migration-status.ts +864 -0
- package/src/commands/migration-verify.ts +180 -0
- package/src/config-loader.ts +13 -3
- package/src/control-api/client.ts +205 -183
- package/src/control-api/contract-enrichment.ts +119 -0
- package/src/control-api/errors.ts +9 -0
- package/src/control-api/operations/contract-emit.ts +181 -0
- package/src/control-api/operations/db-init.ts +53 -49
- package/src/control-api/operations/db-update.ts +220 -0
- package/src/control-api/operations/extract-operation-statements.ts +14 -0
- package/src/control-api/operations/extract-sql-ddl.ts +47 -0
- package/src/control-api/operations/migration-apply.ts +191 -0
- package/src/control-api/operations/migration-helpers.ts +49 -0
- package/src/control-api/types.ts +274 -52
- package/src/exports/config-types.ts +4 -3
- package/src/exports/control-api.ts +15 -5
- package/src/load-ts-contract.ts +30 -19
- package/src/utils/cli-errors.ts +14 -8
- package/src/utils/command-helpers.ts +302 -3
- package/src/utils/formatters/emit.ts +67 -0
- package/src/utils/formatters/errors.ts +82 -0
- package/src/utils/formatters/graph-migration-mapper.ts +240 -0
- package/src/utils/formatters/graph-render.ts +1323 -0
- package/src/utils/formatters/graph-types.ts +120 -0
- package/src/utils/formatters/help.ts +380 -0
- package/src/utils/formatters/helpers.ts +28 -0
- package/src/utils/formatters/migrations.ts +346 -0
- package/src/utils/formatters/styled.ts +212 -0
- package/src/utils/formatters/verify.ts +621 -0
- package/src/utils/framework-components.ts +13 -10
- package/src/utils/global-flags.ts +41 -23
- package/src/utils/migration-command-scaffold.ts +184 -0
- package/src/utils/migration-types.ts +12 -0
- package/src/utils/progress-adapter.ts +18 -29
- package/src/utils/result-handler.ts +12 -13
- package/src/utils/shutdown.ts +92 -0
- package/src/utils/suggest-command.ts +31 -0
- package/src/utils/terminal-ui.ts +276 -0
- package/src/utils/validate-contract-deps.ts +49 -0
- package/dist/chunk-AGOTG4L3.js +0 -965
- package/dist/chunk-AGOTG4L3.js.map +0 -1
- package/dist/chunk-HLLI4YL7.js +0 -180
- package/dist/chunk-HLLI4YL7.js.map +0 -1
- package/dist/chunk-HWYQOCAJ.js +0 -47
- package/dist/chunk-HWYQOCAJ.js.map +0 -1
- package/dist/chunk-VG2R7DGF.js +0 -735
- package/dist/chunk-VG2R7DGF.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/contract-emit.d.ts +0 -3
- package/dist/commands/contract-emit.d.ts.map +0 -1
- package/dist/commands/contract-emit.js +0 -10
- package/dist/commands/contract-emit.js.map +0 -1
- package/dist/commands/db-init.d.ts +0 -3
- package/dist/commands/db-init.d.ts.map +0 -1
- package/dist/commands/db-init.js +0 -257
- package/dist/commands/db-init.js.map +0 -1
- package/dist/commands/db-introspect.d.ts +0 -3
- package/dist/commands/db-introspect.d.ts.map +0 -1
- package/dist/commands/db-introspect.js +0 -155
- package/dist/commands/db-introspect.js.map +0 -1
- package/dist/commands/db-schema-verify.d.ts +0 -3
- package/dist/commands/db-schema-verify.d.ts.map +0 -1
- package/dist/commands/db-schema-verify.js +0 -171
- package/dist/commands/db-schema-verify.js.map +0 -1
- package/dist/commands/db-sign.d.ts +0 -3
- package/dist/commands/db-sign.d.ts.map +0 -1
- package/dist/commands/db-sign.js +0 -195
- package/dist/commands/db-sign.js.map +0 -1
- package/dist/commands/db-verify.d.ts +0 -3
- package/dist/commands/db-verify.d.ts.map +0 -1
- package/dist/commands/db-verify.js +0 -193
- package/dist/commands/db-verify.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js +0 -7
- package/dist/config-loader.js.map +0 -1
- package/dist/control-api/client.d.ts +0 -13
- package/dist/control-api/client.d.ts.map +0 -1
- package/dist/control-api/operations/db-init.d.ts +0 -29
- package/dist/control-api/operations/db-init.d.ts.map +0 -1
- package/dist/control-api/types.d.ts +0 -387
- package/dist/control-api/types.d.ts.map +0 -1
- package/dist/exports/config-types.d.ts +0 -3
- package/dist/exports/config-types.d.ts.map +0 -1
- package/dist/exports/config-types.js +0 -6
- package/dist/exports/config-types.js.map +0 -1
- package/dist/exports/control-api.d.ts +0 -13
- package/dist/exports/control-api.d.ts.map +0 -1
- package/dist/exports/control-api.js +0 -7
- package/dist/exports/control-api.js.map +0 -1
- package/dist/exports/index.d.ts +0 -4
- package/dist/exports/index.d.ts.map +0 -1
- package/dist/exports/index.js +0 -176
- package/dist/exports/index.js.map +0 -1
- package/dist/load-ts-contract.d.ts.map +0 -1
- package/dist/utils/cli-errors.d.ts +0 -7
- package/dist/utils/cli-errors.d.ts.map +0 -1
- package/dist/utils/command-helpers.d.ts +0 -12
- package/dist/utils/command-helpers.d.ts.map +0 -1
- package/dist/utils/framework-components.d.ts +0 -70
- package/dist/utils/framework-components.d.ts.map +0 -1
- package/dist/utils/global-flags.d.ts +0 -25
- package/dist/utils/global-flags.d.ts.map +0 -1
- package/dist/utils/output.d.ts +0 -142
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/progress-adapter.d.ts +0 -26
- package/dist/utils/progress-adapter.d.ts.map +0 -1
- package/dist/utils/result-handler.d.ts +0 -15
- package/dist/utils/result-handler.d.ts.map +0 -1
- package/src/commands/db-introspect.ts +0 -227
- package/src/commands/db-schema-verify.ts +0 -238
- package/src/utils/output.ts +0 -1471
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
3
|
+
import { emit } from '@prisma-next/emitter';
|
|
4
|
+
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
5
|
+
import { abortable } from '@prisma-next/utils/abortable';
|
|
6
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
7
|
+
import { dirname, isAbsolute, join, resolve } from 'pathe';
|
|
8
|
+
import { loadConfig } from '../../config-loader';
|
|
9
|
+
import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
|
|
10
|
+
import { assertFrameworkComponentsCompatible } from '../../utils/framework-components';
|
|
11
|
+
import { enrichContract } from '../contract-enrichment';
|
|
12
|
+
import type { ContractEmitOptions, ContractEmitResult } from '../types';
|
|
13
|
+
|
|
14
|
+
interface ProviderFailureLike {
|
|
15
|
+
readonly summary: string;
|
|
16
|
+
readonly diagnostics: readonly unknown[];
|
|
17
|
+
readonly meta?: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
|
+
return typeof value === 'object' && value !== null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isAbortError(error: unknown): boolean {
|
|
25
|
+
return isRecord(error) && typeof error['name'] === 'string' && error['name'] === 'AbortError';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isProviderFailureLike(value: unknown): value is ProviderFailureLike {
|
|
29
|
+
return (
|
|
30
|
+
isRecord(value) && typeof value['summary'] === 'string' && Array.isArray(value['diagnostics'])
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Executes the contract emit operation.
|
|
36
|
+
*
|
|
37
|
+
* This is an offline operation that:
|
|
38
|
+
* 1. Loads the Prisma Next config from the specified path
|
|
39
|
+
* 2. Resolves the contract source from config
|
|
40
|
+
* 3. Creates a control plane stack and family instance
|
|
41
|
+
* 4. Emits contract artifacts (JSON and DTS)
|
|
42
|
+
* 5. Writes files to the paths specified in config
|
|
43
|
+
*
|
|
44
|
+
* Supports AbortSignal for cancellation, enabling "last change wins" behavior.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Options including configPath and optional signal
|
|
47
|
+
* @returns File paths and hashes of emitted artifacts
|
|
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')
|
|
50
|
+
*/
|
|
51
|
+
export async function executeContractEmit(
|
|
52
|
+
options: ContractEmitOptions,
|
|
53
|
+
): Promise<ContractEmitResult> {
|
|
54
|
+
const { configPath, signal = new AbortController().signal } = options;
|
|
55
|
+
const unlessAborted = abortable(signal);
|
|
56
|
+
|
|
57
|
+
// Load config using the existing config loader
|
|
58
|
+
const config = await unlessAborted(loadConfig(configPath));
|
|
59
|
+
|
|
60
|
+
// Validate contract config is present
|
|
61
|
+
if (!config.contract) {
|
|
62
|
+
throw errorContractConfigMissing({
|
|
63
|
+
why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const contractConfig = config.contract;
|
|
68
|
+
|
|
69
|
+
// Validate output path is present and ends with .json
|
|
70
|
+
if (!contractConfig.output) {
|
|
71
|
+
throw errorContractConfigMissing({
|
|
72
|
+
why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!contractConfig.output.endsWith('.json')) {
|
|
76
|
+
throw errorContractConfigMissing({
|
|
77
|
+
why: 'Contract config output path must end with .json (e.g., "src/prisma/contract.json")',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate source exists and is callable
|
|
82
|
+
if (typeof contractConfig.source !== 'function') {
|
|
83
|
+
throw errorContractConfigMissing({
|
|
84
|
+
why: 'Contract config must include a valid source provider function',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Normalize configPath and resolve artifact paths relative to config file directory
|
|
89
|
+
const normalizedConfigPath = resolve(configPath);
|
|
90
|
+
const configDir = dirname(normalizedConfigPath);
|
|
91
|
+
const outputJsonPath = isAbsolute(contractConfig.output)
|
|
92
|
+
? contractConfig.output
|
|
93
|
+
: join(configDir, contractConfig.output);
|
|
94
|
+
// Colocate .d.ts with .json (contract.json → contract.d.ts)
|
|
95
|
+
const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
|
|
96
|
+
|
|
97
|
+
const sourceContext = {
|
|
98
|
+
composedExtensionPacks: (config.extensionPacks ?? []).map((p) => p.id),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;
|
|
102
|
+
try {
|
|
103
|
+
providerResult = await unlessAborted(contractConfig.source(sourceContext));
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (signal.aborted || isAbortError(error)) {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
throw errorRuntime('Failed to resolve contract source', {
|
|
109
|
+
why: error instanceof Error ? error.message : String(error),
|
|
110
|
+
fix: 'Ensure contract.source resolves to ok(Contract) or returns structured diagnostics.',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {
|
|
115
|
+
throw errorRuntime('Failed to resolve contract source', {
|
|
116
|
+
why: 'Contract source provider returned malformed result shape.',
|
|
117
|
+
fix: 'Ensure contract.source resolves to ok(Contract) or notOk({ summary, diagnostics }).',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (providerResult.ok && !('value' in providerResult)) {
|
|
122
|
+
throw errorRuntime('Failed to resolve contract source', {
|
|
123
|
+
why: 'Contract source provider returned malformed success result: missing value.',
|
|
124
|
+
fix: 'Ensure contract.source success payload is ok(Contract).',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {
|
|
129
|
+
throw errorRuntime('Failed to resolve contract source', {
|
|
130
|
+
why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',
|
|
131
|
+
fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!providerResult.ok) {
|
|
136
|
+
throw errorRuntime('Failed to resolve contract source', {
|
|
137
|
+
why: providerResult.failure.summary,
|
|
138
|
+
fix: 'Fix contract source diagnostics and return ok(Contract).',
|
|
139
|
+
meta: {
|
|
140
|
+
diagnostics: providerResult.failure.diagnostics,
|
|
141
|
+
...ifDefined('providerMeta', providerResult.failure.meta),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const stack = createControlStack(config);
|
|
147
|
+
const familyInstance = config.family.create(stack);
|
|
148
|
+
|
|
149
|
+
const rawComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
|
|
150
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
151
|
+
config.family.familyId,
|
|
152
|
+
config.target.targetId,
|
|
153
|
+
rawComponents,
|
|
154
|
+
);
|
|
155
|
+
const enrichedIR = enrichContract(providerResult.value as Contract, frameworkComponents);
|
|
156
|
+
|
|
157
|
+
familyInstance.validateContract(enrichedIR);
|
|
158
|
+
const emitResult = await unlessAborted(emit(enrichedIR, stack, config.family.emission));
|
|
159
|
+
|
|
160
|
+
// Create directory if needed and write files (both colocated)
|
|
161
|
+
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
|
|
162
|
+
await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));
|
|
163
|
+
await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));
|
|
164
|
+
|
|
165
|
+
// Validate that contract.d.ts type imports are resolvable
|
|
166
|
+
const { validateContractDeps } = await import('../../utils/validate-contract-deps');
|
|
167
|
+
const depsValidation = validateContractDeps(emitResult.contractDts, dirname(outputDtsPath));
|
|
168
|
+
if (depsValidation.warning) {
|
|
169
|
+
process.stderr.write(`\n⚠ ${depsValidation.warning}\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
storageHash: emitResult.storageHash,
|
|
174
|
+
...ifDefined('executionHash', emitResult.executionHash),
|
|
175
|
+
profileHash: emitResult.profileHash,
|
|
176
|
+
files: {
|
|
177
|
+
json: outputJsonPath,
|
|
178
|
+
dts: outputDtsPath,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
3
3
|
import type {
|
|
4
4
|
ControlDriverInstance,
|
|
5
5
|
ControlFamilyInstance,
|
|
6
6
|
MigrationPlan,
|
|
7
7
|
MigrationPlannerResult,
|
|
8
|
-
MigrationPlanOperation,
|
|
9
8
|
MigrationRunnerResult,
|
|
10
9
|
TargetMigrationsCapability,
|
|
11
|
-
} from '@prisma-next/
|
|
10
|
+
} from '@prisma-next/framework-components/control';
|
|
11
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
12
12
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
13
13
|
import type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';
|
|
14
|
+
import { extractOperationStatements } from './extract-operation-statements';
|
|
15
|
+
import { createOperationCallbacks, stripOperations } from './migration-helpers';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Options for executing dbInit operation.
|
|
17
19
|
*/
|
|
18
20
|
export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {
|
|
19
21
|
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
20
|
-
readonly familyInstance: ControlFamilyInstance<TFamilyId>;
|
|
21
|
-
readonly
|
|
22
|
+
readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
|
|
23
|
+
readonly contract: Contract;
|
|
22
24
|
readonly mode: 'plan' | 'apply';
|
|
23
25
|
readonly migrations: TargetMigrationsCapability<
|
|
24
26
|
TFamilyId,
|
|
25
27
|
TTargetId,
|
|
26
|
-
ControlFamilyInstance<TFamilyId>
|
|
28
|
+
ControlFamilyInstance<TFamilyId, unknown>
|
|
27
29
|
>;
|
|
28
30
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
29
31
|
/** Optional progress callback for observing operation progress */
|
|
@@ -43,7 +45,7 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
|
|
|
43
45
|
export async function executeDbInit<TFamilyId extends string, TTargetId extends string>(
|
|
44
46
|
options: ExecuteDbInitOptions<TFamilyId, TTargetId>,
|
|
45
47
|
): Promise<DbInitResult> {
|
|
46
|
-
const { driver, familyInstance,
|
|
48
|
+
const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =
|
|
47
49
|
options;
|
|
48
50
|
|
|
49
51
|
// Create planner and runner from target migrations capability
|
|
@@ -78,7 +80,7 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
78
80
|
label: 'Planning migration',
|
|
79
81
|
});
|
|
80
82
|
const plannerResult: MigrationPlannerResult = await planner.plan({
|
|
81
|
-
contract
|
|
83
|
+
contract,
|
|
82
84
|
schema: schemaIR,
|
|
83
85
|
policy,
|
|
84
86
|
frameworkComponents,
|
|
@@ -114,12 +116,12 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
114
116
|
action: 'dbInit',
|
|
115
117
|
kind: 'spanStart',
|
|
116
118
|
spanId: checkMarkerSpanId,
|
|
117
|
-
label: 'Checking
|
|
119
|
+
label: 'Checking database signature',
|
|
118
120
|
});
|
|
119
121
|
const existingMarker = await familyInstance.readMarker({ driver });
|
|
120
122
|
if (existingMarker) {
|
|
121
123
|
const markerMatchesDestination =
|
|
122
|
-
existingMarker.
|
|
124
|
+
existingMarker.storageHash === migrationPlan.destination.storageHash &&
|
|
123
125
|
(!migrationPlan.destination.profileHash ||
|
|
124
126
|
existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
125
127
|
|
|
@@ -134,15 +136,23 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
134
136
|
const result: DbInitSuccess = {
|
|
135
137
|
mode,
|
|
136
138
|
plan: { operations: [] },
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
destination: {
|
|
140
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
141
|
+
...ifDefined('profileHash', migrationPlan.destination.profileHash),
|
|
142
|
+
},
|
|
143
|
+
...ifDefined(
|
|
144
|
+
'execution',
|
|
145
|
+
mode === 'apply' ? { operationsPlanned: 0, operationsExecuted: 0 } : undefined,
|
|
146
|
+
),
|
|
147
|
+
...ifDefined(
|
|
148
|
+
'marker',
|
|
149
|
+
mode === 'apply'
|
|
150
|
+
? {
|
|
151
|
+
storageHash: existingMarker.storageHash,
|
|
142
152
|
profileHash: existingMarker.profileHash,
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
}
|
|
154
|
+
: undefined,
|
|
155
|
+
),
|
|
146
156
|
summary: 'Database already at target contract state',
|
|
147
157
|
};
|
|
148
158
|
return ok(result);
|
|
@@ -159,11 +169,11 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
159
169
|
code: 'MARKER_ORIGIN_MISMATCH' as const,
|
|
160
170
|
summary: 'Existing contract marker does not match plan destination',
|
|
161
171
|
marker: {
|
|
162
|
-
|
|
172
|
+
storageHash: existingMarker.storageHash,
|
|
163
173
|
profileHash: existingMarker.profileHash,
|
|
164
174
|
},
|
|
165
175
|
destination: {
|
|
166
|
-
|
|
176
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
167
177
|
profileHash: migrationPlan.destination.profileHash,
|
|
168
178
|
},
|
|
169
179
|
why: undefined,
|
|
@@ -181,9 +191,17 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
181
191
|
|
|
182
192
|
// Plan mode - don't execute
|
|
183
193
|
if (mode === 'plan') {
|
|
194
|
+
const planSql = extractOperationStatements(familyInstance.familyId, migrationPlan.operations);
|
|
184
195
|
const result: DbInitSuccess = {
|
|
185
196
|
mode: 'plan',
|
|
186
|
-
plan: {
|
|
197
|
+
plan: {
|
|
198
|
+
operations: stripOperations(migrationPlan.operations),
|
|
199
|
+
...ifDefined('sql', planSql),
|
|
200
|
+
},
|
|
201
|
+
destination: {
|
|
202
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
203
|
+
...ifDefined('profileHash', migrationPlan.destination.profileHash),
|
|
204
|
+
},
|
|
187
205
|
summary: `Planned ${migrationPlan.operations.length} operation(s)`,
|
|
188
206
|
};
|
|
189
207
|
return ok(result);
|
|
@@ -198,34 +216,14 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
198
216
|
label: 'Applying migration plan',
|
|
199
217
|
});
|
|
200
218
|
|
|
201
|
-
const callbacks = onProgress
|
|
202
|
-
? {
|
|
203
|
-
onOperationStart: (op: MigrationPlanOperation) => {
|
|
204
|
-
onProgress({
|
|
205
|
-
action: 'dbInit',
|
|
206
|
-
kind: 'spanStart',
|
|
207
|
-
spanId: `operation:${op.id}`,
|
|
208
|
-
parentSpanId: applySpanId,
|
|
209
|
-
label: op.label,
|
|
210
|
-
});
|
|
211
|
-
},
|
|
212
|
-
onOperationComplete: (op: MigrationPlanOperation) => {
|
|
213
|
-
onProgress({
|
|
214
|
-
action: 'dbInit',
|
|
215
|
-
kind: 'spanEnd',
|
|
216
|
-
spanId: `operation:${op.id}`,
|
|
217
|
-
outcome: 'ok',
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
}
|
|
221
|
-
: undefined;
|
|
219
|
+
const callbacks = createOperationCallbacks(onProgress, 'dbInit', applySpanId);
|
|
222
220
|
|
|
223
221
|
const runnerResult: MigrationRunnerResult = await runner.execute({
|
|
224
222
|
plan: migrationPlan,
|
|
225
223
|
driver,
|
|
226
|
-
destinationContract:
|
|
224
|
+
destinationContract: contract,
|
|
227
225
|
policy,
|
|
228
|
-
...(callbacks
|
|
226
|
+
...ifDefined('callbacks', callbacks),
|
|
229
227
|
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
230
228
|
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
231
229
|
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
@@ -264,18 +262,24 @@ export async function executeDbInit<TFamilyId extends string, TTargetId extends
|
|
|
264
262
|
|
|
265
263
|
const result: DbInitSuccess = {
|
|
266
264
|
mode: 'apply',
|
|
267
|
-
plan: {
|
|
265
|
+
plan: {
|
|
266
|
+
operations: stripOperations(migrationPlan.operations),
|
|
267
|
+
},
|
|
268
|
+
destination: {
|
|
269
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
270
|
+
...ifDefined('profileHash', migrationPlan.destination.profileHash),
|
|
271
|
+
},
|
|
268
272
|
execution: {
|
|
269
273
|
operationsPlanned: execution.operationsPlanned,
|
|
270
274
|
operationsExecuted: execution.operationsExecuted,
|
|
271
275
|
},
|
|
272
276
|
marker: migrationPlan.destination.profileHash
|
|
273
277
|
? {
|
|
274
|
-
|
|
278
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
275
279
|
profileHash: migrationPlan.destination.profileHash,
|
|
276
280
|
}
|
|
277
|
-
: {
|
|
278
|
-
summary: `Applied ${execution.operationsExecuted} operation(s),
|
|
281
|
+
: { storageHash: migrationPlan.destination.storageHash },
|
|
282
|
+
summary: `Applied ${execution.operationsExecuted} operation(s), database signed`,
|
|
279
283
|
};
|
|
280
284
|
return ok(result);
|
|
281
285
|
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
3
|
+
import type {
|
|
4
|
+
ControlDriverInstance,
|
|
5
|
+
ControlFamilyInstance,
|
|
6
|
+
MigrationPlannerResult,
|
|
7
|
+
MigrationRunnerResult,
|
|
8
|
+
TargetMigrationsCapability,
|
|
9
|
+
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
11
|
+
import { notOk, ok } from '@prisma-next/utils/result';
|
|
12
|
+
import type { DbUpdateResult, DbUpdateSuccess, OnControlProgress } from '../types';
|
|
13
|
+
import { extractOperationStatements } from './extract-operation-statements';
|
|
14
|
+
import { createOperationCallbacks, stripOperations } from './migration-helpers';
|
|
15
|
+
|
|
16
|
+
// F12: db update allows additive, widening, and destructive operations.
|
|
17
|
+
const DB_UPDATE_POLICY = {
|
|
18
|
+
allowedOperationClasses: ['additive', 'widening', 'destructive'] as const,
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for the executeDbUpdate operation.
|
|
23
|
+
* Config-agnostic: receives pre-resolved driver, family, contract, and migrations capability.
|
|
24
|
+
*/
|
|
25
|
+
export interface ExecuteDbUpdateOptions<TFamilyId extends string, TTargetId extends string> {
|
|
26
|
+
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
27
|
+
readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
|
|
28
|
+
readonly contract: Contract;
|
|
29
|
+
readonly mode: 'plan' | 'apply';
|
|
30
|
+
readonly migrations: TargetMigrationsCapability<
|
|
31
|
+
TFamilyId,
|
|
32
|
+
TTargetId,
|
|
33
|
+
ControlFamilyInstance<TFamilyId, unknown>
|
|
34
|
+
>;
|
|
35
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
36
|
+
readonly acceptDataLoss?: boolean;
|
|
37
|
+
/** Optional progress callback for observing operation progress. */
|
|
38
|
+
readonly onProgress?: OnControlProgress;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Executes the db update operation: introspect → plan → (optionally) apply → marker.
|
|
43
|
+
*
|
|
44
|
+
* db update is a pure reconciliation command: it introspects the live schema, plans the diff
|
|
45
|
+
* to the destination contract, and applies operations. The marker is bookkeeping only — written
|
|
46
|
+
* after apply so that `verify` and `db init` can reference it, but never read or validated
|
|
47
|
+
* by db update itself. The runner creates the marker table if it does not exist.
|
|
48
|
+
*/
|
|
49
|
+
export async function executeDbUpdate<TFamilyId extends string, TTargetId extends string>(
|
|
50
|
+
options: ExecuteDbUpdateOptions<TFamilyId, TTargetId>,
|
|
51
|
+
): Promise<DbUpdateResult> {
|
|
52
|
+
const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =
|
|
53
|
+
options;
|
|
54
|
+
|
|
55
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
56
|
+
const runner = migrations.createRunner(familyInstance);
|
|
57
|
+
|
|
58
|
+
const introspectSpanId = 'introspect';
|
|
59
|
+
onProgress?.({
|
|
60
|
+
action: 'dbUpdate',
|
|
61
|
+
kind: 'spanStart',
|
|
62
|
+
spanId: introspectSpanId,
|
|
63
|
+
label: 'Introspecting database schema',
|
|
64
|
+
});
|
|
65
|
+
const schemaIR = await familyInstance.introspect({ driver });
|
|
66
|
+
onProgress?.({
|
|
67
|
+
action: 'dbUpdate',
|
|
68
|
+
kind: 'spanEnd',
|
|
69
|
+
spanId: introspectSpanId,
|
|
70
|
+
outcome: 'ok',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const policy = DB_UPDATE_POLICY;
|
|
74
|
+
|
|
75
|
+
const planSpanId = 'plan';
|
|
76
|
+
onProgress?.({
|
|
77
|
+
action: 'dbUpdate',
|
|
78
|
+
kind: 'spanStart',
|
|
79
|
+
spanId: planSpanId,
|
|
80
|
+
label: 'Planning migration',
|
|
81
|
+
});
|
|
82
|
+
const plannerResult: MigrationPlannerResult = await planner.plan({
|
|
83
|
+
contract,
|
|
84
|
+
schema: schemaIR,
|
|
85
|
+
policy,
|
|
86
|
+
frameworkComponents,
|
|
87
|
+
});
|
|
88
|
+
if (plannerResult.kind === 'failure') {
|
|
89
|
+
onProgress?.({
|
|
90
|
+
action: 'dbUpdate',
|
|
91
|
+
kind: 'spanEnd',
|
|
92
|
+
spanId: planSpanId,
|
|
93
|
+
outcome: 'error',
|
|
94
|
+
});
|
|
95
|
+
return notOk({
|
|
96
|
+
code: 'PLANNING_FAILED',
|
|
97
|
+
summary: 'Migration planning failed due to conflicts',
|
|
98
|
+
conflicts: plannerResult.conflicts,
|
|
99
|
+
why: undefined,
|
|
100
|
+
meta: undefined,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
onProgress?.({
|
|
104
|
+
action: 'dbUpdate',
|
|
105
|
+
kind: 'spanEnd',
|
|
106
|
+
spanId: planSpanId,
|
|
107
|
+
outcome: 'ok',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const migrationPlan = plannerResult.plan;
|
|
111
|
+
|
|
112
|
+
if (mode === 'plan') {
|
|
113
|
+
const planSql = extractOperationStatements(familyInstance.familyId, migrationPlan.operations);
|
|
114
|
+
const result: DbUpdateSuccess = {
|
|
115
|
+
mode: 'plan',
|
|
116
|
+
plan: {
|
|
117
|
+
operations: stripOperations(migrationPlan.operations),
|
|
118
|
+
...(planSql !== undefined ? { sql: planSql } : {}),
|
|
119
|
+
},
|
|
120
|
+
destination: {
|
|
121
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
122
|
+
...ifDefined('profileHash', migrationPlan.destination.profileHash),
|
|
123
|
+
},
|
|
124
|
+
summary: `Planned ${migrationPlan.operations.length} operation(s)`,
|
|
125
|
+
};
|
|
126
|
+
return ok(result);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// When applying, require explicit acceptance for destructive operations
|
|
130
|
+
if (!options.acceptDataLoss) {
|
|
131
|
+
const destructiveOps = migrationPlan.operations
|
|
132
|
+
.filter((op) => op.operationClass === 'destructive')
|
|
133
|
+
.map((op) => ({ id: op.id, label: op.label }));
|
|
134
|
+
if (destructiveOps.length > 0) {
|
|
135
|
+
return notOk({
|
|
136
|
+
code: 'DESTRUCTIVE_CHANGES',
|
|
137
|
+
summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
|
|
138
|
+
why: 'Destructive operations require confirmation — re-run with -y to accept',
|
|
139
|
+
conflicts: undefined,
|
|
140
|
+
meta: { destructiveOperations: destructiveOps },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const applySpanId = 'apply';
|
|
146
|
+
onProgress?.({
|
|
147
|
+
action: 'dbUpdate',
|
|
148
|
+
kind: 'spanStart',
|
|
149
|
+
spanId: applySpanId,
|
|
150
|
+
label: 'Applying migration plan',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const callbacks = createOperationCallbacks(onProgress, 'dbUpdate', applySpanId);
|
|
154
|
+
|
|
155
|
+
const runnerResult: MigrationRunnerResult = await runner.execute({
|
|
156
|
+
plan: migrationPlan,
|
|
157
|
+
driver,
|
|
158
|
+
destinationContract: contract,
|
|
159
|
+
policy,
|
|
160
|
+
...(callbacks ? { callbacks } : {}),
|
|
161
|
+
// db update plans and applies from a single introspection pass, so per-operation pre/postchecks
|
|
162
|
+
// and idempotency probes are intentionally disabled to avoid redundant roundtrips.
|
|
163
|
+
executionChecks: {
|
|
164
|
+
prechecks: false,
|
|
165
|
+
postchecks: false,
|
|
166
|
+
idempotencyChecks: false,
|
|
167
|
+
},
|
|
168
|
+
frameworkComponents,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!runnerResult.ok) {
|
|
172
|
+
onProgress?.({
|
|
173
|
+
action: 'dbUpdate',
|
|
174
|
+
kind: 'spanEnd',
|
|
175
|
+
spanId: applySpanId,
|
|
176
|
+
outcome: 'error',
|
|
177
|
+
});
|
|
178
|
+
return notOk({
|
|
179
|
+
code: 'RUNNER_FAILED',
|
|
180
|
+
summary: runnerResult.failure.summary,
|
|
181
|
+
why: runnerResult.failure.why,
|
|
182
|
+
meta: runnerResult.failure.meta,
|
|
183
|
+
conflicts: undefined,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const execution = runnerResult.value;
|
|
188
|
+
onProgress?.({
|
|
189
|
+
action: 'dbUpdate',
|
|
190
|
+
kind: 'spanEnd',
|
|
191
|
+
spanId: applySpanId,
|
|
192
|
+
outcome: 'ok',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result: DbUpdateSuccess = {
|
|
196
|
+
mode: 'apply',
|
|
197
|
+
plan: {
|
|
198
|
+
operations: stripOperations(migrationPlan.operations),
|
|
199
|
+
},
|
|
200
|
+
destination: {
|
|
201
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
202
|
+
...ifDefined('profileHash', migrationPlan.destination.profileHash),
|
|
203
|
+
},
|
|
204
|
+
execution: {
|
|
205
|
+
operationsPlanned: execution.operationsPlanned,
|
|
206
|
+
operationsExecuted: execution.operationsExecuted,
|
|
207
|
+
},
|
|
208
|
+
marker: migrationPlan.destination.profileHash
|
|
209
|
+
? {
|
|
210
|
+
storageHash: migrationPlan.destination.storageHash,
|
|
211
|
+
profileHash: migrationPlan.destination.profileHash,
|
|
212
|
+
}
|
|
213
|
+
: { storageHash: migrationPlan.destination.storageHash },
|
|
214
|
+
summary:
|
|
215
|
+
execution.operationsExecuted === 0
|
|
216
|
+
? 'Database already matches contract, signature updated'
|
|
217
|
+
: `Applied ${execution.operationsExecuted} operation(s), signature updated`,
|
|
218
|
+
};
|
|
219
|
+
return ok(result);
|
|
220
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
|
+
import { extractSqlDdl } from './extract-sql-ddl';
|
|
3
|
+
|
|
4
|
+
export function extractOperationStatements(
|
|
5
|
+
familyId: string,
|
|
6
|
+
operations: readonly MigrationPlanOperation[],
|
|
7
|
+
): string[] | undefined {
|
|
8
|
+
switch (familyId) {
|
|
9
|
+
case 'sql':
|
|
10
|
+
return extractSqlDdl(operations);
|
|
11
|
+
default:
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shape of an SQL execute step on SqlMigrationPlanOperation.
|
|
5
|
+
* Used for runtime type narrowing without importing the concrete SQL type.
|
|
6
|
+
*/
|
|
7
|
+
interface SqlExecuteStep {
|
|
8
|
+
readonly sql: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isDdlStatement(sqlStatement: string): boolean {
|
|
12
|
+
const trimmed = sqlStatement.trim().toLowerCase();
|
|
13
|
+
return (
|
|
14
|
+
trimmed.startsWith('create ') || trimmed.startsWith('alter ') || trimmed.startsWith('drop ')
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function hasExecuteSteps(
|
|
19
|
+
operation: MigrationPlanOperation,
|
|
20
|
+
): operation is MigrationPlanOperation & { readonly execute: readonly SqlExecuteStep[] } {
|
|
21
|
+
const candidate = operation as unknown as Record<string, unknown>;
|
|
22
|
+
if (!('execute' in candidate) || !Array.isArray(candidate['execute'])) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return candidate['execute'].every(
|
|
26
|
+
(step: unknown) => typeof step === 'object' && step !== null && 'sql' in step,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts a best-effort SQL DDL preview for CLI plan output.
|
|
32
|
+
* This helper is presentation-only and is never used to decide migration correctness.
|
|
33
|
+
*/
|
|
34
|
+
export function extractSqlDdl(operations: readonly MigrationPlanOperation[]): string[] {
|
|
35
|
+
const statements: string[] = [];
|
|
36
|
+
for (const operation of operations) {
|
|
37
|
+
if (!hasExecuteSteps(operation)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
for (const step of operation.execute) {
|
|
41
|
+
if (typeof step.sql === 'string' && isDdlStatement(step.sql)) {
|
|
42
|
+
statements.push(step.sql.trim());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return statements;
|
|
47
|
+
}
|