@prisma/orm-toolchain 8.0.0-rc.1-dev.38 → 8.0.0-rc.1-dev.39
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/dist/bin__prisma-next.mjs +8 -301
- package/dist/bin__prisma-next.mjs.map +1 -1
- package/dist/cli.mjs +1944 -256
- package/dist/cli.mjs.map +1 -1
- package/dist/cli__commands__migrate.mjs +1 -1
- package/dist/cli__commands__migration-check.d.mts +1 -1
- package/dist/cli__commands__migration-check.mjs +1 -1
- package/dist/cli__commands__migration-graph.d.mts +1 -1
- package/dist/cli__commands__migration-graph.mjs +1 -1
- package/dist/cli__commands__migration-list.d.mts +1 -1
- package/dist/cli__commands__migration-list.mjs +1 -1
- package/dist/cli__commands__migration-log.d.mts +1 -1
- package/dist/cli__commands__migration-log.mjs +1 -1
- package/dist/cli__commands__migration-show.d.mts +1 -1
- package/dist/cli__commands__migration-status.d.mts +1 -1
- package/dist/cli__commands__migration-status.mjs +2 -2
- package/dist/cli__control-api.d.mts +1 -1
- package/dist/cli__control-api.mjs +1 -1
- package/dist/cli__init-output.d.mts +1 -1
- package/dist/cli__init-output.mjs +1 -1
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs +295 -0
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs.map +1 -0
- package/dist/init-CcJxAIeS-CITTa8YU.mjs +800 -0
- package/dist/init-CcJxAIeS-CITTa8YU.mjs.map +1 -0
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs → migrate-BVEyoBor-CN_EOCzx.mjs} +3 -3
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs.map → migrate-BVEyoBor-CN_EOCzx.mjs.map} +1 -1
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs → migrate-failure-CKMEnA_x-ByHkYjn5.mjs} +3 -3
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs.map → migrate-failure-CKMEnA_x-ByHkYjn5.mjs.map} +1 -1
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs → migration-graph-D4sgTiXa-BLtiLIk7.mjs} +4 -4
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs.map → migration-graph-D4sgTiXa-BLtiLIk7.mjs.map} +1 -1
- package/dist/{migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs → migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs} +17 -3
- package/dist/migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs.map +1 -0
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs} +3 -3
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs.map → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs.map} +1 -1
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs → migration-list-BIL-re1m-CZY4m3d0.mjs} +3 -3
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs.map → migration-list-BIL-re1m-CZY4m3d0.mjs.map} +1 -1
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs → migration-log-BdBKYAUW-DTPxKZtD.mjs} +4 -4
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs.map → migration-log-BdBKYAUW-DTPxKZtD.mjs.map} +1 -1
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs → migration-log-table-eES9lPjv-BCFm8qJr.mjs} +3 -3
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs.map → migration-log-table-eES9lPjv-BCFm8qJr.mjs.map} +1 -1
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs → migration-status-DzPVkkB3-BA7jjITd.mjs} +5 -4
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs.map → migration-status-DzPVkkB3-BA7jjITd.mjs.map} +1 -1
- package/dist/{output-DWNYqZGc-D2sRgLAm.mjs → output-DP3woAjV-BxCr9nHA.mjs} +21 -11
- package/dist/output-DP3woAjV-BxCr9nHA.mjs.map +1 -0
- package/dist/{init-Bco4QkKJ-DqUZVrBU.mjs → redact-secrets-kQ0KKV1u-MnVSKe-u.mjs} +740 -1516
- package/dist/redact-secrets-kQ0KKV1u-MnVSKe-u.mjs.map +1 -0
- package/dist/{schemas-Bm2e3Hk1-qLKF0B36.d.mts → schemas-BGgjiKV0-Rh8RlMCl.d.mts} +4 -2
- package/dist/schemas-BGgjiKV0-Rh8RlMCl.d.mts.map +1 -0
- package/dist/{schemas-BMkqaqvM-CLaqlat4.mjs → schemas-CYSD83Cu-Bff4PPoi.mjs} +3 -2
- package/dist/schemas-CYSD83Cu-Bff4PPoi.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/init-Bco4QkKJ-DqUZVrBU.mjs.map +0 -1
- package/dist/migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs.map +0 -1
- package/dist/output-DWNYqZGc-D2sRgLAm.mjs.map +0 -1
- package/dist/schemas-BMkqaqvM-CLaqlat4.mjs.map +0 -1
- package/dist/schemas-Bm2e3Hk1-qLKF0B36.d.mts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
1
|
import { i as loadConfig } from "./exports-BZLNbtkY.mjs";
|
|
2
|
-
import { I as requireLiveDatabase, L as runCommandAction, M as errorTargetMigrationNotSupported, O as errorRunnerFailed, P as errorUnexpected, a as errorConfigValidation$1, b as errorMigrationPlanningFailed, c as errorContractValidationFailed, d as errorDriverRequired, f as errorFileNotFound, g as errorLegendHumanOnly, k as errorRuntime$1, l as errorDatabaseConnectionRequired, o as errorConsentPlanMismatch, t as CliStructuredError$1$1, u as errorDestructiveChanges, x as errorNoMigrations, y as errorMigrationPackageNotFound } from "./cli-errors-BG6bItjG-DwdKIeGz.mjs";
|
|
2
|
+
import { I as requireLiveDatabase, L as runCommandAction, M as errorTargetMigrationNotSupported, O as errorRunnerFailed, P as errorUnexpected, a as errorConfigValidation$1, b as errorMigrationPlanningFailed, c as errorContractValidationFailed, d as errorDriverRequired, f as errorFileNotFound, g as errorLegendHumanOnly, k as errorRuntime$1, l as errorDatabaseConnectionRequired, o as errorConsentPlanMismatch, r as chooseAction, t as CliStructuredError$1$1, u as errorDestructiveChanges, x as errorNoMigrations, y as errorMigrationPackageNotFound } from "./cli-errors-BG6bItjG-DwdKIeGz.mjs";
|
|
3
3
|
import { r as getEmittedArtifactPaths } from "./exports-BCLg4CiV-05kuwcb0.mjs";
|
|
4
4
|
import { n as executeContractEmit } from "./contract-emit-CZQZ1i4z-p9LSGmmQ.mjs";
|
|
5
5
|
import { h as renderPreviewStatement, m as previewBlockHeader } from "./terminal-ui-Cqz4LVL7-B0mcVdLy.mjs";
|
|
6
6
|
import { c as spaceMigrationDirectory } from "./verify-contract-spaces-DhJTb0zM-_oh6W6lp.mjs";
|
|
7
|
-
import { S as targetSupportsMigrations, f as maskConnectionUrl, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-CwLvFwMB-BWwUQhlT.mjs";
|
|
7
|
+
import { S as targetSupportsMigrations, f as maskConnectionUrl, h as readContractEnvelope, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-CwLvFwMB-BWwUQhlT.mjs";
|
|
8
8
|
import { t as createContractEmitCommand } from "./contract-emit-BQFpcb2A-BwT9iVdb.mjs";
|
|
9
9
|
import { n as executeFormat, t as createFormatCommand } from "./format-cbJY7l6v-CjMUDTAs.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { c as targetLabel, i as scaffoldSpecifierResolverFor, l as targetPackageName, n as dbFile, o as starterSchema, r as defaultSchemaPath, t as configFile } from "./code-templates-CSLH8721-CWRA4Lru.mjs";
|
|
11
|
+
import { a as loadContractSpaceAggregateForCli, i as loadContractRawSafely, l as refusePackageCorruptionOnAggregate, r as buildReadAggregate, s as refuseContractSpaceIntegrity, u as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs";
|
|
11
12
|
import { t as createControlClient } from "./client-DU9qtBxW-BjVHiKzE.mjs";
|
|
12
13
|
import { a as readContractIR, o as resolveRefAdvancementFields, t as advanceRefSafely } from "./ref-advancement-DaZBzm7m-WoqnFAyr.mjs";
|
|
13
14
|
import { t as mapDbInitFailure } from "./db-init-failure-BmCcHUfc-BqDxyrJE.mjs";
|
|
14
15
|
import { t as resolveContractRefToSnapshot } from "./contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs";
|
|
15
16
|
import { t as mapCaughtMigrationError } from "./caught-errors-DLPzfzIW-CQ9BoXD-.mjs";
|
|
16
17
|
import { t as mapContractAtError } from "./contract-at-errors-Dptf8RE_-B5gr04Sc.mjs";
|
|
17
|
-
import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph } from "./invariants-1zL5-9Mc-BCeOLxd_.mjs";
|
|
18
|
+
import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph, r as refuseMissingInvariantPath, t as hasMigrationPath } from "./invariants-1zL5-9Mc-BCeOLxd_.mjs";
|
|
18
19
|
import { t as executeMigrateShowPlan } from "./migrate-show-kVUG1xdo-bVTW8tu4.mjs";
|
|
19
20
|
import { n as resolveMigrationRef, t as resolveContractRef } from "./ref-resolution-DAK4OK3x-B-8ZZc0f.mjs";
|
|
20
|
-
import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList } from "./migration-list-BQTfSGbT-DoNqbNsu.mjs";
|
|
21
|
-
import {
|
|
22
|
-
import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-
|
|
21
|
+
import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList, t as listRefsByContractHash } from "./migration-list-BQTfSGbT-DoNqbNsu.mjs";
|
|
22
|
+
import { C as toneSpans, S as toneDrawing, b as renderMigrationGraphSpaceTrees, d as createToneMigrationListStyler, m as indentMigrationGraphTreeBlock, n as TONE_MIGRATION_GRAPH_PALETTE, v as renderMigrationGraphLegend, x as renderMigrationListWithStyle } from "./migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs";
|
|
23
|
+
import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-CKMEnA_x-ByHkYjn5.mjs";
|
|
23
24
|
import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-BNTSTPAc-D7dQ4K7x.mjs";
|
|
24
25
|
import { t as buildMigrationSpaceGraphEntries } from "./migration-graph-B30X5398-BH3zB9Mn.mjs";
|
|
25
|
-
import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-
|
|
26
|
-
import { a as sortLedgerEntries, i as serializeLedgerEntriesForJson, n as formatLedgerAppliedAt, o as styleHashTransition, t as MIGRATION_LOG_EMPTY_MESSAGE } from "./migration-log-table-
|
|
26
|
+
import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs";
|
|
27
|
+
import { a as sortLedgerEntries, i as serializeLedgerEntriesForJson, n as formatLedgerAppliedAt, o as styleHashTransition, t as MIGRATION_LOG_EMPTY_MESSAGE } from "./migration-log-table-eES9lPjv-BCFm8qJr.mjs";
|
|
28
|
+
import { t as executeMigrationNewCommand } from "./migration-new-Bp5dPKnF-D-Fb6vEp.mjs";
|
|
29
|
+
import { t as executeMigrationPlanCommand } from "./migration-plan-C6KrFHJT-CRkTsE7L.mjs";
|
|
30
|
+
import { a as statusForMigrationHash, i as readMigrationRefs, n as deriveStatusEdgeAnnotations, r as originHashForStatus, t as appliedHashesFromLedger } from "./refs-fQLFcMoq-aTBeUU9H.mjs";
|
|
27
31
|
import { n as executeRefListCommand, r as executeRefSetCommand, t as executeRefDeleteCommand } from "./ref-C8oaaeky-ColNkWpr.mjs";
|
|
32
|
+
import { A as isRecognisedPnpmResolutionError, B as removeDependency, C as findStaleArtifacts, E as formatRunCommand, F as mergeTsConfig, G as validateSchemaPath, H as resolveAuthoring, I as minimalProjectReadmeMd, L as probeServerVersion, M as mergeGitattributes, N as mergeGitignore, O as formatSkillSourceUrl, P as mergePackageScripts, R as quickReferenceMd, S as errorInitWriteFailed, U as resolveProjectSkillInstallCommands, V as requiredGitattributesLines, W as resolveTarget, _ as errorInitProbeFailed, a as TsConfigParseError, b as errorInitStrictProbeWithoutProbe, d as envFileContent, g as errorInitMissingFlags, h as errorInitInvalidTsconfig, i as REQUIRED_SCRIPTS, j as legacySkillDirs, l as ensureEsmModuleType, m as errorInitInvalidManifest, n as DEFAULT_SKILL_SOURCES, o as buildCatalogWarnings, r as MIN_SERVER_VERSION, s as defaultTsConfig, t as DEFAULT_SKILL_AGENTS, u as envExampleContent, x as errorInitUserAborted, z as redactSecrets } from "./redact-secrets-kQ0KKV1u-MnVSKe-u.mjs";
|
|
33
|
+
import { n as buildNextSteps, t as InitOutputSchema } from "./output-DP3woAjV-BxCr9nHA.mjs";
|
|
28
34
|
import { CliStructuredError } from "@prisma/orm-framework/errors/control";
|
|
29
35
|
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
30
|
-
import { basename, dirname, join, normalize, relative, resolve } from "pathe";
|
|
31
|
-
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
32
|
-
import { DOCS_BASE, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
36
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
37
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
38
|
+
import { DOCS_BASE, docsUrlFor, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
33
39
|
import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
|
|
34
40
|
import { InternalError, assertNever } from "@prisma/orm-framework/utils/internal-error";
|
|
35
41
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
36
42
|
import { APP_SPACE_ID, createControlStack } from "@prisma/orm-framework/components/control";
|
|
43
|
+
import { type } from "arktype";
|
|
37
44
|
import { collectConfigIssues } from "@prisma/orm-framework/config/config-validation";
|
|
38
45
|
import { tmpdir } from "node:os";
|
|
46
|
+
import { detect } from "package-manager-detector/detect";
|
|
39
47
|
import { printPsl } from "@prisma/orm-framework/psl-printer";
|
|
40
48
|
import { defineCommand, defineCommandFamily, defineConfigSection, flag, positional } from "@prisma/cli-engine";
|
|
41
49
|
import { CliStructuredError as CliStructuredError$1, notOk, ok } from "@prisma/cli-engine/protocol";
|
|
42
50
|
import { pathToFileURL } from "node:url";
|
|
43
51
|
import { build } from "esbuild";
|
|
44
|
-
//#region ../../../1-framework/3-tooling/cli/dist/load-config-
|
|
52
|
+
//#region ../../../1-framework/3-tooling/cli/dist/load-config-DWFCZvpn.mjs
|
|
45
53
|
const MISSING_CONFIG_ACTION = {
|
|
46
54
|
kind: "run-command",
|
|
47
55
|
label: "Create a config file",
|
|
@@ -608,7 +616,7 @@ function perSpaceBlocks(perSpace, mode) {
|
|
|
608
616
|
roots: spaceNodes(perSpace, mode)
|
|
609
617
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
610
618
|
}
|
|
611
|
-
function operationBlocks$
|
|
619
|
+
function operationBlocks$2(result) {
|
|
612
620
|
const perSpace = result.perSpace;
|
|
613
621
|
if (perSpace !== void 0 && perSpace.length > 0) return perSpaceBlocks(perSpace, result.mode);
|
|
614
622
|
if (result.plan.operations.length === 0) return [];
|
|
@@ -619,7 +627,7 @@ function operationBlocks$1(result) {
|
|
|
619
627
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
620
628
|
}
|
|
621
629
|
/** The statements a database would run, printed verbatim rather than laid out. */
|
|
622
|
-
function previewBlocks$
|
|
630
|
+
function previewBlocks$2(preview) {
|
|
623
631
|
if (preview === void 0) return [];
|
|
624
632
|
const header = {
|
|
625
633
|
kind: "summary",
|
|
@@ -666,7 +674,7 @@ function applySummaryText(result) {
|
|
|
666
674
|
if (executed === 0) return `Database already matches contract${across}`;
|
|
667
675
|
return `Applied ${executed} operation(s)${across}`;
|
|
668
676
|
}
|
|
669
|
-
function planBlocks(result) {
|
|
677
|
+
function planBlocks$1(result) {
|
|
670
678
|
const planned = result.plannedAdvanceRef;
|
|
671
679
|
return [
|
|
672
680
|
{
|
|
@@ -675,7 +683,7 @@ function planBlocks(result) {
|
|
|
675
683
|
text: planSummaryText(result)
|
|
676
684
|
},
|
|
677
685
|
...plannerWarningBlocks(result),
|
|
678
|
-
...operationBlocks$
|
|
686
|
+
...operationBlocks$2(result),
|
|
679
687
|
{
|
|
680
688
|
kind: "fields",
|
|
681
689
|
rows: [{
|
|
@@ -694,7 +702,7 @@ function planBlocks(result) {
|
|
|
694
702
|
tone: "identifier"
|
|
695
703
|
}]
|
|
696
704
|
}],
|
|
697
|
-
...previewBlocks$
|
|
705
|
+
...previewBlocks$2(result.plan.preview),
|
|
698
706
|
{
|
|
699
707
|
kind: "summary",
|
|
700
708
|
status: "info",
|
|
@@ -736,7 +744,7 @@ function applyBlocks(result) {
|
|
|
736
744
|
text: applySummaryText(result)
|
|
737
745
|
},
|
|
738
746
|
...plannerWarningBlocks(result),
|
|
739
|
-
...operationBlocks$
|
|
747
|
+
...operationBlocks$2(result),
|
|
740
748
|
...fallbackMarkerBlocks(result),
|
|
741
749
|
...advanced === null || advanced === void 0 ? [] : [{
|
|
742
750
|
kind: "summary",
|
|
@@ -750,7 +758,7 @@ function applyBlocks(result) {
|
|
|
750
758
|
}
|
|
751
759
|
/** What a `db init` / `db update` result looks like in human mode. */
|
|
752
760
|
function migrationResultBlocks(result) {
|
|
753
|
-
return result.mode === "plan" ? planBlocks(result) : applyBlocks(result);
|
|
761
|
+
return result.mode === "plan" ? planBlocks$1(result) : applyBlocks(result);
|
|
754
762
|
}
|
|
755
763
|
/** Where the user goes next, as typed actions rather than trailing prose. */
|
|
756
764
|
function migrationResultNextActions(result, applyCommand) {
|
|
@@ -769,6 +777,16 @@ function appMigrationsDirFor(config, cwd) {
|
|
|
769
777
|
return spaceMigrationDirectory(migrationsDirFor(config, cwd), APP_SPACE_ID);
|
|
770
778
|
}
|
|
771
779
|
/**
|
|
780
|
+
* The config file an operation should anchor its project paths on. The engine
|
|
781
|
+
* loads the config and hands a handler the value but not the path, and
|
|
782
|
+
* `--config` is an engine flag the handler never sees, so a handler names the
|
|
783
|
+
* invocation directory's file. That equals the loaded file for every
|
|
784
|
+
* invocation whose config sits in the invocation directory.
|
|
785
|
+
*/
|
|
786
|
+
function projectConfigPathFor(cwd) {
|
|
787
|
+
return resolve(cwd, "prisma-next.config.ts");
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
772
790
|
* Where refs live. The framework keeps them under the app subspace rather than
|
|
773
791
|
* at the migrations root.
|
|
774
792
|
*/
|
|
@@ -842,7 +860,7 @@ async function prepareMigrationRun(inputs) {
|
|
|
842
860
|
refsDir: appRefsDirFor(config, cwd)
|
|
843
861
|
});
|
|
844
862
|
}
|
|
845
|
-
function initPresentations(inputs) {
|
|
863
|
+
function initPresentations$1(inputs) {
|
|
846
864
|
const { document, database, dryRun } = inputs;
|
|
847
865
|
return {
|
|
848
866
|
human: () => [{
|
|
@@ -974,7 +992,7 @@ function createDbInitCommand(createClient) {
|
|
|
974
992
|
severity: "verbose",
|
|
975
993
|
text: `Total time: ${document.timings.total}ms`
|
|
976
994
|
});
|
|
977
|
-
return ok(ctx.present({ data: document }, initPresentations({
|
|
995
|
+
return ok(ctx.present({ data: document }, initPresentations$1({
|
|
978
996
|
document,
|
|
979
997
|
contractPath: prepared.value.contractDisplayPath,
|
|
980
998
|
database: prepared.value.database,
|
|
@@ -1478,62 +1496,122 @@ function createFormatCommand$1(execute = executeFormat) {
|
|
|
1478
1496
|
});
|
|
1479
1497
|
}
|
|
1480
1498
|
const formatCommand = createFormatCommand$1();
|
|
1481
|
-
|
|
1499
|
+
/** The invocation that finishes what a failed `init` phase started. */
|
|
1500
|
+
const EMIT_COMMAND = "prisma-next contract emit";
|
|
1482
1501
|
/**
|
|
1483
|
-
* The
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1502
|
+
* The command's own record of a phase that failed after the scaffold was
|
|
1503
|
+
* written. The scaffold is on disk and the run has a result to report, so each
|
|
1504
|
+
* of these rides a completed envelope as a finding rather than replacing it.
|
|
1486
1505
|
*/
|
|
1487
|
-
function
|
|
1488
|
-
const { document, graph, runList } = inputs;
|
|
1489
|
-
const count = document.migrations.length;
|
|
1506
|
+
function initFinding(code, summary, parts) {
|
|
1490
1507
|
return {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
value: inputs.migrationsDir
|
|
1499
|
-
},
|
|
1500
|
-
...inputs.database === void 0 ? [] : [{
|
|
1501
|
-
label: "database",
|
|
1502
|
-
value: inputs.database
|
|
1503
|
-
}],
|
|
1504
|
-
...inputs.from === void 0 ? [] : [{
|
|
1505
|
-
label: "from",
|
|
1506
|
-
value: inputs.from
|
|
1507
|
-
}],
|
|
1508
|
-
...inputs.to === void 0 ? [] : [{
|
|
1509
|
-
label: "to",
|
|
1510
|
-
value: inputs.to
|
|
1511
|
-
}]
|
|
1512
|
-
]
|
|
1513
|
-
},
|
|
1514
|
-
...graph.length === 0 ? [] : [{
|
|
1515
|
-
kind: "drawing",
|
|
1516
|
-
lines: toneDrawing(graph)
|
|
1517
|
-
}],
|
|
1518
|
-
...count === 0 ? [{
|
|
1519
|
-
kind: "summary",
|
|
1520
|
-
status: "info",
|
|
1521
|
-
text: document.summary
|
|
1522
|
-
}] : [{
|
|
1523
|
-
kind: "summary",
|
|
1524
|
-
status: "info",
|
|
1525
|
-
text: `The following ${count} migration${count === 1 ? "" : "s"} will run:`
|
|
1526
|
-
}, {
|
|
1527
|
-
kind: "drawing",
|
|
1528
|
-
lines: toneDrawing(runList.join("\n"))
|
|
1529
|
-
}]
|
|
1530
|
-
],
|
|
1531
|
-
json: () => document
|
|
1508
|
+
code,
|
|
1509
|
+
severity: "error",
|
|
1510
|
+
summary,
|
|
1511
|
+
why: parts.why,
|
|
1512
|
+
nextActions: parts.nextActions,
|
|
1513
|
+
meta: parts.meta,
|
|
1514
|
+
docsUrl: docsUrlFor(code)
|
|
1532
1515
|
};
|
|
1533
1516
|
}
|
|
1534
|
-
|
|
1517
|
+
/**
|
|
1518
|
+
* The dependency install failed. The engine composed and announced the command
|
|
1519
|
+
* line, so its own next action — which carries the redacted spelling — is the
|
|
1520
|
+
* one that tells the user what to run; `init` adds the emit that was waiting
|
|
1521
|
+
* on it.
|
|
1522
|
+
*/
|
|
1523
|
+
function installFailedFinding(failure, filesWritten) {
|
|
1524
|
+
return initFinding("CLI.INIT_INSTALL_FAILED", "Failed to install dependencies", {
|
|
1525
|
+
why: failure.why ?? failure.message,
|
|
1526
|
+
nextActions: [...failure.nextActions, runCommandAction("Emit the contract once the dependencies are installed", EMIT_COMMAND)],
|
|
1527
|
+
meta: {
|
|
1528
|
+
filesWritten,
|
|
1529
|
+
install: failure.meta ?? {}
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
/** The first emit failed against the freshly written scaffold. */
|
|
1534
|
+
function emitFailedFinding(cause, filesWritten) {
|
|
1535
|
+
return initFinding("CLI.INIT_EMIT_FAILED", "Failed to emit contract", {
|
|
1536
|
+
why: `\`${EMIT_COMMAND}\` failed: ${cause}`,
|
|
1537
|
+
nextActions: [chooseAction("Fix the problem the contract source reports, then emit again"), runCommandAction("Emit the contract", EMIT_COMMAND)],
|
|
1538
|
+
meta: {
|
|
1539
|
+
filesWritten,
|
|
1540
|
+
cause
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* An agent-skill install failed; the project itself is complete without it.
|
|
1546
|
+
* The remedy is the skill-install commands themselves, which run against any
|
|
1547
|
+
* directory — advising a re-run of `init` would put the user's schema at risk
|
|
1548
|
+
* to fix something `init` no longer needs to be involved in.
|
|
1549
|
+
*/
|
|
1550
|
+
function skillInstallFailedFinding(failure, filesWritten, skillCommands) {
|
|
1551
|
+
return initFinding("CLI.INIT_SKILL_INSTALL_FAILED", "Failed to install Prisma Next skills", {
|
|
1552
|
+
why: failure.why ?? failure.message,
|
|
1553
|
+
nextActions: [...failure.nextActions, ...skillCommands.map((command) => runCommandAction("Install the Prisma Next skills", command))],
|
|
1554
|
+
meta: {
|
|
1555
|
+
filesWritten,
|
|
1556
|
+
skillInstall: failure.meta ?? {}
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
function fileNodes(paths) {
|
|
1561
|
+
return paths.map((path) => ({
|
|
1562
|
+
label: path,
|
|
1563
|
+
tone: "identifier"
|
|
1564
|
+
}));
|
|
1565
|
+
}
|
|
1566
|
+
function scaffoldTree(document) {
|
|
1567
|
+
const roots = [{
|
|
1568
|
+
label: "written",
|
|
1569
|
+
tone: "heading",
|
|
1570
|
+
children: fileNodes(document.filesWritten)
|
|
1571
|
+
}];
|
|
1572
|
+
if (document.filesDeleted.length > 0) roots.push({
|
|
1573
|
+
label: "removed (stale artifacts and retired skill directories)",
|
|
1574
|
+
tone: "heading",
|
|
1575
|
+
children: fileNodes(document.filesDeleted)
|
|
1576
|
+
});
|
|
1577
|
+
const installed = document.packagesInstalled;
|
|
1578
|
+
if (installed.status === "installed") roots.push({
|
|
1579
|
+
label: "installed",
|
|
1580
|
+
tone: "heading",
|
|
1581
|
+
children: [...fileNodes(installed.deps), ...installed.devDeps.map((dep) => ({
|
|
1582
|
+
label: `${dep} (dev)`,
|
|
1583
|
+
tone: "identifier"
|
|
1584
|
+
}))]
|
|
1585
|
+
});
|
|
1586
|
+
return {
|
|
1587
|
+
kind: "tree",
|
|
1588
|
+
roots
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* The scaffold's follow-up steps, typed. `nextSteps` in the result document
|
|
1593
|
+
* keeps its numbered prose for consumers that already read it; these are the
|
|
1594
|
+
* same advice in the shape an agent can act on.
|
|
1595
|
+
*/
|
|
1596
|
+
function buildInitNextActions(inputs) {
|
|
1597
|
+
const actions = [chooseAction("Set DATABASE_URL in your environment (export it or add it to .env)")];
|
|
1598
|
+
if (!inputs.contractEmitted) actions.push(runCommandAction("Emit the contract", EMIT_COMMAND));
|
|
1599
|
+
actions.push({
|
|
1600
|
+
kind: "edit-file",
|
|
1601
|
+
label: `Edit your schema at ${inputs.schemaPath}, then emit again`
|
|
1602
|
+
});
|
|
1603
|
+
actions.push(chooseAction("Open prisma-next.md for a quick reference on writing your first typed query"));
|
|
1604
|
+
if (inputs.skillRegistered) actions.push(chooseAction("Prisma Next skills are registered with your agent runtime — ask it to add a model, run a query, or plan a migration"));
|
|
1605
|
+
return actions;
|
|
1606
|
+
}
|
|
1607
|
+
const DONE_TEXT = "Done. Open prisma-next.md to get started.";
|
|
1608
|
+
const INCOMPLETE_TEXT = "Scaffold written. Finish the steps above to complete setup.";
|
|
1609
|
+
/**
|
|
1610
|
+
* `init` writes files rather than data another program reads, so it supplies
|
|
1611
|
+
* no `stdout` payload: human mode writes nothing to stdout.
|
|
1612
|
+
*/
|
|
1613
|
+
function initPresentations(inputs) {
|
|
1535
1614
|
const { document } = inputs;
|
|
1536
|
-
const advanced = document.advancedRef;
|
|
1537
1615
|
return {
|
|
1538
1616
|
human: () => [
|
|
1539
1617
|
{
|
|
@@ -1541,133 +1619,985 @@ function applyPresentations(inputs) {
|
|
|
1541
1619
|
rail: true,
|
|
1542
1620
|
rows: [
|
|
1543
1621
|
{
|
|
1544
|
-
label: "
|
|
1545
|
-
value:
|
|
1622
|
+
label: "target",
|
|
1623
|
+
value: document.target
|
|
1546
1624
|
},
|
|
1547
|
-
|
|
1548
|
-
label: "
|
|
1549
|
-
value:
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
label: "
|
|
1553
|
-
value:
|
|
1554
|
-
}
|
|
1625
|
+
{
|
|
1626
|
+
label: "authoring",
|
|
1627
|
+
value: document.authoring
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
label: "schema",
|
|
1631
|
+
value: document.schemaPath
|
|
1632
|
+
}
|
|
1555
1633
|
]
|
|
1556
1634
|
},
|
|
1557
|
-
|
|
1635
|
+
scaffoldTree(document),
|
|
1636
|
+
inputs.complete ? {
|
|
1558
1637
|
kind: "summary",
|
|
1559
1638
|
status: "ok",
|
|
1560
|
-
text:
|
|
1561
|
-
}
|
|
1562
|
-
...document.perSpace.length === 0 ? [] : perSpaceBlocks(document.perSpace, "apply"),
|
|
1563
|
-
...advanced === null || advanced === void 0 ? [] : [{
|
|
1639
|
+
text: DONE_TEXT
|
|
1640
|
+
} : {
|
|
1564
1641
|
kind: "summary",
|
|
1565
|
-
status: "
|
|
1566
|
-
text:
|
|
1567
|
-
|
|
1568
|
-
tone: "identifier"
|
|
1569
|
-
}]
|
|
1570
|
-
}]
|
|
1642
|
+
status: "warn",
|
|
1643
|
+
text: INCOMPLETE_TEXT
|
|
1644
|
+
}
|
|
1571
1645
|
],
|
|
1572
1646
|
json: () => document,
|
|
1573
|
-
next: () =>
|
|
1647
|
+
next: () => inputs.nextActions
|
|
1574
1648
|
};
|
|
1575
1649
|
}
|
|
1576
1650
|
/**
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1651
|
+
* Emits the contract for the project `init` has just scaffolded.
|
|
1652
|
+
*
|
|
1653
|
+
* The config file is the one this run wrote, so it is read here rather than
|
|
1654
|
+
* through `needs.config`: `init` runs where no config exists yet, and the
|
|
1655
|
+
* engine loads a section before the handler starts. The emitter and the config
|
|
1656
|
+
* loader are imported at execution time so a run that skips the install — and
|
|
1657
|
+
* therefore skips the emit — never pays for either.
|
|
1580
1658
|
*/
|
|
1581
|
-
function
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1659
|
+
async function emitScaffoldedContract(ctx) {
|
|
1660
|
+
const { executeContractEmit } = await import("./contract-emit-Bzt11N4v-BQ2Pr8KG.mjs");
|
|
1661
|
+
const { loadConfigForSections } = await import("./exports-BZLNbtkY.mjs").then((n) => n.t);
|
|
1662
|
+
const configPath = join(ctx.cwd, "prisma-next.config.ts");
|
|
1663
|
+
const loaded = await loadConfigForSections(configPath, [
|
|
1664
|
+
"contract",
|
|
1665
|
+
"family",
|
|
1666
|
+
"target",
|
|
1667
|
+
"adapter",
|
|
1668
|
+
"extensions"
|
|
1669
|
+
]);
|
|
1670
|
+
if (!loaded.ok) throw loaded.failure;
|
|
1671
|
+
await executeContractEmit({
|
|
1672
|
+
config: loaded.value,
|
|
1673
|
+
cwd: ctx.cwd,
|
|
1674
|
+
configPath
|
|
1589
1675
|
});
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1676
|
+
}
|
|
1677
|
+
const CONFIG_FILE = "prisma-next.config.ts";
|
|
1678
|
+
const QUICK_REFERENCE_FILE = "prisma-next.md";
|
|
1679
|
+
const ENV_EXAMPLE_FILE = ".env.example";
|
|
1680
|
+
/**
|
|
1681
|
+
* The generated files a run replaces outright, in the order it writes them.
|
|
1682
|
+
* Everything else the scaffold touches it merges (`tsconfig.json`,
|
|
1683
|
+
* `package.json`, the git files) or keeps (`.env`, `README.md`), so replacing
|
|
1684
|
+
* one of these is the destructive act consent is asked for.
|
|
1685
|
+
*
|
|
1686
|
+
* `.env.example` is written the same way but deliberately left out: it is a
|
|
1687
|
+
* conventional filename in projects that have never seen Prisma Next, and a
|
|
1688
|
+
* consent token demanded for it would fire on first runs. The scaffold warns
|
|
1689
|
+
* when it replaces one instead.
|
|
1690
|
+
*/
|
|
1691
|
+
function generatedFilesInitReplaces(schemaPath) {
|
|
1692
|
+
return [
|
|
1693
|
+
schemaPath,
|
|
1694
|
+
CONFIG_FILE,
|
|
1695
|
+
join(dirname(schemaPath), "db.ts"),
|
|
1696
|
+
QUICK_REFERENCE_FILE
|
|
1697
|
+
];
|
|
1698
|
+
}
|
|
1699
|
+
const MANAGERS = /* @__PURE__ */ new Set([
|
|
1700
|
+
"pnpm",
|
|
1701
|
+
"npm",
|
|
1702
|
+
"yarn",
|
|
1703
|
+
"bun",
|
|
1704
|
+
"deno"
|
|
1705
|
+
]);
|
|
1706
|
+
function isPackageManager(name) {
|
|
1707
|
+
return MANAGERS.has(name);
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* The manager the scaffolded quick reference and README name in their example
|
|
1711
|
+
* commands. It resolves the way the engine's package operations resolve theirs
|
|
1712
|
+
* — the project at `cwd` first, then the manager that invoked this process —
|
|
1713
|
+
* so the documentation `init` writes names the manager `init` drove. The
|
|
1714
|
+
* capability itself reports no manager back on success, which is why this is
|
|
1715
|
+
* worked out a second time here.
|
|
1716
|
+
*/
|
|
1717
|
+
async function resolveScaffoldPackageManager(ctx) {
|
|
1718
|
+
const detected = await detect({ cwd: ctx.cwd });
|
|
1719
|
+
if (detected !== null && isPackageManager(detected.name)) return detected.name;
|
|
1720
|
+
const invoking = ctx.env["npm_config_user_agent"]?.split("/")[0];
|
|
1721
|
+
return invoking !== void 0 && isPackageManager(invoking) ? invoking : "npm";
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* True when the parsed manifest declares `name` directly in `dependencies` or
|
|
1725
|
+
* `devDependencies`. Transitive presence is deliberately ignored: detecting it
|
|
1726
|
+
* needs the lockfile, and the realistic risk is a direct pin being clobbered.
|
|
1727
|
+
*/
|
|
1728
|
+
function hasDirectDep(parsed, name) {
|
|
1729
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
1730
|
+
const value = parsed[field];
|
|
1731
|
+
if (value !== null && typeof value === "object" && name in value) return true;
|
|
1732
|
+
}
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* npm package names are lowercase and URL-safe; `basename(cwd)` happily
|
|
1737
|
+
* returns "My Project" or ".hidden", both of which npm refuses to read.
|
|
1738
|
+
*/
|
|
1739
|
+
function sanitisePackageName(raw) {
|
|
1740
|
+
const trimmed = raw.toLowerCase().replace(/[^a-z0-9._~-]/g, "-").replace(/^[._-]+/, "").replace(/-+/g, "-");
|
|
1741
|
+
return trimmed.length > 0 ? trimmed : "my-app";
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* The manifest `init` writes for a directory that has none. Private, so a
|
|
1745
|
+
* stray publish cannot leak the placeholder, and ESM so the scaffolded import
|
|
1746
|
+
* attributes load under Node's loader.
|
|
1747
|
+
*/
|
|
1748
|
+
function defaultPackageJsonContent(rawName) {
|
|
1749
|
+
return `${JSON.stringify({
|
|
1750
|
+
name: sanitisePackageName(rawName),
|
|
1751
|
+
version: "0.0.0",
|
|
1752
|
+
private: true,
|
|
1753
|
+
type: "module"
|
|
1754
|
+
}, null, 2)}\n`;
|
|
1755
|
+
}
|
|
1756
|
+
function hasProjectManifest(cwd) {
|
|
1757
|
+
return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Computes every write before touching disk: reads and parses each file the
|
|
1761
|
+
* scaffold merges with, so a malformed manifest or tsconfig fails with the
|
|
1762
|
+
* user's project byte-identical to how it started.
|
|
1763
|
+
*/
|
|
1764
|
+
function planScaffold(ctx) {
|
|
1765
|
+
const { cwd, inputs, packageManager, resolveImportSpecifier } = ctx;
|
|
1766
|
+
const warnings = [];
|
|
1767
|
+
const schemaDir = dirname(inputs.schemaPath);
|
|
1768
|
+
const configContractPath = isAbsolute(inputs.schemaPath) ? inputs.schemaPath : `./${inputs.schemaPath}`;
|
|
1769
|
+
const runPrefix = formatRunCommand(packageManager, "prisma-next", "").trimEnd();
|
|
1770
|
+
const files = [
|
|
1771
|
+
{
|
|
1772
|
+
path: inputs.schemaPath,
|
|
1773
|
+
content: starterSchema(inputs.target, inputs.authoring, resolveImportSpecifier)
|
|
1595
1774
|
},
|
|
1596
|
-
|
|
1775
|
+
{
|
|
1776
|
+
path: CONFIG_FILE,
|
|
1777
|
+
content: configFile(inputs.target, configContractPath, resolveImportSpecifier)
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
path: join(schemaDir, "db.ts"),
|
|
1781
|
+
content: dbFile(inputs.target, resolveImportSpecifier)
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
path: QUICK_REFERENCE_FILE,
|
|
1785
|
+
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, runPrefix, resolveImportSpecifier)
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
path: ENV_EXAMPLE_FILE,
|
|
1789
|
+
content: envExampleContent(inputs.target)
|
|
1790
|
+
}
|
|
1791
|
+
];
|
|
1792
|
+
if (existsSync(join(cwd, ENV_EXAMPLE_FILE))) warnings.push(`${ENV_EXAMPLE_FILE} already existed and was replaced with the Prisma Next template.`);
|
|
1793
|
+
const filesToDelete = inputs.reinit ? [...findStaleArtifacts(cwd, schemaDir)] : [];
|
|
1794
|
+
const dirsToDelete = legacySkillDirs().filter((rel) => existsSync(join(cwd, rel)));
|
|
1795
|
+
if (inputs.writeEnv) if (existsSync(join(cwd, ".env"))) warnings.push(".env already exists; leaving it untouched. Compare with .env.example for any new keys.");
|
|
1796
|
+
else files.push({
|
|
1797
|
+
path: ".env",
|
|
1798
|
+
content: envFileContent(inputs.target)
|
|
1597
1799
|
});
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1800
|
+
const tsconfigPath = join(cwd, "tsconfig.json");
|
|
1801
|
+
if (existsSync(tsconfigPath)) {
|
|
1802
|
+
const existing = readFileSync(tsconfigPath, "utf-8");
|
|
1803
|
+
let merged;
|
|
1804
|
+
try {
|
|
1805
|
+
merged = mergeTsConfig(existing);
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
if (error instanceof TsConfigParseError) throw errorInitInvalidTsconfig({
|
|
1808
|
+
path: "tsconfig.json",
|
|
1809
|
+
cause: error.message
|
|
1810
|
+
});
|
|
1811
|
+
throw error;
|
|
1812
|
+
}
|
|
1813
|
+
files.push({
|
|
1814
|
+
path: "tsconfig.json",
|
|
1815
|
+
content: merged,
|
|
1816
|
+
note: "Updated tsconfig.json with required compiler options."
|
|
1817
|
+
});
|
|
1818
|
+
} else files.push({
|
|
1819
|
+
path: "tsconfig.json",
|
|
1820
|
+
content: defaultTsConfig()
|
|
1602
1821
|
});
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
const planned = await executeMigrateShowPlan({
|
|
1638
|
-
config: ctx.config,
|
|
1639
|
-
cwd: ctx.cwd,
|
|
1640
|
-
createClient,
|
|
1641
|
-
...ifDefined("db", args.flags.db),
|
|
1642
|
-
...ifDefined("to", args.flags.to),
|
|
1643
|
-
...ifDefined("from", args.flags.from)
|
|
1644
|
-
});
|
|
1645
|
-
if (!planned.ok) return notOk(normalizeError(planned.failure));
|
|
1646
|
-
const plan = planned.value;
|
|
1647
|
-
const paint = {
|
|
1648
|
-
colorize: true,
|
|
1649
|
-
glyphMode: "unicode",
|
|
1650
|
-
styler: createToneMigrationListStyler(),
|
|
1651
|
-
palette: TONE_MIGRATION_GRAPH_PALETTE
|
|
1652
|
-
};
|
|
1653
|
-
const rendering = renderMigrateShowGraph(plan, paint);
|
|
1654
|
-
const document = {
|
|
1655
|
-
ok: true,
|
|
1656
|
-
migrations: plan.migrations,
|
|
1657
|
-
summary: plan.summary
|
|
1658
|
-
};
|
|
1659
|
-
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
1660
|
-
return ok(ctx.present({ data: document }, showPresentations$1({
|
|
1661
|
-
document,
|
|
1662
|
-
graph: rendering.graphOutput,
|
|
1663
|
-
runList: migrateShowRunListRows(plan.migrations, rendering, paint),
|
|
1664
|
-
migrationsDir: migrationsRelative,
|
|
1665
|
-
database: args.flags.from === void 0 && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
|
|
1666
|
-
from: args.flags.from,
|
|
1667
|
-
to: args.flags.to
|
|
1668
|
-
})));
|
|
1822
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
1823
|
+
const nextGitignore = mergeGitignore(existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : void 0);
|
|
1824
|
+
if (nextGitignore !== null) files.push({
|
|
1825
|
+
path: ".gitignore",
|
|
1826
|
+
content: nextGitignore
|
|
1827
|
+
});
|
|
1828
|
+
const gitattributesPath = join(cwd, ".gitattributes");
|
|
1829
|
+
const nextGitattributes = mergeGitattributes(existsSync(gitattributesPath) ? readFileSync(gitattributesPath, "utf-8") : void 0, requiredGitattributesLines(schemaDir, inputs.target));
|
|
1830
|
+
if (nextGitattributes !== null) files.push({
|
|
1831
|
+
path: ".gitattributes",
|
|
1832
|
+
content: nextGitattributes
|
|
1833
|
+
});
|
|
1834
|
+
const manifestPath = join(cwd, "package.json");
|
|
1835
|
+
const manifestExisted = existsSync(manifestPath);
|
|
1836
|
+
const synthesiseManifest = !manifestExisted && !hasProjectManifest(cwd);
|
|
1837
|
+
let parsedManifest = null;
|
|
1838
|
+
if (manifestExisted || synthesiseManifest) {
|
|
1839
|
+
const raw = manifestExisted ? readFileSync(manifestPath, "utf-8") : defaultPackageJsonContent(basename(cwd));
|
|
1840
|
+
try {
|
|
1841
|
+
parsedManifest = JSON.parse(raw);
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
if (error instanceof SyntaxError) throw errorInitInvalidManifest({
|
|
1844
|
+
path: "package.json",
|
|
1845
|
+
cause: error.message
|
|
1846
|
+
});
|
|
1847
|
+
throw error;
|
|
1848
|
+
}
|
|
1849
|
+
let working = raw;
|
|
1850
|
+
let changed = synthesiseManifest;
|
|
1851
|
+
if (inputs.removePreviousFacade !== null) {
|
|
1852
|
+
const next = removeDependency(working, inputs.removePreviousFacade);
|
|
1853
|
+
if (next !== null) {
|
|
1854
|
+
working = next;
|
|
1855
|
+
changed = true;
|
|
1669
1856
|
}
|
|
1670
|
-
|
|
1857
|
+
}
|
|
1858
|
+
const { content: withScripts, warnings: scriptWarnings } = mergePackageScripts(working, REQUIRED_SCRIPTS);
|
|
1859
|
+
if (withScripts !== null) {
|
|
1860
|
+
working = withScripts;
|
|
1861
|
+
changed = true;
|
|
1862
|
+
}
|
|
1863
|
+
const { content: withType, warning: typeWarning } = ensureEsmModuleType(working);
|
|
1864
|
+
if (withType !== null) {
|
|
1865
|
+
working = withType;
|
|
1866
|
+
changed = true;
|
|
1867
|
+
}
|
|
1868
|
+
if (changed) files.push({
|
|
1869
|
+
path: "package.json",
|
|
1870
|
+
content: working
|
|
1871
|
+
});
|
|
1872
|
+
warnings.push(...scriptWarnings);
|
|
1873
|
+
if (typeWarning !== null) warnings.push(typeWarning);
|
|
1874
|
+
if (synthesiseManifest) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
|
|
1875
|
+
}
|
|
1876
|
+
if (existsSync(join(cwd, "src/index.ts"))) if (existsSync(join(cwd, "README.md"))) warnings.push("README.md already exists; leaving it untouched.");
|
|
1877
|
+
else {
|
|
1878
|
+
const declaredName = parsedManifest !== null && typeof parsedManifest["name"] === "string" ? parsedManifest["name"] : basename(cwd);
|
|
1879
|
+
files.push({
|
|
1880
|
+
path: "README.md",
|
|
1881
|
+
content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(declaredName), packageManager)
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
return {
|
|
1885
|
+
files,
|
|
1886
|
+
filesToDelete,
|
|
1887
|
+
dirsToDelete,
|
|
1888
|
+
warnings,
|
|
1889
|
+
hasTypesNode: parsedManifest !== null && hasDirectDep(parsedManifest, "@types/node")
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Plans every write, then performs them. Everything that can fail on the
|
|
1894
|
+
* user's existing files fails during planning, so a failure leaves the project
|
|
1895
|
+
* as it was; after the first write, the scaffold is on disk to keep, and a
|
|
1896
|
+
* write that fails partway names the files that already landed.
|
|
1897
|
+
*/
|
|
1898
|
+
function scaffoldProject(ctx) {
|
|
1899
|
+
const resolveImportSpecifier = scaffoldSpecifierResolverFor(ctx.inputs.target);
|
|
1900
|
+
const plan = planScaffold({
|
|
1901
|
+
...ctx,
|
|
1902
|
+
resolveImportSpecifier
|
|
1903
|
+
});
|
|
1904
|
+
const filesWritten = [];
|
|
1905
|
+
const filesDeleted = [];
|
|
1906
|
+
const notes = [];
|
|
1907
|
+
for (const file of plan.files) {
|
|
1908
|
+
const target = join(ctx.cwd, file.path);
|
|
1909
|
+
try {
|
|
1910
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
1911
|
+
writeFileSync(target, file.content, "utf-8");
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
throw errorInitWriteFailed({
|
|
1914
|
+
path: file.path,
|
|
1915
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
1916
|
+
filesWritten
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
filesWritten.push(file.path);
|
|
1920
|
+
if (file.note !== void 0) notes.push(file.note);
|
|
1921
|
+
}
|
|
1922
|
+
for (const rel of plan.filesToDelete) {
|
|
1923
|
+
const target = join(ctx.cwd, rel);
|
|
1924
|
+
if (!existsSync(target)) continue;
|
|
1925
|
+
try {
|
|
1926
|
+
unlinkSync(target);
|
|
1927
|
+
filesDeleted.push(rel);
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
if (!(error instanceof Error && Reflect.get(error, "code") === "ENOENT")) throw error;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
for (const rel of plan.dirsToDelete) {
|
|
1933
|
+
rmSync(join(ctx.cwd, rel), {
|
|
1934
|
+
recursive: true,
|
|
1935
|
+
force: true
|
|
1936
|
+
});
|
|
1937
|
+
filesDeleted.push(rel);
|
|
1938
|
+
}
|
|
1939
|
+
return {
|
|
1940
|
+
filesWritten,
|
|
1941
|
+
filesDeleted,
|
|
1942
|
+
warnings: plan.warnings,
|
|
1943
|
+
notes,
|
|
1944
|
+
hasTypesNode: plan.hasTypesNode,
|
|
1945
|
+
resolveImportSpecifier
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
const REQUIRED_FLAG_PROMPTS = /* @__PURE__ */ new Set(["target", "authoring"]);
|
|
1949
|
+
function formatFileList(files) {
|
|
1950
|
+
const last = files.at(-1) ?? "";
|
|
1951
|
+
return files.length <= 1 ? last : `${files.slice(0, -1).join(", ")} and ${last}`;
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Names the files at stake rather than the category they belong to: the user
|
|
1955
|
+
* knows `contract.prisma`, not "every generated file".
|
|
1956
|
+
*/
|
|
1957
|
+
function consentQuestion(replaced) {
|
|
1958
|
+
const written = replaced.length === 1 ? "it" : "them";
|
|
1959
|
+
return `Re-initializing replaces ${formatFileList(replaced)} with a fresh scaffold, losing anything you wrote in ${written}.`;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* The engine raises this when a prompt has no default and the session cannot
|
|
1963
|
+
* show it. For the two prompts that stand in for a required flag, that is the
|
|
1964
|
+
* same condition `init` has always reported as a missing flag.
|
|
1965
|
+
*/
|
|
1966
|
+
function isPromptRequired(error) {
|
|
1967
|
+
return error instanceof Error && Reflect.get(error, "code") === "CLI.PROMPT_REQUIRED";
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* `--confirm` matches a token, so the token has to be something the user can
|
|
1971
|
+
* see and type. The working directory's name is it; a directory with no name
|
|
1972
|
+
* of its own (the filesystem root) falls back to its path.
|
|
1973
|
+
*
|
|
1974
|
+
* Trimmed, because the engine compares the typed answer trimmed: an untrimmed
|
|
1975
|
+
* token from a directory named `my app ` is one no keystroke sequence matches.
|
|
1976
|
+
*/
|
|
1977
|
+
function consentToken(cwd) {
|
|
1978
|
+
const name = basename(cwd).trim();
|
|
1979
|
+
return name.length > 0 ? name : cwd.trim();
|
|
1980
|
+
}
|
|
1981
|
+
async function askTarget(prompt) {
|
|
1982
|
+
return prompt.select("What database are you using?", [{
|
|
1983
|
+
value: "postgres",
|
|
1984
|
+
label: "PostgreSQL"
|
|
1985
|
+
}, {
|
|
1986
|
+
value: "mongo",
|
|
1987
|
+
label: "MongoDB"
|
|
1988
|
+
}]);
|
|
1989
|
+
}
|
|
1990
|
+
async function askAuthoring(prompt) {
|
|
1991
|
+
return prompt.select("How do you want to write your schema?", [{
|
|
1992
|
+
value: "psl",
|
|
1993
|
+
label: "Prisma Schema Language (.prisma)"
|
|
1994
|
+
}, {
|
|
1995
|
+
value: "typescript",
|
|
1996
|
+
label: "TypeScript (.ts)"
|
|
1997
|
+
}]);
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* The schema path the user typed goes through the same validator the flag
|
|
2001
|
+
* does, so an answer the engine accepted still has to agree with the authoring
|
|
2002
|
+
* style.
|
|
2003
|
+
*/
|
|
2004
|
+
async function askSchemaPath(prompt, authoring) {
|
|
2005
|
+
return validateSchemaPath(await prompt.text("Where should the schema file go?", { default: defaultSchemaPath(authoring) }), authoring);
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* The facade a previous `init` installed for the other target, when this run
|
|
2009
|
+
* switches targets and one is still declared. Reads every name a previous run
|
|
2010
|
+
* could have written, because older versions scaffolded the workspace name.
|
|
2011
|
+
*/
|
|
2012
|
+
function previousFacade(cwd, target) {
|
|
2013
|
+
const manifestPath = join(cwd, "package.json");
|
|
2014
|
+
if (!existsSync(manifestPath)) return;
|
|
2015
|
+
const otherTarget = target === "postgres" ? "mongo" : "postgres";
|
|
2016
|
+
const candidates = [targetPackageName(otherTarget, scaffoldSpecifierResolverFor(otherTarget)), targetPackageName(otherTarget)];
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
2020
|
+
} catch {
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
const deps = parsed["dependencies"];
|
|
2024
|
+
if (deps === null || typeof deps !== "object" || Array.isArray(deps)) return;
|
|
2025
|
+
const declared = deps;
|
|
2026
|
+
return candidates.find((name) => Object.hasOwn(declared, name));
|
|
2027
|
+
}
|
|
2028
|
+
async function resolveRemovePreviousFacade(ctx) {
|
|
2029
|
+
if (!ctx.reinit || ctx.keepPreviousFacade) return null;
|
|
2030
|
+
const facade = previousFacade(ctx.cwd, ctx.target);
|
|
2031
|
+
if (facade === void 0) return null;
|
|
2032
|
+
const otherTarget = ctx.target === "postgres" ? "mongo" : "postgres";
|
|
2033
|
+
return await ctx.prompt.confirm(`Switching from ${targetLabel(otherTarget)} to ${targetLabel(ctx.target)} — remove ${facade} from package.json dependencies?`, { default: true }) ? facade : null;
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Resolves every input from the flags and, where a flag is absent, from the
|
|
2037
|
+
* engine's prompt surface.
|
|
2038
|
+
*
|
|
2039
|
+
* Order matters twice. Every flag value is validated before anything is asked,
|
|
2040
|
+
* so a typo costs a typo rather than a typed consent token. And consent comes
|
|
2041
|
+
* as soon as the schema path is known, because that is the point at which the
|
|
2042
|
+
* question can name the files it is about — and before any question whose
|
|
2043
|
+
* answer only matters if the run proceeds.
|
|
2044
|
+
*/
|
|
2045
|
+
async function resolveInitInputs(ctx) {
|
|
2046
|
+
const { cwd, flags, prompt } = ctx;
|
|
2047
|
+
if (flags.strictProbe && !flags.probeDb) throw errorInitStrictProbeWithoutProbe();
|
|
2048
|
+
const flagTarget = resolveTarget(flags.target);
|
|
2049
|
+
const flagAuthoring = resolveAuthoring(flags.authoring);
|
|
2050
|
+
let target;
|
|
2051
|
+
let authoring;
|
|
2052
|
+
try {
|
|
2053
|
+
target = flagTarget ?? await askTarget(prompt);
|
|
2054
|
+
authoring = flagAuthoring ?? await askAuthoring(prompt);
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
if (!isPromptRequired(error)) throw error;
|
|
2057
|
+
throw errorInitMissingFlags({
|
|
2058
|
+
missing: [...flagTarget === void 0 ? ["target"] : [], ...flagAuthoring === void 0 ? ["authoring"] : []].filter((flag) => REQUIRED_FLAG_PROMPTS.has(flag)),
|
|
2059
|
+
why: "This session cannot prompt, so the answers have to arrive as flags."
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
const schemaPath = flags.schemaPath !== void 0 ? validateSchemaPath(flags.schemaPath, authoring) : await askSchemaPath(prompt, authoring);
|
|
2063
|
+
const replaced = generatedFilesInitReplaces(schemaPath).filter((relative) => existsSync(join(cwd, relative)));
|
|
2064
|
+
const reinit = replaced.length > 0;
|
|
2065
|
+
if (reinit) {
|
|
2066
|
+
if (!await prompt.consent(consentQuestion(replaced), { token: consentToken(cwd) })) throw errorInitUserAborted();
|
|
2067
|
+
}
|
|
2068
|
+
const writeEnv = flags.writeEnv || await prompt.confirm("Also write a .env file from .env.example? (gitignored)", { default: false });
|
|
2069
|
+
const removePreviousFacade = await resolveRemovePreviousFacade({
|
|
2070
|
+
cwd,
|
|
2071
|
+
target,
|
|
2072
|
+
reinit,
|
|
2073
|
+
keepPreviousFacade: flags.keepPreviousFacade,
|
|
2074
|
+
prompt
|
|
2075
|
+
});
|
|
2076
|
+
return {
|
|
2077
|
+
target,
|
|
2078
|
+
authoring,
|
|
2079
|
+
schemaPath,
|
|
2080
|
+
install: !flags.skipInstall,
|
|
2081
|
+
writeEnv,
|
|
2082
|
+
probeDb: flags.probeDb,
|
|
2083
|
+
strictProbe: flags.strictProbe,
|
|
2084
|
+
reinit,
|
|
2085
|
+
removePreviousFacade,
|
|
2086
|
+
installProjectSkill: !flags.skipSkills
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
function metaString(failure, key) {
|
|
2090
|
+
const value = failure.meta?.[key];
|
|
2091
|
+
return typeof value === "string" ? value : "";
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* pnpm reported a specifier the published artifact leaked, which npm installs
|
|
2095
|
+
* happily. The engine redacts credentials out of the stderr it returns but
|
|
2096
|
+
* leaves error codes alone, which is what this reads.
|
|
2097
|
+
*/
|
|
2098
|
+
function pnpmLeakedASpecifier(failure) {
|
|
2099
|
+
return metaString(failure, "manager") === "pnpm" && isRecognisedPnpmResolutionError(metaString(failure, "stderrTail"));
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* The engine redacts the stderr it hands back, and this redacts it again
|
|
2103
|
+
* before quoting it: what the engine strips is its own business, and registry
|
|
2104
|
+
* auth material is not all URL-shaped.
|
|
2105
|
+
*/
|
|
2106
|
+
function fallbackWarning(failure) {
|
|
2107
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2108
|
+
return [
|
|
2109
|
+
"pnpm could not install: a published Prisma Next dependency leaked a `workspace:*` or `catalog:` specifier.",
|
|
2110
|
+
"Falling back to npm so init can complete.",
|
|
2111
|
+
firstLine === "" ? "" : ` pnpm error: ${firstLine}`,
|
|
2112
|
+
"Both installs ran under npm, which writes a package-lock.json beside the pnpm lockfile — delete whichever of the two you do not want to keep.",
|
|
2113
|
+
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
2114
|
+
].filter((line) => line !== "").join("\n");
|
|
2115
|
+
}
|
|
2116
|
+
/** The pnpm failure the npm retry was meant to recover from, kept alongside the npm one. */
|
|
2117
|
+
function retriedWarning(failure) {
|
|
2118
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2119
|
+
return ["pnpm failed first with a leaked `workspace:*` or `catalog:` specifier, so init retried with npm.", firstLine === "" ? "" : ` pnpm error: ${firstLine}`].filter((line) => line !== "").join("\n");
|
|
2120
|
+
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Adds the runtime and development dependencies through the engine's package
|
|
2123
|
+
* manager. The retry is `init`'s alone: the engine spells and runs the
|
|
2124
|
+
* command, and this decides — from the stderr it returned — that another
|
|
2125
|
+
* manager is worth a try.
|
|
2126
|
+
*/
|
|
2127
|
+
async function installProjectDependencies(ctx) {
|
|
2128
|
+
const pair = async (manager) => {
|
|
2129
|
+
const runtimeDeps = await ctx.packages.install({
|
|
2130
|
+
packages: ctx.deps,
|
|
2131
|
+
cwd: ctx.cwd,
|
|
2132
|
+
...ifDefined("manager", manager)
|
|
2133
|
+
});
|
|
2134
|
+
if (!runtimeDeps.ok) return runtimeDeps.failure;
|
|
2135
|
+
const developmentDeps = await ctx.packages.install({
|
|
2136
|
+
packages: ctx.devDeps,
|
|
2137
|
+
dev: true,
|
|
2138
|
+
cwd: ctx.cwd,
|
|
2139
|
+
...ifDefined("manager", manager)
|
|
2140
|
+
});
|
|
2141
|
+
return developmentDeps.ok ? void 0 : developmentDeps.failure;
|
|
2142
|
+
};
|
|
2143
|
+
const failure = await pair();
|
|
2144
|
+
if (failure === void 0) return {
|
|
2145
|
+
failure: void 0,
|
|
2146
|
+
manager: void 0,
|
|
2147
|
+
warnings: ctx.catalogWarnings
|
|
2148
|
+
};
|
|
2149
|
+
if (!pnpmLeakedASpecifier(failure)) return {
|
|
2150
|
+
failure,
|
|
2151
|
+
manager: void 0,
|
|
2152
|
+
warnings: []
|
|
2153
|
+
};
|
|
2154
|
+
const retryFailure = await pair("npm");
|
|
2155
|
+
if (retryFailure !== void 0) return {
|
|
2156
|
+
failure: retryFailure,
|
|
2157
|
+
manager: void 0,
|
|
2158
|
+
warnings: [retriedWarning(failure)]
|
|
2159
|
+
};
|
|
2160
|
+
return {
|
|
2161
|
+
failure: void 0,
|
|
2162
|
+
manager: "npm",
|
|
2163
|
+
warnings: [fallbackWarning(failure)]
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
function skillArgs(url, skill) {
|
|
2167
|
+
return [
|
|
2168
|
+
"add",
|
|
2169
|
+
url,
|
|
2170
|
+
"--agent",
|
|
2171
|
+
...DEFAULT_SKILL_AGENTS,
|
|
2172
|
+
"--skill",
|
|
2173
|
+
skill,
|
|
2174
|
+
"-y"
|
|
2175
|
+
];
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Registers every Prisma Next skill with the agent runtimes, in order. The
|
|
2179
|
+
* first failure stops the loop: the user opted into Prisma Next by running
|
|
2180
|
+
* `init`, and a half-installed skill set leaves the project ambiguous.
|
|
2181
|
+
*/
|
|
2182
|
+
async function installAgentSkills(ctx) {
|
|
2183
|
+
for (const source of DEFAULT_SKILL_SOURCES) {
|
|
2184
|
+
const result = await ctx.packages.run({
|
|
2185
|
+
package: "skills@latest",
|
|
2186
|
+
args: skillArgs(formatSkillSourceUrl(source, ctx.env), source.skill),
|
|
2187
|
+
cwd: ctx.cwd,
|
|
2188
|
+
...ifDefined("manager", ctx.manager)
|
|
2189
|
+
});
|
|
2190
|
+
if (!result.ok) return result.failure;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/** The scaffold is on disk from here on, so each of these is a finding. */
|
|
2194
|
+
const INIT_EXIT_CODES = {
|
|
2195
|
+
4: "scaffold written; dependency install failed",
|
|
2196
|
+
5: "scaffold written and installed; contract emit failed",
|
|
2197
|
+
6: "scaffold complete; agent-skill install failed"
|
|
2198
|
+
};
|
|
2199
|
+
function probeWarning(outcome, strictProbe) {
|
|
2200
|
+
switch (outcome.kind) {
|
|
2201
|
+
case "ok": return {
|
|
2202
|
+
warning: void 0,
|
|
2203
|
+
fatal: void 0
|
|
2204
|
+
};
|
|
2205
|
+
case "below-minimum": return {
|
|
2206
|
+
warning: outcome.message,
|
|
2207
|
+
fatal: void 0
|
|
2208
|
+
};
|
|
2209
|
+
case "no-database-url":
|
|
2210
|
+
case "connection-failed":
|
|
2211
|
+
case "driver-missing": return strictProbe ? {
|
|
2212
|
+
warning: void 0,
|
|
2213
|
+
fatal: outcome.message
|
|
2214
|
+
} : {
|
|
2215
|
+
warning: outcome.message,
|
|
2216
|
+
fatal: void 0
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function causeMessage(error) {
|
|
2221
|
+
return error instanceof Error ? error.message : String(error);
|
|
2222
|
+
}
|
|
2223
|
+
const createInitCommand = (injected) => defineOrmCommand({
|
|
2224
|
+
help: {
|
|
2225
|
+
summary: "Initialize a new Prisma Next project",
|
|
2226
|
+
description: "Scaffolds config, schema, and runtime files, installs dependencies,\nand emits the contract. Gets you from zero to typed queries in one step.\n\nRun it interactively for a guided setup, or supply --target and --authoring\nfor a fully scriptable run (CI, AI coding agents, automation).",
|
|
2227
|
+
examples: [
|
|
2228
|
+
"init",
|
|
2229
|
+
"init --target postgres --authoring psl",
|
|
2230
|
+
"init --target mongodb --authoring typescript --json",
|
|
2231
|
+
"init --skip-install",
|
|
2232
|
+
"init --skip-skills",
|
|
2233
|
+
"init --target postgres --keep-previous-facade"
|
|
2234
|
+
]
|
|
2235
|
+
},
|
|
2236
|
+
args: { flags: {
|
|
2237
|
+
target: flag.string({
|
|
2238
|
+
brief: "Database target: postgres or mongodb",
|
|
2239
|
+
placeholder: "db"
|
|
2240
|
+
}),
|
|
2241
|
+
authoring: flag.string({
|
|
2242
|
+
brief: "Schema authoring style: psl or typescript",
|
|
2243
|
+
placeholder: "style"
|
|
2244
|
+
}),
|
|
2245
|
+
schemaPath: flag.string({
|
|
2246
|
+
brief: "Where to write the starter schema",
|
|
2247
|
+
placeholder: "path"
|
|
2248
|
+
}),
|
|
2249
|
+
writeEnv: flag.boolean({ brief: "Write a .env file from .env.example (gitignored)" }),
|
|
2250
|
+
probeDb: flag.boolean({ brief: "Connect to DATABASE_URL once and check the server version" }),
|
|
2251
|
+
strictProbe: flag.boolean({ brief: "Treat a failed --probe-db as fatal" }),
|
|
2252
|
+
skipInstall: flag.boolean({ brief: "Skip dependency installation and contract emission" }),
|
|
2253
|
+
skipSkills: flag.boolean({ brief: "Skip the Prisma Next agent-skill install" }),
|
|
2254
|
+
keepPreviousFacade: flag.boolean({ brief: "Keep the previous target package in package.json when switching targets" })
|
|
2255
|
+
} },
|
|
2256
|
+
exitCodes: INIT_EXIT_CODES,
|
|
2257
|
+
installsPackages: true,
|
|
2258
|
+
handler: async (args, ctx) => {
|
|
2259
|
+
const warnings = [];
|
|
2260
|
+
const warn = (text) => {
|
|
2261
|
+
warnings.push(text);
|
|
2262
|
+
ctx.report({
|
|
2263
|
+
kind: "message",
|
|
2264
|
+
severity: "warn",
|
|
2265
|
+
text
|
|
2266
|
+
});
|
|
2267
|
+
};
|
|
2268
|
+
const inputs = await resolveInitInputs({
|
|
2269
|
+
cwd: ctx.cwd,
|
|
2270
|
+
flags: args.flags,
|
|
2271
|
+
prompt: ctx.prompt
|
|
2272
|
+
});
|
|
2273
|
+
const packageManager = await resolveScaffoldPackageManager({
|
|
2274
|
+
cwd: ctx.cwd,
|
|
2275
|
+
env: ctx.env
|
|
2276
|
+
});
|
|
2277
|
+
const scaffold = scaffoldProject({
|
|
2278
|
+
cwd: ctx.cwd,
|
|
2279
|
+
inputs,
|
|
2280
|
+
packageManager
|
|
2281
|
+
});
|
|
2282
|
+
for (const warning of scaffold.warnings) warn(warning);
|
|
2283
|
+
for (const note of scaffold.notes) ctx.report({
|
|
2284
|
+
kind: "message",
|
|
2285
|
+
severity: "info",
|
|
2286
|
+
text: note
|
|
2287
|
+
});
|
|
2288
|
+
const deps = [targetPackageName(inputs.target, scaffold.resolveImportSpecifier), "dotenv"];
|
|
2289
|
+
const devDeps = scaffold.hasTypesNode ? ["prisma-next"] : ["prisma-next", "@types/node"];
|
|
2290
|
+
const findings = [];
|
|
2291
|
+
const extraActions = [];
|
|
2292
|
+
let installedManager;
|
|
2293
|
+
let packagesInstalled = "skipped";
|
|
2294
|
+
let contractEmitted = false;
|
|
2295
|
+
let skillRegistered = false;
|
|
2296
|
+
const settle = (exitCode) => {
|
|
2297
|
+
const installed = packagesInstalled === "installed";
|
|
2298
|
+
const document = {
|
|
2299
|
+
ok: true,
|
|
2300
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
2301
|
+
authoring: inputs.authoring,
|
|
2302
|
+
schemaPath: inputs.schemaPath,
|
|
2303
|
+
filesWritten: scaffold.filesWritten,
|
|
2304
|
+
filesDeleted: scaffold.filesDeleted,
|
|
2305
|
+
packagesInstalled: {
|
|
2306
|
+
status: packagesInstalled,
|
|
2307
|
+
deps: installed ? deps : [],
|
|
2308
|
+
devDeps: installed ? devDeps : []
|
|
2309
|
+
},
|
|
2310
|
+
contractEmitted,
|
|
2311
|
+
nextSteps: buildNextSteps({
|
|
2312
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
2313
|
+
packagesInstalled,
|
|
2314
|
+
contractEmitted,
|
|
2315
|
+
emitCommand: EMIT_COMMAND,
|
|
2316
|
+
schemaPath: inputs.schemaPath,
|
|
2317
|
+
skillRegistered
|
|
2318
|
+
}),
|
|
2319
|
+
warnings
|
|
2320
|
+
};
|
|
2321
|
+
const validated = InitOutputSchema(document);
|
|
2322
|
+
if (validated instanceof type.errors) return notOk(new CliStructuredError$1("CLI.INIT_INVALID_OUTPUT_DOCUMENT", "Init produced an invalid output document", {
|
|
2323
|
+
why: `The success document failed schema validation: ${String(validated)}`,
|
|
2324
|
+
nextActions: [chooseAction("This is a bug in prisma-next. Please report it with `-v` output.")],
|
|
2325
|
+
docsUrl: docsUrlFor("CLI.INIT_INVALID_OUTPUT_DOCUMENT")
|
|
2326
|
+
}));
|
|
2327
|
+
return ok(ctx.present({
|
|
2328
|
+
data: document,
|
|
2329
|
+
exitCode,
|
|
2330
|
+
diagnostics: findings
|
|
2331
|
+
}, initPresentations({
|
|
2332
|
+
document,
|
|
2333
|
+
complete: exitCode === 0,
|
|
2334
|
+
nextActions: [...extraActions, ...buildInitNextActions({
|
|
2335
|
+
contractEmitted,
|
|
2336
|
+
schemaPath: inputs.schemaPath,
|
|
2337
|
+
skillRegistered
|
|
2338
|
+
})]
|
|
2339
|
+
})));
|
|
2340
|
+
};
|
|
2341
|
+
if (inputs.install) {
|
|
2342
|
+
const outcome = await installProjectDependencies({
|
|
2343
|
+
packages: ctx.packages,
|
|
2344
|
+
cwd: ctx.cwd,
|
|
2345
|
+
deps,
|
|
2346
|
+
devDeps,
|
|
2347
|
+
catalogWarnings: packageManager === "pnpm" ? buildCatalogWarnings(ctx.cwd, [...deps, ...devDeps]) : []
|
|
2348
|
+
});
|
|
2349
|
+
for (const warning of outcome.warnings) warn(warning);
|
|
2350
|
+
if (outcome.failure !== void 0) {
|
|
2351
|
+
packagesInstalled = "failed";
|
|
2352
|
+
findings.push(installFailedFinding(outcome.failure, scaffold.filesWritten));
|
|
2353
|
+
return settle(4);
|
|
2354
|
+
}
|
|
2355
|
+
packagesInstalled = "installed";
|
|
2356
|
+
installedManager = outcome.manager;
|
|
2357
|
+
const emitStep = "Emit the contract";
|
|
2358
|
+
ctx.report({
|
|
2359
|
+
kind: "step-started",
|
|
2360
|
+
step: emitStep
|
|
2361
|
+
});
|
|
2362
|
+
try {
|
|
2363
|
+
await injected.emitScaffoldedContract({ cwd: ctx.cwd });
|
|
2364
|
+
contractEmitted = true;
|
|
2365
|
+
ctx.report({
|
|
2366
|
+
kind: "step-finished",
|
|
2367
|
+
step: emitStep,
|
|
2368
|
+
outcome: "ok"
|
|
2369
|
+
});
|
|
2370
|
+
} catch (error) {
|
|
2371
|
+
ctx.report({
|
|
2372
|
+
kind: "step-finished",
|
|
2373
|
+
step: emitStep,
|
|
2374
|
+
outcome: "failed"
|
|
2375
|
+
});
|
|
2376
|
+
findings.push(emitFailedFinding(causeMessage(error), scaffold.filesWritten));
|
|
2377
|
+
return settle(5);
|
|
2378
|
+
}
|
|
2379
|
+
} else extraActions.push(chooseAction(`Install the project dependencies with your package manager: ${deps.join(", ")} (and ${devDeps.join(", ")} as development dependencies)`));
|
|
2380
|
+
if (inputs.probeDb) {
|
|
2381
|
+
const probe = probeWarning(await probeServerVersion({
|
|
2382
|
+
baseDir: ctx.cwd,
|
|
2383
|
+
target: inputs.target,
|
|
2384
|
+
databaseUrl: ctx.env["DATABASE_URL"],
|
|
2385
|
+
minVersion: MIN_SERVER_VERSION[inputs.target]
|
|
2386
|
+
}, {}), inputs.strictProbe);
|
|
2387
|
+
if (probe.warning !== void 0) warn(probe.warning);
|
|
2388
|
+
if (probe.fatal !== void 0) return notOk(normalizeError(errorInitProbeFailed({
|
|
2389
|
+
cause: probe.fatal,
|
|
2390
|
+
filesWritten: scaffold.filesWritten
|
|
2391
|
+
})));
|
|
2392
|
+
}
|
|
2393
|
+
const skillCommands = resolveProjectSkillInstallCommands(installedManager ?? packageManager, ctx.env);
|
|
2394
|
+
if (inputs.installProjectSkill) {
|
|
2395
|
+
const failure = await installAgentSkills({
|
|
2396
|
+
packages: ctx.packages,
|
|
2397
|
+
cwd: ctx.cwd,
|
|
2398
|
+
env: ctx.env,
|
|
2399
|
+
manager: installedManager
|
|
2400
|
+
});
|
|
2401
|
+
if (failure !== void 0) {
|
|
2402
|
+
findings.push(skillInstallFailedFinding(failure, scaffold.filesWritten, skillCommands));
|
|
2403
|
+
return settle(6);
|
|
2404
|
+
}
|
|
2405
|
+
skillRegistered = true;
|
|
2406
|
+
} else warn(`Skipped the Prisma Next agent-skill install (--skip-skills). To install them later, run: ${skillCommands.map((command) => `\`${command}\``).join(" && ")}`);
|
|
2407
|
+
return settle(0);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
const initCommand = createInitCommand({ emitScaffoldedContract });
|
|
2411
|
+
const STATUS_ACTION = runCommandAction("Check every space against the database", "prisma-next migration status");
|
|
2412
|
+
/**
|
|
2413
|
+
* The route the migration will take. The graph is a drawing — the engine cannot
|
|
2414
|
+
* derive a lane gutter — and the ordered list under it is drawn with the same
|
|
2415
|
+
* widths so every arrow lands in one column.
|
|
2416
|
+
*/
|
|
2417
|
+
function showPresentations$1(inputs) {
|
|
2418
|
+
const { document, graph, runList } = inputs;
|
|
2419
|
+
const count = document.migrations.length;
|
|
2420
|
+
return {
|
|
2421
|
+
human: () => [
|
|
2422
|
+
{
|
|
2423
|
+
kind: "fields",
|
|
2424
|
+
rail: true,
|
|
2425
|
+
rows: [
|
|
2426
|
+
{
|
|
2427
|
+
label: "migrations",
|
|
2428
|
+
value: inputs.migrationsDir
|
|
2429
|
+
},
|
|
2430
|
+
...inputs.database === void 0 ? [] : [{
|
|
2431
|
+
label: "database",
|
|
2432
|
+
value: inputs.database
|
|
2433
|
+
}],
|
|
2434
|
+
...inputs.from === void 0 ? [] : [{
|
|
2435
|
+
label: "from",
|
|
2436
|
+
value: inputs.from
|
|
2437
|
+
}],
|
|
2438
|
+
...inputs.to === void 0 ? [] : [{
|
|
2439
|
+
label: "to",
|
|
2440
|
+
value: inputs.to
|
|
2441
|
+
}]
|
|
2442
|
+
]
|
|
2443
|
+
},
|
|
2444
|
+
...graph.length === 0 ? [] : [{
|
|
2445
|
+
kind: "drawing",
|
|
2446
|
+
lines: toneDrawing(graph)
|
|
2447
|
+
}],
|
|
2448
|
+
...count === 0 ? [{
|
|
2449
|
+
kind: "summary",
|
|
2450
|
+
status: "info",
|
|
2451
|
+
text: document.summary
|
|
2452
|
+
}] : [{
|
|
2453
|
+
kind: "summary",
|
|
2454
|
+
status: "info",
|
|
2455
|
+
text: `The following ${count} migration${count === 1 ? "" : "s"} will run:`
|
|
2456
|
+
}, {
|
|
2457
|
+
kind: "drawing",
|
|
2458
|
+
lines: toneDrawing(runList.join("\n"))
|
|
2459
|
+
}]
|
|
2460
|
+
],
|
|
2461
|
+
json: () => document
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
function applyPresentations(inputs) {
|
|
2465
|
+
const { document } = inputs;
|
|
2466
|
+
const advanced = document.advancedRef;
|
|
2467
|
+
return {
|
|
2468
|
+
human: () => [
|
|
2469
|
+
{
|
|
2470
|
+
kind: "fields",
|
|
2471
|
+
rail: true,
|
|
2472
|
+
rows: [
|
|
2473
|
+
{
|
|
2474
|
+
label: "migrations",
|
|
2475
|
+
value: inputs.migrationsDir
|
|
2476
|
+
},
|
|
2477
|
+
...inputs.database === void 0 ? [] : [{
|
|
2478
|
+
label: "database",
|
|
2479
|
+
value: inputs.database
|
|
2480
|
+
}],
|
|
2481
|
+
...inputs.to === void 0 ? [] : [{
|
|
2482
|
+
label: "to",
|
|
2483
|
+
value: inputs.to
|
|
2484
|
+
}]
|
|
2485
|
+
]
|
|
2486
|
+
},
|
|
2487
|
+
{
|
|
2488
|
+
kind: "summary",
|
|
2489
|
+
status: "ok",
|
|
2490
|
+
text: document.summary
|
|
2491
|
+
},
|
|
2492
|
+
...document.perSpace.length === 0 ? [] : perSpaceBlocks(document.perSpace, "apply"),
|
|
2493
|
+
...advanced === null || advanced === void 0 ? [] : [{
|
|
2494
|
+
kind: "summary",
|
|
2495
|
+
status: "ok",
|
|
2496
|
+
text: [{ text: `Advanced ref "${advanced.name}" → ` }, {
|
|
2497
|
+
text: advanced.hash,
|
|
2498
|
+
tone: "identifier"
|
|
2499
|
+
}]
|
|
2500
|
+
}]
|
|
2501
|
+
],
|
|
2502
|
+
json: () => document,
|
|
2503
|
+
next: () => [STATUS_ACTION]
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* `--to` as a contract the app graph knows. A ref target keeps the invariants
|
|
2508
|
+
* the ref declares; a bare hash carries none. Omitting `--to` targets the
|
|
2509
|
+
* emitted contract, which needs no resolution at all.
|
|
2510
|
+
*/
|
|
2511
|
+
function resolveRequestedTarget(to, refs, graph) {
|
|
2512
|
+
if (to === void 0) return ok({
|
|
2513
|
+
entry: void 0,
|
|
2514
|
+
refName: void 0
|
|
2515
|
+
});
|
|
2516
|
+
const resolved = resolveContractRef(to, {
|
|
2517
|
+
graph,
|
|
2518
|
+
refs
|
|
2519
|
+
});
|
|
2520
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
2521
|
+
if (resolved.value.provenance.kind !== "ref") return ok({
|
|
2522
|
+
entry: {
|
|
2523
|
+
hash: resolved.value.hash,
|
|
2524
|
+
invariants: []
|
|
2525
|
+
},
|
|
2526
|
+
refName: void 0
|
|
2527
|
+
});
|
|
2528
|
+
const refName = resolved.value.provenance.refName;
|
|
2529
|
+
return ok({
|
|
2530
|
+
entry: refs[refName],
|
|
2531
|
+
refName
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
function createMigrateCommand(createClient) {
|
|
2535
|
+
return defineOrmCommand({
|
|
2536
|
+
help: {
|
|
2537
|
+
summary: "Apply planned migrations to advance the database",
|
|
2538
|
+
description: "Walks every contract space (app + extensions) and applies pending on-disk\nmigrations in canonical order (extensions alphabetically, then app). It\nreplays the on-disk migration graph and never invents an edge. Use --to to\ntarget a specific contract (hash, ref name, or migration directory) and\n--show for a read-only preview of the route it would take.",
|
|
2539
|
+
examples: [
|
|
2540
|
+
"migrate",
|
|
2541
|
+
"migrate --db $DATABASE_URL",
|
|
2542
|
+
"migrate --to production",
|
|
2543
|
+
"migrate --show",
|
|
2544
|
+
"migrate --show --from @contract --to production"
|
|
2545
|
+
]
|
|
2546
|
+
},
|
|
2547
|
+
args: { flags: {
|
|
2548
|
+
db: dbFlag,
|
|
2549
|
+
to: flag.string({
|
|
2550
|
+
brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
2551
|
+
placeholder: "contract"
|
|
2552
|
+
}),
|
|
2553
|
+
advanceRef: flag.string({
|
|
2554
|
+
brief: "Advance the named ref to the post-apply marker after success",
|
|
2555
|
+
placeholder: "name"
|
|
2556
|
+
}),
|
|
2557
|
+
show: flag.boolean({ brief: "Preview the migration route without applying (read-only)" }),
|
|
2558
|
+
from: flag.string({
|
|
2559
|
+
brief: "From-state for the --show preview (@contract, @db, hash, ref name, or dir)",
|
|
2560
|
+
placeholder: "contract"
|
|
2561
|
+
})
|
|
2562
|
+
} },
|
|
2563
|
+
needs: { config: ormConfigSection },
|
|
2564
|
+
handler: async (args, ctx) => {
|
|
2565
|
+
const migrationsRelative = displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd);
|
|
2566
|
+
if (args.flags.show) {
|
|
2567
|
+
const planned = await executeMigrateShowPlan({
|
|
2568
|
+
config: ctx.config,
|
|
2569
|
+
cwd: ctx.cwd,
|
|
2570
|
+
createClient,
|
|
2571
|
+
...ifDefined("db", args.flags.db),
|
|
2572
|
+
...ifDefined("to", args.flags.to),
|
|
2573
|
+
...ifDefined("from", args.flags.from)
|
|
2574
|
+
});
|
|
2575
|
+
if (!planned.ok) return notOk(normalizeError(planned.failure));
|
|
2576
|
+
const plan = planned.value;
|
|
2577
|
+
const paint = {
|
|
2578
|
+
colorize: true,
|
|
2579
|
+
glyphMode: "unicode",
|
|
2580
|
+
styler: createToneMigrationListStyler(),
|
|
2581
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE
|
|
2582
|
+
};
|
|
2583
|
+
const rendering = renderMigrateShowGraph(plan, paint);
|
|
2584
|
+
const document = {
|
|
2585
|
+
ok: true,
|
|
2586
|
+
migrations: plan.migrations,
|
|
2587
|
+
summary: plan.summary
|
|
2588
|
+
};
|
|
2589
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
2590
|
+
return ok(ctx.present({ data: document }, showPresentations$1({
|
|
2591
|
+
document,
|
|
2592
|
+
graph: rendering.graphOutput,
|
|
2593
|
+
runList: migrateShowRunListRows(plan.migrations, rendering, paint),
|
|
2594
|
+
migrationsDir: migrationsRelative,
|
|
2595
|
+
database: args.flags.from === void 0 && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
|
|
2596
|
+
from: args.flags.from,
|
|
2597
|
+
to: args.flags.to
|
|
2598
|
+
})));
|
|
2599
|
+
}
|
|
2600
|
+
const startedAt = Date.now();
|
|
1671
2601
|
const prepared = await prepareMigrationRun({
|
|
1672
2602
|
config: ctx.config,
|
|
1673
2603
|
cwd: ctx.cwd,
|
|
@@ -2020,85 +2950,392 @@ function ledgerGrid(entries, options) {
|
|
|
2020
2950
|
})
|
|
2021
2951
|
};
|
|
2022
2952
|
}
|
|
2023
|
-
function logPresentations(inputs) {
|
|
2024
|
-
const grid = inputs.grid;
|
|
2953
|
+
function logPresentations(inputs) {
|
|
2954
|
+
const grid = inputs.grid;
|
|
2955
|
+
return {
|
|
2956
|
+
human: () => [...inputs.database === void 0 ? [] : [{
|
|
2957
|
+
kind: "fields",
|
|
2958
|
+
rail: true,
|
|
2959
|
+
rows: [{
|
|
2960
|
+
label: "database",
|
|
2961
|
+
value: inputs.database
|
|
2962
|
+
}]
|
|
2963
|
+
}], ...grid === void 0 ? [{
|
|
2964
|
+
kind: "summary",
|
|
2965
|
+
status: "info",
|
|
2966
|
+
text: MIGRATION_LOG_EMPTY_MESSAGE
|
|
2967
|
+
}] : [{
|
|
2968
|
+
kind: "table",
|
|
2969
|
+
columns: grid.headings,
|
|
2970
|
+
rows: grid.rows
|
|
2971
|
+
}]],
|
|
2972
|
+
json: () => inputs.document
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
const migrationLogCommand = defineOrmCommand({
|
|
2976
|
+
help: {
|
|
2977
|
+
summary: "Show executed migration history",
|
|
2978
|
+
description: "Reads the database ledger and displays every applied migration edge in\nchronological order, including rollbacks and re-applies, merged across\nall contract spaces. Requires a database connection.",
|
|
2979
|
+
examples: [
|
|
2980
|
+
"migration log",
|
|
2981
|
+
"migration log --db $DATABASE_URL",
|
|
2982
|
+
"migration log --utc",
|
|
2983
|
+
"migration log --json"
|
|
2984
|
+
]
|
|
2985
|
+
},
|
|
2986
|
+
args: { flags: {
|
|
2987
|
+
db: dbFlag,
|
|
2988
|
+
utc: flag.boolean({ brief: "Render human timestamps in UTC instead of local time" }),
|
|
2989
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
2990
|
+
} },
|
|
2991
|
+
needs: { config: ormConfigSection },
|
|
2992
|
+
handler: async (args, ctx) => {
|
|
2993
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
2994
|
+
const missingDb = requireLiveDatabase({
|
|
2995
|
+
dbConnection,
|
|
2996
|
+
hasDriver: ctx.config.driver !== void 0,
|
|
2997
|
+
why: "migration log needs a database connection and driver to read the ledger (set db.connection in prisma-next.config.ts, or pass --db <url>)",
|
|
2998
|
+
commandName: "migration log"
|
|
2999
|
+
});
|
|
3000
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
3001
|
+
if (!targetSupportsMigrations(ctx.config.target)) return notOk(normalizeError(errorTargetMigrationNotSupported({ why: "migration log reads the ledger a migration runner writes, and the configured target provides no runner." })));
|
|
3002
|
+
const client = createControlClient({
|
|
3003
|
+
family: ctx.config.family,
|
|
3004
|
+
target: ctx.config.target,
|
|
3005
|
+
adapter: ctx.config.adapter,
|
|
3006
|
+
...ifDefined("driver", ctx.config.driver),
|
|
3007
|
+
extensions: ctx.config.extensions ?? []
|
|
3008
|
+
});
|
|
3009
|
+
let entries;
|
|
3010
|
+
try {
|
|
3011
|
+
await client.connect(dbConnection);
|
|
3012
|
+
entries = await client.readLedger();
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
|
|
3015
|
+
} finally {
|
|
3016
|
+
await closeQuietly(client);
|
|
3017
|
+
}
|
|
3018
|
+
const records = serializeLedgerEntriesForJson(entries);
|
|
3019
|
+
const document = {
|
|
3020
|
+
ok: true,
|
|
3021
|
+
records,
|
|
3022
|
+
summary: `${records.length} migration(s) applied`
|
|
3023
|
+
};
|
|
3024
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
3025
|
+
return ok(ctx.present({ data: document }, logPresentations({
|
|
3026
|
+
document,
|
|
3027
|
+
grid: entries.length === 0 ? void 0 : ledgerGrid(entries, {
|
|
3028
|
+
utc: args.flags.utc,
|
|
3029
|
+
glyphMode
|
|
3030
|
+
}),
|
|
3031
|
+
database: typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0
|
|
3032
|
+
})));
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
function newPresentations(inputs) {
|
|
3036
|
+
const { document } = inputs;
|
|
3037
|
+
const migrationTs = join(document.dir, "migration.ts");
|
|
3038
|
+
return {
|
|
3039
|
+
human: () => [
|
|
3040
|
+
{
|
|
3041
|
+
kind: "fields",
|
|
3042
|
+
rail: true,
|
|
3043
|
+
rows: [{
|
|
3044
|
+
label: "contract",
|
|
3045
|
+
value: inputs.contractPath
|
|
3046
|
+
}, {
|
|
3047
|
+
label: "migrations",
|
|
3048
|
+
value: inputs.appMigrationsRelative
|
|
3049
|
+
}]
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
kind: "summary",
|
|
3053
|
+
status: "ok",
|
|
3054
|
+
text: document.summary
|
|
3055
|
+
},
|
|
3056
|
+
{
|
|
3057
|
+
kind: "fields",
|
|
3058
|
+
rows: [{
|
|
3059
|
+
label: "from",
|
|
3060
|
+
value: document.from === null ? [{
|
|
3061
|
+
text: "(baseline)",
|
|
3062
|
+
tone: "muted"
|
|
3063
|
+
}] : [{
|
|
3064
|
+
text: document.from,
|
|
3065
|
+
tone: "identifier"
|
|
3066
|
+
}]
|
|
3067
|
+
}, {
|
|
3068
|
+
label: "to",
|
|
3069
|
+
value: [{
|
|
3070
|
+
text: document.to,
|
|
3071
|
+
tone: "identifier"
|
|
3072
|
+
}]
|
|
3073
|
+
}]
|
|
3074
|
+
}
|
|
3075
|
+
],
|
|
3076
|
+
json: () => document,
|
|
3077
|
+
next: () => [{
|
|
3078
|
+
kind: "edit-file",
|
|
3079
|
+
label: `Write the migration body in ${migrationTs}`
|
|
3080
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)]
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
const migrationNewCommand = defineOrmCommand({
|
|
3084
|
+
help: {
|
|
3085
|
+
summary: "Scaffold a new migration for manual authoring",
|
|
3086
|
+
description: "Creates a migration package with a migration.ts file for manual authoring.\nWrite the migration body in migration.ts, then run the file with Node\n(`node migration.ts`) to self-emit ops.json and attest the package.\nOffline — does not consult the database.",
|
|
3087
|
+
examples: [
|
|
3088
|
+
"migration new --name split-name",
|
|
3089
|
+
"migration new --name custom-fk --from abc123",
|
|
3090
|
+
"migration new --json"
|
|
3091
|
+
]
|
|
3092
|
+
},
|
|
3093
|
+
args: { flags: {
|
|
3094
|
+
name: flag.string({
|
|
3095
|
+
brief: "Migration name (used in directory name)",
|
|
3096
|
+
placeholder: "slug"
|
|
3097
|
+
}),
|
|
3098
|
+
from: flag.string({
|
|
3099
|
+
brief: "Starting contract hash (default: latest migration target)",
|
|
3100
|
+
placeholder: "hash"
|
|
3101
|
+
})
|
|
3102
|
+
} },
|
|
3103
|
+
needs: { config: ormConfigSection },
|
|
3104
|
+
handler: async (args, ctx) => {
|
|
3105
|
+
const scaffolded = await executeMigrationNewCommand({
|
|
3106
|
+
config: ctx.config,
|
|
3107
|
+
cwd: ctx.cwd,
|
|
3108
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
3109
|
+
...ifDefined("name", args.flags.name),
|
|
3110
|
+
...ifDefined("from", args.flags.from)
|
|
3111
|
+
});
|
|
3112
|
+
if (!scaffolded.ok) return notOk(normalizeError(scaffolded.failure));
|
|
3113
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
3114
|
+
return ok(ctx.present({ data: scaffolded.value }, newPresentations({
|
|
3115
|
+
document: scaffolded.value,
|
|
3116
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
3117
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd)
|
|
3118
|
+
})));
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
3121
|
+
function hashRow(label, hash) {
|
|
3122
|
+
return {
|
|
3123
|
+
label,
|
|
3124
|
+
value: hash === null ? [{
|
|
3125
|
+
text: "(baseline)",
|
|
3126
|
+
tone: "muted"
|
|
3127
|
+
}] : [{
|
|
3128
|
+
text: hash,
|
|
3129
|
+
tone: "identifier"
|
|
3130
|
+
}]
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Where the run put things: the contract edge it planned, then every directory
|
|
3135
|
+
* it wrote — the app-space package, the auto-baseline package that precedes it,
|
|
3136
|
+
* and any extension-space package the seed phase materialised.
|
|
3137
|
+
*/
|
|
3138
|
+
function outcomeFields(result, migrationsRelative) {
|
|
3139
|
+
return {
|
|
3140
|
+
kind: "fields",
|
|
3141
|
+
rows: [
|
|
3142
|
+
hashRow("from", result.from),
|
|
3143
|
+
hashRow("to", result.to),
|
|
3144
|
+
...result.baselineDir === void 0 ? [] : [{
|
|
3145
|
+
label: "baseline",
|
|
3146
|
+
value: result.baselineDir
|
|
3147
|
+
}],
|
|
3148
|
+
...result.dir === void 0 ? [] : [{
|
|
3149
|
+
label: "app space",
|
|
3150
|
+
value: result.dir
|
|
3151
|
+
}],
|
|
3152
|
+
...result.emittedExtensionDirs.map((entry) => ({
|
|
3153
|
+
label: `space ${entry.spaceId}`,
|
|
3154
|
+
value: join(migrationsRelative, entry.spaceId, entry.dirName)
|
|
3155
|
+
}))
|
|
3156
|
+
]
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
function operationNodes$1(result) {
|
|
3160
|
+
return result.operations.map((operation) => operation.operationClass === "destructive" ? {
|
|
3161
|
+
label: operation.label,
|
|
3162
|
+
status: "warn"
|
|
3163
|
+
} : { label: operation.label });
|
|
3164
|
+
}
|
|
3165
|
+
function operationBlocks$1(result) {
|
|
3166
|
+
if (result.operations.length === 0) return [];
|
|
3167
|
+
const destructive = result.operations.some((operation) => operation.operationClass === "destructive");
|
|
3168
|
+
return [{
|
|
3169
|
+
kind: "tree",
|
|
3170
|
+
roots: [{
|
|
3171
|
+
label: result.dir ?? "operations",
|
|
3172
|
+
children: operationNodes$1(result)
|
|
3173
|
+
}]
|
|
3174
|
+
}, ...destructive ? [{
|
|
3175
|
+
kind: "summary",
|
|
3176
|
+
status: "warn",
|
|
3177
|
+
text: "This migration contains destructive operations that may cause data loss."
|
|
3178
|
+
}] : []];
|
|
3179
|
+
}
|
|
3180
|
+
/** Statements a database would run, printed verbatim rather than laid out. */
|
|
3181
|
+
function previewBlocks$1(result) {
|
|
3182
|
+
const preview = result.preview;
|
|
3183
|
+
if (preview === void 0) return [];
|
|
3184
|
+
const statements = preview.statements.map((statement) => statement.text.trim()).filter((text) => text.length > 0).map((text) => text.endsWith(";") ? text : `${text};`);
|
|
3185
|
+
if (statements.length === 0) return [];
|
|
3186
|
+
return [{
|
|
3187
|
+
kind: "summary",
|
|
3188
|
+
status: "info",
|
|
3189
|
+
tone: "muted",
|
|
3190
|
+
text: previewBlockHeader(preview)
|
|
3191
|
+
}, {
|
|
3192
|
+
kind: "drawing",
|
|
3193
|
+
lines: statements
|
|
3194
|
+
}];
|
|
3195
|
+
}
|
|
3196
|
+
function planBlocks(result, migrationsRelative) {
|
|
3197
|
+
const outcome = outcomeFields(result, migrationsRelative);
|
|
3198
|
+
if (result.noOp) return [{
|
|
3199
|
+
kind: "summary",
|
|
3200
|
+
status: "ok",
|
|
3201
|
+
text: "No changes detected"
|
|
3202
|
+
}, outcome];
|
|
3203
|
+
if (result.pendingPlaceholders === true) return [{
|
|
3204
|
+
kind: "summary",
|
|
3205
|
+
status: "warn",
|
|
3206
|
+
text: result.summary
|
|
3207
|
+
}, outcome];
|
|
3208
|
+
return [
|
|
3209
|
+
{
|
|
3210
|
+
kind: "summary",
|
|
3211
|
+
status: "ok",
|
|
3212
|
+
text: result.summary
|
|
3213
|
+
},
|
|
3214
|
+
...operationBlocks$1(result),
|
|
3215
|
+
outcome,
|
|
3216
|
+
...previewBlocks$1(result)
|
|
3217
|
+
];
|
|
3218
|
+
}
|
|
3219
|
+
function planNextActions(result, migrationsRelative) {
|
|
3220
|
+
if (result.pendingPlaceholders === true) {
|
|
3221
|
+
const migrationTs = join(result.dir ?? "<dir>", "migration.ts");
|
|
3222
|
+
return [{
|
|
3223
|
+
kind: "edit-file",
|
|
3224
|
+
label: `Replace each placeholder(...) call in ${migrationTs} with your query`
|
|
3225
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)];
|
|
3226
|
+
}
|
|
3227
|
+
const written = [
|
|
3228
|
+
...result.baselineDir === void 0 ? [] : [result.baselineDir],
|
|
3229
|
+
...result.dir === void 0 ? [] : [result.dir],
|
|
3230
|
+
...result.emittedExtensionDirs.map((entry) => join(migrationsRelative, entry.spaceId, entry.dirName))
|
|
3231
|
+
];
|
|
3232
|
+
if (written.length === 0) return [];
|
|
3233
|
+
return [{
|
|
3234
|
+
kind: "edit-file",
|
|
3235
|
+
label: `Review ${written.join(" and ")}`
|
|
3236
|
+
}, runCommandAction("Apply the migration", "prisma-next migrate")];
|
|
3237
|
+
}
|
|
3238
|
+
function planPresentations(inputs) {
|
|
2025
3239
|
return {
|
|
2026
|
-
human: () => [
|
|
3240
|
+
human: () => [{
|
|
2027
3241
|
kind: "fields",
|
|
2028
3242
|
rail: true,
|
|
2029
|
-
rows: [
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
3243
|
+
rows: [
|
|
3244
|
+
{
|
|
3245
|
+
label: "contract",
|
|
3246
|
+
value: inputs.contractPath
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
label: "migrations",
|
|
3250
|
+
value: inputs.appMigrationsRelative
|
|
3251
|
+
},
|
|
3252
|
+
...inputs.from === void 0 ? [] : [{
|
|
3253
|
+
label: "from",
|
|
3254
|
+
value: inputs.from
|
|
3255
|
+
}],
|
|
3256
|
+
...inputs.to === void 0 ? [] : [{
|
|
3257
|
+
label: "to",
|
|
3258
|
+
value: inputs.to
|
|
3259
|
+
}],
|
|
3260
|
+
...inputs.name === void 0 ? [] : [{
|
|
3261
|
+
label: "name",
|
|
3262
|
+
value: inputs.name
|
|
3263
|
+
}]
|
|
3264
|
+
]
|
|
3265
|
+
}, ...planBlocks(inputs.document, inputs.migrationsRelative)],
|
|
3266
|
+
json: () => inputs.document,
|
|
3267
|
+
next: () => planNextActions(inputs.document, inputs.migrationsRelative)
|
|
2043
3268
|
};
|
|
2044
3269
|
}
|
|
2045
|
-
const
|
|
3270
|
+
const migrationPlanCommand = defineOrmCommand({
|
|
2046
3271
|
help: {
|
|
2047
|
-
summary: "
|
|
2048
|
-
description: "
|
|
3272
|
+
summary: "Plan a migration from contract changes",
|
|
3273
|
+
description: "Compares the emitted contract against the latest on-disk migration state\nand produces a new migration package with the required operations.\nOffline — does not consult the database.",
|
|
2049
3274
|
examples: [
|
|
2050
|
-
"migration
|
|
2051
|
-
"migration
|
|
2052
|
-
"migration
|
|
2053
|
-
"migration
|
|
3275
|
+
"migration plan",
|
|
3276
|
+
"migration plan --name add-users-table",
|
|
3277
|
+
"migration plan --to <migration-dir>^ --name rollback",
|
|
3278
|
+
"migration plan --json"
|
|
2054
3279
|
]
|
|
2055
3280
|
},
|
|
2056
3281
|
args: { flags: {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
3282
|
+
name: flag.string({
|
|
3283
|
+
brief: "Name slug for the migration directory",
|
|
3284
|
+
placeholder: "slug"
|
|
3285
|
+
}),
|
|
3286
|
+
from: flag.string({
|
|
3287
|
+
brief: "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
3288
|
+
placeholder: "contract"
|
|
3289
|
+
}),
|
|
3290
|
+
to: flag.string({
|
|
3291
|
+
brief: "Destination contract reference; defaults to the emitted contract. Same grammar as --from",
|
|
3292
|
+
placeholder: "contract"
|
|
3293
|
+
})
|
|
2060
3294
|
} },
|
|
2061
3295
|
needs: { config: ormConfigSection },
|
|
2062
3296
|
handler: async (args, ctx) => {
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
await client.connect(dbConnection);
|
|
2082
|
-
entries = await client.readLedger();
|
|
2083
|
-
} catch (error) {
|
|
2084
|
-
return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
|
|
2085
|
-
} finally {
|
|
2086
|
-
await closeQuietly(client);
|
|
2087
|
-
}
|
|
2088
|
-
const records = serializeLedgerEntriesForJson(entries);
|
|
2089
|
-
const document = {
|
|
2090
|
-
ok: true,
|
|
2091
|
-
records,
|
|
2092
|
-
summary: `${records.length} migration(s) applied`
|
|
3297
|
+
const seeded = (record) => {
|
|
3298
|
+
if (record.action !== "updated") return;
|
|
3299
|
+
const step = `Seed contract space ${record.spaceId}`;
|
|
3300
|
+
ctx.report({
|
|
3301
|
+
kind: "step-started",
|
|
3302
|
+
step,
|
|
3303
|
+
id: record.spaceId
|
|
3304
|
+
});
|
|
3305
|
+
ctx.report({
|
|
3306
|
+
kind: "step-finished",
|
|
3307
|
+
step,
|
|
3308
|
+
id: record.spaceId,
|
|
3309
|
+
outcome: "ok",
|
|
3310
|
+
data: {
|
|
3311
|
+
newHash: record.newHash,
|
|
3312
|
+
newMigrationDirs: record.newMigrationDirs
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
2093
3315
|
};
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
3316
|
+
const planned = await executeMigrationPlanCommand({
|
|
3317
|
+
config: ctx.config,
|
|
3318
|
+
cwd: ctx.cwd,
|
|
3319
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
3320
|
+
...ifDefined("name", args.flags.name),
|
|
3321
|
+
...ifDefined("from", args.flags.from),
|
|
3322
|
+
...ifDefined("to", args.flags.to)
|
|
3323
|
+
}, Date.now(), { onSeeded: seeded });
|
|
3324
|
+
if (!planned.ok) return notOk(normalizeError(planned.failure));
|
|
3325
|
+
ctx.report({
|
|
3326
|
+
kind: "message",
|
|
3327
|
+
severity: "verbose",
|
|
3328
|
+
text: `Total time: ${planned.value.timings.total}ms`
|
|
3329
|
+
});
|
|
3330
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
3331
|
+
return ok(ctx.present({ data: planned.value }, planPresentations({
|
|
3332
|
+
document: planned.value,
|
|
3333
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
3334
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
3335
|
+
migrationsRelative: displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
3336
|
+
from: args.flags.from,
|
|
3337
|
+
to: args.flags.to,
|
|
3338
|
+
name: args.flags.name
|
|
2102
3339
|
})));
|
|
2103
3340
|
}
|
|
2104
3341
|
});
|
|
@@ -2336,6 +3573,426 @@ const migrationShowCommand = defineOrmCommand({
|
|
|
2336
3573
|
})));
|
|
2337
3574
|
}
|
|
2338
3575
|
});
|
|
3576
|
+
const EMIT_CONTRACT = runCommandAction("Regenerate the contract", "prisma-next contract emit");
|
|
3577
|
+
function contractUnreadableFinding(reason) {
|
|
3578
|
+
const message = `Could not read contract: ${reason}`;
|
|
3579
|
+
return {
|
|
3580
|
+
document: {
|
|
3581
|
+
code: "CONTRACT.UNREADABLE",
|
|
3582
|
+
severity: "warn",
|
|
3583
|
+
message,
|
|
3584
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
|
|
3585
|
+
},
|
|
3586
|
+
diagnostic: {
|
|
3587
|
+
code: "CONTRACT.UNREADABLE",
|
|
3588
|
+
severity: "warn",
|
|
3589
|
+
summary: message,
|
|
3590
|
+
why: "The status tree falls back to the on-disk migration graph when the emitted contract cannot be read.",
|
|
3591
|
+
nextActions: [EMIT_CONTRACT]
|
|
3592
|
+
}
|
|
3593
|
+
};
|
|
3594
|
+
}
|
|
3595
|
+
function markerNotInHistoryFinding(space) {
|
|
3596
|
+
const message = `Database was updated outside the migration system (marker for space "${space}" does not match any migration)`;
|
|
3597
|
+
return {
|
|
3598
|
+
document: {
|
|
3599
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
3600
|
+
severity: "warn",
|
|
3601
|
+
message,
|
|
3602
|
+
hints: ["Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database"]
|
|
3603
|
+
},
|
|
3604
|
+
diagnostic: {
|
|
3605
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
3606
|
+
severity: "warn",
|
|
3607
|
+
summary: message,
|
|
3608
|
+
why: "The marker the database carries names no contract in the on-disk migration graph.",
|
|
3609
|
+
meta: { space },
|
|
3610
|
+
nextActions: [runCommandAction("Overwrite the marker if the database already matches the contract", "prisma-next db sign"), runCommandAction("Or push the current contract to the database", "prisma-next db update")]
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
function missingInvariantsFinding(inputs) {
|
|
3615
|
+
const message = `missing invariant(s): ${inputs.missing.join(", ")}`;
|
|
3616
|
+
return {
|
|
3617
|
+
document: {
|
|
3618
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
3619
|
+
severity: "warn",
|
|
3620
|
+
...ifDefined("ref", inputs.refName),
|
|
3621
|
+
invariants: [...inputs.missing],
|
|
3622
|
+
message
|
|
3623
|
+
},
|
|
3624
|
+
diagnostic: {
|
|
3625
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
3626
|
+
severity: "warn",
|
|
3627
|
+
summary: message,
|
|
3628
|
+
why: inputs.refName === void 0 ? "The database marker does not carry every invariant the target requires." : `The database marker does not carry every invariant \`${inputs.refName}\` requires.`,
|
|
3629
|
+
nextActions: [runCommandAction("Apply the migrations that provide them", "prisma-next migrate")],
|
|
3630
|
+
meta: {
|
|
3631
|
+
invariants: [...inputs.missing],
|
|
3632
|
+
...ifDefined("ref", inputs.refName)
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
const SHORT_HASH_LENGTH = 12;
|
|
3638
|
+
function shortDisplayHash(hash) {
|
|
3639
|
+
return hash.slice(0, SHORT_HASH_LENGTH);
|
|
3640
|
+
}
|
|
3641
|
+
const NO_DATABASE_STATE = {
|
|
3642
|
+
markersBySpace: /* @__PURE__ */ new Map(),
|
|
3643
|
+
ledgersBySpace: /* @__PURE__ */ new Map()
|
|
3644
|
+
};
|
|
3645
|
+
async function readDatabaseState(inputs) {
|
|
3646
|
+
const { client } = inputs;
|
|
3647
|
+
try {
|
|
3648
|
+
await client.connect(inputs.connection);
|
|
3649
|
+
const markersBySpace = new Map(await client.readAllMarkers());
|
|
3650
|
+
const ledgersBySpace = /* @__PURE__ */ new Map();
|
|
3651
|
+
for (const spaceId of inputs.spaceIds) ledgersBySpace.set(spaceId, await client.readLedger(spaceId));
|
|
3652
|
+
return ok({
|
|
3653
|
+
markersBySpace,
|
|
3654
|
+
ledgersBySpace
|
|
3655
|
+
});
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
return notOk(normalizeError(CliStructuredError$1.is(error) ? error : errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}` })));
|
|
3658
|
+
} finally {
|
|
3659
|
+
await closeQuietly(client);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
function buildNoPathSummary(args) {
|
|
3663
|
+
const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
|
|
3664
|
+
const targetShort = shortDisplayHash(args.targetHash);
|
|
3665
|
+
if (!args.explicitTarget) return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
|
|
3666
|
+
return `No migration path from ${markerPart} to ${args.refName !== void 0 ? `the target (${targetShort} via \`${args.refName}\`)` : `the target (${targetShort})`}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
|
|
3667
|
+
}
|
|
3668
|
+
function buildStatusHeadline(args) {
|
|
3669
|
+
if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
|
|
3670
|
+
if (args.pendingCount === 0) return "Up to date";
|
|
3671
|
+
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* One space's section: its heading, where more than one space is in scope, on
|
|
3675
|
+
* top of its tree. Heading and tree are one drawing so the heading stays a
|
|
3676
|
+
* bare label — a `summary` would put a status glyph in front of it.
|
|
3677
|
+
*/
|
|
3678
|
+
function sectionBlock(section) {
|
|
3679
|
+
const heading = section.showHeading ? [[{
|
|
3680
|
+
text: `${section.space}:`,
|
|
3681
|
+
tone: "heading"
|
|
3682
|
+
}]] : [];
|
|
3683
|
+
const body = section.tree.length === 0 ? [[{
|
|
3684
|
+
text: "(no migrations)",
|
|
3685
|
+
tone: "muted"
|
|
3686
|
+
}]] : toneDrawing(section.tree);
|
|
3687
|
+
return {
|
|
3688
|
+
kind: "drawing",
|
|
3689
|
+
lines: [...heading, ...body]
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
function statusPresentations(inputs) {
|
|
3693
|
+
const legend = inputs.legend;
|
|
3694
|
+
return {
|
|
3695
|
+
human: () => [
|
|
3696
|
+
{
|
|
3697
|
+
kind: "fields",
|
|
3698
|
+
rail: true,
|
|
3699
|
+
rows: [
|
|
3700
|
+
{
|
|
3701
|
+
label: "migrations",
|
|
3702
|
+
value: inputs.migrationsDir
|
|
3703
|
+
},
|
|
3704
|
+
...inputs.database === void 0 ? [] : [{
|
|
3705
|
+
label: "database",
|
|
3706
|
+
value: inputs.database
|
|
3707
|
+
}],
|
|
3708
|
+
...inputs.ref === void 0 ? [] : [{
|
|
3709
|
+
label: "ref",
|
|
3710
|
+
value: inputs.ref
|
|
3711
|
+
}],
|
|
3712
|
+
...inputs.from === void 0 ? [] : [{
|
|
3713
|
+
label: "from",
|
|
3714
|
+
value: inputs.from
|
|
3715
|
+
}],
|
|
3716
|
+
...inputs.space === void 0 ? [] : [{
|
|
3717
|
+
label: "space",
|
|
3718
|
+
value: inputs.space
|
|
3719
|
+
}]
|
|
3720
|
+
]
|
|
3721
|
+
},
|
|
3722
|
+
...legend === void 0 ? [] : [{
|
|
3723
|
+
kind: "drawing",
|
|
3724
|
+
lines: toneDrawing(legend)
|
|
3725
|
+
}],
|
|
3726
|
+
...inputs.sections.map(sectionBlock),
|
|
3727
|
+
inputs.headline
|
|
3728
|
+
],
|
|
3729
|
+
json: () => inputs.document
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
const migrationStatusCommand = defineOrmCommand({
|
|
3733
|
+
help: {
|
|
3734
|
+
summary: "Show migration path and pending status",
|
|
3735
|
+
description: "Shows which migrations are pending between the database marker and the\ntarget contract. Requires a database connection. Pass --from for an\noffline path preview without a database. Use `migration graph` for\ntopology, `migration log` for history, and `migration list` for on-disk\nenumeration.",
|
|
3736
|
+
examples: [
|
|
3737
|
+
"migration status",
|
|
3738
|
+
"migration status --db $DATABASE_URL",
|
|
3739
|
+
"migration status --to production",
|
|
3740
|
+
"migration status --from abc123 --to production",
|
|
3741
|
+
"migration status --ascii",
|
|
3742
|
+
"migration status --legend"
|
|
3743
|
+
]
|
|
3744
|
+
},
|
|
3745
|
+
args: { flags: {
|
|
3746
|
+
db: dbFlag,
|
|
3747
|
+
space: flag.string({
|
|
3748
|
+
brief: "Narrow output to a single contract space",
|
|
3749
|
+
placeholder: "id"
|
|
3750
|
+
}),
|
|
3751
|
+
to: flag.string({
|
|
3752
|
+
brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
3753
|
+
placeholder: "contract"
|
|
3754
|
+
}),
|
|
3755
|
+
from: flag.string({
|
|
3756
|
+
brief: "Origin contract reference; same grammar as --to. Supplying it switches to offline path computation",
|
|
3757
|
+
placeholder: "contract"
|
|
3758
|
+
}),
|
|
3759
|
+
legend: flag.boolean({ brief: "Print a key for the tree glyphs and lane colors" }),
|
|
3760
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
3761
|
+
} },
|
|
3762
|
+
needs: { config: ormConfigSection },
|
|
3763
|
+
handler: async (args, ctx) => {
|
|
3764
|
+
const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
|
|
3765
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
3766
|
+
const hasDriver = ctx.config.driver !== void 0;
|
|
3767
|
+
const usingFromOverride = args.flags.from !== void 0;
|
|
3768
|
+
if (!usingFromOverride) {
|
|
3769
|
+
const missingDb = requireLiveDatabase({
|
|
3770
|
+
dbConnection,
|
|
3771
|
+
hasDriver,
|
|
3772
|
+
why: "migration status needs a database connection to read the marker and ledger (or pass --from for an offline path preview)",
|
|
3773
|
+
retryCommand: "prisma-next migration status --from <contract>"
|
|
3774
|
+
});
|
|
3775
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
3776
|
+
}
|
|
3777
|
+
const refsResult = await readMigrationRefs(appRefsDirFor(ctx.config, ctx.cwd));
|
|
3778
|
+
if (!refsResult.ok) return notOk(normalizeError(refsResult.failure));
|
|
3779
|
+
const refs = refsResult.value;
|
|
3780
|
+
const findings = [];
|
|
3781
|
+
const loaded = await buildReadAggregate(ctx.config, { migrationsDir });
|
|
3782
|
+
if (!loaded.ok) return notOk(normalizeError(loaded.failure));
|
|
3783
|
+
const { aggregate, contractHash } = loaded.value;
|
|
3784
|
+
const contractConfig = { contract: ifDefined("output", contractPathFor(ctx.config, ctx.cwd)) };
|
|
3785
|
+
try {
|
|
3786
|
+
await readContractEnvelope(contractConfig);
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
findings.push(contractUnreadableFinding(error instanceof Error ? error.message : "unknown error"));
|
|
3789
|
+
}
|
|
3790
|
+
if (await loadContractRawSafely(contractConfig) !== null) {
|
|
3791
|
+
const corruption = refusePackageCorruptionOnAggregate(aggregate);
|
|
3792
|
+
if (corruption) return notOk(normalizeError(corruption));
|
|
3793
|
+
}
|
|
3794
|
+
const appGraph = aggregate.app.graph();
|
|
3795
|
+
let activeRefHash;
|
|
3796
|
+
let activeRefName;
|
|
3797
|
+
let activeRefEntry;
|
|
3798
|
+
if (args.flags.to !== void 0) {
|
|
3799
|
+
const resolved = resolveContractRef(args.flags.to, {
|
|
3800
|
+
graph: appGraph,
|
|
3801
|
+
refs
|
|
3802
|
+
});
|
|
3803
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
3804
|
+
activeRefHash = resolved.value.hash;
|
|
3805
|
+
if (resolved.value.provenance.kind === "ref") {
|
|
3806
|
+
activeRefName = resolved.value.provenance.refName;
|
|
3807
|
+
activeRefEntry = refs[activeRefName];
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
let fromOverrideHash;
|
|
3811
|
+
if (args.flags.from !== void 0) {
|
|
3812
|
+
const resolved = resolveContractRef(args.flags.from, {
|
|
3813
|
+
graph: appGraph,
|
|
3814
|
+
refs
|
|
3815
|
+
});
|
|
3816
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
3817
|
+
fromOverrideHash = resolved.value.hash;
|
|
3818
|
+
}
|
|
3819
|
+
const listed = runMigrationList({
|
|
3820
|
+
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
|
|
3821
|
+
...ifDefined("spaceFilter", args.flags.space)
|
|
3822
|
+
});
|
|
3823
|
+
if (!listed.ok) return notOk(normalizeError(listed.failure));
|
|
3824
|
+
const scopedSpaces = listed.value.spaces;
|
|
3825
|
+
const connects = dbConnection !== void 0 && hasDriver && !usingFromOverride;
|
|
3826
|
+
let database = NO_DATABASE_STATE;
|
|
3827
|
+
if (connects) {
|
|
3828
|
+
const read = await readDatabaseState({
|
|
3829
|
+
client: createControlClient({
|
|
3830
|
+
family: ctx.config.family,
|
|
3831
|
+
target: ctx.config.target,
|
|
3832
|
+
adapter: ctx.config.adapter,
|
|
3833
|
+
...ifDefined("driver", ctx.config.driver),
|
|
3834
|
+
extensions: ctx.config.extensions ?? []
|
|
3835
|
+
}),
|
|
3836
|
+
connection: dbConnection,
|
|
3837
|
+
spaceIds: scopedSpaces.map((entry) => entry.space)
|
|
3838
|
+
});
|
|
3839
|
+
if (!read.ok) return notOk(read.failure);
|
|
3840
|
+
database = read.value;
|
|
3841
|
+
}
|
|
3842
|
+
const appMarker = database.markersBySpace.get(aggregate.app.spaceId);
|
|
3843
|
+
if (activeRefEntry !== void 0 && activeRefEntry.invariants.length > 0 && connects) {
|
|
3844
|
+
const unknown = refuseUnknownInvariants({
|
|
3845
|
+
graph: appGraph,
|
|
3846
|
+
markerInvariants: appMarker?.invariants ?? [],
|
|
3847
|
+
refInvariants: activeRefEntry.invariants,
|
|
3848
|
+
...ifDefined("refName", activeRefName)
|
|
3849
|
+
});
|
|
3850
|
+
if (unknown) return notOk(normalizeError(unknown));
|
|
3851
|
+
}
|
|
3852
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
3853
|
+
const styler = createToneMigrationListStyler();
|
|
3854
|
+
const showSpaceHeadings = scopedSpaces.length > 1;
|
|
3855
|
+
const statusSpaces = [];
|
|
3856
|
+
const renderInputs = [];
|
|
3857
|
+
const emptySpaces = [];
|
|
3858
|
+
let divergedMarker;
|
|
3859
|
+
let noPath;
|
|
3860
|
+
let headlineTargetHash = activeRefHash ?? contractHash;
|
|
3861
|
+
let totalPending = 0;
|
|
3862
|
+
for (const entry of scopedSpaces) {
|
|
3863
|
+
const space = aggregate.space(entry.space);
|
|
3864
|
+
if (space === void 0) continue;
|
|
3865
|
+
const graph = space.graph();
|
|
3866
|
+
const spaceContractHash = space.contract().storage.storageHash;
|
|
3867
|
+
const targetHash = activeRefHash ?? spaceContractHash;
|
|
3868
|
+
if (entry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
|
|
3869
|
+
const markerHash = usingFromOverride ? fromOverrideHash : database.markersBySpace.get(entry.space)?.storageHash;
|
|
3870
|
+
const originHash = originHashForStatus(markerHash);
|
|
3871
|
+
const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
|
|
3872
|
+
if (connects && markerInGraph && originHash !== targetHash && noPath === void 0 && !hasMigrationPath(graph, originHash, targetHash)) noPath = {
|
|
3873
|
+
markerHash,
|
|
3874
|
+
targetHash
|
|
3875
|
+
};
|
|
3876
|
+
if (connects && markerHash !== void 0 && !markerInGraph) {
|
|
3877
|
+
divergedMarker ??= {
|
|
3878
|
+
space: entry.space,
|
|
3879
|
+
markerHash
|
|
3880
|
+
};
|
|
3881
|
+
findings.push(markerNotInHistoryFinding(entry.space));
|
|
3882
|
+
}
|
|
3883
|
+
const ledger = database.ledgersBySpace.get(entry.space) ?? [];
|
|
3884
|
+
const annotations = deriveStatusEdgeAnnotations({
|
|
3885
|
+
graph,
|
|
3886
|
+
targetHash,
|
|
3887
|
+
originHash,
|
|
3888
|
+
appliedMigrationHashes: connects ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
|
|
3889
|
+
showAppliedOverlay: connects
|
|
3890
|
+
});
|
|
3891
|
+
const migrations = entry.migrations.map((migration) => ({
|
|
3892
|
+
...migration,
|
|
3893
|
+
status: statusForMigrationHash(migration.hash, annotations)
|
|
3894
|
+
}));
|
|
3895
|
+
totalPending += migrations.filter((migration) => migration.status === "pending").length;
|
|
3896
|
+
statusSpaces.push({
|
|
3897
|
+
space: entry.space,
|
|
3898
|
+
currentContract: markerHash ?? null,
|
|
3899
|
+
targetContract: targetHash,
|
|
3900
|
+
migrations
|
|
3901
|
+
});
|
|
3902
|
+
if (graph.nodes.size === 0) {
|
|
3903
|
+
emptySpaces.push(entry.space);
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3906
|
+
renderInputs.push({
|
|
3907
|
+
spaceId: entry.space,
|
|
3908
|
+
graph,
|
|
3909
|
+
migrations: entry.migrations,
|
|
3910
|
+
liveContractHash: contractHash,
|
|
3911
|
+
refsByHash: listRefsByContractHash(space),
|
|
3912
|
+
statusOverlayByHash: annotations,
|
|
3913
|
+
colorize: true,
|
|
3914
|
+
glyphMode,
|
|
3915
|
+
styler,
|
|
3916
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE,
|
|
3917
|
+
isAppSpace: entry.space === aggregate.app.spaceId,
|
|
3918
|
+
...connects && markerHash !== void 0 ? { dbHash: markerHash } : {}
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
|
|
3922
|
+
if (connects && requiredInvariants.length > 0) {
|
|
3923
|
+
const held = new Set(appMarker?.invariants ?? []);
|
|
3924
|
+
const missing = requiredInvariants.filter((id) => !held.has(id));
|
|
3925
|
+
if (missing.length > 0) {
|
|
3926
|
+
findings.push(missingInvariantsFinding({
|
|
3927
|
+
missing,
|
|
3928
|
+
refName: activeRefName
|
|
3929
|
+
}));
|
|
3930
|
+
if (activeRefHash !== void 0) {
|
|
3931
|
+
const unreachable = refuseMissingInvariantPath({
|
|
3932
|
+
graph: appGraph,
|
|
3933
|
+
originHash: originHashForStatus(appMarker?.storageHash),
|
|
3934
|
+
targetHash: activeRefHash,
|
|
3935
|
+
missing,
|
|
3936
|
+
...ifDefined("refName", activeRefName)
|
|
3937
|
+
});
|
|
3938
|
+
if (unreachable) return notOk(normalizeError(unreachable));
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
const rendered = renderMigrationGraphSpaceTrees(renderInputs);
|
|
3943
|
+
const treesBySpace = new Map(renderInputs.map((input, index) => [input.spaceId, rendered[index] ?? ""]));
|
|
3944
|
+
const sections = scopedSpaces.map((entry) => {
|
|
3945
|
+
const tree = treesBySpace.get(entry.space) ?? "";
|
|
3946
|
+
return {
|
|
3947
|
+
space: entry.space,
|
|
3948
|
+
tree: showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree,
|
|
3949
|
+
showHeading: showSpaceHeadings
|
|
3950
|
+
};
|
|
3951
|
+
});
|
|
3952
|
+
const summary = scopedSpaces.every((entry) => entry.migrations.length === 0) ? "No migrations found" : noPath !== void 0 ? buildNoPathSummary({
|
|
3953
|
+
markerHash: noPath.markerHash,
|
|
3954
|
+
targetHash: noPath.targetHash,
|
|
3955
|
+
explicitTarget: args.flags.to !== void 0,
|
|
3956
|
+
refName: activeRefName
|
|
3957
|
+
}) : buildStatusHeadline({
|
|
3958
|
+
pendingCount: totalPending,
|
|
3959
|
+
targetHash: headlineTargetHash,
|
|
3960
|
+
markerDiverged: divergedMarker !== void 0,
|
|
3961
|
+
markerHash: divergedMarker?.markerHash
|
|
3962
|
+
});
|
|
3963
|
+
const diagnostics = findings.map((finding) => finding.diagnostic);
|
|
3964
|
+
const document = {
|
|
3965
|
+
ok: true,
|
|
3966
|
+
spaces: statusSpaces,
|
|
3967
|
+
summary,
|
|
3968
|
+
diagnostics: findings.map((finding) => finding.document)
|
|
3969
|
+
};
|
|
3970
|
+
const alarming = divergedMarker !== void 0 || totalPending > 0 || noPath !== void 0;
|
|
3971
|
+
return ok(ctx.present({
|
|
3972
|
+
data: document,
|
|
3973
|
+
diagnostics
|
|
3974
|
+
}, statusPresentations({
|
|
3975
|
+
document,
|
|
3976
|
+
sections,
|
|
3977
|
+
headline: {
|
|
3978
|
+
kind: "summary",
|
|
3979
|
+
status: alarming ? "warn" : "ok",
|
|
3980
|
+
text: summary
|
|
3981
|
+
},
|
|
3982
|
+
migrationsDir: displayPath(migrationsDir, ctx.cwd),
|
|
3983
|
+
database: connects && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
|
|
3984
|
+
ref: activeRefName,
|
|
3985
|
+
from: args.flags.from,
|
|
3986
|
+
space: args.flags.space,
|
|
3987
|
+
legend: args.flags.legend ? renderMigrationGraphLegend({
|
|
3988
|
+
colorize: true,
|
|
3989
|
+
glyphMode,
|
|
3990
|
+
styler,
|
|
3991
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE
|
|
3992
|
+
}) : void 0
|
|
3993
|
+
})));
|
|
3994
|
+
}
|
|
3995
|
+
});
|
|
2339
3996
|
function deletePresentations(document) {
|
|
2340
3997
|
return {
|
|
2341
3998
|
human: () => [{
|
|
@@ -2509,25 +4166,56 @@ const ormCommandFamily = defineCommandFamily({
|
|
|
2509
4166
|
"db schema": dbSchemaCommand,
|
|
2510
4167
|
"db update": dbUpdateCommand,
|
|
2511
4168
|
format: formatCommand,
|
|
4169
|
+
init: initCommand,
|
|
2512
4170
|
migrate: migrateCommand,
|
|
2513
4171
|
"migration graph": migrationGraphCommand,
|
|
2514
4172
|
"migration list": migrationListCommand,
|
|
2515
4173
|
"migration log": migrationLogCommand,
|
|
4174
|
+
"migration new": migrationNewCommand,
|
|
4175
|
+
"migration plan": migrationPlanCommand,
|
|
2516
4176
|
"migration show": migrationShowCommand,
|
|
4177
|
+
"migration status": migrationStatusCommand,
|
|
2517
4178
|
"ref delete": refDeleteCommand,
|
|
2518
4179
|
"ref list": refListCommand,
|
|
2519
4180
|
"ref set": refSetCommand
|
|
2520
4181
|
},
|
|
2521
4182
|
docsBaseUrl: DOCS_BASE_URL,
|
|
2522
|
-
redirects: [
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
4183
|
+
redirects: [
|
|
4184
|
+
{
|
|
4185
|
+
from: "migration apply",
|
|
4186
|
+
replacement: "{bin} migrate --to <contract>",
|
|
4187
|
+
reason: "Applying a migration is a move to a target contract, not a verb of its own."
|
|
4188
|
+
},
|
|
4189
|
+
{
|
|
4190
|
+
from: "migration ref",
|
|
4191
|
+
replacement: "{bin} ref set|list|delete",
|
|
4192
|
+
reason: "Refs are managed by their own command, for every space rather than migrations alone."
|
|
4193
|
+
},
|
|
4194
|
+
{
|
|
4195
|
+
from: "migration status",
|
|
4196
|
+
flag: "graph",
|
|
4197
|
+
replacement: "{bin} migration graph",
|
|
4198
|
+
reason: "Topology is its own command rather than a mode of the status report."
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
from: "migration status",
|
|
4202
|
+
flag: "all",
|
|
4203
|
+
replacement: "{bin} migration log --db <url>",
|
|
4204
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
4205
|
+
},
|
|
4206
|
+
{
|
|
4207
|
+
from: "migration status",
|
|
4208
|
+
flag: "limit",
|
|
4209
|
+
replacement: "{bin} migration log --db <url>",
|
|
4210
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
from: "migration status",
|
|
4214
|
+
flag: "ref",
|
|
4215
|
+
replacement: "{bin} migration status --to <contract>",
|
|
4216
|
+
reason: "One flag names the target contract, whether it is a ref, a hash or a migration."
|
|
4217
|
+
}
|
|
4218
|
+
]
|
|
2531
4219
|
});
|
|
2532
4220
|
/** The file the ORM's loader reads when `--config` names none. */
|
|
2533
4221
|
const ORM_CONFIG_FILENAME = "prisma-next.config.ts";
|