@prisma-next/cli 0.1.0-dev.3 → 0.1.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 +237 -31
- package/dist/{chunk-4Q3MO4TK.js → chunk-464LNZCE.js} +3 -3
- package/dist/chunk-464LNZCE.js.map +1 -0
- package/dist/{chunk-3EODSNGS.js → chunk-BZMBKEEQ.js} +157 -74
- package/dist/chunk-BZMBKEEQ.js.map +1 -0
- package/dist/chunk-C7QQMZ3I.js +94 -0
- package/dist/chunk-C7QQMZ3I.js.map +1 -0
- package/dist/cli.js +669 -167
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.js +2 -2
- package/dist/commands/db-init.d.ts +5 -0
- package/dist/commands/db-init.js +341 -0
- package/dist/commands/db-init.js.map +1 -0
- package/dist/commands/db-introspect.js +18 -9
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.js +32 -15
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.js +39 -26
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.js +14 -5
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +3 -9
- package/dist/index.js.map +1 -1
- package/package.json +18 -14
- package/dist/chunk-3EODSNGS.js.map +0 -1
- package/dist/chunk-4Q3MO4TK.js.map +0 -1
- package/dist/chunk-W5YXBFPY.js +0 -96
- package/dist/chunk-W5YXBFPY.js.map +0 -1
- package/dist/pack-loading.d.ts +0 -6
- package/dist/pack-loading.js +0 -9
- package/dist/pack-loading.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command7 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/contract-emit.ts
|
|
7
7
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -52,6 +52,45 @@ async function loadConfig(configPath) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// src/utils/action.ts
|
|
56
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
57
|
+
|
|
58
|
+
// src/utils/cli-errors.ts
|
|
59
|
+
import {
|
|
60
|
+
CliStructuredError,
|
|
61
|
+
errorConfigFileNotFound as errorConfigFileNotFound2,
|
|
62
|
+
errorConfigValidation,
|
|
63
|
+
errorContractConfigMissing,
|
|
64
|
+
errorContractMissingExtensionPacks,
|
|
65
|
+
errorContractValidationFailed,
|
|
66
|
+
errorDatabaseUrlRequired,
|
|
67
|
+
errorDriverRequired,
|
|
68
|
+
errorFamilyReadMarkerSqlRequired,
|
|
69
|
+
errorFileNotFound,
|
|
70
|
+
errorHashMismatch,
|
|
71
|
+
errorJsonFormatNotSupported,
|
|
72
|
+
errorMarkerMissing,
|
|
73
|
+
errorMigrationPlanningFailed,
|
|
74
|
+
errorQueryRunnerFactoryRequired,
|
|
75
|
+
errorRuntime,
|
|
76
|
+
errorTargetMigrationNotSupported,
|
|
77
|
+
errorTargetMismatch,
|
|
78
|
+
errorUnexpected as errorUnexpected2
|
|
79
|
+
} from "@prisma-next/core-control-plane/errors";
|
|
80
|
+
|
|
81
|
+
// src/utils/action.ts
|
|
82
|
+
async function performAction(fn) {
|
|
83
|
+
try {
|
|
84
|
+
const value = await fn();
|
|
85
|
+
return ok(value);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof CliStructuredError) {
|
|
88
|
+
return notOk(error);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
55
94
|
// src/utils/command-helpers.ts
|
|
56
95
|
function setCommandDescriptions(command, shortDescription, longDescription) {
|
|
57
96
|
command.description(shortDescription);
|
|
@@ -165,6 +204,36 @@ function formatErrorOutput(error, flags) {
|
|
|
165
204
|
const whereLine = error.where.line ? `${error.where.path}:${error.where.line}` : error.where.path;
|
|
166
205
|
lines.push(`${prefix}${formatDimText(` Where: ${whereLine}`)}`);
|
|
167
206
|
}
|
|
207
|
+
if (error.meta?.["conflicts"]) {
|
|
208
|
+
const conflicts = error.meta["conflicts"];
|
|
209
|
+
if (conflicts.length > 0) {
|
|
210
|
+
const maxToShow = isVerbose(flags, 1) ? conflicts.length : Math.min(3, conflicts.length);
|
|
211
|
+
const header = isVerbose(flags, 1) ? " Conflicts:" : ` Conflicts (showing ${maxToShow} of ${conflicts.length}):`;
|
|
212
|
+
lines.push(`${prefix}${formatDimText(header)}`);
|
|
213
|
+
for (const conflict of conflicts.slice(0, maxToShow)) {
|
|
214
|
+
lines.push(`${prefix}${formatDimText(` - [${conflict.kind}] ${conflict.summary}`)}`);
|
|
215
|
+
}
|
|
216
|
+
if (!isVerbose(flags, 1) && conflicts.length > maxToShow) {
|
|
217
|
+
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all conflicts")}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (error.meta?.["issues"]) {
|
|
222
|
+
const issues = error.meta["issues"];
|
|
223
|
+
if (issues.length > 0) {
|
|
224
|
+
const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);
|
|
225
|
+
const header = isVerbose(flags, 1) ? " Issues:" : ` Issues (showing ${maxToShow} of ${issues.length}):`;
|
|
226
|
+
lines.push(`${prefix}${formatDimText(header)}`);
|
|
227
|
+
for (const issue of issues.slice(0, maxToShow)) {
|
|
228
|
+
const kind = issue.kind ?? "issue";
|
|
229
|
+
const message = issue.message ?? "";
|
|
230
|
+
lines.push(`${prefix}${formatDimText(` - [${kind}] ${message}`)}`);
|
|
231
|
+
}
|
|
232
|
+
if (!isVerbose(flags, 1) && issues.length > maxToShow) {
|
|
233
|
+
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all issues")}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
168
237
|
if (error.docsUrl && isVerbose(flags, 1)) {
|
|
169
238
|
lines.push(formatDimText(error.docsUrl));
|
|
170
239
|
}
|
|
@@ -598,6 +667,69 @@ function formatSignOutput(result, flags) {
|
|
|
598
667
|
function formatSignJson(result) {
|
|
599
668
|
return JSON.stringify(result, null, 2);
|
|
600
669
|
}
|
|
670
|
+
function formatDbInitPlanOutput(result, flags) {
|
|
671
|
+
if (flags.quiet) {
|
|
672
|
+
return "";
|
|
673
|
+
}
|
|
674
|
+
const lines = [];
|
|
675
|
+
const prefix = createPrefix(flags);
|
|
676
|
+
const useColor = flags.color !== false;
|
|
677
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
678
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
679
|
+
const operationCount = result.plan?.operations.length ?? 0;
|
|
680
|
+
lines.push(`${prefix}${formatGreen("\u2714")} Planned ${operationCount} operation(s)`);
|
|
681
|
+
if (result.plan?.operations && result.plan.operations.length > 0) {
|
|
682
|
+
lines.push(`${prefix}${formatDimText("\u2502")}`);
|
|
683
|
+
for (let i = 0; i < result.plan.operations.length; i++) {
|
|
684
|
+
const op = result.plan.operations[i];
|
|
685
|
+
if (!op) continue;
|
|
686
|
+
const isLast = i === result.plan.operations.length - 1;
|
|
687
|
+
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
688
|
+
const opClass = formatDimText(`[${op.operationClass}]`);
|
|
689
|
+
lines.push(`${prefix}${formatDimText(treeChar)}\u2500 ${op.label} ${opClass}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (result.plan?.destination) {
|
|
693
|
+
lines.push(`${prefix}`);
|
|
694
|
+
lines.push(
|
|
695
|
+
`${prefix}${formatDimText(`Destination hash: ${result.plan.destination.coreHash}`)}`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
if (isVerbose(flags, 1)) {
|
|
699
|
+
lines.push(`${prefix}${formatDimText(`Total time: ${result.timings.total}ms`)}`);
|
|
700
|
+
}
|
|
701
|
+
lines.push(`${prefix}`);
|
|
702
|
+
lines.push(`${prefix}${formatDimText("This is a dry run. No changes were applied.")}`);
|
|
703
|
+
lines.push(`${prefix}${formatDimText("Run without --plan to apply changes.")}`);
|
|
704
|
+
return lines.join("\n");
|
|
705
|
+
}
|
|
706
|
+
function formatDbInitApplyOutput(result, flags) {
|
|
707
|
+
if (flags.quiet) {
|
|
708
|
+
return "";
|
|
709
|
+
}
|
|
710
|
+
const lines = [];
|
|
711
|
+
const prefix = createPrefix(flags);
|
|
712
|
+
const useColor = flags.color !== false;
|
|
713
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
714
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
715
|
+
if (result.ok) {
|
|
716
|
+
const executed = result.execution?.operationsExecuted ?? 0;
|
|
717
|
+
lines.push(`${prefix}${formatGreen("\u2714")} Applied ${executed} operation(s)`);
|
|
718
|
+
if (result.marker) {
|
|
719
|
+
lines.push(`${prefix}${formatDimText(` Marker written: ${result.marker.coreHash}`)}`);
|
|
720
|
+
if (result.marker.profileHash) {
|
|
721
|
+
lines.push(`${prefix}${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (isVerbose(flags, 1)) {
|
|
725
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return lines.join("\n");
|
|
729
|
+
}
|
|
730
|
+
function formatDbInitJson(result) {
|
|
731
|
+
return JSON.stringify(result, null, 2);
|
|
732
|
+
}
|
|
601
733
|
var LEFT_COLUMN_WIDTH = 20;
|
|
602
734
|
var RIGHT_COLUMN_MIN_WIDTH = 40;
|
|
603
735
|
var RIGHT_COLUMN_MAX_WIDTH = 90;
|
|
@@ -908,44 +1040,6 @@ function formatRootHelp(options) {
|
|
|
908
1040
|
`;
|
|
909
1041
|
}
|
|
910
1042
|
|
|
911
|
-
// src/utils/cli-errors.ts
|
|
912
|
-
import {
|
|
913
|
-
CliStructuredError,
|
|
914
|
-
errorConfigFileNotFound as errorConfigFileNotFound2,
|
|
915
|
-
errorConfigValidation,
|
|
916
|
-
errorContractConfigMissing,
|
|
917
|
-
errorContractValidationFailed,
|
|
918
|
-
errorDatabaseUrlRequired,
|
|
919
|
-
errorDriverRequired,
|
|
920
|
-
errorFamilyReadMarkerSqlRequired,
|
|
921
|
-
errorFileNotFound,
|
|
922
|
-
errorHashMismatch,
|
|
923
|
-
errorMarkerMissing,
|
|
924
|
-
errorQueryRunnerFactoryRequired,
|
|
925
|
-
errorRuntime,
|
|
926
|
-
errorTargetMismatch,
|
|
927
|
-
errorUnexpected as errorUnexpected2
|
|
928
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
929
|
-
|
|
930
|
-
// src/utils/result.ts
|
|
931
|
-
function ok(value) {
|
|
932
|
-
return { ok: true, value };
|
|
933
|
-
}
|
|
934
|
-
function err(error) {
|
|
935
|
-
return { ok: false, error };
|
|
936
|
-
}
|
|
937
|
-
async function performAction(fn) {
|
|
938
|
-
try {
|
|
939
|
-
const value = await fn();
|
|
940
|
-
return ok(value);
|
|
941
|
-
} catch (error) {
|
|
942
|
-
if (error instanceof CliStructuredError) {
|
|
943
|
-
return err(error);
|
|
944
|
-
}
|
|
945
|
-
throw error;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
1043
|
// src/utils/result-handler.ts
|
|
950
1044
|
function handleResult(result, flags, onSuccess) {
|
|
951
1045
|
if (result.ok) {
|
|
@@ -954,59 +1048,36 @@ function handleResult(result, flags, onSuccess) {
|
|
|
954
1048
|
}
|
|
955
1049
|
return 0;
|
|
956
1050
|
}
|
|
957
|
-
const envelope = result.
|
|
958
|
-
if (flags.json
|
|
1051
|
+
const envelope = result.failure.toEnvelope();
|
|
1052
|
+
if (flags.json) {
|
|
959
1053
|
console.error(formatErrorJson(envelope));
|
|
960
1054
|
} else {
|
|
961
1055
|
console.error(formatErrorOutput(envelope, flags));
|
|
962
1056
|
}
|
|
963
|
-
const exitCode = result.
|
|
1057
|
+
const exitCode = result.failure.domain === "CLI" ? 2 : 1;
|
|
964
1058
|
return exitCode;
|
|
965
1059
|
}
|
|
966
1060
|
|
|
967
1061
|
// src/utils/spinner.ts
|
|
968
1062
|
import ora from "ora";
|
|
969
1063
|
async function withSpinner(operation, options) {
|
|
970
|
-
const { message, flags
|
|
1064
|
+
const { message, flags } = options;
|
|
971
1065
|
const shouldShowSpinner = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
|
|
972
1066
|
if (!shouldShowSpinner) {
|
|
973
1067
|
return operation();
|
|
974
1068
|
}
|
|
975
1069
|
const startTime = Date.now();
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (!operationCompleted) {
|
|
981
|
-
spinner = ora({
|
|
982
|
-
text: message,
|
|
983
|
-
color: flags.color !== false ? "cyan" : false
|
|
984
|
-
}).start();
|
|
985
|
-
}
|
|
986
|
-
}, delayThreshold);
|
|
1070
|
+
const spinner = ora({
|
|
1071
|
+
text: message,
|
|
1072
|
+
color: flags.color !== false ? "cyan" : false
|
|
1073
|
+
}).start();
|
|
987
1074
|
try {
|
|
988
1075
|
const result = await operation();
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
clearTimeout(timeoutId);
|
|
992
|
-
timeoutId = null;
|
|
993
|
-
}
|
|
994
|
-
if (spinner !== null) {
|
|
995
|
-
const elapsed = Date.now() - startTime;
|
|
996
|
-
spinner.succeed(`${message} (${elapsed}ms)`);
|
|
997
|
-
}
|
|
1076
|
+
const elapsed = Date.now() - startTime;
|
|
1077
|
+
spinner.succeed(`${message} (${elapsed}ms)`);
|
|
998
1078
|
return result;
|
|
999
1079
|
} catch (error) {
|
|
1000
|
-
|
|
1001
|
-
if (timeoutId) {
|
|
1002
|
-
clearTimeout(timeoutId);
|
|
1003
|
-
timeoutId = null;
|
|
1004
|
-
}
|
|
1005
|
-
if (spinner !== null) {
|
|
1006
|
-
spinner.fail(
|
|
1007
|
-
`${message} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1080
|
+
spinner.fail(`${message} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1010
1081
|
throw error;
|
|
1011
1082
|
}
|
|
1012
1083
|
}
|
|
@@ -1067,7 +1138,7 @@ function createContractEmitCommand() {
|
|
|
1067
1138
|
target: config.target,
|
|
1068
1139
|
adapter: config.adapter,
|
|
1069
1140
|
driver: config.driver,
|
|
1070
|
-
|
|
1141
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1071
1142
|
});
|
|
1072
1143
|
let contractRaw;
|
|
1073
1144
|
if (typeof contractConfig.source === "function") {
|
|
@@ -1121,18 +1192,413 @@ function createContractEmitCommand() {
|
|
|
1121
1192
|
return command;
|
|
1122
1193
|
}
|
|
1123
1194
|
|
|
1124
|
-
// src/commands/db-
|
|
1195
|
+
// src/commands/db-init.ts
|
|
1125
1196
|
import { readFile } from "fs/promises";
|
|
1126
1197
|
import { relative as relative3, resolve as resolve3 } from "path";
|
|
1198
|
+
import { redactDatabaseUrl } from "@prisma-next/utils/redact-db-url";
|
|
1199
|
+
import { Command as Command2 } from "commander";
|
|
1200
|
+
|
|
1201
|
+
// src/utils/framework-components.ts
|
|
1202
|
+
import {
|
|
1203
|
+
checkContractComponentRequirements
|
|
1204
|
+
} from "@prisma-next/contract/framework-components";
|
|
1205
|
+
function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
|
|
1206
|
+
for (let i = 0; i < frameworkComponents.length; i++) {
|
|
1207
|
+
const component = frameworkComponents[i];
|
|
1208
|
+
if (typeof component !== "object" || component === null) {
|
|
1209
|
+
throw errorConfigValidation("frameworkComponents[]", {
|
|
1210
|
+
why: `Framework component at index ${i} must be an object`
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
const record = component;
|
|
1214
|
+
if (!Object.hasOwn(record, "kind")) {
|
|
1215
|
+
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
1216
|
+
why: `Framework component at index ${i} must have 'kind' property`
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
const kind = record["kind"];
|
|
1220
|
+
if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") {
|
|
1221
|
+
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
1222
|
+
why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
if (!Object.hasOwn(record, "familyId")) {
|
|
1226
|
+
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
1227
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
const familyId = record["familyId"];
|
|
1231
|
+
if (familyId !== expectedFamilyId) {
|
|
1232
|
+
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
1233
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
if (!Object.hasOwn(record, "targetId")) {
|
|
1237
|
+
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
1238
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
const targetId = record["targetId"];
|
|
1242
|
+
if (targetId !== expectedTargetId) {
|
|
1243
|
+
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
1244
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return frameworkComponents;
|
|
1249
|
+
}
|
|
1250
|
+
function assertContractRequirementsSatisfied({
|
|
1251
|
+
contract,
|
|
1252
|
+
family,
|
|
1253
|
+
target,
|
|
1254
|
+
adapter,
|
|
1255
|
+
extensionPacks
|
|
1256
|
+
}) {
|
|
1257
|
+
const providedComponentIds = /* @__PURE__ */ new Set([target.id, adapter.id]);
|
|
1258
|
+
for (const extension of extensionPacks ?? []) {
|
|
1259
|
+
providedComponentIds.add(extension.id);
|
|
1260
|
+
}
|
|
1261
|
+
const result = checkContractComponentRequirements({
|
|
1262
|
+
contract,
|
|
1263
|
+
expectedTargetFamily: family.familyId,
|
|
1264
|
+
expectedTargetId: target.targetId,
|
|
1265
|
+
providedComponentIds
|
|
1266
|
+
});
|
|
1267
|
+
if (result.familyMismatch) {
|
|
1268
|
+
throw errorConfigValidation("contract.targetFamily", {
|
|
1269
|
+
why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
if (result.targetMismatch) {
|
|
1273
|
+
throw errorConfigValidation("contract.target", {
|
|
1274
|
+
why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
if (result.missingExtensionPackIds.length > 0) {
|
|
1278
|
+
throw errorContractMissingExtensionPacks({
|
|
1279
|
+
missingExtensionPacks: result.missingExtensionPackIds,
|
|
1280
|
+
providedComponentIds: [...providedComponentIds]
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/commands/db-init.ts
|
|
1286
|
+
function createDbInitCommand() {
|
|
1287
|
+
const command = new Command2("init");
|
|
1288
|
+
setCommandDescriptions(
|
|
1289
|
+
command,
|
|
1290
|
+
"Bootstrap a database to match the current contract and write the contract marker",
|
|
1291
|
+
"Initializes a database to match your emitted contract using additive-only operations.\nCreates any missing tables, columns, indexes, and constraints defined in your contract.\nLeaves existing compatible structures in place, surfaces conflicts when destructive changes\nwould be required, and writes a contract marker to track the database state. Use --plan to\npreview changes without applying."
|
|
1292
|
+
);
|
|
1293
|
+
command.configureHelp({
|
|
1294
|
+
formatHelp: (cmd) => {
|
|
1295
|
+
const flags = parseGlobalFlags({});
|
|
1296
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
1297
|
+
}
|
|
1298
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--plan", "Preview planned operations without applying", false).option("--json [format]", "Output as JSON (object)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output: debug info, timings").option("-vv, --trace", "Trace output: deep internals, stack traces").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output").action(async (options) => {
|
|
1299
|
+
const flags = parseGlobalFlags(options);
|
|
1300
|
+
const startTime = Date.now();
|
|
1301
|
+
const result = await performAction(async () => {
|
|
1302
|
+
if (flags.json === "ndjson") {
|
|
1303
|
+
throw errorJsonFormatNotSupported({
|
|
1304
|
+
command: "db init",
|
|
1305
|
+
format: "ndjson",
|
|
1306
|
+
supportedFormats: ["object"]
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
const config = await loadConfig(options.config);
|
|
1310
|
+
const configPath = options.config ? relative3(process.cwd(), resolve3(options.config)) : "prisma-next.config.ts";
|
|
1311
|
+
const contractPathAbsolute = config.contract?.output ? resolve3(config.contract.output) : resolve3("src/prisma/contract.json");
|
|
1312
|
+
const contractPath = relative3(process.cwd(), contractPathAbsolute);
|
|
1313
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
1314
|
+
const details = [
|
|
1315
|
+
{ label: "config", value: configPath },
|
|
1316
|
+
{ label: "contract", value: contractPath }
|
|
1317
|
+
];
|
|
1318
|
+
if (options.db) {
|
|
1319
|
+
details.push({ label: "database", value: options.db });
|
|
1320
|
+
}
|
|
1321
|
+
if (options.plan) {
|
|
1322
|
+
details.push({ label: "mode", value: "plan (dry run)" });
|
|
1323
|
+
}
|
|
1324
|
+
const header = formatStyledHeader({
|
|
1325
|
+
command: "db init",
|
|
1326
|
+
description: "Bootstrap a database to match the current contract",
|
|
1327
|
+
url: "https://pris.ly/db-init",
|
|
1328
|
+
details,
|
|
1329
|
+
flags
|
|
1330
|
+
});
|
|
1331
|
+
console.log(header);
|
|
1332
|
+
}
|
|
1333
|
+
let contractJsonContent;
|
|
1334
|
+
try {
|
|
1335
|
+
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
1338
|
+
throw errorFileNotFound(contractPathAbsolute, {
|
|
1339
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
1340
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
throw errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1344
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
let contractJson;
|
|
1348
|
+
try {
|
|
1349
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
throw errorContractValidationFailed(
|
|
1352
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
1353
|
+
{ where: { path: contractPathAbsolute } }
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
const dbUrl = options.db ?? config.db?.url;
|
|
1357
|
+
if (!dbUrl) {
|
|
1358
|
+
throw errorDatabaseUrlRequired({
|
|
1359
|
+
why: `Database URL is required for db init (set db.url in ${configPath}, or pass --db <url>)`
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
if (!config.driver) {
|
|
1363
|
+
throw errorDriverRequired({ why: "Config.driver is required for db init" });
|
|
1364
|
+
}
|
|
1365
|
+
const driverDescriptor = config.driver;
|
|
1366
|
+
if (!config.target.migrations) {
|
|
1367
|
+
throw errorTargetMigrationNotSupported({
|
|
1368
|
+
why: `Target "${config.target.id}" does not support migrations`
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
const migrations = config.target.migrations;
|
|
1372
|
+
let driver;
|
|
1373
|
+
try {
|
|
1374
|
+
driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
1375
|
+
message: "Connecting to database...",
|
|
1376
|
+
flags
|
|
1377
|
+
});
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1380
|
+
const code = error.code;
|
|
1381
|
+
const redacted = redactDatabaseUrl(dbUrl);
|
|
1382
|
+
throw errorRuntime("Database connection failed", {
|
|
1383
|
+
why: message,
|
|
1384
|
+
fix: "Verify the database URL, ensure the database is reachable, and confirm credentials/permissions",
|
|
1385
|
+
meta: {
|
|
1386
|
+
...typeof code !== "undefined" ? { code } : {},
|
|
1387
|
+
...redacted
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
const familyInstance = config.family.create({
|
|
1393
|
+
target: config.target,
|
|
1394
|
+
adapter: config.adapter,
|
|
1395
|
+
driver: driverDescriptor,
|
|
1396
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1397
|
+
});
|
|
1398
|
+
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1399
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1400
|
+
config.family.familyId,
|
|
1401
|
+
config.target.targetId,
|
|
1402
|
+
rawComponents
|
|
1403
|
+
);
|
|
1404
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1405
|
+
assertContractRequirementsSatisfied({
|
|
1406
|
+
contract: contractIR,
|
|
1407
|
+
family: config.family,
|
|
1408
|
+
target: config.target,
|
|
1409
|
+
adapter: config.adapter,
|
|
1410
|
+
extensionPacks: config.extensionPacks
|
|
1411
|
+
});
|
|
1412
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
1413
|
+
const runner = migrations.createRunner(familyInstance);
|
|
1414
|
+
const schemaIR = await withSpinner(() => familyInstance.introspect({ driver }), {
|
|
1415
|
+
message: "Introspecting database schema...",
|
|
1416
|
+
flags
|
|
1417
|
+
});
|
|
1418
|
+
const policy = { allowedOperationClasses: ["additive"] };
|
|
1419
|
+
const plannerResult = await withSpinner(
|
|
1420
|
+
async () => planner.plan({
|
|
1421
|
+
contract: contractIR,
|
|
1422
|
+
schema: schemaIR,
|
|
1423
|
+
policy,
|
|
1424
|
+
frameworkComponents
|
|
1425
|
+
}),
|
|
1426
|
+
{
|
|
1427
|
+
message: "Planning migration...",
|
|
1428
|
+
flags
|
|
1429
|
+
}
|
|
1430
|
+
);
|
|
1431
|
+
if (plannerResult.kind === "failure") {
|
|
1432
|
+
throw errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts });
|
|
1433
|
+
}
|
|
1434
|
+
const migrationPlan = plannerResult.plan;
|
|
1435
|
+
const existingMarker = await familyInstance.readMarker({ driver });
|
|
1436
|
+
if (existingMarker) {
|
|
1437
|
+
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
1438
|
+
if (markerMatchesDestination) {
|
|
1439
|
+
const dbInitResult2 = {
|
|
1440
|
+
ok: true,
|
|
1441
|
+
mode: options.plan ? "plan" : "apply",
|
|
1442
|
+
plan: {
|
|
1443
|
+
targetId: migrationPlan.targetId,
|
|
1444
|
+
destination: migrationPlan.destination,
|
|
1445
|
+
operations: []
|
|
1446
|
+
},
|
|
1447
|
+
...options.plan ? {} : {
|
|
1448
|
+
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
1449
|
+
marker: {
|
|
1450
|
+
coreHash: existingMarker.coreHash,
|
|
1451
|
+
profileHash: existingMarker.profileHash
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
summary: "Database already at target contract state",
|
|
1455
|
+
timings: { total: Date.now() - startTime }
|
|
1456
|
+
};
|
|
1457
|
+
return dbInitResult2;
|
|
1458
|
+
}
|
|
1459
|
+
const coreHashMismatch = existingMarker.coreHash !== migrationPlan.destination.coreHash;
|
|
1460
|
+
const profileHashMismatch = migrationPlan.destination.profileHash && existingMarker.profileHash !== migrationPlan.destination.profileHash;
|
|
1461
|
+
const mismatchParts = [];
|
|
1462
|
+
if (coreHashMismatch) {
|
|
1463
|
+
mismatchParts.push(
|
|
1464
|
+
`coreHash (marker: ${existingMarker.coreHash}, destination: ${migrationPlan.destination.coreHash})`
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
if (profileHashMismatch) {
|
|
1468
|
+
mismatchParts.push(
|
|
1469
|
+
`profileHash (marker: ${existingMarker.profileHash}, destination: ${migrationPlan.destination.profileHash})`
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
throw errorRuntime(
|
|
1473
|
+
`Existing contract marker does not match plan destination. Mismatch in ${mismatchParts.join(" and ")}.`,
|
|
1474
|
+
{
|
|
1475
|
+
why: "Database has an existing contract marker that does not match the target contract",
|
|
1476
|
+
fix: "If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow",
|
|
1477
|
+
meta: {
|
|
1478
|
+
code: "MARKER_ORIGIN_MISMATCH",
|
|
1479
|
+
markerCoreHash: existingMarker.coreHash,
|
|
1480
|
+
destinationCoreHash: migrationPlan.destination.coreHash,
|
|
1481
|
+
...existingMarker.profileHash ? { markerProfileHash: existingMarker.profileHash } : {},
|
|
1482
|
+
...migrationPlan.destination.profileHash ? { destinationProfileHash: migrationPlan.destination.profileHash } : {}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
if (options.plan) {
|
|
1488
|
+
const dbInitResult2 = {
|
|
1489
|
+
ok: true,
|
|
1490
|
+
mode: "plan",
|
|
1491
|
+
plan: {
|
|
1492
|
+
targetId: migrationPlan.targetId,
|
|
1493
|
+
destination: migrationPlan.destination,
|
|
1494
|
+
operations: migrationPlan.operations.map((op) => ({
|
|
1495
|
+
id: op.id,
|
|
1496
|
+
label: op.label,
|
|
1497
|
+
operationClass: op.operationClass
|
|
1498
|
+
}))
|
|
1499
|
+
},
|
|
1500
|
+
summary: `Planned ${migrationPlan.operations.length} operation(s)`,
|
|
1501
|
+
timings: { total: Date.now() - startTime }
|
|
1502
|
+
};
|
|
1503
|
+
return dbInitResult2;
|
|
1504
|
+
}
|
|
1505
|
+
if (!flags.quiet && flags.json !== "object") {
|
|
1506
|
+
console.log("Applying migration plan and verifying schema...");
|
|
1507
|
+
}
|
|
1508
|
+
const callbacks = {
|
|
1509
|
+
onOperationStart: (op) => {
|
|
1510
|
+
if (!flags.quiet && flags.json !== "object") {
|
|
1511
|
+
console.log(` \u2192 ${op.label}...`);
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
onOperationComplete: (_op) => {
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
const runnerResult = await runner.execute({
|
|
1518
|
+
plan: migrationPlan,
|
|
1519
|
+
driver,
|
|
1520
|
+
destinationContract: contractIR,
|
|
1521
|
+
policy,
|
|
1522
|
+
callbacks,
|
|
1523
|
+
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
1524
|
+
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
1525
|
+
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
1526
|
+
executionChecks: {
|
|
1527
|
+
prechecks: false,
|
|
1528
|
+
postchecks: false,
|
|
1529
|
+
idempotencyChecks: false
|
|
1530
|
+
},
|
|
1531
|
+
frameworkComponents
|
|
1532
|
+
});
|
|
1533
|
+
if (!runnerResult.ok) {
|
|
1534
|
+
const meta = {
|
|
1535
|
+
code: runnerResult.failure.code,
|
|
1536
|
+
...runnerResult.failure.meta ?? {}
|
|
1537
|
+
};
|
|
1538
|
+
const sqlState = typeof meta["sqlState"] === "string" ? meta["sqlState"] : void 0;
|
|
1539
|
+
const fix = sqlState === "42501" ? "Grant the database user sufficient privileges (insufficient_privilege), or run db init as a more privileged role" : runnerResult.failure.code === "SCHEMA_VERIFY_FAILED" ? "Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`" : void 0;
|
|
1540
|
+
throw errorRuntime(runnerResult.failure.summary, {
|
|
1541
|
+
why: runnerResult.failure.why ?? `Migration runner failed: ${runnerResult.failure.code}`,
|
|
1542
|
+
...fix ? { fix } : {},
|
|
1543
|
+
meta
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const execution = runnerResult.value;
|
|
1547
|
+
const dbInitResult = {
|
|
1548
|
+
ok: true,
|
|
1549
|
+
mode: "apply",
|
|
1550
|
+
plan: {
|
|
1551
|
+
targetId: migrationPlan.targetId,
|
|
1552
|
+
destination: migrationPlan.destination,
|
|
1553
|
+
operations: migrationPlan.operations.map((op) => ({
|
|
1554
|
+
id: op.id,
|
|
1555
|
+
label: op.label,
|
|
1556
|
+
operationClass: op.operationClass
|
|
1557
|
+
}))
|
|
1558
|
+
},
|
|
1559
|
+
execution: {
|
|
1560
|
+
operationsPlanned: execution.operationsPlanned,
|
|
1561
|
+
operationsExecuted: execution.operationsExecuted
|
|
1562
|
+
},
|
|
1563
|
+
marker: migrationPlan.destination.profileHash ? {
|
|
1564
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
1565
|
+
profileHash: migrationPlan.destination.profileHash
|
|
1566
|
+
} : { coreHash: migrationPlan.destination.coreHash },
|
|
1567
|
+
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,
|
|
1568
|
+
timings: { total: Date.now() - startTime }
|
|
1569
|
+
};
|
|
1570
|
+
return dbInitResult;
|
|
1571
|
+
} finally {
|
|
1572
|
+
await driver.close();
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
const exitCode = handleResult(result, flags, (dbInitResult) => {
|
|
1576
|
+
if (flags.json === "object") {
|
|
1577
|
+
console.log(formatDbInitJson(dbInitResult));
|
|
1578
|
+
} else {
|
|
1579
|
+
const output = dbInitResult.mode === "plan" ? formatDbInitPlanOutput(dbInitResult, flags) : formatDbInitApplyOutput(dbInitResult, flags);
|
|
1580
|
+
if (output) {
|
|
1581
|
+
console.log(output);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
process.exit(exitCode);
|
|
1586
|
+
});
|
|
1587
|
+
return command;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// src/commands/db-introspect.ts
|
|
1591
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1592
|
+
import { relative as relative4, resolve as resolve4 } from "path";
|
|
1127
1593
|
import {
|
|
1128
1594
|
errorDatabaseUrlRequired as errorDatabaseUrlRequired2,
|
|
1129
1595
|
errorDriverRequired as errorDriverRequired2,
|
|
1130
1596
|
errorRuntime as errorRuntime2,
|
|
1131
1597
|
errorUnexpected as errorUnexpected3
|
|
1132
1598
|
} from "@prisma-next/core-control-plane/errors";
|
|
1133
|
-
import { Command as
|
|
1599
|
+
import { Command as Command3 } from "commander";
|
|
1134
1600
|
function createDbIntrospectCommand() {
|
|
1135
|
-
const command = new
|
|
1601
|
+
const command = new Command3("introspect");
|
|
1136
1602
|
setCommandDescriptions(
|
|
1137
1603
|
command,
|
|
1138
1604
|
"Inspect the database schema",
|
|
@@ -1148,14 +1614,13 @@ function createDbIntrospectCommand() {
|
|
|
1148
1614
|
const result = await performAction(async () => {
|
|
1149
1615
|
const startTime = Date.now();
|
|
1150
1616
|
const config = await loadConfig(options.config);
|
|
1151
|
-
const configPath = options.config ?
|
|
1617
|
+
const configPath = options.config ? relative4(process.cwd(), resolve4(options.config)) : "prisma-next.config.ts";
|
|
1152
1618
|
let contractIR;
|
|
1153
1619
|
if (config.contract?.output) {
|
|
1154
|
-
const contractPath =
|
|
1620
|
+
const contractPath = resolve4(config.contract.output);
|
|
1155
1621
|
try {
|
|
1156
|
-
const contractJsonContent = await
|
|
1157
|
-
|
|
1158
|
-
contractIR = contractJson;
|
|
1622
|
+
const contractJsonContent = await readFile2(contractPath, "utf-8");
|
|
1623
|
+
contractIR = JSON.parse(contractJsonContent);
|
|
1159
1624
|
} catch (error) {
|
|
1160
1625
|
if (error instanceof Error && error.code !== "ENOENT") {
|
|
1161
1626
|
throw errorUnexpected3(error.message, {
|
|
@@ -1201,16 +1666,23 @@ function createDbIntrospectCommand() {
|
|
|
1201
1666
|
target: config.target,
|
|
1202
1667
|
adapter: config.adapter,
|
|
1203
1668
|
driver: driverDescriptor,
|
|
1204
|
-
|
|
1669
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1205
1670
|
});
|
|
1206
|
-
const typedFamilyInstance = familyInstance;
|
|
1207
1671
|
if (contractIR) {
|
|
1208
|
-
|
|
1672
|
+
const validatedContract = familyInstance.validateContractIR(contractIR);
|
|
1673
|
+
assertContractRequirementsSatisfied({
|
|
1674
|
+
contract: validatedContract,
|
|
1675
|
+
family: config.family,
|
|
1676
|
+
target: config.target,
|
|
1677
|
+
adapter: config.adapter,
|
|
1678
|
+
extensionPacks: config.extensionPacks
|
|
1679
|
+
});
|
|
1680
|
+
contractIR = validatedContract;
|
|
1209
1681
|
}
|
|
1210
1682
|
let schemaIR;
|
|
1211
1683
|
try {
|
|
1212
1684
|
schemaIR = await withSpinner(
|
|
1213
|
-
() =>
|
|
1685
|
+
() => familyInstance.introspect({
|
|
1214
1686
|
driver,
|
|
1215
1687
|
contractIR
|
|
1216
1688
|
}),
|
|
@@ -1225,9 +1697,9 @@ function createDbIntrospectCommand() {
|
|
|
1225
1697
|
});
|
|
1226
1698
|
}
|
|
1227
1699
|
let schemaView;
|
|
1228
|
-
if (
|
|
1700
|
+
if (familyInstance.toSchemaView) {
|
|
1229
1701
|
try {
|
|
1230
|
-
schemaView =
|
|
1702
|
+
schemaView = familyInstance.toSchemaView(schemaIR);
|
|
1231
1703
|
} catch (error) {
|
|
1232
1704
|
if (flags.verbose) {
|
|
1233
1705
|
console.error(
|
|
@@ -1285,8 +1757,8 @@ function createDbIntrospectCommand() {
|
|
|
1285
1757
|
}
|
|
1286
1758
|
|
|
1287
1759
|
// src/commands/db-schema-verify.ts
|
|
1288
|
-
import { readFile as
|
|
1289
|
-
import { relative as
|
|
1760
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1761
|
+
import { relative as relative5, resolve as resolve5 } from "path";
|
|
1290
1762
|
import {
|
|
1291
1763
|
errorDatabaseUrlRequired as errorDatabaseUrlRequired3,
|
|
1292
1764
|
errorDriverRequired as errorDriverRequired3,
|
|
@@ -1294,9 +1766,9 @@ import {
|
|
|
1294
1766
|
errorRuntime as errorRuntime3,
|
|
1295
1767
|
errorUnexpected as errorUnexpected4
|
|
1296
1768
|
} from "@prisma-next/core-control-plane/errors";
|
|
1297
|
-
import { Command as
|
|
1769
|
+
import { Command as Command4 } from "commander";
|
|
1298
1770
|
function createDbSchemaVerifyCommand() {
|
|
1299
|
-
const command = new
|
|
1771
|
+
const command = new Command4("schema-verify");
|
|
1300
1772
|
setCommandDescriptions(
|
|
1301
1773
|
command,
|
|
1302
1774
|
"Check whether the database schema satisfies your contract",
|
|
@@ -1311,9 +1783,9 @@ function createDbSchemaVerifyCommand() {
|
|
|
1311
1783
|
const flags = parseGlobalFlags(options);
|
|
1312
1784
|
const result = await performAction(async () => {
|
|
1313
1785
|
const config = await loadConfig(options.config);
|
|
1314
|
-
const configPath = options.config ?
|
|
1315
|
-
const contractPathAbsolute = config.contract?.output ?
|
|
1316
|
-
const contractPath =
|
|
1786
|
+
const configPath = options.config ? relative5(process.cwd(), resolve5(options.config)) : "prisma-next.config.ts";
|
|
1787
|
+
const contractPathAbsolute = config.contract?.output ? resolve5(config.contract.output) : resolve5("src/prisma/contract.json");
|
|
1788
|
+
const contractPath = relative5(process.cwd(), contractPathAbsolute);
|
|
1317
1789
|
if (flags.json !== "object" && !flags.quiet) {
|
|
1318
1790
|
const details = [
|
|
1319
1791
|
{ label: "config", value: configPath },
|
|
@@ -1333,7 +1805,7 @@ function createDbSchemaVerifyCommand() {
|
|
|
1333
1805
|
}
|
|
1334
1806
|
let contractJsonContent;
|
|
1335
1807
|
try {
|
|
1336
|
-
contractJsonContent = await
|
|
1808
|
+
contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
|
|
1337
1809
|
} catch (error) {
|
|
1338
1810
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
1339
1811
|
throw errorFileNotFound2(contractPathAbsolute, {
|
|
@@ -1345,36 +1817,49 @@ function createDbSchemaVerifyCommand() {
|
|
|
1345
1817
|
});
|
|
1346
1818
|
}
|
|
1347
1819
|
const contractJson = JSON.parse(contractJsonContent);
|
|
1348
|
-
const dbUrl = options.db ?? config.db?.url;
|
|
1349
|
-
if (!dbUrl) {
|
|
1350
|
-
throw errorDatabaseUrlRequired3();
|
|
1351
|
-
}
|
|
1352
1820
|
if (!config.driver) {
|
|
1353
1821
|
throw errorDriverRequired3();
|
|
1354
1822
|
}
|
|
1355
1823
|
const driverDescriptor = config.driver;
|
|
1824
|
+
const familyInstance = config.family.create({
|
|
1825
|
+
target: config.target,
|
|
1826
|
+
adapter: config.adapter,
|
|
1827
|
+
driver: driverDescriptor,
|
|
1828
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1829
|
+
});
|
|
1830
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1831
|
+
assertContractRequirementsSatisfied({
|
|
1832
|
+
contract: contractIR,
|
|
1833
|
+
family: config.family,
|
|
1834
|
+
target: config.target,
|
|
1835
|
+
adapter: config.adapter,
|
|
1836
|
+
extensionPacks: config.extensionPacks
|
|
1837
|
+
});
|
|
1838
|
+
const dbUrl = options.db ?? config.db?.url;
|
|
1839
|
+
if (!dbUrl) {
|
|
1840
|
+
throw errorDatabaseUrlRequired3();
|
|
1841
|
+
}
|
|
1356
1842
|
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
1357
1843
|
message: "Connecting to database...",
|
|
1358
1844
|
flags
|
|
1359
1845
|
});
|
|
1360
1846
|
try {
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const typedFamilyInstance = familyInstance;
|
|
1368
|
-
const contractIR = typedFamilyInstance.validateContractIR(contractJson);
|
|
1847
|
+
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1848
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1849
|
+
config.family.familyId,
|
|
1850
|
+
config.target.targetId,
|
|
1851
|
+
rawComponents
|
|
1852
|
+
);
|
|
1369
1853
|
let schemaVerifyResult;
|
|
1370
1854
|
try {
|
|
1371
1855
|
schemaVerifyResult = await withSpinner(
|
|
1372
|
-
() =>
|
|
1856
|
+
() => familyInstance.schemaVerify({
|
|
1373
1857
|
driver,
|
|
1374
1858
|
contractIR,
|
|
1375
1859
|
strict: options.strict ?? false,
|
|
1376
1860
|
contractPath: contractPathAbsolute,
|
|
1377
|
-
configPath
|
|
1861
|
+
configPath,
|
|
1862
|
+
frameworkComponents
|
|
1378
1863
|
}),
|
|
1379
1864
|
{
|
|
1380
1865
|
message: "Verifying database schema...",
|
|
@@ -1414,8 +1899,8 @@ function createDbSchemaVerifyCommand() {
|
|
|
1414
1899
|
}
|
|
1415
1900
|
|
|
1416
1901
|
// src/commands/db-sign.ts
|
|
1417
|
-
import { readFile as
|
|
1418
|
-
import { relative as
|
|
1902
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1903
|
+
import { relative as relative6, resolve as resolve6 } from "path";
|
|
1419
1904
|
import {
|
|
1420
1905
|
errorDatabaseUrlRequired as errorDatabaseUrlRequired4,
|
|
1421
1906
|
errorDriverRequired as errorDriverRequired4,
|
|
@@ -1423,9 +1908,9 @@ import {
|
|
|
1423
1908
|
errorRuntime as errorRuntime4,
|
|
1424
1909
|
errorUnexpected as errorUnexpected5
|
|
1425
1910
|
} from "@prisma-next/core-control-plane/errors";
|
|
1426
|
-
import { Command as
|
|
1911
|
+
import { Command as Command5 } from "commander";
|
|
1427
1912
|
function createDbSignCommand() {
|
|
1428
|
-
const command = new
|
|
1913
|
+
const command = new Command5("sign");
|
|
1429
1914
|
setCommandDescriptions(
|
|
1430
1915
|
command,
|
|
1431
1916
|
"Sign the database with your contract so you can safely run queries",
|
|
@@ -1440,9 +1925,9 @@ function createDbSignCommand() {
|
|
|
1440
1925
|
const flags = parseGlobalFlags(options);
|
|
1441
1926
|
const result = await performAction(async () => {
|
|
1442
1927
|
const config = await loadConfig(options.config);
|
|
1443
|
-
const configPath = options.config ?
|
|
1444
|
-
const contractPathAbsolute = config.contract?.output ?
|
|
1445
|
-
const contractPath =
|
|
1928
|
+
const configPath = options.config ? relative6(process.cwd(), resolve6(options.config)) : "prisma-next.config.ts";
|
|
1929
|
+
const contractPathAbsolute = config.contract?.output ? resolve6(config.contract.output) : resolve6("src/prisma/contract.json");
|
|
1930
|
+
const contractPath = relative6(process.cwd(), contractPathAbsolute);
|
|
1446
1931
|
if (flags.json !== "object" && !flags.quiet) {
|
|
1447
1932
|
const details = [
|
|
1448
1933
|
{ label: "config", value: configPath },
|
|
@@ -1462,7 +1947,7 @@ function createDbSignCommand() {
|
|
|
1462
1947
|
}
|
|
1463
1948
|
let contractJsonContent;
|
|
1464
1949
|
try {
|
|
1465
|
-
contractJsonContent = await
|
|
1950
|
+
contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
|
|
1466
1951
|
} catch (error) {
|
|
1467
1952
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
1468
1953
|
throw errorFileNotFound3(contractPathAbsolute, {
|
|
@@ -1482,28 +1967,39 @@ function createDbSignCommand() {
|
|
|
1482
1967
|
throw errorDriverRequired4();
|
|
1483
1968
|
}
|
|
1484
1969
|
const driverDescriptor = config.driver;
|
|
1970
|
+
const familyInstance = config.family.create({
|
|
1971
|
+
target: config.target,
|
|
1972
|
+
adapter: config.adapter,
|
|
1973
|
+
driver: driverDescriptor,
|
|
1974
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1975
|
+
});
|
|
1976
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1977
|
+
assertContractRequirementsSatisfied({
|
|
1978
|
+
contract: contractIR,
|
|
1979
|
+
family: config.family,
|
|
1980
|
+
target: config.target,
|
|
1981
|
+
adapter: config.adapter,
|
|
1982
|
+
extensionPacks: config.extensionPacks
|
|
1983
|
+
});
|
|
1984
|
+
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1985
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1986
|
+
config.family.familyId,
|
|
1987
|
+
config.target.targetId,
|
|
1988
|
+
rawComponents
|
|
1989
|
+
);
|
|
1485
1990
|
const driver = await driverDescriptor.create(dbUrl);
|
|
1486
1991
|
try {
|
|
1487
|
-
const familyInstance = config.family.create({
|
|
1488
|
-
target: config.target,
|
|
1489
|
-
adapter: config.adapter,
|
|
1490
|
-
driver: driverDescriptor,
|
|
1491
|
-
extensions: config.extensions ?? []
|
|
1492
|
-
});
|
|
1493
|
-
const typedFamilyInstance = familyInstance;
|
|
1494
|
-
const contractIR = typedFamilyInstance.validateContractIR(contractJson);
|
|
1495
1992
|
let schemaVerifyResult;
|
|
1496
1993
|
try {
|
|
1497
1994
|
schemaVerifyResult = await withSpinner(
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
},
|
|
1995
|
+
() => familyInstance.schemaVerify({
|
|
1996
|
+
driver,
|
|
1997
|
+
contractIR,
|
|
1998
|
+
strict: false,
|
|
1999
|
+
contractPath: contractPathAbsolute,
|
|
2000
|
+
configPath,
|
|
2001
|
+
frameworkComponents
|
|
2002
|
+
}),
|
|
1507
2003
|
{
|
|
1508
2004
|
message: "Verifying database satisfies contract",
|
|
1509
2005
|
flags
|
|
@@ -1520,14 +2016,12 @@ function createDbSignCommand() {
|
|
|
1520
2016
|
let signResult;
|
|
1521
2017
|
try {
|
|
1522
2018
|
signResult = await withSpinner(
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
});
|
|
1530
|
-
},
|
|
2019
|
+
() => familyInstance.sign({
|
|
2020
|
+
driver,
|
|
2021
|
+
contractIR,
|
|
2022
|
+
contractPath: contractPathAbsolute,
|
|
2023
|
+
configPath
|
|
2024
|
+
}),
|
|
1531
2025
|
{
|
|
1532
2026
|
message: "Signing database...",
|
|
1533
2027
|
flags
|
|
@@ -1580,8 +2074,8 @@ function createDbSignCommand() {
|
|
|
1580
2074
|
}
|
|
1581
2075
|
|
|
1582
2076
|
// src/commands/db-verify.ts
|
|
1583
|
-
import { readFile as
|
|
1584
|
-
import { relative as
|
|
2077
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2078
|
+
import { relative as relative7, resolve as resolve7 } from "path";
|
|
1585
2079
|
import {
|
|
1586
2080
|
errorDatabaseUrlRequired as errorDatabaseUrlRequired5,
|
|
1587
2081
|
errorDriverRequired as errorDriverRequired5,
|
|
@@ -1592,9 +2086,9 @@ import {
|
|
|
1592
2086
|
errorTargetMismatch as errorTargetMismatch2,
|
|
1593
2087
|
errorUnexpected as errorUnexpected6
|
|
1594
2088
|
} from "@prisma-next/core-control-plane/errors";
|
|
1595
|
-
import { Command as
|
|
2089
|
+
import { Command as Command6 } from "commander";
|
|
1596
2090
|
function createDbVerifyCommand() {
|
|
1597
|
-
const command = new
|
|
2091
|
+
const command = new Command6("verify");
|
|
1598
2092
|
setCommandDescriptions(
|
|
1599
2093
|
command,
|
|
1600
2094
|
"Check whether the database has been signed with your contract",
|
|
@@ -1609,9 +2103,9 @@ function createDbVerifyCommand() {
|
|
|
1609
2103
|
const flags = parseGlobalFlags(options);
|
|
1610
2104
|
const result = await performAction(async () => {
|
|
1611
2105
|
const config = await loadConfig(options.config);
|
|
1612
|
-
const configPath = options.config ?
|
|
1613
|
-
const contractPathAbsolute = config.contract?.output ?
|
|
1614
|
-
const contractPath =
|
|
2106
|
+
const configPath = options.config ? relative7(process.cwd(), resolve7(options.config)) : "prisma-next.config.ts";
|
|
2107
|
+
const contractPathAbsolute = config.contract?.output ? resolve7(config.contract.output) : resolve7("src/prisma/contract.json");
|
|
2108
|
+
const contractPath = relative7(process.cwd(), contractPathAbsolute);
|
|
1615
2109
|
if (flags.json !== "object" && !flags.quiet) {
|
|
1616
2110
|
const details = [
|
|
1617
2111
|
{ label: "config", value: configPath },
|
|
@@ -1631,7 +2125,7 @@ function createDbVerifyCommand() {
|
|
|
1631
2125
|
}
|
|
1632
2126
|
let contractJsonContent;
|
|
1633
2127
|
try {
|
|
1634
|
-
contractJsonContent = await
|
|
2128
|
+
contractJsonContent = await readFile5(contractPathAbsolute, "utf-8");
|
|
1635
2129
|
} catch (error) {
|
|
1636
2130
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
1637
2131
|
throw errorFileNotFound4(contractPathAbsolute, {
|
|
@@ -1660,14 +2154,20 @@ function createDbVerifyCommand() {
|
|
|
1660
2154
|
target: config.target,
|
|
1661
2155
|
adapter: config.adapter,
|
|
1662
2156
|
driver: driverDescriptor,
|
|
1663
|
-
|
|
2157
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2158
|
+
});
|
|
2159
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
2160
|
+
assertContractRequirementsSatisfied({
|
|
2161
|
+
contract: contractIR,
|
|
2162
|
+
family: config.family,
|
|
2163
|
+
target: config.target,
|
|
2164
|
+
adapter: config.adapter,
|
|
2165
|
+
extensionPacks: config.extensionPacks
|
|
1664
2166
|
});
|
|
1665
|
-
const typedFamilyInstance = familyInstance;
|
|
1666
|
-
const contractIR = typedFamilyInstance.validateContractIR(contractJson);
|
|
1667
2167
|
let verifyResult;
|
|
1668
2168
|
try {
|
|
1669
2169
|
verifyResult = await withSpinner(
|
|
1670
|
-
() =>
|
|
2170
|
+
() => familyInstance.verify({
|
|
1671
2171
|
driver,
|
|
1672
2172
|
contractIR,
|
|
1673
2173
|
expectedTargetId: config.target.targetId,
|
|
@@ -1726,7 +2226,7 @@ function createDbVerifyCommand() {
|
|
|
1726
2226
|
}
|
|
1727
2227
|
|
|
1728
2228
|
// src/cli.ts
|
|
1729
|
-
var program = new
|
|
2229
|
+
var program = new Command7();
|
|
1730
2230
|
program.name("prisma-next").description("Prisma Next CLI").version("0.0.1");
|
|
1731
2231
|
var versionOption = program.options.find((opt) => opt.flags.includes("--version"));
|
|
1732
2232
|
if (versionOption) {
|
|
@@ -1746,11 +2246,11 @@ program.configureHelp({
|
|
|
1746
2246
|
formatHelp: rootHelpFormatter,
|
|
1747
2247
|
subcommandDescription: () => ""
|
|
1748
2248
|
});
|
|
1749
|
-
program.exitOverride((
|
|
1750
|
-
if (
|
|
1751
|
-
const errorCode =
|
|
1752
|
-
const errorMessage = String(
|
|
1753
|
-
const errorName =
|
|
2249
|
+
program.exitOverride((err) => {
|
|
2250
|
+
if (err) {
|
|
2251
|
+
const errorCode = err.code;
|
|
2252
|
+
const errorMessage = String(err.message ?? "");
|
|
2253
|
+
const errorName = err.name ?? "";
|
|
1754
2254
|
const isUnknownCommandError = errorCode === "commander.unknownCommand" || errorCode === "commander.unknownArgument" || errorName === "CommanderError" && (errorMessage.includes("unknown command") || errorMessage.includes("unknown argument"));
|
|
1755
2255
|
if (isUnknownCommandError) {
|
|
1756
2256
|
const flags = parseGlobalFlags({});
|
|
@@ -1782,15 +2282,15 @@ program.exitOverride((err2) => {
|
|
|
1782
2282
|
process.exit(0);
|
|
1783
2283
|
return;
|
|
1784
2284
|
}
|
|
1785
|
-
console.error(`Unhandled error: ${
|
|
1786
|
-
if (
|
|
1787
|
-
console.error(
|
|
2285
|
+
console.error(`Unhandled error: ${err.message}`);
|
|
2286
|
+
if (err.stack) {
|
|
2287
|
+
console.error(err.stack);
|
|
1788
2288
|
}
|
|
1789
2289
|
process.exit(1);
|
|
1790
2290
|
}
|
|
1791
2291
|
process.exit(0);
|
|
1792
2292
|
});
|
|
1793
|
-
var contractCommand = new
|
|
2293
|
+
var contractCommand = new Command7("contract");
|
|
1794
2294
|
setCommandDescriptions(
|
|
1795
2295
|
contractCommand,
|
|
1796
2296
|
"Contract management commands",
|
|
@@ -1806,7 +2306,7 @@ contractCommand.configureHelp({
|
|
|
1806
2306
|
var contractEmitCommand = createContractEmitCommand();
|
|
1807
2307
|
contractCommand.addCommand(contractEmitCommand);
|
|
1808
2308
|
program.addCommand(contractCommand);
|
|
1809
|
-
var dbCommand = new
|
|
2309
|
+
var dbCommand = new Command7("db");
|
|
1810
2310
|
setCommandDescriptions(
|
|
1811
2311
|
dbCommand,
|
|
1812
2312
|
"Database management commands",
|
|
@@ -1821,6 +2321,8 @@ dbCommand.configureHelp({
|
|
|
1821
2321
|
});
|
|
1822
2322
|
var dbVerifyCommand = createDbVerifyCommand();
|
|
1823
2323
|
dbCommand.addCommand(dbVerifyCommand);
|
|
2324
|
+
var dbInitCommand = createDbInitCommand();
|
|
2325
|
+
dbCommand.addCommand(dbInitCommand);
|
|
1824
2326
|
var dbIntrospectCommand = createDbIntrospectCommand();
|
|
1825
2327
|
dbCommand.addCommand(dbIntrospectCommand);
|
|
1826
2328
|
var dbSchemaVerifyCommand = createDbSchemaVerifyCommand();
|
|
@@ -1828,7 +2330,7 @@ dbCommand.addCommand(dbSchemaVerifyCommand);
|
|
|
1828
2330
|
var dbSignCommand = createDbSignCommand();
|
|
1829
2331
|
dbCommand.addCommand(dbSignCommand);
|
|
1830
2332
|
program.addCommand(dbCommand);
|
|
1831
|
-
var helpCommand = new
|
|
2333
|
+
var helpCommand = new Command7("help").description("Show usage instructions").configureHelp({
|
|
1832
2334
|
formatHelp: (cmd) => {
|
|
1833
2335
|
const flags = parseGlobalFlags({});
|
|
1834
2336
|
return formatCommandHelp({ command: cmd, flags });
|