@prisma-next/cli 0.3.0-dev.6 → 0.3.0-dev.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +314 -80
- package/dist/cli-errors-JlPTsazx.mjs +3 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.js +1 -2376
- package/dist/cli.mjs +198 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-PimzSD1f.mjs +981 -0
- package/dist/client-PimzSD1f.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts +7 -0
- package/dist/commands/contract-emit.d.mts.map +1 -0
- package/dist/commands/contract-emit.mjs +150 -0
- package/dist/commands/contract-emit.mjs.map +1 -0
- package/dist/commands/db-init.d.mts +7 -0
- package/dist/commands/db-init.d.mts.map +1 -0
- package/dist/commands/db-init.mjs +132 -0
- package/dist/commands/db-init.mjs.map +1 -0
- package/dist/commands/db-introspect.d.mts +7 -0
- package/dist/commands/db-introspect.d.mts.map +1 -0
- package/dist/commands/db-introspect.mjs +117 -0
- package/dist/commands/db-introspect.mjs.map +1 -0
- package/dist/commands/db-schema-verify.d.mts +7 -0
- package/dist/commands/db-schema-verify.d.mts.map +1 -0
- package/dist/commands/db-schema-verify.mjs +119 -0
- package/dist/commands/db-schema-verify.mjs.map +1 -0
- package/dist/commands/db-sign.d.mts +7 -0
- package/dist/commands/db-sign.d.mts.map +1 -0
- package/dist/commands/db-sign.mjs +141 -0
- package/dist/commands/db-sign.mjs.map +1 -0
- package/dist/commands/db-update.d.mts +7 -0
- package/dist/commands/db-update.d.mts.map +1 -0
- package/dist/commands/db-update.mjs +121 -0
- package/dist/commands/db-update.mjs.map +1 -0
- package/dist/commands/db-verify.d.mts +7 -0
- package/dist/commands/db-verify.d.mts.map +1 -0
- package/dist/commands/db-verify.mjs +132 -0
- package/dist/commands/db-verify.mjs.map +1 -0
- package/dist/commands/migration-apply.d.mts +23 -0
- package/dist/commands/migration-apply.d.mts.map +1 -0
- package/dist/commands/migration-apply.mjs +249 -0
- package/dist/commands/migration-apply.mjs.map +1 -0
- package/dist/commands/migration-plan.d.mts +25 -0
- package/dist/commands/migration-plan.d.mts.map +1 -0
- package/dist/commands/migration-plan.mjs +266 -0
- package/dist/commands/migration-plan.mjs.map +1 -0
- package/dist/commands/migration-show.d.mts +28 -0
- package/dist/commands/migration-show.d.mts.map +1 -0
- package/dist/commands/migration-show.mjs +138 -0
- package/dist/commands/migration-show.mjs.map +1 -0
- package/dist/commands/migration-status.d.mts +35 -0
- package/dist/commands/migration-status.d.mts.map +1 -0
- package/dist/commands/migration-status.mjs +259 -0
- package/dist/commands/migration-status.mjs.map +1 -0
- package/dist/commands/migration-verify.d.mts +16 -0
- package/dist/commands/migration-verify.d.mts.map +1 -0
- package/dist/commands/migration-verify.mjs +86 -0
- package/dist/commands/migration-verify.mjs.map +1 -0
- package/dist/config-loader-PPf4CtDj.mjs +43 -0
- package/dist/config-loader-PPf4CtDj.mjs.map +1 -0
- package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
- package/dist/config-loader.d.mts.map +1 -0
- package/dist/config-loader.mjs +3 -0
- package/dist/exports/config-types.d.mts +2 -0
- package/dist/exports/config-types.mjs +3 -0
- package/dist/exports/control-api.d.mts +621 -0
- package/dist/exports/control-api.d.mts.map +1 -0
- package/dist/exports/control-api.mjs +97 -0
- package/dist/exports/control-api.mjs.map +1 -0
- package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +130 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/extract-sql-ddl-BmlKvk4o.mjs +26 -0
- package/dist/extract-sql-ddl-BmlKvk4o.mjs.map +1 -0
- package/dist/framework-components-CjV_jD8f.mjs +59 -0
- package/dist/framework-components-CjV_jD8f.mjs.map +1 -0
- package/dist/migration-command-scaffold-DfY_F3ev.mjs +97 -0
- package/dist/migration-command-scaffold-DfY_F3ev.mjs.map +1 -0
- package/dist/progress-adapter-DENrzF6I.mjs +49 -0
- package/dist/progress-adapter-DENrzF6I.mjs.map +1 -0
- package/dist/result-handler-iA9JtUC7.mjs +1186 -0
- package/dist/result-handler-iA9JtUC7.mjs.map +1 -0
- package/package.json +75 -38
- package/src/cli.ts +43 -0
- package/src/commands/contract-emit.ts +221 -111
- package/src/commands/db-init.ts +217 -426
- package/src/commands/db-introspect.ts +148 -185
- package/src/commands/db-schema-verify.ts +162 -149
- package/src/commands/db-sign.ts +215 -202
- package/src/commands/db-update.ts +220 -0
- package/src/commands/db-verify.ts +193 -156
- package/src/commands/migration-apply.ts +431 -0
- package/src/commands/migration-plan.ts +446 -0
- package/src/commands/migration-show.ts +255 -0
- package/src/commands/migration-status.ts +436 -0
- package/src/commands/migration-verify.ts +151 -0
- package/src/config-loader.ts +13 -3
- package/src/control-api/client.ts +605 -0
- package/src/control-api/errors.ts +9 -0
- package/src/control-api/operations/contract-emit.ts +161 -0
- package/src/control-api/operations/db-init.ts +286 -0
- package/src/control-api/operations/db-update.ts +221 -0
- package/src/control-api/operations/extract-sql-ddl.ts +47 -0
- package/src/control-api/operations/migration-apply.ts +195 -0
- package/src/control-api/operations/migration-helpers.ts +49 -0
- package/src/control-api/types.ts +687 -0
- package/src/exports/config-types.ts +3 -3
- package/src/exports/control-api.ts +53 -0
- package/src/load-ts-contract.ts +16 -11
- package/src/utils/cli-errors.ts +3 -1
- package/src/utils/command-helpers.ts +92 -3
- package/src/utils/framework-components.ts +11 -30
- package/src/utils/migration-command-scaffold.ts +190 -0
- package/src/utils/output.ts +363 -25
- package/src/utils/progress-adapter.ts +86 -0
- package/dist/chunk-464LNZCE.js +0 -134
- package/dist/chunk-464LNZCE.js.map +0 -1
- package/dist/chunk-BZMBKEEQ.js +0 -997
- package/dist/chunk-BZMBKEEQ.js.map +0 -1
- package/dist/chunk-HWYQOCAJ.js +0 -47
- package/dist/chunk-HWYQOCAJ.js.map +0 -1
- package/dist/chunk-ZKYEJROM.js +0 -94
- package/dist/chunk-ZKYEJROM.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/contract-emit.d.ts +0 -3
- package/dist/commands/contract-emit.d.ts.map +0 -1
- package/dist/commands/contract-emit.js +0 -9
- package/dist/commands/contract-emit.js.map +0 -1
- package/dist/commands/db-init.d.ts +0 -3
- package/dist/commands/db-init.d.ts.map +0 -1
- package/dist/commands/db-init.js +0 -341
- package/dist/commands/db-init.js.map +0 -1
- package/dist/commands/db-introspect.d.ts +0 -3
- package/dist/commands/db-introspect.d.ts.map +0 -1
- package/dist/commands/db-introspect.js +0 -190
- package/dist/commands/db-introspect.js.map +0 -1
- package/dist/commands/db-schema-verify.d.ts +0 -3
- package/dist/commands/db-schema-verify.d.ts.map +0 -1
- package/dist/commands/db-schema-verify.js +0 -164
- package/dist/commands/db-schema-verify.js.map +0 -1
- package/dist/commands/db-sign.d.ts +0 -3
- package/dist/commands/db-sign.d.ts.map +0 -1
- package/dist/commands/db-sign.js +0 -199
- package/dist/commands/db-sign.js.map +0 -1
- package/dist/commands/db-verify.d.ts +0 -3
- package/dist/commands/db-verify.d.ts.map +0 -1
- package/dist/commands/db-verify.js +0 -173
- package/dist/commands/db-verify.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js +0 -7
- package/dist/config-loader.js.map +0 -1
- package/dist/exports/config-types.d.ts +0 -3
- package/dist/exports/config-types.d.ts.map +0 -1
- package/dist/exports/config-types.js +0 -6
- package/dist/exports/config-types.js.map +0 -1
- package/dist/exports/index.d.ts +0 -4
- package/dist/exports/index.d.ts.map +0 -1
- package/dist/exports/index.js +0 -175
- package/dist/exports/index.js.map +0 -1
- package/dist/load-ts-contract.d.ts.map +0 -1
- package/dist/utils/action.d.ts +0 -16
- package/dist/utils/action.d.ts.map +0 -1
- package/dist/utils/cli-errors.d.ts +0 -7
- package/dist/utils/cli-errors.d.ts.map +0 -1
- package/dist/utils/command-helpers.d.ts +0 -12
- package/dist/utils/command-helpers.d.ts.map +0 -1
- package/dist/utils/framework-components.d.ts +0 -81
- package/dist/utils/framework-components.d.ts.map +0 -1
- package/dist/utils/global-flags.d.ts +0 -25
- package/dist/utils/global-flags.d.ts.map +0 -1
- package/dist/utils/output.d.ts +0 -142
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/result-handler.d.ts +0 -15
- package/dist/utils/result-handler.d.ts.map +0 -1
- package/dist/utils/spinner.d.ts +0 -29
- package/dist/utils/spinner.d.ts.map +0 -1
- package/src/utils/action.ts +0 -43
- package/src/utils/spinner.ts +0 -67
package/src/utils/output.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
2
2
|
import { bgGreen, blue, bold, cyan, dim, green, magenta, red, yellow } from 'colorette';
|
|
3
3
|
import type { Command } from 'commander';
|
|
4
|
+
import { relative } from 'pathe';
|
|
4
5
|
import stringWidth from 'string-width';
|
|
5
6
|
import stripAnsi from 'strip-ansi';
|
|
6
7
|
import wrapAnsi from 'wrap-ansi';
|
|
7
8
|
// EmitContractResult type for CLI output formatting (includes file paths)
|
|
8
9
|
export interface EmitContractResult {
|
|
9
|
-
readonly
|
|
10
|
+
readonly storageHash: string;
|
|
11
|
+
readonly executionHash?: string;
|
|
10
12
|
readonly profileHash: string;
|
|
11
13
|
readonly outDir: string;
|
|
12
14
|
readonly files: {
|
|
@@ -94,7 +96,10 @@ export function formatEmitOutput(result: EmitContractResult, flags: GlobalFlags)
|
|
|
94
96
|
|
|
95
97
|
lines.push(`${prefix}✔ Emitted contract.json → ${jsonPath}`);
|
|
96
98
|
lines.push(`${prefix}✔ Emitted contract.d.ts → ${dtsPath}`);
|
|
97
|
-
lines.push(`${prefix}
|
|
99
|
+
lines.push(`${prefix} storageHash: ${result.storageHash}`);
|
|
100
|
+
if (result.executionHash) {
|
|
101
|
+
lines.push(`${prefix} executionHash: ${result.executionHash}`);
|
|
102
|
+
}
|
|
98
103
|
if (result.profileHash) {
|
|
99
104
|
lines.push(`${prefix} profileHash: ${result.profileHash}`);
|
|
100
105
|
}
|
|
@@ -111,7 +116,8 @@ export function formatEmitOutput(result: EmitContractResult, flags: GlobalFlags)
|
|
|
111
116
|
export function formatEmitJson(result: EmitContractResult): string {
|
|
112
117
|
const output = {
|
|
113
118
|
ok: true,
|
|
114
|
-
|
|
119
|
+
storageHash: result.storageHash,
|
|
120
|
+
...ifDefined('executionHash', result.executionHash),
|
|
115
121
|
...(result.profileHash ? { profileHash: result.profileHash } : {}),
|
|
116
122
|
outDir: result.outDir,
|
|
117
123
|
files: result.files,
|
|
@@ -223,7 +229,7 @@ export function formatVerifyOutput(result: VerifyDatabaseResult, flags: GlobalFl
|
|
|
223
229
|
|
|
224
230
|
if (result.ok) {
|
|
225
231
|
lines.push(`${prefix}${formatGreen('✔')} ${result.summary}`);
|
|
226
|
-
lines.push(`${prefix}${formatDimText(`
|
|
232
|
+
lines.push(`${prefix}${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);
|
|
227
233
|
if (result.contract.profileHash) {
|
|
228
234
|
lines.push(`${prefix}${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
229
235
|
}
|
|
@@ -772,8 +778,8 @@ export function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags)
|
|
|
772
778
|
lines.push(`${prefix}${formatGreen('✔')} Database signed`);
|
|
773
779
|
|
|
774
780
|
// Show from -> to hashes with clear labels
|
|
775
|
-
const previousHash = result.marker.previous?.
|
|
776
|
-
const currentHash = result.contract.
|
|
781
|
+
const previousHash = result.marker.previous?.storageHash ?? 'none';
|
|
782
|
+
const currentHash = result.contract.storageHash;
|
|
777
783
|
|
|
778
784
|
lines.push(`${prefix}${formatDimText(` from: ${previousHash}`)}`);
|
|
779
785
|
lines.push(`${prefix}${formatDimText(` to: ${currentHash}`)}`);
|
|
@@ -802,19 +808,19 @@ export function formatSignJson(result: SignDatabaseResult): string {
|
|
|
802
808
|
}
|
|
803
809
|
|
|
804
810
|
// ============================================================================
|
|
805
|
-
//
|
|
811
|
+
// Migration Command Output Formatters (shared by db init and db update)
|
|
806
812
|
// ============================================================================
|
|
807
813
|
|
|
808
814
|
/**
|
|
809
|
-
*
|
|
815
|
+
* Shared CLI output type for migration commands (db init, db update).
|
|
810
816
|
*/
|
|
811
|
-
export interface
|
|
812
|
-
readonly ok:
|
|
817
|
+
export interface MigrationCommandResult {
|
|
818
|
+
readonly ok: true;
|
|
813
819
|
readonly mode: 'plan' | 'apply';
|
|
814
|
-
readonly plan
|
|
820
|
+
readonly plan: {
|
|
815
821
|
readonly targetId: string;
|
|
816
822
|
readonly destination: {
|
|
817
|
-
readonly
|
|
823
|
+
readonly storageHash: string;
|
|
818
824
|
readonly profileHash?: string;
|
|
819
825
|
};
|
|
820
826
|
readonly operations: readonly {
|
|
@@ -822,13 +828,14 @@ export interface DbInitResult {
|
|
|
822
828
|
readonly label: string;
|
|
823
829
|
readonly operationClass: string;
|
|
824
830
|
}[];
|
|
831
|
+
readonly sql?: readonly string[];
|
|
825
832
|
};
|
|
826
833
|
readonly execution?: {
|
|
827
834
|
readonly operationsPlanned: number;
|
|
828
835
|
readonly operationsExecuted: number;
|
|
829
836
|
};
|
|
830
837
|
readonly marker?: {
|
|
831
|
-
readonly
|
|
838
|
+
readonly storageHash: string;
|
|
832
839
|
readonly profileHash?: string;
|
|
833
840
|
};
|
|
834
841
|
readonly summary: string;
|
|
@@ -838,9 +845,12 @@ export interface DbInitResult {
|
|
|
838
845
|
}
|
|
839
846
|
|
|
840
847
|
/**
|
|
841
|
-
* Formats human-readable output for db init plan mode.
|
|
848
|
+
* Formats human-readable output for migration commands (db init, db update) in plan mode.
|
|
842
849
|
*/
|
|
843
|
-
export function
|
|
850
|
+
export function formatMigrationPlanOutput(
|
|
851
|
+
result: MigrationCommandResult,
|
|
852
|
+
flags: GlobalFlags,
|
|
853
|
+
): string {
|
|
844
854
|
if (flags.quiet) {
|
|
845
855
|
return '';
|
|
846
856
|
}
|
|
@@ -857,14 +867,26 @@ export function formatDbInitPlanOutput(result: DbInitResult, flags: GlobalFlags)
|
|
|
857
867
|
|
|
858
868
|
// Show operations tree
|
|
859
869
|
if (result.plan?.operations && result.plan.operations.length > 0) {
|
|
870
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
860
871
|
lines.push(`${prefix}${formatDimText('│')}`);
|
|
861
872
|
for (let i = 0; i < result.plan.operations.length; i++) {
|
|
862
873
|
const op = result.plan.operations[i];
|
|
863
874
|
if (!op) continue;
|
|
864
875
|
const isLast = i === result.plan.operations.length - 1;
|
|
865
876
|
const treeChar = isLast ? '└' : '├';
|
|
866
|
-
const
|
|
867
|
-
|
|
877
|
+
const opClassLabel =
|
|
878
|
+
op.operationClass === 'destructive'
|
|
879
|
+
? formatYellow(`[${op.operationClass}]`)
|
|
880
|
+
: formatDimText(`[${op.operationClass}]`);
|
|
881
|
+
lines.push(`${prefix}${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const hasDestructive = result.plan.operations.some((op) => op.operationClass === 'destructive');
|
|
885
|
+
if (hasDestructive) {
|
|
886
|
+
lines.push(`${prefix}`);
|
|
887
|
+
lines.push(
|
|
888
|
+
`${prefix}${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,
|
|
889
|
+
);
|
|
868
890
|
}
|
|
869
891
|
}
|
|
870
892
|
|
|
@@ -872,10 +894,28 @@ export function formatDbInitPlanOutput(result: DbInitResult, flags: GlobalFlags)
|
|
|
872
894
|
if (result.plan?.destination) {
|
|
873
895
|
lines.push(`${prefix}`);
|
|
874
896
|
lines.push(
|
|
875
|
-
`${prefix}${formatDimText(`Destination hash: ${result.plan.destination.
|
|
897
|
+
`${prefix}${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`,
|
|
876
898
|
);
|
|
877
899
|
}
|
|
878
900
|
|
|
901
|
+
// SQL DDL preview (SQL family only)
|
|
902
|
+
const planSql = result.plan?.sql;
|
|
903
|
+
if (planSql) {
|
|
904
|
+
lines.push(`${prefix}`);
|
|
905
|
+
lines.push(`${prefix}${formatDimText('DDL preview')}`);
|
|
906
|
+
if (planSql.length === 0) {
|
|
907
|
+
lines.push(`${prefix}${formatDimText('No DDL operations.')}`);
|
|
908
|
+
} else {
|
|
909
|
+
lines.push(`${prefix}`);
|
|
910
|
+
for (const statement of planSql) {
|
|
911
|
+
const trimmed = statement.trim();
|
|
912
|
+
if (!trimmed) continue;
|
|
913
|
+
const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
914
|
+
lines.push(`${prefix}${line}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
879
919
|
// Timings in verbose mode
|
|
880
920
|
if (isVerbose(flags, 1)) {
|
|
881
921
|
lines.push(`${prefix}${formatDimText(`Total time: ${result.timings.total}ms`)}`);
|
|
@@ -889,10 +929,303 @@ export function formatDbInitPlanOutput(result: DbInitResult, flags: GlobalFlags)
|
|
|
889
929
|
return lines.join('\n');
|
|
890
930
|
}
|
|
891
931
|
|
|
932
|
+
export interface MigrationApplyCommandOutputResult {
|
|
933
|
+
readonly migrationsApplied: number;
|
|
934
|
+
readonly markerHash: string;
|
|
935
|
+
readonly applied: readonly {
|
|
936
|
+
readonly dirName: string;
|
|
937
|
+
readonly operationsExecuted: number;
|
|
938
|
+
}[];
|
|
939
|
+
readonly summary: string;
|
|
940
|
+
readonly timings?: {
|
|
941
|
+
readonly total: number;
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export interface MigrationVerifyCommandOutputResult {
|
|
946
|
+
readonly status: 'verified' | 'attested';
|
|
947
|
+
readonly migrationId?: string;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export function formatMigrationApplyCommandOutput(
|
|
951
|
+
result: MigrationApplyCommandOutputResult,
|
|
952
|
+
flags: GlobalFlags,
|
|
953
|
+
): string {
|
|
954
|
+
if (flags.quiet) {
|
|
955
|
+
return '';
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const lines: string[] = [];
|
|
959
|
+
const useColor = flags.color !== false;
|
|
960
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
961
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
962
|
+
|
|
963
|
+
if (result.migrationsApplied === 0) {
|
|
964
|
+
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
965
|
+
lines.push(formatDimText(` marker: ${result.markerHash}`));
|
|
966
|
+
return lines.join('\n');
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
970
|
+
lines.push('');
|
|
971
|
+
|
|
972
|
+
for (let i = 0; i < result.applied.length; i++) {
|
|
973
|
+
const migration = result.applied[i]!;
|
|
974
|
+
const isLast = i === result.applied.length - 1;
|
|
975
|
+
const treeChar = isLast ? '└' : '├';
|
|
976
|
+
lines.push(
|
|
977
|
+
`${formatDimText(treeChar)}─ ${migration.dirName} ${formatDimText(`[${migration.operationsExecuted} op(s)]`)}`,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
lines.push('');
|
|
982
|
+
lines.push(formatDimText(`marker: ${result.markerHash}`));
|
|
983
|
+
|
|
984
|
+
if (isVerbose(flags, 1) && result.timings) {
|
|
985
|
+
lines.push('');
|
|
986
|
+
lines.push(formatDimText(`Total time: ${result.timings.total}ms`));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return lines.join('\n');
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
export function formatMigrationVerifyCommandOutput(
|
|
993
|
+
result: MigrationVerifyCommandOutputResult,
|
|
994
|
+
flags: GlobalFlags,
|
|
995
|
+
): string {
|
|
996
|
+
if (flags.quiet) {
|
|
997
|
+
return '';
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const lines: string[] = [];
|
|
1001
|
+
const useColor = flags.color !== false;
|
|
1002
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1003
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
1004
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
1005
|
+
|
|
1006
|
+
switch (result.status) {
|
|
1007
|
+
case 'verified':
|
|
1008
|
+
lines.push(`${formatGreen('✔')} Migration verified`);
|
|
1009
|
+
lines.push(formatDimText(` migrationId: ${result.migrationId}`));
|
|
1010
|
+
break;
|
|
1011
|
+
case 'attested':
|
|
1012
|
+
lines.push(`${formatYellow('◉')} Draft migration attested`);
|
|
1013
|
+
lines.push(formatDimText(` migrationId: ${result.migrationId}`));
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return lines.join('\n');
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
interface MigrationShowResult {
|
|
1021
|
+
readonly dirName: string;
|
|
1022
|
+
readonly dirPath: string;
|
|
1023
|
+
readonly from: string;
|
|
1024
|
+
readonly to: string;
|
|
1025
|
+
readonly migrationId: string | null;
|
|
1026
|
+
readonly kind: string;
|
|
1027
|
+
readonly createdAt: string;
|
|
1028
|
+
readonly operations: readonly {
|
|
1029
|
+
readonly id: string;
|
|
1030
|
+
readonly label: string;
|
|
1031
|
+
readonly operationClass: string;
|
|
1032
|
+
}[];
|
|
1033
|
+
readonly sql: readonly string[];
|
|
1034
|
+
readonly summary: string;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export function formatMigrationShowOutput(result: MigrationShowResult, flags: GlobalFlags): string {
|
|
1038
|
+
if (flags.quiet) {
|
|
1039
|
+
return '';
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const lines: string[] = [];
|
|
1043
|
+
const prefix = createPrefix(flags);
|
|
1044
|
+
const useColor = flags.color !== false;
|
|
1045
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1046
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
1047
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
1048
|
+
|
|
1049
|
+
lines.push(`${prefix}${formatGreen('✔')} ${result.dirName}`);
|
|
1050
|
+
lines.push(`${prefix}${formatDimText(` kind: ${result.kind}`)}`);
|
|
1051
|
+
lines.push(`${prefix}${formatDimText(` from: ${result.from}`)}`);
|
|
1052
|
+
lines.push(`${prefix}${formatDimText(` to: ${result.to}`)}`);
|
|
1053
|
+
if (result.migrationId) {
|
|
1054
|
+
lines.push(`${prefix}${formatDimText(` migrationId: ${result.migrationId}`)}`);
|
|
1055
|
+
} else {
|
|
1056
|
+
lines.push(`${prefix}${formatYellow(' migrationId: (draft — not yet attested)')}`);
|
|
1057
|
+
}
|
|
1058
|
+
lines.push(`${prefix}${formatDimText(` created: ${result.createdAt}`)}`);
|
|
1059
|
+
|
|
1060
|
+
lines.push(`${prefix}`);
|
|
1061
|
+
lines.push(`${prefix}${result.operations.length} operation(s)`);
|
|
1062
|
+
|
|
1063
|
+
if (result.operations.length > 0) {
|
|
1064
|
+
lines.push(`${prefix}${formatDimText('│')}`);
|
|
1065
|
+
for (let i = 0; i < result.operations.length; i++) {
|
|
1066
|
+
const op = result.operations[i]!;
|
|
1067
|
+
const isLast = i === result.operations.length - 1;
|
|
1068
|
+
const treeChar = isLast ? '└' : '├';
|
|
1069
|
+
const opClassLabel =
|
|
1070
|
+
op.operationClass === 'destructive'
|
|
1071
|
+
? formatYellow(`[${op.operationClass}]`)
|
|
1072
|
+
: formatDimText(`[${op.operationClass}]`);
|
|
1073
|
+
lines.push(`${prefix}${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');
|
|
1077
|
+
if (hasDestructive) {
|
|
1078
|
+
lines.push(`${prefix}`);
|
|
1079
|
+
lines.push(
|
|
1080
|
+
`${prefix}${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (result.sql.length > 0) {
|
|
1086
|
+
lines.push(`${prefix}`);
|
|
1087
|
+
lines.push(`${prefix}${formatDimText('DDL preview')}`);
|
|
1088
|
+
lines.push(`${prefix}`);
|
|
1089
|
+
for (const statement of result.sql) {
|
|
1090
|
+
const trimmed = statement.trim();
|
|
1091
|
+
if (!trimmed) continue;
|
|
1092
|
+
const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
1093
|
+
lines.push(`${prefix}${line}`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return lines.join('\n');
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
interface MigrationStatusEntry {
|
|
1101
|
+
readonly dirName: string;
|
|
1102
|
+
readonly to: string;
|
|
1103
|
+
readonly migrationId: string | null;
|
|
1104
|
+
readonly operationSummary: string;
|
|
1105
|
+
readonly hasDestructive: boolean;
|
|
1106
|
+
readonly status: 'applied' | 'pending' | 'unknown';
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
interface StatusDiagnostic {
|
|
1110
|
+
readonly code: string;
|
|
1111
|
+
readonly severity: 'warn' | 'info';
|
|
1112
|
+
readonly message: string;
|
|
1113
|
+
readonly hints: readonly string[];
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
interface MigrationStatusResult {
|
|
1117
|
+
readonly mode: 'online' | 'offline';
|
|
1118
|
+
readonly migrations: readonly MigrationStatusEntry[];
|
|
1119
|
+
readonly markerHash?: string;
|
|
1120
|
+
readonly leafHash: string;
|
|
1121
|
+
readonly contractHash: string;
|
|
1122
|
+
readonly summary: string;
|
|
1123
|
+
readonly diagnostics?: readonly StatusDiagnostic[];
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
export function formatMigrationStatusOutput(
|
|
1127
|
+
result: MigrationStatusResult,
|
|
1128
|
+
flags: GlobalFlags,
|
|
1129
|
+
): string {
|
|
1130
|
+
if (flags.quiet) {
|
|
1131
|
+
return '';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const lines: string[] = [];
|
|
1135
|
+
const prefix = createPrefix(flags);
|
|
1136
|
+
const useColor = flags.color !== false;
|
|
1137
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1138
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
1139
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
1140
|
+
const formatCyan = createColorFormatter(useColor, cyan);
|
|
1141
|
+
|
|
1142
|
+
if (result.migrations.length === 0) {
|
|
1143
|
+
lines.push(`${prefix}${formatDimText('No migrations found')}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
lines.push(`${prefix}${formatDimText('∅ (empty)')}`);
|
|
1146
|
+
lines.push(`${prefix}${formatDimText('│')}`);
|
|
1147
|
+
|
|
1148
|
+
for (let i = 0; i < result.migrations.length; i++) {
|
|
1149
|
+
const entry = result.migrations[i]!;
|
|
1150
|
+
const isLast = i === result.migrations.length - 1;
|
|
1151
|
+
const treeChar = isLast ? '└' : '├';
|
|
1152
|
+
const continueLine = isLast ? ' ' : '│';
|
|
1153
|
+
|
|
1154
|
+
let statusBadge = '';
|
|
1155
|
+
if (entry.status === 'applied') {
|
|
1156
|
+
statusBadge = formatGreen(' ✓ Applied');
|
|
1157
|
+
} else if (entry.status === 'pending') {
|
|
1158
|
+
statusBadge = formatYellow(' ⧗ Pending');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
let marker = '';
|
|
1162
|
+
if (result.mode === 'online') {
|
|
1163
|
+
const isLastApplied =
|
|
1164
|
+
entry.status === 'applied' &&
|
|
1165
|
+
(i === result.migrations.length - 1 || result.migrations[i + 1]?.status !== 'applied');
|
|
1166
|
+
if (isLastApplied) {
|
|
1167
|
+
marker = formatCyan(' ◄ DB');
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (isLast && entry.to === result.contractHash) {
|
|
1171
|
+
marker += ` ${formatCyan('◄ Contract')}`;
|
|
1172
|
+
} else if (isLast && result.contractHash !== entry.to) {
|
|
1173
|
+
marker += ` ${formatYellow('◄ Contract is ahead — run migration plan')}`;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
lines.push(`${prefix}${formatDimText(treeChar)}─ ${entry.dirName}${statusBadge}${marker}`);
|
|
1177
|
+
|
|
1178
|
+
const opsSummary = entry.hasDestructive
|
|
1179
|
+
? formatYellow(entry.operationSummary)
|
|
1180
|
+
: formatDimText(entry.operationSummary);
|
|
1181
|
+
lines.push(`${prefix}${formatDimText(continueLine)} ${opsSummary}`);
|
|
1182
|
+
|
|
1183
|
+
const hashDisplay = entry.to.length > 20 ? `${entry.to.slice(0, 20)}...` : entry.to;
|
|
1184
|
+
lines.push(`${prefix}${formatDimText(continueLine)} ${formatDimText(`→ ${hashDisplay}`)}`);
|
|
1185
|
+
|
|
1186
|
+
if (!isLast) {
|
|
1187
|
+
lines.push(`${prefix}${formatDimText('│')}`);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
lines.push(`${prefix}`);
|
|
1192
|
+
|
|
1193
|
+
if (result.mode === 'online') {
|
|
1194
|
+
const hasUnknown = result.migrations.some((e) => e.status === 'unknown');
|
|
1195
|
+
const pendingCount = result.migrations.filter((e) => e.status === 'pending').length;
|
|
1196
|
+
if (hasUnknown) {
|
|
1197
|
+
lines.push(`${prefix}${formatYellow('⚠')} ${result.summary}`);
|
|
1198
|
+
} else if (pendingCount === 0) {
|
|
1199
|
+
lines.push(`${prefix}${formatGreen('✔')} ${result.summary}`);
|
|
1200
|
+
} else {
|
|
1201
|
+
lines.push(`${prefix}${formatYellow('⧗')} ${result.summary}`);
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
lines.push(`${prefix}${result.summary}`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const warnings = result.diagnostics?.filter((d) => d.severity === 'warn') ?? [];
|
|
1209
|
+
if (warnings.length > 0) {
|
|
1210
|
+
lines.push('');
|
|
1211
|
+
for (const diag of warnings) {
|
|
1212
|
+
lines.push(`${prefix}${formatYellow('⚠')} ${diag.message}`);
|
|
1213
|
+
for (const hint of diag.hints) {
|
|
1214
|
+
lines.push(`${prefix} ${formatDimText(hint)}`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return lines.join('\n');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
892
1222
|
/**
|
|
893
|
-
* Formats human-readable output for db init apply mode.
|
|
1223
|
+
* Formats human-readable output for migration commands (db init, db update) in apply mode.
|
|
894
1224
|
*/
|
|
895
|
-
export function
|
|
1225
|
+
export function formatMigrationApplyOutput(
|
|
1226
|
+
result: MigrationCommandResult,
|
|
1227
|
+
flags: GlobalFlags,
|
|
1228
|
+
): string {
|
|
896
1229
|
if (flags.quiet) {
|
|
897
1230
|
return '';
|
|
898
1231
|
}
|
|
@@ -906,11 +1239,15 @@ export function formatDbInitApplyOutput(result: DbInitResult, flags: GlobalFlags
|
|
|
906
1239
|
if (result.ok) {
|
|
907
1240
|
// Success summary
|
|
908
1241
|
const executed = result.execution?.operationsExecuted ?? 0;
|
|
909
|
-
|
|
1242
|
+
if (executed === 0) {
|
|
1243
|
+
lines.push(`${prefix}${formatGreen('✔')} Database already matches contract`);
|
|
1244
|
+
} else {
|
|
1245
|
+
lines.push(`${prefix}${formatGreen('✔')} Applied ${executed} operation(s)`);
|
|
1246
|
+
}
|
|
910
1247
|
|
|
911
1248
|
// Marker info
|
|
912
1249
|
if (result.marker) {
|
|
913
|
-
lines.push(`${prefix}${formatDimText(`
|
|
1250
|
+
lines.push(`${prefix}${formatDimText(` Signature: ${result.marker.storageHash}`)}`);
|
|
914
1251
|
if (result.marker.profileHash) {
|
|
915
1252
|
lines.push(`${prefix}${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
|
|
916
1253
|
}
|
|
@@ -926,9 +1263,9 @@ export function formatDbInitApplyOutput(result: DbInitResult, flags: GlobalFlags
|
|
|
926
1263
|
}
|
|
927
1264
|
|
|
928
1265
|
/**
|
|
929
|
-
* Formats JSON output for db init
|
|
1266
|
+
* Formats JSON output for migration commands (db init, db update).
|
|
930
1267
|
*/
|
|
931
|
-
export function
|
|
1268
|
+
export function formatMigrationJson(result: MigrationCommandResult): string {
|
|
932
1269
|
return JSON.stringify(result, null, 2);
|
|
933
1270
|
}
|
|
934
1271
|
|
|
@@ -1241,6 +1578,7 @@ function getCommandDocsUrl(commandPath: string): string | undefined {
|
|
|
1241
1578
|
const docsMap: Record<string, string> = {
|
|
1242
1579
|
'contract emit': 'https://pris.ly/contract-emit',
|
|
1243
1580
|
'db verify': 'https://pris.ly/db-verify',
|
|
1581
|
+
'db update': 'https://pris.ly/db-update',
|
|
1244
1582
|
};
|
|
1245
1583
|
return docsMap[commandPath];
|
|
1246
1584
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import type { ControlProgressEvent, OnControlProgress } from '../control-api/types';
|
|
3
|
+
import type { GlobalFlags } from './global-flags';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating a progress adapter.
|
|
7
|
+
*/
|
|
8
|
+
interface ProgressAdapterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Global flags that control progress output behavior (quiet, json, color).
|
|
11
|
+
*/
|
|
12
|
+
readonly flags: GlobalFlags;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* State for tracking active spans in the progress adapter.
|
|
17
|
+
*/
|
|
18
|
+
interface SpanState {
|
|
19
|
+
readonly spinner: ReturnType<typeof ora>;
|
|
20
|
+
readonly startTime: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a progress adapter that converts control-api progress events
|
|
25
|
+
* into CLI spinner/progress output.
|
|
26
|
+
*
|
|
27
|
+
* The adapter:
|
|
28
|
+
* - Starts/succeeds spinners for top-level span boundaries
|
|
29
|
+
* - Prints per-operation lines for nested spans (e.g., migration operations under 'apply')
|
|
30
|
+
* - Respects quiet/json/non-TTY flags (no-op in those cases)
|
|
31
|
+
*
|
|
32
|
+
* @param options - Progress adapter configuration
|
|
33
|
+
* @returns An onProgress callback compatible with control-api operations
|
|
34
|
+
*/
|
|
35
|
+
export function createProgressAdapter(options: ProgressAdapterOptions): OnControlProgress {
|
|
36
|
+
const { flags } = options;
|
|
37
|
+
|
|
38
|
+
// Skip progress if quiet, JSON output, or non-TTY
|
|
39
|
+
const shouldShowProgress = !flags.quiet && flags.json !== 'object' && process.stdout.isTTY;
|
|
40
|
+
|
|
41
|
+
if (!shouldShowProgress) {
|
|
42
|
+
// Return a no-op callback
|
|
43
|
+
return () => {
|
|
44
|
+
// No-op
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Track active spans by spanId
|
|
49
|
+
const activeSpans = new Map<string, SpanState>();
|
|
50
|
+
|
|
51
|
+
return (event: ControlProgressEvent) => {
|
|
52
|
+
if (event.kind === 'spanStart') {
|
|
53
|
+
// Nested spans (with parentSpanId) are printed as lines, not spinners
|
|
54
|
+
if (event.parentSpanId) {
|
|
55
|
+
console.log(` → ${event.label}...`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Top-level spans get a spinner
|
|
60
|
+
const spinner = ora({
|
|
61
|
+
text: event.label,
|
|
62
|
+
color: flags.color !== false ? 'cyan' : false,
|
|
63
|
+
}).start();
|
|
64
|
+
|
|
65
|
+
activeSpans.set(event.spanId, {
|
|
66
|
+
spinner,
|
|
67
|
+
startTime: Date.now(),
|
|
68
|
+
});
|
|
69
|
+
} else if (event.kind === 'spanEnd') {
|
|
70
|
+
// Complete the spinner for this span (only top-level spans have spinners)
|
|
71
|
+
const spanState = activeSpans.get(event.spanId);
|
|
72
|
+
if (spanState) {
|
|
73
|
+
const elapsed = Date.now() - spanState.startTime;
|
|
74
|
+
if (event.outcome === 'error') {
|
|
75
|
+
spanState.spinner.fail(`${spanState.spinner.text} (failed)`);
|
|
76
|
+
} else if (event.outcome === 'skipped') {
|
|
77
|
+
spanState.spinner.info(`${spanState.spinner.text} (skipped)`);
|
|
78
|
+
} else {
|
|
79
|
+
spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);
|
|
80
|
+
}
|
|
81
|
+
activeSpans.delete(event.spanId);
|
|
82
|
+
}
|
|
83
|
+
// Nested span ends are no-ops (could log completion if needed)
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|