@prisma-next/cli 0.5.0-dev.3 → 0.5.0-dev.30
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 +131 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-keSCAgjW.mjs} +43 -19
- package/dist/client-keSCAgjW.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +8 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -5
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +8 -7
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +10 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +10 -9
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +2 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +15 -38
- 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 +24 -30
- 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 -47
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +31 -40
- 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 +28 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -4
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +7 -2
- 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-DS5NzZh2.mjs +6 -0
- package/dist/contract-emit-DWtGQYCD.mjs +150 -0
- package/dist/contract-emit-DWtGQYCD.mjs.map +1 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.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-BP3DrGgz.mjs → contract-infer-GztVCOCJ.mjs} +11 -19
- package/dist/contract-infer-GztVCOCJ.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +78 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +7 -5
- package/dist/exports/index.mjs +8 -3
- package/dist/exports/index.mjs.map +1 -1
- 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-DAbQMxIR.mjs +2062 -0
- package/dist/init-DAbQMxIR.mjs.map +1 -0
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-BaR9ISwa.mjs} +9 -9
- package/dist/inspect-live-schema-BaR9ISwa.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-CLMD302g.mjs → migration-command-scaffold-D1dWuEWQ.mjs} +7 -7
- package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-D1dWuEWQ.mjs.map} +1 -1
- package/dist/{migration-status-B0HLF7So.mjs → migration-status-CP5k8O5i.mjs} +21 -35
- package/dist/migration-status-CP5k8O5i.mjs.map +1 -0
- package/dist/{migrations-B0dOQlk0.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-CIyu0Pdt.mjs → result-handler-BmVh8AeV.mjs} +12 -93
- package/dist/result-handler-BmVh8AeV.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-BxiVp50b.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-BxiVp50b.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +21 -15
- 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 +16 -51
- package/src/commands/migration-new.ts +26 -32
- package/src/commands/migration-plan.ts +80 -55
- package/src/commands/migration-ref.ts +40 -54
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +33 -50
- 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 +8 -5
- package/src/control-api/operations/db-update.ts +8 -5
- package/src/control-api/operations/migration-apply.ts +29 -9
- package/src/control-api/types.ts +61 -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 +13 -26
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +2 -2
- 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-CNYyzJwF.mjs +0 -195
- package/dist/contract-emit-CNYyzJwF.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-fhNwwhkQ.mjs +0 -4
- package/dist/contract-infer-BP3DrGgz.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-DWzf4Q_m.mjs.map +0 -1
- package/dist/migration-status-B0HLF7So.mjs.map +0 -1
- package/dist/migrations-B0dOQlk0.mjs.map +0 -1
- package/dist/result-handler-CIyu0Pdt.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,18 +1,13 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
1
|
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
3
2
|
import { errorContractConfigMissing } from '@prisma-next/errors/control';
|
|
3
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
4
4
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { dirname, relative, resolve } from 'pathe';
|
|
7
7
|
import { loadConfig } from '../config-loader';
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
11
|
-
CliStructuredError,
|
|
12
|
-
errorContractValidationFailed,
|
|
13
|
-
errorRuntime,
|
|
14
|
-
errorUnexpected,
|
|
15
|
-
} from '../utils/cli-errors';
|
|
8
|
+
import { executeContractEmit } from '../control-api/operations/contract-emit';
|
|
9
|
+
import type { ContractEmitResult } from '../control-api/types';
|
|
10
|
+
import { CliStructuredError, errorUnexpected } from '../utils/cli-errors';
|
|
16
11
|
import {
|
|
17
12
|
addGlobalOptions,
|
|
18
13
|
setCommandDescriptions,
|
|
@@ -34,74 +29,29 @@ interface ContractEmitOptions extends CommonCommandOptions {
|
|
|
34
29
|
readonly config?: string;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return diagnostics.map((diagnostic) => {
|
|
42
|
-
const location =
|
|
43
|
-
diagnostic.sourceId && diagnostic.span
|
|
44
|
-
? ` (${diagnostic.sourceId}:${diagnostic.span.start.line}:${diagnostic.span.start.column})`
|
|
45
|
-
: diagnostic.sourceId
|
|
46
|
-
? ` (${diagnostic.sourceId})`
|
|
47
|
-
: '';
|
|
48
|
-
return {
|
|
49
|
-
kind: diagnostic.code,
|
|
50
|
-
message: `${diagnostic.message}${location}`,
|
|
51
|
-
};
|
|
52
|
-
});
|
|
32
|
+
interface HeaderPaths {
|
|
33
|
+
readonly displayConfigPath: string;
|
|
34
|
+
readonly outputJsonPath: string;
|
|
35
|
+
readonly outputDtsPath: string;
|
|
53
36
|
}
|
|
54
37
|
|
|
55
38
|
/**
|
|
56
|
-
*
|
|
39
|
+
* Pre-load the config just to compute display paths for the styled header. The
|
|
40
|
+
* actual emit work goes through `executeContractEmit`, which loads the config
|
|
41
|
+
* itself; the redundant load here is bounded and lets the header render before
|
|
42
|
+
* the emit spans start.
|
|
57
43
|
*/
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return errorRuntime(failure.summary, {
|
|
65
|
-
why: failure.why ?? 'Contract source provider failed',
|
|
66
|
-
fix: 'Check your contract source provider in prisma-next.config.ts and ensure it returns Result<Contract, Diagnostics>',
|
|
67
|
-
...(issues.length > 0 ? { meta: { issues } } : {}),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (failure.code === 'CONTRACT_VALIDATION_FAILED') {
|
|
72
|
-
return errorContractValidationFailed(
|
|
73
|
-
failure.why ?? 'Contract validation failed while emitting',
|
|
74
|
-
context?.configPath ? { where: { path: context.configPath } } : undefined,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (failure.code === 'EMIT_FAILED') {
|
|
79
|
-
return errorRuntime(failure.summary, {
|
|
80
|
-
why: failure.why ?? 'Failed to emit contract',
|
|
81
|
-
fix: 'Check your contract configuration and ensure the source is valid',
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Exhaustive check - TypeScript will error if a new code is added but not handled
|
|
86
|
-
const exhaustive: never = failure.code;
|
|
87
|
-
throw new Error(`Unhandled EmitFailure code: ${exhaustive}`);
|
|
88
|
-
}
|
|
44
|
+
async function resolveHeaderPaths(
|
|
45
|
+
configOption: string | undefined,
|
|
46
|
+
): Promise<Result<HeaderPaths, CliStructuredError>> {
|
|
47
|
+
const displayConfigPath = configOption
|
|
48
|
+
? relative(process.cwd(), resolve(configOption))
|
|
49
|
+
: 'prisma-next.config.ts';
|
|
89
50
|
|
|
90
|
-
/**
|
|
91
|
-
* Executes the contract emit command and returns a structured Result.
|
|
92
|
-
*/
|
|
93
|
-
async function executeContractEmitCommand(
|
|
94
|
-
options: ContractEmitOptions,
|
|
95
|
-
flags: GlobalFlags,
|
|
96
|
-
ui: TerminalUI,
|
|
97
|
-
startTime: number,
|
|
98
|
-
): Promise<Result<EmitContractResult, CliStructuredError>> {
|
|
99
|
-
// Load config
|
|
100
51
|
let config: Awaited<ReturnType<typeof loadConfig>>;
|
|
101
52
|
try {
|
|
102
|
-
config = await loadConfig(
|
|
53
|
+
config = await loadConfig(configOption);
|
|
103
54
|
} catch (error) {
|
|
104
|
-
// Convert thrown CliStructuredError to Result
|
|
105
55
|
if (error instanceof CliStructuredError) {
|
|
106
56
|
return notOk(error);
|
|
107
57
|
}
|
|
@@ -112,33 +62,19 @@ async function executeContractEmitCommand(
|
|
|
112
62
|
);
|
|
113
63
|
}
|
|
114
64
|
|
|
115
|
-
|
|
116
|
-
? relative(process.cwd(), resolve(options.config))
|
|
117
|
-
: 'prisma-next.config.ts';
|
|
118
|
-
|
|
119
|
-
// Resolve contract from config
|
|
120
|
-
if (!config.contract) {
|
|
65
|
+
if (!config.contract?.output) {
|
|
121
66
|
return notOk(
|
|
122
67
|
errorContractConfigMissing({
|
|
123
|
-
why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
|
|
68
|
+
why: 'Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }',
|
|
124
69
|
}),
|
|
125
70
|
);
|
|
126
71
|
}
|
|
127
72
|
|
|
128
|
-
// Contract config is already normalized by defineConfig() with defaults applied
|
|
129
|
-
const contractConfig = config.contract;
|
|
130
|
-
|
|
131
|
-
// Resolve artifact paths from config (already normalized by defineConfig() with defaults)
|
|
132
|
-
if (!contractConfig.output) {
|
|
133
|
-
return notOk(
|
|
134
|
-
errorContractConfigMissing({
|
|
135
|
-
why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;
|
|
140
73
|
try {
|
|
141
|
-
|
|
74
|
+
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = getEmittedArtifactPaths(
|
|
75
|
+
config.contract.output,
|
|
76
|
+
);
|
|
77
|
+
return ok({ displayConfigPath, outputJsonPath, outputDtsPath });
|
|
142
78
|
} catch (error) {
|
|
143
79
|
return notOk(
|
|
144
80
|
errorContractConfigMissing({
|
|
@@ -146,96 +82,65 @@ async function executeContractEmitCommand(
|
|
|
146
82
|
}),
|
|
147
83
|
);
|
|
148
84
|
}
|
|
149
|
-
|
|
85
|
+
}
|
|
150
86
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{ label: 'config', value: configPath },
|
|
161
|
-
{ label: 'contract', value: contractPath },
|
|
162
|
-
{ label: 'types', value: typesPath },
|
|
163
|
-
],
|
|
164
|
-
flags,
|
|
165
|
-
});
|
|
166
|
-
ui.stderr(header);
|
|
87
|
+
async function executeContractEmitCommand(
|
|
88
|
+
options: ContractEmitOptions,
|
|
89
|
+
flags: GlobalFlags,
|
|
90
|
+
ui: TerminalUI,
|
|
91
|
+
startTime: number,
|
|
92
|
+
): Promise<Result<EmitContractResult, CliStructuredError>> {
|
|
93
|
+
const headerPathsResult = await resolveHeaderPaths(options.config);
|
|
94
|
+
if (!headerPathsResult.ok) {
|
|
95
|
+
return headerPathsResult;
|
|
167
96
|
}
|
|
97
|
+
const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;
|
|
168
98
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
99
|
+
if (!flags.json && !flags.quiet) {
|
|
100
|
+
ui.stderr(
|
|
101
|
+
formatStyledHeader({
|
|
102
|
+
command: 'contract emit',
|
|
103
|
+
description: 'Emit your contract artifacts',
|
|
104
|
+
url: 'https://pris.ly/contract-emit',
|
|
105
|
+
details: [
|
|
106
|
+
{ label: 'config', value: displayConfigPath },
|
|
107
|
+
{ label: 'contract', value: relative(process.cwd(), outputJsonPath) },
|
|
108
|
+
{ label: 'types', value: relative(process.cwd(), outputDtsPath) },
|
|
109
|
+
],
|
|
110
|
+
flags,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
176
114
|
|
|
177
|
-
// Create progress adapter
|
|
178
115
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
116
|
+
const configPath = options.config ? resolve(options.config) : 'prisma-next.config.ts';
|
|
179
117
|
|
|
118
|
+
let result: ContractEmitResult;
|
|
180
119
|
try {
|
|
181
|
-
|
|
182
|
-
const result = await client.emit({
|
|
183
|
-
contractConfig: {
|
|
184
|
-
source: contractConfig.source,
|
|
185
|
-
output: outputJsonPath,
|
|
186
|
-
},
|
|
187
|
-
onProgress,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Handle failures by mapping to CLI structured error
|
|
191
|
-
if (!result.ok) {
|
|
192
|
-
return notOk(mapEmitFailure(result.failure, { configPath }));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Create directories if needed
|
|
196
|
-
mkdirSync(dirname(outputJsonPath), { recursive: true });
|
|
197
|
-
mkdirSync(dirname(outputDtsPath), { recursive: true });
|
|
198
|
-
|
|
199
|
-
// Write the results to files
|
|
200
|
-
writeFileSync(outputJsonPath, result.value.contractJson, 'utf-8');
|
|
201
|
-
writeFileSync(outputDtsPath, result.value.contractDts, 'utf-8');
|
|
202
|
-
|
|
203
|
-
// Validate that contract.d.ts type imports are resolvable
|
|
204
|
-
const { validateContractDeps } = await import('../utils/validate-contract-deps');
|
|
205
|
-
const depsValidation = validateContractDeps(result.value.contractDts, dirname(outputDtsPath));
|
|
206
|
-
if (depsValidation.warning) {
|
|
207
|
-
ui.warn(depsValidation.warning);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Convert success result to CLI output format
|
|
211
|
-
const emitResult: EmitContractResult = {
|
|
212
|
-
storageHash: result.value.storageHash,
|
|
213
|
-
...(result.value.executionHash ? { executionHash: result.value.executionHash } : {}),
|
|
214
|
-
profileHash: result.value.profileHash,
|
|
215
|
-
outDir: dirname(outputJsonPath),
|
|
216
|
-
files: {
|
|
217
|
-
json: outputJsonPath,
|
|
218
|
-
dts: outputDtsPath,
|
|
219
|
-
},
|
|
220
|
-
timings: { total: Date.now() - startTime },
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
return ok(emitResult);
|
|
120
|
+
result = await executeContractEmit({ configPath, onProgress });
|
|
224
121
|
} catch (error) {
|
|
225
|
-
// Use static type guard to work across module boundaries
|
|
226
122
|
if (CliStructuredError.is(error)) {
|
|
227
123
|
return notOk(error);
|
|
228
124
|
}
|
|
229
|
-
|
|
230
|
-
// Wrap unexpected errors
|
|
231
125
|
return notOk(
|
|
232
126
|
errorUnexpected('Unexpected error during contract emit', {
|
|
233
127
|
why: error instanceof Error ? error.message : String(error),
|
|
234
128
|
}),
|
|
235
129
|
);
|
|
236
|
-
} finally {
|
|
237
|
-
await client.close();
|
|
238
130
|
}
|
|
131
|
+
|
|
132
|
+
if (result.validationWarning) {
|
|
133
|
+
ui.warn(result.validationWarning);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return ok({
|
|
137
|
+
storageHash: result.storageHash,
|
|
138
|
+
...ifDefined('executionHash', result.executionHash),
|
|
139
|
+
profileHash: result.profileHash,
|
|
140
|
+
outDir: dirname(result.files.json),
|
|
141
|
+
files: result.files,
|
|
142
|
+
timings: { total: Date.now() - startTime },
|
|
143
|
+
});
|
|
239
144
|
}
|
|
240
145
|
|
|
241
146
|
export function createContractEmitCommand(): Command {
|
|
@@ -260,7 +165,6 @@ export function createContractEmitCommand(): Command {
|
|
|
260
165
|
|
|
261
166
|
const result = await executeContractEmitCommand(options, flags, ui, startTime);
|
|
262
167
|
|
|
263
|
-
// Handle result - formats output and returns exit code
|
|
264
168
|
const exitCode = handleResult(result, flags, ui, (emitResult) => {
|
|
265
169
|
if (flags.json) {
|
|
266
170
|
ui.output(formatEmitJson(emitResult));
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
3
|
-
import { printPsl
|
|
4
|
-
import {
|
|
5
|
-
createPostgresDefaultMapping,
|
|
6
|
-
createPostgresTypeMap,
|
|
7
|
-
extractEnumInfo,
|
|
8
|
-
parseRawDefault,
|
|
9
|
-
} from '@prisma-next/psl-printer/postgres';
|
|
3
|
+
import { printPsl } from '@prisma-next/psl-printer';
|
|
10
4
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
11
5
|
import { Command } from 'commander';
|
|
12
6
|
import { dirname, relative } from 'pathe';
|
|
@@ -59,26 +53,19 @@ async function executeContractInferCommand(
|
|
|
59
53
|
return inspectResult;
|
|
60
54
|
}
|
|
61
55
|
|
|
62
|
-
const { config, target, meta } = inspectResult.value;
|
|
56
|
+
const { config, target, meta, pslContractAst } = inspectResult.value;
|
|
63
57
|
|
|
64
|
-
if (
|
|
58
|
+
if (!pslContractAst) {
|
|
65
59
|
return notOk(
|
|
66
|
-
errorRuntime(
|
|
67
|
-
why: 'contract
|
|
68
|
-
fix: 'Use
|
|
60
|
+
errorRuntime('contract infer is not supported for this family', {
|
|
61
|
+
why: 'The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.',
|
|
62
|
+
fix: 'Use a family that supports contract inference (e.g. SQL/Postgres).',
|
|
69
63
|
}),
|
|
70
64
|
);
|
|
71
65
|
}
|
|
72
66
|
|
|
73
|
-
const schema = validatePrintableSqlSchemaIR(inspectResult.value.schema);
|
|
74
67
|
const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
|
|
75
|
-
const
|
|
76
|
-
const pslContent = printPsl(schema, {
|
|
77
|
-
defaultMapping: createPostgresDefaultMapping(),
|
|
78
|
-
typeMap: createPostgresTypeMap(enumInfo.typeNames),
|
|
79
|
-
enumInfo,
|
|
80
|
-
parseRawDefault,
|
|
81
|
-
});
|
|
68
|
+
const pslContent = printPsl(pslContractAst);
|
|
82
69
|
|
|
83
70
|
if (existsSync(outputPath) && !flags.json && !flags.quiet) {
|
|
84
71
|
ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
|
package/src/commands/db-init.ts
CHANGED
|
@@ -112,7 +112,7 @@ async function executeDbUpdateCommand(
|
|
|
112
112
|
label: op.label,
|
|
113
113
|
operationClass: op.operationClass,
|
|
114
114
|
})),
|
|
115
|
-
...ifDefined('
|
|
115
|
+
...ifDefined('preview', result.value.plan.preview),
|
|
116
116
|
},
|
|
117
117
|
...ifDefined(
|
|
118
118
|
'execution',
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'pathe';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Catalog entry detected in a `pnpm-workspace.yaml` that overrides one of
|
|
6
|
+
* the packages `init` installs. The `version` is the raw value as written
|
|
7
|
+
* in the workspace file (no normalisation), so the warning surfaces the
|
|
8
|
+
* exact text the user can search for if they want to find the override.
|
|
9
|
+
*/
|
|
10
|
+
export interface PnpmCatalogOverride {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly version: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result of scanning for a pnpm workspace catalog that overrides any of
|
|
17
|
+
* the packages `init` is about to install. Returns `null` when no
|
|
18
|
+
* `pnpm-workspace.yaml` is found in `baseDir` or any ancestor; an empty
|
|
19
|
+
* `entries` array means a workspace exists but contains none of our
|
|
20
|
+
* packages.
|
|
21
|
+
*/
|
|
22
|
+
export interface PnpmCatalogScanResult {
|
|
23
|
+
/** Absolute path of the `pnpm-workspace.yaml` that was consulted. */
|
|
24
|
+
readonly workspaceFile: string;
|
|
25
|
+
readonly entries: readonly PnpmCatalogOverride[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
|
|
30
|
+
* its top-level `catalog:` block for entries that match any of `packages`.
|
|
31
|
+
*
|
|
32
|
+
* Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
|
|
33
|
+
* inside a pnpm workspace whose catalog overrides one of the packages it
|
|
34
|
+
* installs, surface a structured warning so the user knows the catalog
|
|
35
|
+
* version (not the published `latest`) is what ended up in their
|
|
36
|
+
* `node_modules`. pnpm itself does this silently; the warning closes the
|
|
37
|
+
* "looks fine, must be wrong version six months later" gap.
|
|
38
|
+
*
|
|
39
|
+
* Notes / scope:
|
|
40
|
+
*
|
|
41
|
+
* - We only inspect the unnamed top-level `catalog:` block. pnpm also
|
|
42
|
+
* supports `catalogs:` (plural — *named* catalogs referenced via
|
|
43
|
+
* `catalog:foo` specifiers); those don't apply to a vanilla
|
|
44
|
+
* `pnpm add prisma-next` invocation, so we skip them.
|
|
45
|
+
* - We don't validate YAML syntax exhaustively. The file format pnpm
|
|
46
|
+
* ships is line-oriented and well-known; a minimal regex is more
|
|
47
|
+
* robust than depending on a YAML parser for one warning.
|
|
48
|
+
* - We don't compare against the registry's `latest` — pnpm uses the
|
|
49
|
+
* catalog version regardless, so the warning fires whenever a match
|
|
50
|
+
* exists. The user-facing copy explains how to opt out.
|
|
51
|
+
*/
|
|
52
|
+
export function detectPnpmCatalogOverrides(
|
|
53
|
+
baseDir: string,
|
|
54
|
+
packages: readonly string[],
|
|
55
|
+
): PnpmCatalogScanResult | null {
|
|
56
|
+
const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
|
|
57
|
+
if (workspaceFile === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const contents = readFileSync(workspaceFile, 'utf-8');
|
|
62
|
+
const catalog = extractCatalogBlock(contents);
|
|
63
|
+
if (catalog === null) {
|
|
64
|
+
return { workspaceFile, entries: [] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const wanted = new Set(packages);
|
|
68
|
+
const entries: PnpmCatalogOverride[] = [];
|
|
69
|
+
for (const [name, version] of catalog) {
|
|
70
|
+
if (wanted.has(name)) {
|
|
71
|
+
entries.push({ name, version });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { workspaceFile, entries };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findNearestPnpmWorkspaceFile(baseDir: string): string | null {
|
|
78
|
+
let dir = baseDir;
|
|
79
|
+
let prev = '';
|
|
80
|
+
while (dir !== prev) {
|
|
81
|
+
const candidate = join(dir, 'pnpm-workspace.yaml');
|
|
82
|
+
if (existsSync(candidate)) {
|
|
83
|
+
return candidate;
|
|
84
|
+
}
|
|
85
|
+
prev = dir;
|
|
86
|
+
dir = dirname(dir);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the entries inside the top-level `catalog:` block as `[name, version]`
|
|
93
|
+
* pairs in document order, or `null` when no `catalog:` block exists.
|
|
94
|
+
*
|
|
95
|
+
* The parser is intentionally minimal: it reads line-by-line, locates the
|
|
96
|
+
* top-level `catalog:` line (no leading whitespace), then collects every
|
|
97
|
+
* subsequent indented line of the form `<key>: <value>` until the next
|
|
98
|
+
* top-level key (or end of file). Quotes around `<key>` and `<value>`
|
|
99
|
+
* are stripped; comments (`#…`) are ignored.
|
|
100
|
+
*/
|
|
101
|
+
function extractCatalogBlock(contents: string): Array<[string, string]> | null {
|
|
102
|
+
const lines = contents.split(/\r?\n/);
|
|
103
|
+
const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
|
|
104
|
+
if (startIdx === -1) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const entries: Array<[string, string]> = [];
|
|
109
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
110
|
+
const raw = lines[i] ?? '';
|
|
111
|
+
if (raw.trim() === '' || /^\s*#/.test(raw)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!/^\s/.test(raw)) {
|
|
115
|
+
// Hit the next top-level key — catalog block ended.
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
|
|
119
|
+
if (!match) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const name = match[1] ?? match[2] ?? match[3];
|
|
123
|
+
if (name === undefined) continue;
|
|
124
|
+
const rawValue = match[4] ?? '';
|
|
125
|
+
const version = stripQuotes(rawValue.trim());
|
|
126
|
+
if (version === '') continue;
|
|
127
|
+
entries.push([name, version]);
|
|
128
|
+
}
|
|
129
|
+
return entries;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function stripQuotes(value: string): string {
|
|
133
|
+
if (value.length >= 2) {
|
|
134
|
+
const first = value[0];
|
|
135
|
+
const last = value[value.length - 1];
|
|
136
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
137
|
+
return value.slice(1, -1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|