@prisma-next/cli 0.3.0-pr.99.6 → 0.3.0
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 +381 -128
- package/dist/agent-skill-mongo.md +106 -0
- package/dist/agent-skill-postgres.md +106 -0
- package/dist/cli-errors-BDCYR5ap.mjs +4 -0
- package/dist/cli-errors-DStABy9d.d.mts +3 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.js +1 -2910
- package/dist/cli.mjs +261 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-DiUkJAeN.mjs +987 -0
- package/dist/client-DiUkJAeN.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 +9 -0
- package/dist/commands/contract-infer.d.mts +7 -0
- package/dist/commands/contract-infer.d.mts.map +1 -0
- package/dist/commands/contract-infer.mjs +10 -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 +126 -0
- package/dist/commands/db-init.mjs.map +1 -0
- package/dist/commands/db-schema.d.mts +7 -0
- package/dist/commands/db-schema.d.mts.map +1 -0
- package/dist/commands/db-schema.mjs +56 -0
- package/dist/commands/db-schema.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 +137 -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 +123 -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 +323 -0
- package/dist/commands/db-verify.mjs.map +1 -0
- package/dist/commands/migration-apply.d.mts +36 -0
- package/dist/commands/migration-apply.d.mts.map +1 -0
- package/dist/commands/migration-apply.mjs +245 -0
- package/dist/commands/migration-apply.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts +8 -0
- package/dist/commands/migration-new.d.mts.map +1 -0
- package/dist/commands/migration-new.mjs +152 -0
- package/dist/commands/migration-new.mjs.map +1 -0
- package/dist/commands/migration-plan.d.mts +47 -0
- package/dist/commands/migration-plan.d.mts.map +1 -0
- package/dist/commands/migration-plan.mjs +313 -0
- package/dist/commands/migration-plan.mjs.map +1 -0
- package/dist/commands/migration-ref.d.mts +43 -0
- package/dist/commands/migration-ref.d.mts.map +1 -0
- package/dist/commands/migration-ref.mjs +195 -0
- package/dist/commands/migration-ref.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 +140 -0
- package/dist/commands/migration-show.mjs.map +1 -0
- package/dist/commands/migration-status.d.mts +86 -0
- package/dist/commands/migration-status.d.mts.map +1 -0
- package/dist/commands/migration-status.mjs +9 -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 +110 -0
- package/dist/commands/migration-verify.mjs.map +1 -0
- package/dist/config-loader-C4VXKl8f.mjs +43 -0
- package/dist/config-loader-C4VXKl8f.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/contract-emit-D2wDXfyo.mjs +191 -0
- package/dist/contract-emit-D2wDXfyo.mjs.map +1 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
- package/dist/contract-emit-kN-IkKTE.mjs +6 -0
- package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
- package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
- package/dist/contract-infer-DozZT511.mjs +90 -0
- package/dist/contract-infer-DozZT511.mjs.map +1 -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 +624 -0
- package/dist/exports/control-api.d.mts.map +1 -0
- package/dist/exports/control-api.mjs +8 -0
- package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +142 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/extract-operation-statements-DZUJNmL3.mjs +13 -0
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +1 -0
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +26 -0
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +1 -0
- package/dist/framework-components-BAsliT4V.mjs +59 -0
- package/dist/framework-components-BAsliT4V.mjs.map +1 -0
- package/dist/init-6Pvm_esG.mjs +430 -0
- package/dist/init-6Pvm_esG.mjs.map +1 -0
- package/dist/inspect-live-schema-BYnhztxZ.mjs +91 -0
- package/dist/inspect-live-schema-BYnhztxZ.mjs.map +1 -0
- package/dist/migration-command-scaffold-CntCcntR.mjs +105 -0
- package/dist/migration-command-scaffold-CntCcntR.mjs.map +1 -0
- package/dist/migration-status-CJANY4yr.mjs +1583 -0
- package/dist/migration-status-CJANY4yr.mjs.map +1 -0
- package/dist/migrations-DTZBYXm1.mjs +173 -0
- package/dist/migrations-DTZBYXm1.mjs.map +1 -0
- package/dist/progress-adapter-B-YvmcDu.mjs +43 -0
- package/dist/progress-adapter-B-YvmcDu.mjs.map +1 -0
- package/dist/quick-reference-mongo.md +93 -0
- package/dist/quick-reference-postgres.md +91 -0
- package/dist/result-handler-oK_vA-Fn.mjs +697 -0
- package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
- package/dist/terminal-ui-C5k88MmW.mjs +274 -0
- package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
- package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
- package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
- package/dist/verify-DlFQ2FOw.mjs +385 -0
- package/dist/verify-DlFQ2FOw.mjs.map +1 -0
- package/package.json +87 -40
- package/src/cli.ts +118 -58
- package/src/commands/contract-emit.ts +101 -78
- package/src/commands/contract-infer-paths.ts +32 -0
- package/src/commands/contract-infer.ts +143 -0
- package/src/commands/db-init.ts +97 -219
- package/src/commands/db-schema.ts +77 -0
- package/src/commands/db-sign.ts +46 -73
- package/src/commands/db-update.ts +236 -0
- package/src/commands/db-verify.ts +409 -119
- package/src/commands/init/detect-package-manager.ts +47 -0
- package/src/commands/init/index.ts +21 -0
- package/src/commands/init/init.ts +203 -0
- package/src/commands/init/templates/agent-skill-mongo.md +106 -0
- package/src/commands/init/templates/agent-skill-postgres.md +106 -0
- package/src/commands/init/templates/agent-skill.ts +19 -0
- package/src/commands/init/templates/code-templates.ts +168 -0
- package/src/commands/init/templates/quick-reference-mongo.md +93 -0
- package/src/commands/init/templates/quick-reference-postgres.md +91 -0
- package/src/commands/init/templates/quick-reference.ts +19 -0
- package/src/commands/init/templates/render.ts +20 -0
- package/src/commands/init/templates/tsconfig.ts +35 -0
- package/src/commands/inspect-live-schema.ts +170 -0
- package/src/commands/migration-apply.ts +427 -0
- package/src/commands/migration-new.ts +260 -0
- package/src/commands/migration-plan.ts +519 -0
- package/src/commands/migration-ref.ts +305 -0
- package/src/commands/migration-show.ts +246 -0
- package/src/commands/migration-status.ts +864 -0
- package/src/commands/migration-verify.ts +180 -0
- package/src/config-loader.ts +13 -3
- package/src/control-api/client.ts +205 -183
- package/src/control-api/contract-enrichment.ts +119 -0
- package/src/control-api/errors.ts +9 -0
- package/src/control-api/operations/contract-emit.ts +181 -0
- package/src/control-api/operations/db-init.ts +53 -49
- package/src/control-api/operations/db-update.ts +220 -0
- package/src/control-api/operations/extract-operation-statements.ts +14 -0
- package/src/control-api/operations/extract-sql-ddl.ts +47 -0
- package/src/control-api/operations/migration-apply.ts +191 -0
- package/src/control-api/operations/migration-helpers.ts +49 -0
- package/src/control-api/types.ts +274 -52
- package/src/exports/config-types.ts +4 -3
- package/src/exports/control-api.ts +15 -5
- package/src/load-ts-contract.ts +30 -19
- package/src/utils/cli-errors.ts +14 -8
- package/src/utils/command-helpers.ts +302 -3
- package/src/utils/formatters/emit.ts +67 -0
- package/src/utils/formatters/errors.ts +82 -0
- package/src/utils/formatters/graph-migration-mapper.ts +240 -0
- package/src/utils/formatters/graph-render.ts +1323 -0
- package/src/utils/formatters/graph-types.ts +120 -0
- package/src/utils/formatters/help.ts +380 -0
- package/src/utils/formatters/helpers.ts +28 -0
- package/src/utils/formatters/migrations.ts +346 -0
- package/src/utils/formatters/styled.ts +212 -0
- package/src/utils/formatters/verify.ts +621 -0
- package/src/utils/framework-components.ts +13 -10
- package/src/utils/global-flags.ts +41 -23
- package/src/utils/migration-command-scaffold.ts +184 -0
- package/src/utils/migration-types.ts +12 -0
- package/src/utils/progress-adapter.ts +18 -29
- package/src/utils/result-handler.ts +12 -13
- package/src/utils/shutdown.ts +92 -0
- package/src/utils/suggest-command.ts +31 -0
- package/src/utils/terminal-ui.ts +276 -0
- package/src/utils/validate-contract-deps.ts +49 -0
- package/dist/chunk-AGOTG4L3.js +0 -965
- package/dist/chunk-AGOTG4L3.js.map +0 -1
- package/dist/chunk-HLLI4YL7.js +0 -180
- package/dist/chunk-HLLI4YL7.js.map +0 -1
- package/dist/chunk-HWYQOCAJ.js +0 -47
- package/dist/chunk-HWYQOCAJ.js.map +0 -1
- package/dist/chunk-VG2R7DGF.js +0 -735
- package/dist/chunk-VG2R7DGF.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 -10
- 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 -257
- 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 -155
- 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 -171
- 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 -195
- 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 -193
- 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/control-api/client.d.ts +0 -13
- package/dist/control-api/client.d.ts.map +0 -1
- package/dist/control-api/operations/db-init.d.ts +0 -29
- package/dist/control-api/operations/db-init.d.ts.map +0 -1
- package/dist/control-api/types.d.ts +0 -387
- package/dist/control-api/types.d.ts.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/control-api.d.ts +0 -13
- package/dist/exports/control-api.d.ts.map +0 -1
- package/dist/exports/control-api.js +0 -7
- package/dist/exports/control-api.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 -176
- package/dist/exports/index.js.map +0 -1
- package/dist/load-ts-contract.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 -70
- 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/progress-adapter.d.ts +0 -26
- package/dist/utils/progress-adapter.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/src/commands/db-introspect.ts +0 -227
- package/src/commands/db-schema-verify.ts +0 -238
- package/src/utils/output.ts +0 -1471
|
@@ -1,42 +1,49 @@
|
|
|
1
1
|
export interface GlobalFlags {
|
|
2
|
-
readonly json?:
|
|
2
|
+
readonly json?: boolean;
|
|
3
3
|
readonly quiet?: boolean;
|
|
4
4
|
readonly verbose?: number; // 0, 1, or 2
|
|
5
|
-
readonly timestamps?: boolean;
|
|
6
5
|
readonly color?: boolean;
|
|
6
|
+
readonly interactive?: boolean;
|
|
7
|
+
readonly yes?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Common options parsed by Commander.js for every command.
|
|
12
|
+
* Extend this for command-specific options instead of duplicating these fields.
|
|
13
|
+
*/
|
|
14
|
+
export interface CommonCommandOptions {
|
|
10
15
|
readonly json?: string | boolean;
|
|
11
16
|
readonly quiet?: boolean;
|
|
12
17
|
readonly q?: boolean;
|
|
13
18
|
readonly verbose?: boolean;
|
|
14
19
|
readonly v?: boolean;
|
|
15
|
-
readonly vv?: boolean;
|
|
16
20
|
readonly trace?: boolean;
|
|
17
|
-
readonly timestamps?: boolean;
|
|
18
21
|
readonly color?: boolean;
|
|
19
22
|
readonly 'no-color'?: boolean;
|
|
23
|
+
readonly interactive?: boolean;
|
|
24
|
+
readonly 'no-interactive'?: boolean;
|
|
25
|
+
readonly yes?: boolean;
|
|
26
|
+
readonly y?: boolean;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* Parses global flags from CLI options.
|
|
24
|
-
* Handles verbosity flags (-v,
|
|
31
|
+
* Handles verbosity flags (-v, --trace), JSON output, quiet mode, color,
|
|
32
|
+
* interactivity (--interactive/--no-interactive), and auto-accept (-y/--yes).
|
|
25
33
|
*/
|
|
26
|
-
export function parseGlobalFlags(options:
|
|
34
|
+
export function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {
|
|
27
35
|
const flags: {
|
|
28
|
-
json?:
|
|
36
|
+
json?: boolean;
|
|
29
37
|
quiet?: boolean;
|
|
30
38
|
verbose?: number;
|
|
31
|
-
timestamps?: boolean;
|
|
32
39
|
color?: boolean;
|
|
40
|
+
interactive?: boolean;
|
|
41
|
+
yes?: boolean;
|
|
33
42
|
} = {};
|
|
34
43
|
|
|
35
|
-
// JSON output
|
|
36
|
-
if (options.json
|
|
37
|
-
flags.json =
|
|
38
|
-
} else if (options.json === 'ndjson') {
|
|
39
|
-
flags.json = 'ndjson';
|
|
44
|
+
// JSON output: explicit --json flag or auto-detect piped stdout (Unix convention)
|
|
45
|
+
if (options.json || !process.stdout.isTTY) {
|
|
46
|
+
flags.json = true;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
// Quiet mode
|
|
@@ -44,22 +51,18 @@ export function parseGlobalFlags(options: CliOptions): GlobalFlags {
|
|
|
44
51
|
flags.quiet = true;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
// Verbosity: -v = 1,
|
|
48
|
-
|
|
54
|
+
// Verbosity: -v = 1, --trace = 2
|
|
55
|
+
// Env toggles: PRISMA_NEXT_TRACE=1 ≅ --trace, PRISMA_NEXT_DEBUG=1 ≅ -v
|
|
56
|
+
if (options.trace || process.env['PRISMA_NEXT_TRACE'] === '1') {
|
|
49
57
|
flags.verbose = 2;
|
|
50
|
-
} else if (options.verbose || options.v) {
|
|
58
|
+
} else if (options.verbose || options.v || process.env['PRISMA_NEXT_DEBUG'] === '1') {
|
|
51
59
|
flags.verbose = 1;
|
|
52
60
|
} else {
|
|
53
61
|
flags.verbose = 0;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
// Timestamps
|
|
57
|
-
if (options.timestamps) {
|
|
58
|
-
flags.timestamps = true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
64
|
// Color: respect NO_COLOR env var, --color/--no-color flags
|
|
62
|
-
// When JSON output is enabled
|
|
65
|
+
// When JSON output is enabled, disable color to ensure clean JSON output
|
|
63
66
|
if (process.env['NO_COLOR'] || flags.json) {
|
|
64
67
|
flags.color = false;
|
|
65
68
|
} else if (options['no-color']) {
|
|
@@ -71,5 +74,20 @@ export function parseGlobalFlags(options: CliOptions): GlobalFlags {
|
|
|
71
74
|
flags.color = process.stdout.isTTY && !process.env['CI'];
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
// Interactivity: --interactive/--no-interactive
|
|
78
|
+
// Default: interactive when stdout is a TTY
|
|
79
|
+
if (options['no-interactive']) {
|
|
80
|
+
flags.interactive = false;
|
|
81
|
+
} else if (options.interactive !== undefined) {
|
|
82
|
+
flags.interactive = options.interactive;
|
|
83
|
+
} else {
|
|
84
|
+
flags.interactive = !!process.stdout.isTTY;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Auto-accept prompts: -y/--yes
|
|
88
|
+
if (options.yes || options.y) {
|
|
89
|
+
flags.yes = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
return flags as GlobalFlags;
|
|
75
93
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
|
+
import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
4
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
5
|
+
import type { Command } from 'commander';
|
|
6
|
+
import { loadConfig } from '../config-loader';
|
|
7
|
+
import { createControlClient } from '../control-api/client';
|
|
8
|
+
import type { ControlClient } from '../control-api/types';
|
|
9
|
+
import {
|
|
10
|
+
type CliStructuredError,
|
|
11
|
+
errorContractValidationFailed,
|
|
12
|
+
errorDatabaseConnectionRequired,
|
|
13
|
+
errorDriverRequired,
|
|
14
|
+
errorFileNotFound,
|
|
15
|
+
errorTargetMigrationNotSupported,
|
|
16
|
+
errorUnexpected,
|
|
17
|
+
} from './cli-errors';
|
|
18
|
+
import { addGlobalOptions, maskConnectionUrl, resolveContractPath } from './command-helpers';
|
|
19
|
+
import { formatStyledHeader } from './formatters/styled';
|
|
20
|
+
import type { GlobalFlags } from './global-flags';
|
|
21
|
+
import { createProgressAdapter } from './progress-adapter';
|
|
22
|
+
import type { TerminalUI } from './terminal-ui';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolved context for a migration command.
|
|
26
|
+
* Contains everything needed to invoke a control-api operation.
|
|
27
|
+
*/
|
|
28
|
+
export interface MigrationContext {
|
|
29
|
+
readonly client: ControlClient;
|
|
30
|
+
readonly contractJson: Record<string, unknown>;
|
|
31
|
+
readonly dbConnection: unknown;
|
|
32
|
+
readonly onProgress: ReturnType<typeof createProgressAdapter>;
|
|
33
|
+
readonly configPath: string;
|
|
34
|
+
readonly contractPath: string;
|
|
35
|
+
readonly contractPathAbsolute: string;
|
|
36
|
+
readonly config: Awaited<ReturnType<typeof loadConfig>>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Command-specific configuration for the shared scaffold.
|
|
41
|
+
*/
|
|
42
|
+
export interface MigrationCommandDescriptor {
|
|
43
|
+
readonly commandName: string;
|
|
44
|
+
readonly description: string;
|
|
45
|
+
readonly url: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Prepares the shared context for migration commands (db init, db update).
|
|
50
|
+
*
|
|
51
|
+
* Handles: config loading, contract file reading, JSON parsing, connection resolution,
|
|
52
|
+
* driver/migration-support validation, client creation, and header output.
|
|
53
|
+
*
|
|
54
|
+
* Returns a Result with either the resolved context or a structured error.
|
|
55
|
+
*/
|
|
56
|
+
export async function prepareMigrationContext(
|
|
57
|
+
options: { readonly db?: string; readonly config?: string; readonly dryRun?: boolean },
|
|
58
|
+
flags: GlobalFlags,
|
|
59
|
+
ui: TerminalUI,
|
|
60
|
+
descriptor: MigrationCommandDescriptor,
|
|
61
|
+
): Promise<Result<MigrationContext, CliStructuredError>> {
|
|
62
|
+
// Load config
|
|
63
|
+
const config = await loadConfig(options.config);
|
|
64
|
+
const configPath = options.config
|
|
65
|
+
? relative(process.cwd(), resolve(options.config))
|
|
66
|
+
: 'prisma-next.config.ts';
|
|
67
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
68
|
+
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
69
|
+
|
|
70
|
+
// Output header to stderr (decoration)
|
|
71
|
+
if (!flags.json && !flags.quiet) {
|
|
72
|
+
const details: Array<{ label: string; value: string }> = [
|
|
73
|
+
{ label: 'config', value: configPath },
|
|
74
|
+
{ label: 'contract', value: contractPath },
|
|
75
|
+
];
|
|
76
|
+
if (options.db) {
|
|
77
|
+
details.push({ label: 'database', value: maskConnectionUrl(options.db) });
|
|
78
|
+
}
|
|
79
|
+
if (options.dryRun) {
|
|
80
|
+
details.push({ label: 'mode', value: 'dry run' });
|
|
81
|
+
}
|
|
82
|
+
const header = formatStyledHeader({
|
|
83
|
+
command: descriptor.commandName,
|
|
84
|
+
description: descriptor.description,
|
|
85
|
+
url: descriptor.url,
|
|
86
|
+
details,
|
|
87
|
+
flags,
|
|
88
|
+
});
|
|
89
|
+
ui.stderr(header);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Load contract file
|
|
93
|
+
let contractJsonContent: string;
|
|
94
|
+
try {
|
|
95
|
+
contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
98
|
+
return notOk(
|
|
99
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
100
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
101
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return notOk(
|
|
106
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
107
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Parse contract JSON
|
|
113
|
+
let contractJson: Record<string, unknown>;
|
|
114
|
+
try {
|
|
115
|
+
contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return notOk(
|
|
118
|
+
errorContractValidationFailed(
|
|
119
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
120
|
+
{ where: { path: contractPathAbsolute } },
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
126
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
127
|
+
if (!dbConnection) {
|
|
128
|
+
return notOk(
|
|
129
|
+
errorDatabaseConnectionRequired({
|
|
130
|
+
why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
131
|
+
commandName: descriptor.commandName,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for driver
|
|
137
|
+
if (!config.driver) {
|
|
138
|
+
return notOk(
|
|
139
|
+
errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!hasMigrations(config.target)) {
|
|
144
|
+
return notOk(
|
|
145
|
+
errorTargetMigrationNotSupported({
|
|
146
|
+
why: `Target "${config.target.id}" does not support migrations`,
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Create control client
|
|
152
|
+
const client = createControlClient({
|
|
153
|
+
family: config.family,
|
|
154
|
+
target: config.target,
|
|
155
|
+
adapter: config.adapter,
|
|
156
|
+
driver: config.driver,
|
|
157
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Create progress adapter
|
|
161
|
+
const onProgress = createProgressAdapter({ ui, flags });
|
|
162
|
+
|
|
163
|
+
return ok({
|
|
164
|
+
client,
|
|
165
|
+
contractJson,
|
|
166
|
+
dbConnection,
|
|
167
|
+
onProgress,
|
|
168
|
+
configPath,
|
|
169
|
+
contractPath,
|
|
170
|
+
contractPathAbsolute,
|
|
171
|
+
config,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Registers the shared CLI options for migration commands (db init, db update).
|
|
177
|
+
*/
|
|
178
|
+
export function addMigrationCommandOptions(command: Command): Command {
|
|
179
|
+
addGlobalOptions(command);
|
|
180
|
+
return command
|
|
181
|
+
.option('--db <url>', 'Database connection string')
|
|
182
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
183
|
+
.option('--dry-run', 'Preview planned operations without applying', false);
|
|
184
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface StatusRef {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly hash: string;
|
|
4
|
+
readonly active: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface StatusDiagnostic {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly severity: 'warn' | 'info';
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly hints: readonly string[];
|
|
12
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { SpinnerResult } from '@clack/prompts';
|
|
2
2
|
import type { ControlProgressEvent, OnControlProgress } from '../control-api/types';
|
|
3
3
|
import type { GlobalFlags } from './global-flags';
|
|
4
|
+
import type { TerminalUI } from './terminal-ui';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Options for creating a progress adapter.
|
|
7
8
|
*/
|
|
8
9
|
interface ProgressAdapterOptions {
|
|
9
|
-
|
|
10
|
-
* Global flags that control progress output behavior (quiet, json, color).
|
|
11
|
-
*/
|
|
10
|
+
readonly ui: TerminalUI;
|
|
12
11
|
readonly flags: GlobalFlags;
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -16,33 +15,26 @@ interface ProgressAdapterOptions {
|
|
|
16
15
|
* State for tracking active spans in the progress adapter.
|
|
17
16
|
*/
|
|
18
17
|
interface SpanState {
|
|
19
|
-
readonly spinner:
|
|
18
|
+
readonly spinner: SpinnerResult;
|
|
20
19
|
readonly startTime: number;
|
|
20
|
+
readonly label: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Creates a progress adapter that converts control-api progress events
|
|
25
|
-
* into CLI spinner/progress output.
|
|
25
|
+
* into CLI spinner/progress output on stderr.
|
|
26
26
|
*
|
|
27
27
|
* The adapter:
|
|
28
28
|
* - Starts/succeeds spinners for top-level span boundaries
|
|
29
29
|
* - Prints per-operation lines for nested spans (e.g., migration operations under 'apply')
|
|
30
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
31
|
*/
|
|
35
32
|
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;
|
|
33
|
+
const { ui, flags } = options;
|
|
40
34
|
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
return () => {
|
|
44
|
-
// No-op
|
|
45
|
-
};
|
|
35
|
+
// Skip progress if quiet, JSON output, or non-interactive
|
|
36
|
+
if (flags.quiet || flags.json || !ui.isInteractive) {
|
|
37
|
+
return () => {};
|
|
46
38
|
}
|
|
47
39
|
|
|
48
40
|
// Track active spans by spanId
|
|
@@ -50,37 +42,34 @@ export function createProgressAdapter(options: ProgressAdapterOptions): OnContro
|
|
|
50
42
|
|
|
51
43
|
return (event: ControlProgressEvent) => {
|
|
52
44
|
if (event.kind === 'spanStart') {
|
|
53
|
-
// Nested spans (with parentSpanId) are printed as lines
|
|
45
|
+
// Nested spans (with parentSpanId) are printed as step lines
|
|
54
46
|
if (event.parentSpanId) {
|
|
55
|
-
|
|
47
|
+
ui.step(`${event.label}...`);
|
|
56
48
|
return;
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
// Top-level spans get a spinner
|
|
60
|
-
const spinner =
|
|
61
|
-
|
|
62
|
-
color: flags.color !== false ? 'cyan' : false,
|
|
63
|
-
}).start();
|
|
52
|
+
const spinner = ui.spinner();
|
|
53
|
+
spinner.start(event.label);
|
|
64
54
|
|
|
65
55
|
activeSpans.set(event.spanId, {
|
|
66
56
|
spinner,
|
|
67
57
|
startTime: Date.now(),
|
|
58
|
+
label: event.label,
|
|
68
59
|
});
|
|
69
60
|
} else if (event.kind === 'spanEnd') {
|
|
70
|
-
// Complete the spinner for this span (only top-level spans have spinners)
|
|
71
61
|
const spanState = activeSpans.get(event.spanId);
|
|
72
62
|
if (spanState) {
|
|
73
63
|
const elapsed = Date.now() - spanState.startTime;
|
|
74
64
|
if (event.outcome === 'error') {
|
|
75
|
-
spanState.spinner.
|
|
65
|
+
spanState.spinner.error(`${spanState.label} (failed)`);
|
|
76
66
|
} else if (event.outcome === 'skipped') {
|
|
77
|
-
spanState.spinner.
|
|
67
|
+
spanState.spinner.stop(`${spanState.label} (skipped)`);
|
|
78
68
|
} else {
|
|
79
|
-
spanState.spinner.
|
|
69
|
+
spanState.spinner.stop(`${spanState.label} (${elapsed}ms)`);
|
|
80
70
|
}
|
|
81
71
|
activeSpans.delete(event.spanId);
|
|
82
72
|
}
|
|
83
|
-
// Nested span ends are no-ops (could log completion if needed)
|
|
84
73
|
}
|
|
85
74
|
};
|
|
86
75
|
}
|
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
import type { Result } from '@prisma-next/utils/result';
|
|
2
2
|
import type { CliStructuredError } from './cli-errors';
|
|
3
|
+
import { formatErrorJson, formatErrorOutput } from './formatters/errors';
|
|
3
4
|
import type { GlobalFlags } from './global-flags';
|
|
4
|
-
import {
|
|
5
|
+
import type { TerminalUI } from './terminal-ui';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Processes a CLI command result, handling both success and error cases.
|
|
8
9
|
* Formats output appropriately and returns the exit code.
|
|
9
10
|
* Never throws - returns exit code for commands to use with process.exit().
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @returns The exit code that should be used (0 for success, non-zero for errors)
|
|
12
|
+
* Error output:
|
|
13
|
+
* - JSON mode: JSON error to stdout (piped) via ui.output(), human sees nothing on stderr.
|
|
14
|
+
* - Interactive: human-readable error to stderr.
|
|
15
15
|
*/
|
|
16
16
|
export function handleResult<T>(
|
|
17
17
|
result: Result<T, CliStructuredError>,
|
|
18
18
|
flags: GlobalFlags,
|
|
19
|
+
ui: TerminalUI,
|
|
19
20
|
onSuccess?: (value: T) => void,
|
|
20
21
|
): number {
|
|
21
22
|
if (result.ok) {
|
|
22
|
-
// Success case
|
|
23
23
|
if (onSuccess) {
|
|
24
24
|
onSuccess(result.value);
|
|
25
25
|
}
|
|
26
26
|
return 0;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// Convert to CLI envelope
|
|
30
30
|
const envelope = result.failure.toEnvelope();
|
|
31
31
|
|
|
32
|
-
// Output error based on flags
|
|
33
32
|
if (flags.json) {
|
|
34
|
-
// JSON error
|
|
35
|
-
|
|
33
|
+
// JSON error → stdout only
|
|
34
|
+
ui.output(formatErrorJson(envelope));
|
|
36
35
|
} else {
|
|
37
|
-
// Human-readable error
|
|
38
|
-
|
|
36
|
+
// Human-readable error → stderr
|
|
37
|
+
ui.error(formatErrorOutput(envelope, flags));
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
// Infer exit code from error domain: CLI errors = 2,
|
|
40
|
+
// Infer exit code from error domain: CLI errors = 2, RUN errors = 1
|
|
42
41
|
const exitCode = result.failure.domain === 'CLI' ? 2 : 1;
|
|
43
42
|
return exitCode;
|
|
44
43
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global shutdown controller for graceful SIGINT/SIGTERM handling.
|
|
3
|
+
*
|
|
4
|
+
* The CLI installs signal handlers once at startup. When a signal fires:
|
|
5
|
+
* 1. The AbortController is aborted — in-flight async work (DB queries, emit) can check `signal.aborted`.
|
|
6
|
+
* 2. A 3-second grace timer starts — gives `finally` blocks time to close connections.
|
|
7
|
+
* 3. If the process hasn't exited by then, force-exit with code 130 (128 + SIGINT).
|
|
8
|
+
* 4. A second signal during the grace period force-exits immediately.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a shutdown handler with its own AbortController.
|
|
13
|
+
* Exposed for testing — production code uses the singleton below.
|
|
14
|
+
*/
|
|
15
|
+
export interface ShutdownHandler {
|
|
16
|
+
readonly signal: AbortSignal;
|
|
17
|
+
readonly isShuttingDown: () => boolean;
|
|
18
|
+
readonly onSignal: () => void;
|
|
19
|
+
readonly clearGraceTimer: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createShutdownHandler(options?: {
|
|
23
|
+
readonly exit?: (code: number) => void;
|
|
24
|
+
readonly gracePeriodMs?: number;
|
|
25
|
+
}): ShutdownHandler {
|
|
26
|
+
const exit = options?.exit ?? ((code: number) => process.exit(code));
|
|
27
|
+
const gracePeriodMs = options?.gracePeriodMs ?? 3000;
|
|
28
|
+
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
let shuttingDown = false;
|
|
31
|
+
let graceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
32
|
+
|
|
33
|
+
const onSignal = () => {
|
|
34
|
+
if (shuttingDown) {
|
|
35
|
+
// Second signal — force exit
|
|
36
|
+
exit(130);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
shuttingDown = true;
|
|
40
|
+
controller.abort();
|
|
41
|
+
|
|
42
|
+
// Give finally blocks time to clean up, then force-exit
|
|
43
|
+
graceTimer = setTimeout(() => exit(130), gracePeriodMs);
|
|
44
|
+
graceTimer.unref();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
isShuttingDown: () => shuttingDown,
|
|
50
|
+
onSignal,
|
|
51
|
+
clearGraceTimer: () => {
|
|
52
|
+
if (graceTimer !== undefined) {
|
|
53
|
+
clearTimeout(graceTimer);
|
|
54
|
+
graceTimer = undefined;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Singleton for production use
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
const globalHandler = createShutdownHandler();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The global AbortSignal. Pass this to any async operation that should
|
|
68
|
+
* be cancellable on Ctrl+C (e.g., DB queries, long-running emit).
|
|
69
|
+
*/
|
|
70
|
+
export const shutdownSignal: AbortSignal = globalHandler.signal;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether a shutdown has been initiated.
|
|
74
|
+
*/
|
|
75
|
+
export function isShuttingDown(): boolean {
|
|
76
|
+
return globalHandler.isShuttingDown();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Installs SIGINT and SIGTERM handlers. Call once at CLI startup.
|
|
81
|
+
*
|
|
82
|
+
* - First signal: aborts the controller, starts a 3s grace timer.
|
|
83
|
+
* - Second signal: force-exits immediately.
|
|
84
|
+
*/
|
|
85
|
+
let installed = false;
|
|
86
|
+
|
|
87
|
+
export function installShutdownHandlers(): void {
|
|
88
|
+
if (installed) return;
|
|
89
|
+
installed = true;
|
|
90
|
+
process.on('SIGINT', globalHandler.onSignal);
|
|
91
|
+
process.on('SIGTERM', globalHandler.onSignal);
|
|
92
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { distance } from 'closest-match';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Suggests similar command names for a mistyped input.
|
|
5
|
+
*
|
|
6
|
+
* Uses Levenshtein distance to find close matches. Only suggests commands
|
|
7
|
+
* within a reasonable distance threshold (40% of the input length, minimum 2).
|
|
8
|
+
* Returns up to 3 suggestions in case of ties.
|
|
9
|
+
*
|
|
10
|
+
* @returns Array of suggested command names (empty if nothing is close enough).
|
|
11
|
+
*/
|
|
12
|
+
export function suggestCommands(input: string, candidates: readonly string[]): string[] {
|
|
13
|
+
if (candidates.length === 0) return [];
|
|
14
|
+
|
|
15
|
+
// Threshold: at most 40% of the input length (min 2) to avoid absurd suggestions
|
|
16
|
+
const maxDistance = Math.max(2, Math.ceil(input.length * 0.4));
|
|
17
|
+
|
|
18
|
+
const scored = candidates
|
|
19
|
+
.map((name) => ({ name, dist: distance(input, name) }))
|
|
20
|
+
.filter((entry) => entry.dist <= maxDistance)
|
|
21
|
+
.sort((a, b) => a.dist - b.dist);
|
|
22
|
+
|
|
23
|
+
if (scored.length === 0) return [];
|
|
24
|
+
|
|
25
|
+
// Take the best distance, then include ties (up to 3)
|
|
26
|
+
const bestDist = scored[0]!.dist;
|
|
27
|
+
return scored
|
|
28
|
+
.filter((entry) => entry.dist === bestDist)
|
|
29
|
+
.slice(0, 3)
|
|
30
|
+
.map((entry) => entry.name);
|
|
31
|
+
}
|