@prisma-next/cli 0.3.0-dev.13 → 0.3.0-dev.14
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 +109 -25
- package/dist/{chunk-CVNWLFXO.js → chunk-6EPKRATC.js} +2 -2
- package/dist/{chunk-BZMBKEEQ.js → chunk-DIJPT5TZ.js} +3 -33
- package/dist/chunk-DIJPT5TZ.js.map +1 -0
- package/dist/{chunk-QUPBU4KV.js → chunk-MG7PBERL.js} +2 -2
- package/dist/chunk-VI2YETW7.js +38 -0
- package/dist/chunk-VI2YETW7.js.map +1 -0
- package/dist/cli.js +34 -38
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.js +3 -2
- package/dist/commands/db-init.d.ts.map +1 -1
- package/dist/commands/db-init.js +21 -19
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-introspect.d.ts.map +1 -1
- package/dist/commands/db-introspect.js +13 -16
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.d.ts.map +1 -1
- package/dist/commands/db-schema-verify.js +8 -7
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.js +8 -7
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.d.ts.map +1 -1
- package/dist/commands/db-verify.js +8 -7
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/control-api/client.d.ts +13 -0
- package/dist/control-api/client.d.ts.map +1 -0
- package/dist/control-api/operations/db-init.d.ts +27 -0
- package/dist/control-api/operations/db-init.d.ts.map +1 -0
- package/dist/control-api/types.d.ts +203 -0
- package/dist/control-api/types.d.ts.map +1 -0
- package/dist/exports/control-api.d.ts +13 -0
- package/dist/exports/control-api.d.ts.map +1 -0
- package/dist/exports/control-api.js +240 -0
- package/dist/exports/control-api.js.map +1 -0
- package/dist/exports/index.js +3 -2
- package/dist/exports/index.js.map +1 -1
- package/dist/utils/cli-errors.d.ts +1 -1
- package/dist/utils/cli-errors.d.ts.map +1 -1
- package/package.json +14 -10
- package/src/commands/db-init.ts +12 -11
- package/src/commands/db-introspect.ts +16 -18
- package/src/commands/db-schema-verify.ts +6 -7
- package/src/commands/db-sign.ts +6 -6
- package/src/commands/db-verify.ts +6 -7
- package/src/control-api/client.ts +229 -0
- package/src/control-api/operations/db-init.ts +167 -0
- package/src/control-api/types.ts +251 -0
- package/src/exports/control-api.ts +34 -0
- package/src/utils/cli-errors.ts +1 -1
- package/dist/chunk-BZMBKEEQ.js.map +0 -1
- /package/dist/{chunk-CVNWLFXO.js.map → chunk-6EPKRATC.js.map} +0 -0
- /package/dist/{chunk-QUPBU4KV.js.map → chunk-MG7PBERL.js.map} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertFrameworkComponentsCompatible
|
|
3
|
+
} from "../chunk-6EPKRATC.js";
|
|
4
|
+
import "../chunk-VI2YETW7.js";
|
|
5
|
+
|
|
6
|
+
// src/control-api/client.ts
|
|
7
|
+
import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
|
|
8
|
+
|
|
9
|
+
// src/control-api/operations/db-init.ts
|
|
10
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
11
|
+
async function executeDbInit(options) {
|
|
12
|
+
const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents } = options;
|
|
13
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
14
|
+
const runner = migrations.createRunner(familyInstance);
|
|
15
|
+
const schemaIR = await familyInstance.introspect({ driver });
|
|
16
|
+
const policy = { allowedOperationClasses: ["additive"] };
|
|
17
|
+
const plannerResult = await planner.plan({
|
|
18
|
+
contract: contractIR,
|
|
19
|
+
schema: schemaIR,
|
|
20
|
+
policy,
|
|
21
|
+
frameworkComponents
|
|
22
|
+
});
|
|
23
|
+
if (plannerResult.kind === "failure") {
|
|
24
|
+
return notOk({
|
|
25
|
+
code: "PLANNING_FAILED",
|
|
26
|
+
summary: "Migration planning failed due to conflicts",
|
|
27
|
+
conflicts: plannerResult.conflicts
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const migrationPlan = plannerResult.plan;
|
|
31
|
+
const existingMarker = await familyInstance.readMarker({ driver });
|
|
32
|
+
if (existingMarker) {
|
|
33
|
+
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
34
|
+
if (markerMatchesDestination) {
|
|
35
|
+
const result2 = {
|
|
36
|
+
mode,
|
|
37
|
+
plan: { operations: [] },
|
|
38
|
+
...mode === "apply" ? {
|
|
39
|
+
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
40
|
+
marker: {
|
|
41
|
+
coreHash: existingMarker.coreHash,
|
|
42
|
+
profileHash: existingMarker.profileHash
|
|
43
|
+
}
|
|
44
|
+
} : {},
|
|
45
|
+
summary: "Database already at target contract state"
|
|
46
|
+
};
|
|
47
|
+
return ok(result2);
|
|
48
|
+
}
|
|
49
|
+
return notOk({
|
|
50
|
+
code: "MARKER_ORIGIN_MISMATCH",
|
|
51
|
+
summary: "Existing contract marker does not match plan destination",
|
|
52
|
+
marker: {
|
|
53
|
+
coreHash: existingMarker.coreHash,
|
|
54
|
+
profileHash: existingMarker.profileHash
|
|
55
|
+
},
|
|
56
|
+
destination: {
|
|
57
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
58
|
+
profileHash: migrationPlan.destination.profileHash
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (mode === "plan") {
|
|
63
|
+
const result2 = {
|
|
64
|
+
mode: "plan",
|
|
65
|
+
plan: { operations: migrationPlan.operations },
|
|
66
|
+
summary: `Planned ${migrationPlan.operations.length} operation(s)`
|
|
67
|
+
};
|
|
68
|
+
return ok(result2);
|
|
69
|
+
}
|
|
70
|
+
const runnerResult = await runner.execute({
|
|
71
|
+
plan: migrationPlan,
|
|
72
|
+
driver,
|
|
73
|
+
destinationContract: contractIR,
|
|
74
|
+
policy,
|
|
75
|
+
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
76
|
+
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
77
|
+
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
78
|
+
executionChecks: {
|
|
79
|
+
prechecks: false,
|
|
80
|
+
postchecks: false,
|
|
81
|
+
idempotencyChecks: false
|
|
82
|
+
},
|
|
83
|
+
frameworkComponents
|
|
84
|
+
});
|
|
85
|
+
if (!runnerResult.ok) {
|
|
86
|
+
return notOk({
|
|
87
|
+
code: "RUNNER_FAILED",
|
|
88
|
+
summary: runnerResult.failure.summary
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const execution = runnerResult.value;
|
|
92
|
+
const result = {
|
|
93
|
+
mode: "apply",
|
|
94
|
+
plan: { operations: migrationPlan.operations },
|
|
95
|
+
execution: {
|
|
96
|
+
operationsPlanned: execution.operationsPlanned,
|
|
97
|
+
operationsExecuted: execution.operationsExecuted
|
|
98
|
+
},
|
|
99
|
+
marker: migrationPlan.destination.profileHash ? {
|
|
100
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
101
|
+
profileHash: migrationPlan.destination.profileHash
|
|
102
|
+
} : { coreHash: migrationPlan.destination.coreHash },
|
|
103
|
+
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
|
|
104
|
+
};
|
|
105
|
+
return ok(result);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/control-api/client.ts
|
|
109
|
+
function createControlClient(options) {
|
|
110
|
+
return new ControlClientImpl(options);
|
|
111
|
+
}
|
|
112
|
+
var ControlClientImpl = class {
|
|
113
|
+
options;
|
|
114
|
+
stack = null;
|
|
115
|
+
driver = null;
|
|
116
|
+
familyInstance = null;
|
|
117
|
+
frameworkComponents = null;
|
|
118
|
+
initialized = false;
|
|
119
|
+
defaultConnection;
|
|
120
|
+
constructor(options) {
|
|
121
|
+
this.options = options;
|
|
122
|
+
this.defaultConnection = options.connection;
|
|
123
|
+
}
|
|
124
|
+
init() {
|
|
125
|
+
if (this.initialized) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.stack = createControlPlaneStack({
|
|
129
|
+
target: this.options.target,
|
|
130
|
+
adapter: this.options.adapter,
|
|
131
|
+
driver: this.options.driver,
|
|
132
|
+
extensionPacks: this.options.extensionPacks
|
|
133
|
+
});
|
|
134
|
+
this.familyInstance = this.options.family.create(this.stack);
|
|
135
|
+
const rawComponents = [
|
|
136
|
+
this.options.target,
|
|
137
|
+
this.options.adapter,
|
|
138
|
+
...this.options.extensionPacks ?? []
|
|
139
|
+
];
|
|
140
|
+
this.frameworkComponents = assertFrameworkComponentsCompatible(
|
|
141
|
+
this.options.family.familyId,
|
|
142
|
+
this.options.target.targetId,
|
|
143
|
+
rawComponents
|
|
144
|
+
);
|
|
145
|
+
this.initialized = true;
|
|
146
|
+
}
|
|
147
|
+
async connect(connection) {
|
|
148
|
+
this.init();
|
|
149
|
+
if (this.driver) {
|
|
150
|
+
throw new Error("Already connected. Call close() before reconnecting.");
|
|
151
|
+
}
|
|
152
|
+
const resolvedConnection = connection ?? this.defaultConnection;
|
|
153
|
+
if (resolvedConnection === void 0) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"No connection provided. Pass a connection to connect() or provide a default connection when creating the client."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (!this.stack?.driver) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations."
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
this.driver = await this.stack?.driver.create(resolvedConnection);
|
|
164
|
+
}
|
|
165
|
+
async close() {
|
|
166
|
+
if (this.driver) {
|
|
167
|
+
await this.driver.close();
|
|
168
|
+
this.driver = null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async ensureConnected() {
|
|
172
|
+
this.init();
|
|
173
|
+
if (!this.driver && this.defaultConnection !== void 0) {
|
|
174
|
+
await this.connect(this.defaultConnection);
|
|
175
|
+
}
|
|
176
|
+
if (!this.driver || !this.familyInstance || !this.frameworkComponents) {
|
|
177
|
+
throw new Error("Not connected. Call connect(connection) first.");
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
driver: this.driver,
|
|
181
|
+
familyInstance: this.familyInstance,
|
|
182
|
+
frameworkComponents: this.frameworkComponents
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async verify(options) {
|
|
186
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
187
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
188
|
+
return familyInstance.verify({
|
|
189
|
+
driver,
|
|
190
|
+
contractIR,
|
|
191
|
+
expectedTargetId: this.options.target.targetId,
|
|
192
|
+
contractPath: ""
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async schemaVerify(options) {
|
|
196
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
197
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
198
|
+
return familyInstance.schemaVerify({
|
|
199
|
+
driver,
|
|
200
|
+
contractIR,
|
|
201
|
+
strict: options.strict ?? false,
|
|
202
|
+
contractPath: "",
|
|
203
|
+
frameworkComponents
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async sign(options) {
|
|
207
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
208
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
209
|
+
return familyInstance.sign({
|
|
210
|
+
driver,
|
|
211
|
+
contractIR,
|
|
212
|
+
contractPath: ""
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async dbInit(options) {
|
|
216
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
217
|
+
if (!this.options.target.migrations) {
|
|
218
|
+
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
219
|
+
}
|
|
220
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
221
|
+
return executeDbInit({
|
|
222
|
+
driver,
|
|
223
|
+
familyInstance,
|
|
224
|
+
contractIR,
|
|
225
|
+
mode: options.mode,
|
|
226
|
+
migrations: this.options.target.migrations,
|
|
227
|
+
frameworkComponents
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async introspect(options) {
|
|
231
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
232
|
+
const _schema = options?.schema;
|
|
233
|
+
void _schema;
|
|
234
|
+
return familyInstance.introspect({ driver });
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
export {
|
|
238
|
+
createControlClient
|
|
239
|
+
};
|
|
240
|
+
//# sourceMappingURL=control-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/control-api/client.ts","../../src/control-api/operations/db-init.ts"],"sourcesContent":["import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlPlaneStack,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/core-control-plane/types';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { executeDbInit } from './operations/db-init';\nimport type {\n ControlClient,\n ControlClientOptions,\n DbInitOptions,\n DbInitResult,\n IntrospectOptions,\n SchemaVerifyOptions,\n SignOptions,\n VerifyOptions,\n} from './types';\n\n/**\n * Creates a programmatic control client for Prisma Next operations.\n *\n * The client accepts framework component descriptors at creation time,\n * manages driver lifecycle via connect()/close(), and exposes domain\n * operations that delegate to the existing family instance methods.\n *\n * @see {@link ControlClient} for the client interface\n * @see README.md \"Programmatic Control API\" section for usage examples\n */\nexport function createControlClient(options: ControlClientOptions): ControlClient {\n return new ControlClientImpl(options);\n}\n\n/**\n * Implementation of ControlClient.\n * Manages initialization and connection state, delegates operations to family instance.\n */\nclass ControlClientImpl implements ControlClient {\n private readonly options: ControlClientOptions;\n private stack: ControlPlaneStack<string, string> | null = null;\n private driver: ControlDriverInstance<string, string> | null = null;\n private familyInstance: ControlFamilyInstance<string> | null = null;\n private frameworkComponents: ReadonlyArray<\n TargetBoundComponentDescriptor<string, string>\n > | null = null;\n private initialized = false;\n private readonly defaultConnection: unknown;\n\n constructor(options: ControlClientOptions) {\n this.options = options;\n this.defaultConnection = options.connection;\n }\n\n init(): void {\n if (this.initialized) {\n return; // Idempotent\n }\n\n // Create the control plane stack\n this.stack = createControlPlaneStack({\n target: this.options.target,\n adapter: this.options.adapter,\n driver: this.options.driver,\n extensionPacks: this.options.extensionPacks,\n });\n\n // Create family instance using the stack\n this.familyInstance = this.options.family.create(this.stack);\n\n // Validate and type-narrow framework components\n const rawComponents = [\n this.options.target,\n this.options.adapter,\n ...(this.options.extensionPacks ?? []),\n ];\n this.frameworkComponents = assertFrameworkComponentsCompatible(\n this.options.family.familyId,\n this.options.target.targetId,\n rawComponents,\n );\n\n this.initialized = true;\n }\n\n async connect(connection?: unknown): Promise<void> {\n // Auto-init if needed\n this.init();\n\n if (this.driver) {\n throw new Error('Already connected. Call close() before reconnecting.');\n }\n\n // Resolve connection: argument > default from options\n const resolvedConnection = connection ?? this.defaultConnection;\n if (resolvedConnection === undefined) {\n throw new Error(\n 'No connection provided. Pass a connection to connect() or provide a default connection when creating the client.',\n );\n }\n\n // Check for driver descriptor\n if (!this.stack?.driver) {\n throw new Error(\n 'Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.',\n );\n }\n\n // Create driver instance\n // Cast through any since connection type is driver-specific at runtime.\n // The driver descriptor is typed with any for TConnection in ControlClientOptions,\n // but createControlPlaneStack defaults it to string. We bridge this at runtime.\n // biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility\n this.driver = await this.stack?.driver.create(resolvedConnection as any);\n }\n\n async close(): Promise<void> {\n if (this.driver) {\n await this.driver.close();\n this.driver = null;\n }\n }\n\n private async ensureConnected(): Promise<{\n driver: ControlDriverInstance<string, string>;\n familyInstance: ControlFamilyInstance<string>;\n frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;\n }> {\n // Auto-init if needed\n this.init();\n\n // Auto-connect if not connected and default connection is available\n if (!this.driver && this.defaultConnection !== undefined) {\n await this.connect(this.defaultConnection);\n }\n\n if (!this.driver || !this.familyInstance || !this.frameworkComponents) {\n throw new Error('Not connected. Call connect(connection) first.');\n }\n return {\n driver: this.driver,\n familyInstance: this.familyInstance,\n frameworkComponents: this.frameworkComponents,\n };\n }\n\n async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Delegate to family instance verify method\n // Note: We pass empty strings for contractPath/configPath since the programmatic\n // API doesn't deal with file paths. The family instance accepts these as optional\n // metadata for error reporting.\n return familyInstance.verify({\n driver,\n contractIR,\n expectedTargetId: this.options.target.targetId,\n contractPath: '',\n });\n }\n\n async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Delegate to family instance schemaVerify method\n return familyInstance.schemaVerify({\n driver,\n contractIR,\n strict: options.strict ?? false,\n contractPath: '',\n frameworkComponents,\n });\n }\n\n async sign(options: SignOptions): Promise<SignDatabaseResult> {\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Delegate to family instance sign method\n return familyInstance.sign({\n driver,\n contractIR,\n contractPath: '',\n });\n }\n\n async dbInit(options: DbInitOptions): Promise<DbInitResult> {\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n // Check target supports migrations\n if (!this.options.target.migrations) {\n throw new Error(`Target \"${this.options.target.targetId}\" does not support migrations`);\n }\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Delegate to extracted dbInit operation\n return executeDbInit({\n driver,\n familyInstance,\n contractIR,\n mode: options.mode,\n migrations: this.options.target.migrations,\n frameworkComponents,\n });\n }\n\n async introspect(options?: IntrospectOptions): Promise<unknown> {\n const { driver, familyInstance } = await this.ensureConnected();\n\n // TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented\n const _schema = options?.schema;\n void _schema;\n\n return familyInstance.introspect({ driver });\n }\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n MigrationPlan,\n MigrationPlannerResult,\n MigrationRunnerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/core-control-plane/types';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { DbInitResult, DbInitSuccess } from '../types';\n\n/**\n * Options for executing dbInit operation.\n */\nexport interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {\n readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;\n readonly familyInstance: ControlFamilyInstance<TFamilyId>;\n readonly contractIR: ContractIR;\n readonly mode: 'plan' | 'apply';\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Executes the dbInit operation.\n *\n * This is the core logic extracted from the CLI command, without any file I/O,\n * process.exit(), or console output. It uses the Result pattern to return\n * success or failure details.\n *\n * @param options - The options for executing dbInit\n * @returns Result with DbInitSuccess on success, DbInitFailure on failure\n */\nexport async function executeDbInit<TFamilyId extends string, TTargetId extends string>(\n options: ExecuteDbInitOptions<TFamilyId, TTargetId>,\n): Promise<DbInitResult> {\n const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents } = options;\n\n // Create planner and runner from target migrations capability\n const planner = migrations.createPlanner(familyInstance);\n const runner = migrations.createRunner(familyInstance);\n\n // Introspect live schema\n const schemaIR = await familyInstance.introspect({ driver });\n\n // Policy for init mode (additive only)\n const policy = { allowedOperationClasses: ['additive'] as const };\n\n // Plan migration\n const plannerResult: MigrationPlannerResult = await planner.plan({\n contract: contractIR,\n schema: schemaIR,\n policy,\n frameworkComponents,\n });\n\n if (plannerResult.kind === 'failure') {\n return notOk({\n code: 'PLANNING_FAILED' as const,\n summary: 'Migration planning failed due to conflicts',\n conflicts: plannerResult.conflicts,\n });\n }\n\n const migrationPlan: MigrationPlan = plannerResult.plan;\n\n // Check for existing marker - handle idempotency and mismatch errors\n const existingMarker = await familyInstance.readMarker({ driver });\n if (existingMarker) {\n const markerMatchesDestination =\n existingMarker.coreHash === migrationPlan.destination.coreHash &&\n (!migrationPlan.destination.profileHash ||\n existingMarker.profileHash === migrationPlan.destination.profileHash);\n\n if (markerMatchesDestination) {\n // Already at destination - return success with no operations\n const result: DbInitSuccess = {\n mode,\n plan: { operations: [] },\n ...(mode === 'apply'\n ? {\n execution: { operationsPlanned: 0, operationsExecuted: 0 },\n marker: {\n coreHash: existingMarker.coreHash,\n profileHash: existingMarker.profileHash,\n },\n }\n : {}),\n summary: 'Database already at target contract state',\n };\n return ok(result);\n }\n\n // Marker exists but doesn't match destination - fail\n return notOk({\n code: 'MARKER_ORIGIN_MISMATCH' as const,\n summary: 'Existing contract marker does not match plan destination',\n marker: {\n coreHash: existingMarker.coreHash,\n profileHash: existingMarker.profileHash,\n },\n destination: {\n coreHash: migrationPlan.destination.coreHash,\n profileHash: migrationPlan.destination.profileHash,\n },\n });\n }\n\n // Plan mode - don't execute\n if (mode === 'plan') {\n const result: DbInitSuccess = {\n mode: 'plan',\n plan: { operations: migrationPlan.operations },\n summary: `Planned ${migrationPlan.operations.length} operation(s)`,\n };\n return ok(result);\n }\n\n // Apply mode - execute runner\n const runnerResult: MigrationRunnerResult = await runner.execute({\n plan: migrationPlan,\n driver,\n destinationContract: contractIR,\n policy,\n // db init plans and applies back-to-back from a fresh introspection, so per-operation\n // pre/postchecks and the idempotency probe are usually redundant overhead. We still\n // enforce marker/origin compatibility and a full schema verification after apply.\n executionChecks: {\n prechecks: false,\n postchecks: false,\n idempotencyChecks: false,\n },\n frameworkComponents,\n });\n\n if (!runnerResult.ok) {\n return notOk({\n code: 'RUNNER_FAILED' as const,\n summary: runnerResult.failure.summary,\n });\n }\n\n const execution = runnerResult.value;\n\n const result: DbInitSuccess = {\n mode: 'apply',\n plan: { operations: migrationPlan.operations },\n execution: {\n operationsPlanned: execution.operationsPlanned,\n operationsExecuted: execution.operationsExecuted,\n },\n marker: migrationPlan.destination.profileHash\n ? {\n coreHash: migrationPlan.destination.coreHash,\n profileHash: migrationPlan.destination.profileHash,\n }\n : { coreHash: migrationPlan.destination.coreHash },\n summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,\n };\n return ok(result);\n}\n"],"mappings":"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","names":["result"]}
|
package/dist/exports/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createContractEmitCommand
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-MG7PBERL.js";
|
|
4
|
+
import "../chunk-DIJPT5TZ.js";
|
|
5
5
|
import "../chunk-HWYQOCAJ.js";
|
|
6
|
+
import "../chunk-VI2YETW7.js";
|
|
6
7
|
|
|
7
8
|
// src/load-ts-contract.ts
|
|
8
9
|
import { existsSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/load-ts-contract.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type { Plugin } from 'esbuild';\nimport { build } from 'esbuild';\n\nexport interface LoadTsContractOptions {\n readonly allowlist?: ReadonlyArray<string>;\n}\n\nconst DEFAULT_ALLOWLIST = ['@prisma-next/*'];\n\nfunction isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>): boolean {\n for (const pattern of allowlist) {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n if (importPath === prefix || importPath.startsWith(`${prefix}/`)) {\n return true;\n }\n } else if (importPath === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction validatePurity(value: unknown): void {\n if (typeof value !== 'object' || value === null) {\n return;\n }\n\n const seen = new WeakSet();\n function check(value: unknown): void {\n if (value === null || typeof value !== 'object') {\n return;\n }\n\n if (seen.has(value)) {\n throw new Error('Contract export contains circular references');\n }\n seen.add(value);\n\n for (const key in value) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor && (descriptor.get || descriptor.set)) {\n throw new Error(`Contract export contains getter/setter at key \"${key}\"`);\n }\n if (descriptor && typeof descriptor.value === 'function') {\n throw new Error(`Contract export contains function at key \"${key}\"`);\n }\n check((value as Record<string, unknown>)[key]);\n }\n }\n\n try {\n check(value);\n JSON.stringify(value);\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('getter') || error.message.includes('circular')) {\n throw error;\n }\n throw new Error(`Contract export is not JSON-serializable: ${error.message}`);\n }\n throw new Error('Contract export is not JSON-serializable');\n }\n}\n\nfunction createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath: string): Plugin {\n return {\n name: 'import-allowlist',\n setup(build) {\n build.onResolve({ filter: /.*/ }, (args) => {\n if (args.kind === 'entry-point') {\n return undefined;\n }\n if (args.path.startsWith('.') || args.path.startsWith('/')) {\n return undefined;\n }\n const isFromEntryPoint = args.importer === entryPath || args.importer === '<stdin>';\n if (isFromEntryPoint && !isAllowedImport(args.path, allowlist)) {\n return {\n path: args.path,\n external: true,\n };\n }\n return undefined;\n });\n },\n };\n}\n\n/**\n * Loads a contract from a TypeScript file and returns it as ContractIR.\n *\n * **Responsibility: Parsing Only**\n * This function loads and parses a TypeScript contract file. It does NOT normalize the contract.\n * The contract should already be normalized if it was built using the contract builder.\n *\n * Normalization must happen in the contract builder when the contract is created.\n * This function only validates that the contract is JSON-serializable and returns it as-is.\n *\n * @param entryPath - Path to the TypeScript contract file\n * @param options - Optional configuration (import allowlist)\n * @returns The contract as ContractIR (should already be normalized)\n * @throws Error if the contract cannot be loaded or is not JSON-serializable\n */\nexport async function loadContractFromTs(\n entryPath: string,\n options?: LoadTsContractOptions,\n): Promise<ContractIR> {\n const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;\n\n if (!existsSync(entryPath)) {\n throw new Error(`Contract file not found: ${entryPath}`);\n }\n\n const tempFile = join(\n tmpdir(),\n `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`,\n );\n\n try {\n const result = await build({\n entryPoints: [entryPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'es2022',\n outfile: tempFile,\n write: false,\n metafile: true,\n plugins: [createImportAllowlistPlugin(allowlist, entryPath)],\n logLevel: 'error',\n });\n\n if (result.errors.length > 0) {\n const errorMessages = result.errors.map((e: { text: string }) => e.text).join('\\n');\n throw new Error(`Failed to bundle contract file: ${errorMessages}`);\n }\n\n if (!result.outputFiles || result.outputFiles.length === 0) {\n throw new Error('No output files generated from bundling');\n }\n\n const disallowedImports: string[] = [];\n if (result.metafile) {\n const inputs = result.metafile.inputs;\n for (const [, inputData] of Object.entries(inputs)) {\n const imports =\n (inputData as { imports?: Array<{ path: string; external?: boolean }> }).imports || [];\n for (const imp of imports) {\n if (\n imp.external &&\n !imp.path.startsWith('.') &&\n !imp.path.startsWith('/') &&\n !isAllowedImport(imp.path, allowlist)\n ) {\n disallowedImports.push(imp.path);\n }\n }\n }\n }\n\n if (disallowedImports.length > 0) {\n throw new Error(\n `Disallowed imports detected. Only imports matching the allowlist are permitted:\\n Allowlist: ${allowlist.join(', ')}\\n Disallowed imports: ${disallowedImports.join(', ')}\\n\\nOnly @prisma-next/* packages are allowed in contract files.`,\n );\n }\n\n const bundleContent = result.outputFiles[0]?.text;\n if (bundleContent === undefined) {\n throw new Error('Bundle content is undefined');\n }\n writeFileSync(tempFile, bundleContent, 'utf-8');\n\n const module = (await import(`file://${tempFile}`)) as {\n default?: unknown;\n contract?: unknown;\n };\n unlinkSync(tempFile);\n\n let contract: unknown;\n\n if (module.default !== undefined) {\n contract = module.default;\n } else if (module.contract !== undefined) {\n contract = module.contract;\n } else {\n throw new Error(\n `Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module as Record<string, unknown>).join(', ') || 'none'}`,\n );\n }\n\n if (typeof contract !== 'object' || contract === null) {\n throw new Error(`Contract export must be an object, got ${typeof contract}`);\n }\n\n validatePurity(contract);\n\n return contract as ContractIR;\n } catch (error) {\n try {\n if (tempFile) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);\n }\n}\n"],"mappings":"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","names":["value","build"]}
|
|
1
|
+
{"version":3,"sources":["../../src/load-ts-contract.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type { Plugin } from 'esbuild';\nimport { build } from 'esbuild';\n\nexport interface LoadTsContractOptions {\n readonly allowlist?: ReadonlyArray<string>;\n}\n\nconst DEFAULT_ALLOWLIST = ['@prisma-next/*'];\n\nfunction isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>): boolean {\n for (const pattern of allowlist) {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n if (importPath === prefix || importPath.startsWith(`${prefix}/`)) {\n return true;\n }\n } else if (importPath === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction validatePurity(value: unknown): void {\n if (typeof value !== 'object' || value === null) {\n return;\n }\n\n const seen = new WeakSet();\n function check(value: unknown): void {\n if (value === null || typeof value !== 'object') {\n return;\n }\n\n if (seen.has(value)) {\n throw new Error('Contract export contains circular references');\n }\n seen.add(value);\n\n for (const key in value) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor && (descriptor.get || descriptor.set)) {\n throw new Error(`Contract export contains getter/setter at key \"${key}\"`);\n }\n if (descriptor && typeof descriptor.value === 'function') {\n throw new Error(`Contract export contains function at key \"${key}\"`);\n }\n check((value as Record<string, unknown>)[key]);\n }\n }\n\n try {\n check(value);\n JSON.stringify(value);\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('getter') || error.message.includes('circular')) {\n throw error;\n }\n throw new Error(`Contract export is not JSON-serializable: ${error.message}`);\n }\n throw new Error('Contract export is not JSON-serializable');\n }\n}\n\nfunction createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath: string): Plugin {\n return {\n name: 'import-allowlist',\n setup(build) {\n build.onResolve({ filter: /.*/ }, (args) => {\n if (args.kind === 'entry-point') {\n return undefined;\n }\n if (args.path.startsWith('.') || args.path.startsWith('/')) {\n return undefined;\n }\n const isFromEntryPoint = args.importer === entryPath || args.importer === '<stdin>';\n if (isFromEntryPoint && !isAllowedImport(args.path, allowlist)) {\n return {\n path: args.path,\n external: true,\n };\n }\n return undefined;\n });\n },\n };\n}\n\n/**\n * Loads a contract from a TypeScript file and returns it as ContractIR.\n *\n * **Responsibility: Parsing Only**\n * This function loads and parses a TypeScript contract file. It does NOT normalize the contract.\n * The contract should already be normalized if it was built using the contract builder.\n *\n * Normalization must happen in the contract builder when the contract is created.\n * This function only validates that the contract is JSON-serializable and returns it as-is.\n *\n * @param entryPath - Path to the TypeScript contract file\n * @param options - Optional configuration (import allowlist)\n * @returns The contract as ContractIR (should already be normalized)\n * @throws Error if the contract cannot be loaded or is not JSON-serializable\n */\nexport async function loadContractFromTs(\n entryPath: string,\n options?: LoadTsContractOptions,\n): Promise<ContractIR> {\n const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;\n\n if (!existsSync(entryPath)) {\n throw new Error(`Contract file not found: ${entryPath}`);\n }\n\n const tempFile = join(\n tmpdir(),\n `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`,\n );\n\n try {\n const result = await build({\n entryPoints: [entryPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'es2022',\n outfile: tempFile,\n write: false,\n metafile: true,\n plugins: [createImportAllowlistPlugin(allowlist, entryPath)],\n logLevel: 'error',\n });\n\n if (result.errors.length > 0) {\n const errorMessages = result.errors.map((e: { text: string }) => e.text).join('\\n');\n throw new Error(`Failed to bundle contract file: ${errorMessages}`);\n }\n\n if (!result.outputFiles || result.outputFiles.length === 0) {\n throw new Error('No output files generated from bundling');\n }\n\n const disallowedImports: string[] = [];\n if (result.metafile) {\n const inputs = result.metafile.inputs;\n for (const [, inputData] of Object.entries(inputs)) {\n const imports =\n (inputData as { imports?: Array<{ path: string; external?: boolean }> }).imports || [];\n for (const imp of imports) {\n if (\n imp.external &&\n !imp.path.startsWith('.') &&\n !imp.path.startsWith('/') &&\n !isAllowedImport(imp.path, allowlist)\n ) {\n disallowedImports.push(imp.path);\n }\n }\n }\n }\n\n if (disallowedImports.length > 0) {\n throw new Error(\n `Disallowed imports detected. Only imports matching the allowlist are permitted:\\n Allowlist: ${allowlist.join(', ')}\\n Disallowed imports: ${disallowedImports.join(', ')}\\n\\nOnly @prisma-next/* packages are allowed in contract files.`,\n );\n }\n\n const bundleContent = result.outputFiles[0]?.text;\n if (bundleContent === undefined) {\n throw new Error('Bundle content is undefined');\n }\n writeFileSync(tempFile, bundleContent, 'utf-8');\n\n const module = (await import(`file://${tempFile}`)) as {\n default?: unknown;\n contract?: unknown;\n };\n unlinkSync(tempFile);\n\n let contract: unknown;\n\n if (module.default !== undefined) {\n contract = module.default;\n } else if (module.contract !== undefined) {\n contract = module.contract;\n } else {\n throw new Error(\n `Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module as Record<string, unknown>).join(', ') || 'none'}`,\n );\n }\n\n if (typeof contract !== 'object' || contract === null) {\n throw new Error(`Contract export must be an object, got ${typeof contract}`);\n }\n\n validatePurity(contract);\n\n return contract as ContractIR;\n } catch (error) {\n try {\n if (tempFile) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);\n }\n}\n"],"mappings":"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","names":["value","build"]}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* CLI-specific errors (e.g., Commander.js argument validation) can be added here if needed.
|
|
4
4
|
*/
|
|
5
5
|
export type { CliErrorConflict, CliErrorEnvelope } from '@prisma-next/core-control-plane/errors';
|
|
6
|
-
export { CliStructuredError, errorConfigFileNotFound, errorConfigValidation, errorContractConfigMissing, errorContractMissingExtensionPacks, errorContractValidationFailed,
|
|
6
|
+
export { CliStructuredError, errorConfigFileNotFound, errorConfigValidation, errorContractConfigMissing, errorContractMissingExtensionPacks, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFamilyReadMarkerSqlRequired, errorFileNotFound, errorHashMismatch, errorJsonFormatNotSupported, errorMarkerMissing, errorMigrationPlanningFailed, errorQueryRunnerFactoryRequired, errorRuntime, errorTargetMigrationNotSupported, errorTargetMismatch, errorUnexpected, } from '@prisma-next/core-control-plane/errors';
|
|
7
7
|
//# sourceMappingURL=cli-errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-errors.d.ts","sourceRoot":"","sources":["../../src/utils/cli-errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AACjG,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,EAC1B,kCAAkC,EAClC,6BAA6B,EAC7B,
|
|
1
|
+
{"version":3,"file":"cli-errors.d.ts","sourceRoot":"","sources":["../../src/utils/cli-errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AACjG,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,EAC1B,kCAAkC,EAClC,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,gCAAgC,EAChC,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,EAClB,4BAA4B,EAC5B,+BAA+B,EAC/B,YAAY,EACZ,gCAAgC,EAChC,mBAAmB,EACnB,eAAe,GAChB,MAAM,wCAAwC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/cli",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"string-width": "^7.2.0",
|
|
21
21
|
"strip-ansi": "^7.1.2",
|
|
22
22
|
"wrap-ansi": "^9.0.2",
|
|
23
|
-
"@prisma-next/contract": "0.3.0-dev.
|
|
24
|
-
"@prisma-next/core-control-plane": "0.3.0-dev.
|
|
25
|
-
"@prisma-next/emitter": "0.3.0-dev.
|
|
26
|
-
"@prisma-next/utils": "0.3.0-dev.
|
|
23
|
+
"@prisma-next/contract": "0.3.0-dev.14",
|
|
24
|
+
"@prisma-next/core-control-plane": "0.3.0-dev.14",
|
|
25
|
+
"@prisma-next/emitter": "0.3.0-dev.14",
|
|
26
|
+
"@prisma-next/utils": "0.3.0-dev.14"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "24.10.4",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"tsup": "8.5.1",
|
|
32
32
|
"typescript": "5.9.3",
|
|
33
33
|
"vitest": "4.0.16",
|
|
34
|
-
"@prisma-next/sql-contract": "0.3.0-dev.
|
|
35
|
-
"@prisma-next/sql-contract-emitter": "0.3.0-dev.
|
|
36
|
-
"@prisma-next/sql-contract-ts": "0.3.0-dev.
|
|
37
|
-
"@prisma-next/sql-operations": "0.3.0-dev.
|
|
38
|
-
"@prisma-next/sql-runtime": "0.3.0-dev.
|
|
34
|
+
"@prisma-next/sql-contract": "0.3.0-dev.14",
|
|
35
|
+
"@prisma-next/sql-contract-emitter": "0.3.0-dev.14",
|
|
36
|
+
"@prisma-next/sql-contract-ts": "0.3.0-dev.14",
|
|
37
|
+
"@prisma-next/sql-operations": "0.3.0-dev.14",
|
|
38
|
+
"@prisma-next/sql-runtime": "0.3.0-dev.14",
|
|
39
39
|
"@prisma-next/test-utils": "0.0.1"
|
|
40
40
|
},
|
|
41
41
|
"exports": {
|
|
@@ -74,6 +74,10 @@
|
|
|
74
74
|
"./config-loader": {
|
|
75
75
|
"types": "./dist/config-loader.d.ts",
|
|
76
76
|
"import": "./dist/config-loader.js"
|
|
77
|
+
},
|
|
78
|
+
"./control-api": {
|
|
79
|
+
"types": "./dist/exports/control-api.d.ts",
|
|
80
|
+
"import": "./dist/exports/control-api.js"
|
|
77
81
|
}
|
|
78
82
|
},
|
|
79
83
|
"scripts": {
|
package/src/commands/db-init.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { loadConfig } from '../config-loader';
|
|
|
13
13
|
import { performAction } from '../utils/action';
|
|
14
14
|
import {
|
|
15
15
|
errorContractValidationFailed,
|
|
16
|
-
|
|
16
|
+
errorDatabaseConnectionRequired,
|
|
17
17
|
errorDriverRequired,
|
|
18
18
|
errorFileNotFound,
|
|
19
19
|
errorJsonFormatNotSupported,
|
|
@@ -154,11 +154,11 @@ export function createDbInitCommand(): Command {
|
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// Resolve database
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
160
|
-
throw
|
|
161
|
-
why: `Database
|
|
157
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
158
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
159
|
+
if (!dbConnection) {
|
|
160
|
+
throw errorDatabaseConnectionRequired({
|
|
161
|
+
why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -176,23 +176,24 @@ export function createDbInitCommand(): Command {
|
|
|
176
176
|
}
|
|
177
177
|
const migrations = config.target.migrations;
|
|
178
178
|
|
|
179
|
-
// Create driver
|
|
180
179
|
let driver: Awaited<ReturnType<(typeof driverDescriptor)['create']>>;
|
|
181
180
|
try {
|
|
182
|
-
driver = await withSpinner(() => driverDescriptor.create(
|
|
181
|
+
driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
183
182
|
message: 'Connecting to database...',
|
|
184
183
|
flags,
|
|
185
184
|
});
|
|
186
185
|
} catch (error) {
|
|
187
186
|
const message = error instanceof Error ? error.message : String(error);
|
|
188
187
|
const code = (error as { code?: unknown }).code;
|
|
189
|
-
|
|
188
|
+
// Only redact if connection is a string (URL)
|
|
189
|
+
const redacted =
|
|
190
|
+
typeof dbConnection === 'string' ? redactDatabaseUrl(dbConnection) : undefined;
|
|
190
191
|
throw errorRuntime('Database connection failed', {
|
|
191
192
|
why: message,
|
|
192
|
-
fix: 'Verify the database
|
|
193
|
+
fix: 'Verify the database connection, ensure the database is reachable, and confirm credentials/permissions',
|
|
193
194
|
meta: {
|
|
194
195
|
...(typeof code !== 'undefined' ? { code } : {}),
|
|
195
|
-
...redacted,
|
|
196
|
+
...(redacted ?? {}),
|
|
196
197
|
},
|
|
197
198
|
});
|
|
198
199
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
errorDatabaseConnectionRequired,
|
|
5
5
|
errorDriverRequired,
|
|
6
6
|
errorRuntime,
|
|
7
7
|
errorUnexpected,
|
|
@@ -103,9 +103,9 @@ export function createDbIntrospectCommand(): Command {
|
|
|
103
103
|
// Mask password in URL for security
|
|
104
104
|
const maskedUrl = options.db.replace(/:([^:@]+)@/, ':****@');
|
|
105
105
|
details.push({ label: 'database', value: maskedUrl });
|
|
106
|
-
} else if (config.db?.
|
|
106
|
+
} else if (config.db?.connection && typeof config.db.connection === 'string') {
|
|
107
107
|
// Mask password in URL for security
|
|
108
|
-
const maskedUrl = config.db.
|
|
108
|
+
const maskedUrl = config.db.connection.replace(/:([^:@]+)@/, ':****@');
|
|
109
109
|
details.push({ label: 'database', value: maskedUrl });
|
|
110
110
|
}
|
|
111
111
|
const header = formatStyledHeader({
|
|
@@ -118,10 +118,10 @@ export function createDbIntrospectCommand(): Command {
|
|
|
118
118
|
console.log(header);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// Resolve database
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
124
|
-
throw
|
|
121
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
122
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
123
|
+
if (!dbConnection) {
|
|
124
|
+
throw errorDatabaseConnectionRequired();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Check for driver
|
|
@@ -132,8 +132,7 @@ export function createDbIntrospectCommand(): Command {
|
|
|
132
132
|
// Store driver descriptor after null check
|
|
133
133
|
const driverDescriptor = config.driver;
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
135
|
+
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
137
136
|
message: 'Connecting to database...',
|
|
138
137
|
flags,
|
|
139
138
|
});
|
|
@@ -199,6 +198,12 @@ export function createDbIntrospectCommand(): Command {
|
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
// Build result envelope
|
|
201
|
+
// Get masked connection URL for meta (only for string connections)
|
|
202
|
+
const connectionForMeta =
|
|
203
|
+
typeof dbConnection === 'string'
|
|
204
|
+
? dbConnection.replace(/:([^:@]+)@/, ':****@')
|
|
205
|
+
: undefined;
|
|
206
|
+
|
|
202
207
|
const introspectResult: IntrospectSchemaResult<unknown> = {
|
|
203
208
|
ok: true,
|
|
204
209
|
summary: 'Schema introspected successfully',
|
|
@@ -207,18 +212,11 @@ export function createDbIntrospectCommand(): Command {
|
|
|
207
212
|
id: config.target.targetId,
|
|
208
213
|
},
|
|
209
214
|
schema: schemaIR,
|
|
210
|
-
...(configPath ||
|
|
215
|
+
...(configPath || connectionForMeta
|
|
211
216
|
? {
|
|
212
217
|
meta: {
|
|
213
218
|
...(configPath ? { configPath } : {}),
|
|
214
|
-
...(
|
|
215
|
-
? {
|
|
216
|
-
dbUrl: (options.db ?? config.db?.url ?? '').replace(
|
|
217
|
-
/:([^:@]+)@/,
|
|
218
|
-
':****@',
|
|
219
|
-
),
|
|
220
|
-
}
|
|
221
|
-
: {}),
|
|
219
|
+
...(connectionForMeta ? { dbUrl: connectionForMeta } : {}),
|
|
222
220
|
},
|
|
223
221
|
}
|
|
224
222
|
: {}),
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
3
|
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
errorDatabaseConnectionRequired,
|
|
6
6
|
errorDriverRequired,
|
|
7
7
|
errorFileNotFound,
|
|
8
8
|
errorRuntime,
|
|
@@ -144,14 +144,13 @@ export function createDbSchemaVerifyCommand(): Command {
|
|
|
144
144
|
// Validate contract requirements fail-fast before connecting to database
|
|
145
145
|
assertContractRequirementsSatisfied({ contract: contractIR, stack });
|
|
146
146
|
|
|
147
|
-
// Resolve database
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
throw
|
|
147
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
148
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
149
|
+
if (!dbConnection) {
|
|
150
|
+
throw errorDatabaseConnectionRequired();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
153
|
+
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
155
154
|
message: 'Connecting to database...',
|
|
156
155
|
flags,
|
|
157
156
|
});
|
package/src/commands/db-sign.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
errorDatabaseConnectionRequired,
|
|
5
5
|
errorDriverRequired,
|
|
6
6
|
errorFileNotFound,
|
|
7
7
|
errorRuntime,
|
|
@@ -124,10 +124,10 @@ export function createDbSignCommand(): Command {
|
|
|
124
124
|
}
|
|
125
125
|
const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
126
126
|
|
|
127
|
-
// Resolve database
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
130
|
-
throw
|
|
127
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
128
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
129
|
+
if (!dbConnection) {
|
|
130
|
+
throw errorDatabaseConnectionRequired();
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Check for driver
|
|
@@ -159,7 +159,7 @@ export function createDbSignCommand(): Command {
|
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
// Create driver (expensive operation - done after validation)
|
|
162
|
-
const driver = await driverDescriptor.create(
|
|
162
|
+
const driver = await driverDescriptor.create(dbConnection);
|
|
163
163
|
|
|
164
164
|
try {
|
|
165
165
|
// Schema verification precondition with spinner
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
3
|
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
errorDatabaseConnectionRequired,
|
|
6
6
|
errorDriverRequired,
|
|
7
7
|
errorFileNotFound,
|
|
8
8
|
errorHashMismatch,
|
|
@@ -118,10 +118,10 @@ export function createDbVerifyCommand(): Command {
|
|
|
118
118
|
}
|
|
119
119
|
const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
120
120
|
|
|
121
|
-
// Resolve database
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
124
|
-
throw
|
|
121
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
122
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
123
|
+
if (!dbConnection) {
|
|
124
|
+
throw errorDatabaseConnectionRequired();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Check for driver
|
|
@@ -132,8 +132,7 @@ export function createDbVerifyCommand(): Command {
|
|
|
132
132
|
// Store driver descriptor after null check
|
|
133
133
|
const driverDescriptor = config.driver;
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
135
|
+
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
137
136
|
message: 'Connecting to database...',
|
|
138
137
|
flags,
|
|
139
138
|
});
|