@prisma-next/cli 0.3.0-pr.99.6 → 0.4.0-dev.1
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 +254 -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 +4 -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 +4 -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 +125 -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 +53 -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 +136 -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 +122 -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 +322 -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 +244 -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 +4 -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-D9WOShFz.mjs +4 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
- package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -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 +6 -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 +137 -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-DQ8auNB4.mjs +430 -0
- package/dist/init-DQ8auNB4.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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
3
|
+
import { printPsl, validatePrintableSqlSchemaIR } from '@prisma-next/psl-printer';
|
|
4
|
+
import {
|
|
5
|
+
createPostgresDefaultMapping,
|
|
6
|
+
createPostgresTypeMap,
|
|
7
|
+
extractEnumInfo,
|
|
8
|
+
parseRawDefault,
|
|
9
|
+
} from '@prisma-next/psl-printer/postgres';
|
|
10
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { dirname, relative } from 'pathe';
|
|
13
|
+
import type { CliStructuredError } from '../utils/cli-errors';
|
|
14
|
+
import {
|
|
15
|
+
addGlobalOptions,
|
|
16
|
+
setCommandDescriptions,
|
|
17
|
+
setCommandExamples,
|
|
18
|
+
} from '../utils/command-helpers';
|
|
19
|
+
import { parseGlobalFlags } from '../utils/global-flags';
|
|
20
|
+
import { handleResult } from '../utils/result-handler';
|
|
21
|
+
import { TerminalUI } from '../utils/terminal-ui';
|
|
22
|
+
import { resolveContractInferOutputPath } from './contract-infer-paths';
|
|
23
|
+
import {
|
|
24
|
+
type InspectLiveSchemaOptions,
|
|
25
|
+
type InspectLiveSchemaResult,
|
|
26
|
+
inspectLiveSchema,
|
|
27
|
+
} from './inspect-live-schema';
|
|
28
|
+
|
|
29
|
+
interface ContractInferOptions extends InspectLiveSchemaOptions {
|
|
30
|
+
readonly output?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ContractInferSuccessResult {
|
|
34
|
+
readonly ok: true;
|
|
35
|
+
readonly summary: string;
|
|
36
|
+
readonly target: InspectLiveSchemaResult['target'];
|
|
37
|
+
readonly psl: {
|
|
38
|
+
readonly path: string;
|
|
39
|
+
};
|
|
40
|
+
readonly meta: InspectLiveSchemaResult['meta'];
|
|
41
|
+
readonly timings: {
|
|
42
|
+
readonly total: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function executeContractInferCommand(
|
|
47
|
+
options: ContractInferOptions,
|
|
48
|
+
ui: TerminalUI,
|
|
49
|
+
startTime: number,
|
|
50
|
+
): Promise<Result<ContractInferSuccessResult, CliStructuredError>> {
|
|
51
|
+
const flags = parseGlobalFlags(options);
|
|
52
|
+
const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {
|
|
53
|
+
commandName: 'contract infer',
|
|
54
|
+
description: 'Infer a PSL contract from the live database schema',
|
|
55
|
+
url: 'https://pris.ly/contract-infer',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!inspectResult.ok) {
|
|
59
|
+
return inspectResult;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { config, target, meta } = inspectResult.value;
|
|
63
|
+
|
|
64
|
+
if (target.familyId !== 'sql') {
|
|
65
|
+
return notOk(
|
|
66
|
+
errorRuntime(`contract infer is not supported for family "${target.familyId}"`, {
|
|
67
|
+
why: 'contract infer currently supports SQL targets only',
|
|
68
|
+
fix: 'Use an SQL target (e.g. Postgres) with this command',
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const schema = validatePrintableSqlSchemaIR(inspectResult.value.schema);
|
|
74
|
+
const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
|
|
75
|
+
const enumInfo = extractEnumInfo(schema.annotations);
|
|
76
|
+
const pslContent = printPsl(schema, {
|
|
77
|
+
defaultMapping: createPostgresDefaultMapping(),
|
|
78
|
+
typeMap: createPostgresTypeMap(enumInfo.typeNames),
|
|
79
|
+
enumInfo,
|
|
80
|
+
parseRawDefault,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (existsSync(outputPath) && !flags.json && !flags.quiet) {
|
|
84
|
+
ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
88
|
+
writeFileSync(outputPath, pslContent, 'utf-8');
|
|
89
|
+
|
|
90
|
+
const pslPath = relative(process.cwd(), outputPath);
|
|
91
|
+
if (!flags.json && !flags.quiet) {
|
|
92
|
+
ui.stderr(`\u2714 Contract written to ${pslPath}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return ok({
|
|
96
|
+
ok: true,
|
|
97
|
+
summary: 'Contract inferred successfully',
|
|
98
|
+
target,
|
|
99
|
+
psl: {
|
|
100
|
+
path: pslPath,
|
|
101
|
+
},
|
|
102
|
+
meta,
|
|
103
|
+
timings: {
|
|
104
|
+
total: Date.now() - startTime,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createContractInferCommand(): Command {
|
|
110
|
+
const command = new Command('infer');
|
|
111
|
+
setCommandDescriptions(
|
|
112
|
+
command,
|
|
113
|
+
'Infer a PSL contract from the live database schema',
|
|
114
|
+
'Reads the live database schema and writes an inferred PSL contract to disk.\n' +
|
|
115
|
+
'This command stops at `contract.prisma`; follow it with `contract emit` and\n' +
|
|
116
|
+
'`db sign` as separate steps.',
|
|
117
|
+
);
|
|
118
|
+
setCommandExamples(command, [
|
|
119
|
+
'prisma-next contract infer --db $DATABASE_URL',
|
|
120
|
+
'prisma-next contract infer --db $DATABASE_URL --output ./prisma/contract.prisma',
|
|
121
|
+
'prisma-next contract infer --db $DATABASE_URL --json',
|
|
122
|
+
]);
|
|
123
|
+
addGlobalOptions(command)
|
|
124
|
+
.option('--db <url>', 'Database connection string')
|
|
125
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
126
|
+
.option('--output <path>', 'Write the inferred PSL contract to the specified path')
|
|
127
|
+
.action(async (options: ContractInferOptions) => {
|
|
128
|
+
const flags = parseGlobalFlags(options);
|
|
129
|
+
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
|
|
132
|
+
const result = await executeContractInferCommand(options, ui, startTime);
|
|
133
|
+
const exitCode = handleResult(result, flags, ui, (value) => {
|
|
134
|
+
if (flags.json) {
|
|
135
|
+
ui.output(JSON.stringify(value, null, 2));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
process.exit(exitCode);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return command;
|
|
143
|
+
}
|
package/src/commands/db-init.ts
CHANGED
|
@@ -1,50 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { relative, resolve } from 'node:path';
|
|
1
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
2
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
4
3
|
import { Command } from 'commander';
|
|
5
|
-
import {
|
|
6
|
-
import { createControlClient } from '../control-api/client';
|
|
4
|
+
import { ContractValidationError } from '../control-api/errors';
|
|
7
5
|
import type { DbInitFailure } from '../control-api/types';
|
|
8
6
|
import {
|
|
9
7
|
CliStructuredError,
|
|
10
8
|
errorContractValidationFailed,
|
|
11
|
-
errorDatabaseConnectionRequired,
|
|
12
|
-
errorDriverRequired,
|
|
13
|
-
errorFileNotFound,
|
|
14
|
-
errorJsonFormatNotSupported,
|
|
15
9
|
errorMigrationPlanningFailed,
|
|
10
|
+
errorRunnerFailed,
|
|
16
11
|
errorRuntime,
|
|
17
|
-
errorTargetMigrationNotSupported,
|
|
18
12
|
errorUnexpected,
|
|
19
13
|
} from '../utils/cli-errors';
|
|
20
|
-
import {
|
|
14
|
+
import type { MigrationCommandOptions } from '../utils/command-helpers';
|
|
15
|
+
import {
|
|
16
|
+
sanitizeErrorMessage,
|
|
17
|
+
setCommandDescriptions,
|
|
18
|
+
setCommandExamples,
|
|
19
|
+
} from '../utils/command-helpers';
|
|
20
|
+
import {
|
|
21
|
+
formatMigrationApplyOutput,
|
|
22
|
+
formatMigrationJson,
|
|
23
|
+
formatMigrationPlanOutput,
|
|
24
|
+
type MigrationCommandResult,
|
|
25
|
+
} from '../utils/formatters/migrations';
|
|
21
26
|
import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
|
|
22
27
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
formatDbInitJson,
|
|
27
|
-
formatDbInitPlanOutput,
|
|
28
|
-
formatStyledHeader,
|
|
29
|
-
} from '../utils/output';
|
|
30
|
-
import { createProgressAdapter } from '../utils/progress-adapter';
|
|
28
|
+
addMigrationCommandOptions,
|
|
29
|
+
prepareMigrationContext,
|
|
30
|
+
} from '../utils/migration-command-scaffold';
|
|
31
31
|
import { handleResult } from '../utils/result-handler';
|
|
32
|
+
import { TerminalUI } from '../utils/terminal-ui';
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
readonly db?: string;
|
|
35
|
-
readonly config?: string;
|
|
36
|
-
readonly plan?: boolean;
|
|
37
|
-
readonly json?: string | boolean;
|
|
38
|
-
readonly quiet?: boolean;
|
|
39
|
-
readonly q?: boolean;
|
|
40
|
-
readonly verbose?: boolean;
|
|
41
|
-
readonly v?: boolean;
|
|
42
|
-
readonly vv?: boolean;
|
|
43
|
-
readonly trace?: boolean;
|
|
44
|
-
readonly timestamps?: boolean;
|
|
45
|
-
readonly color?: boolean;
|
|
46
|
-
readonly 'no-color'?: boolean;
|
|
47
|
-
}
|
|
34
|
+
type DbInitOptions = MigrationCommandOptions;
|
|
48
35
|
|
|
49
36
|
/**
|
|
50
37
|
* Maps a DbInitFailure to a CliStructuredError for consistent error handling.
|
|
@@ -57,12 +44,12 @@ function mapDbInitFailure(failure: DbInitFailure): CliStructuredError {
|
|
|
57
44
|
if (failure.code === 'MARKER_ORIGIN_MISMATCH') {
|
|
58
45
|
const mismatchParts: string[] = [];
|
|
59
46
|
if (
|
|
60
|
-
failure.marker?.
|
|
61
|
-
failure.marker?.
|
|
62
|
-
failure.destination?.
|
|
47
|
+
failure.marker?.storageHash !== failure.destination?.storageHash &&
|
|
48
|
+
failure.marker?.storageHash &&
|
|
49
|
+
failure.destination?.storageHash
|
|
63
50
|
) {
|
|
64
51
|
mismatchParts.push(
|
|
65
|
-
`
|
|
52
|
+
`storageHash (marker: ${failure.marker.storageHash}, destination: ${failure.destination.storageHash})`,
|
|
66
53
|
);
|
|
67
54
|
}
|
|
68
55
|
if (
|
|
@@ -76,33 +63,28 @@ function mapDbInitFailure(failure: DbInitFailure): CliStructuredError {
|
|
|
76
63
|
}
|
|
77
64
|
|
|
78
65
|
return errorRuntime(
|
|
79
|
-
`Existing
|
|
66
|
+
`Existing database signature does not match plan destination.${mismatchParts.length > 0 ? ` Mismatch in ${mismatchParts.join(' and ')}.` : ''}`,
|
|
80
67
|
{
|
|
81
|
-
why: 'Database has an existing
|
|
68
|
+
why: 'Database has an existing signature (marker) that does not match the target contract',
|
|
82
69
|
fix: 'If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow',
|
|
83
70
|
meta: {
|
|
84
71
|
code: 'MARKER_ORIGIN_MISMATCH',
|
|
85
|
-
...(failure.marker?.
|
|
86
|
-
...(failure.destination?.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
...(failure.marker?.profileHash ? { markerProfileHash: failure.marker.profileHash } : {}),
|
|
90
|
-
...(failure.destination?.profileHash
|
|
91
|
-
? { destinationProfileHash: failure.destination.profileHash }
|
|
92
|
-
: {}),
|
|
72
|
+
...ifDefined('markerStorageHash', failure.marker?.storageHash),
|
|
73
|
+
...ifDefined('destinationStorageHash', failure.destination?.storageHash),
|
|
74
|
+
...ifDefined('markerProfileHash', failure.marker?.profileHash),
|
|
75
|
+
...ifDefined('destinationProfileHash', failure.destination?.profileHash),
|
|
93
76
|
},
|
|
94
77
|
},
|
|
95
78
|
);
|
|
96
79
|
}
|
|
97
80
|
|
|
98
81
|
if (failure.code === 'RUNNER_FAILED') {
|
|
99
|
-
return
|
|
82
|
+
return errorRunnerFailed(failure.summary, {
|
|
100
83
|
why: failure.why ?? 'Migration runner failed',
|
|
101
84
|
fix: 'Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`',
|
|
102
|
-
meta
|
|
103
|
-
code: 'RUNNER_FAILED',
|
|
104
|
-
|
|
105
|
-
},
|
|
85
|
+
...(failure.meta
|
|
86
|
+
? { meta: { code: 'RUNNER_FAILED', ...failure.meta } }
|
|
87
|
+
: { meta: { code: 'RUNNER_FAILED' } }),
|
|
106
88
|
});
|
|
107
89
|
}
|
|
108
90
|
|
|
@@ -117,114 +99,25 @@ function mapDbInitFailure(failure: DbInitFailure): CliStructuredError {
|
|
|
117
99
|
async function executeDbInitCommand(
|
|
118
100
|
options: DbInitOptions,
|
|
119
101
|
flags: GlobalFlags,
|
|
102
|
+
ui: TerminalUI,
|
|
120
103
|
startTime: number,
|
|
121
|
-
): Promise<Result<
|
|
122
|
-
//
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
: '
|
|
127
|
-
const contractPathAbsolute = config.contract?.output
|
|
128
|
-
? resolve(config.contract.output)
|
|
129
|
-
: resolve('src/prisma/contract.json');
|
|
130
|
-
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
131
|
-
|
|
132
|
-
// Output header
|
|
133
|
-
if (flags.json !== 'object' && !flags.quiet) {
|
|
134
|
-
const details: Array<{ label: string; value: string }> = [
|
|
135
|
-
{ label: 'config', value: configPath },
|
|
136
|
-
{ label: 'contract', value: contractPath },
|
|
137
|
-
];
|
|
138
|
-
if (options.db) {
|
|
139
|
-
details.push({ label: 'database', value: options.db });
|
|
140
|
-
}
|
|
141
|
-
if (options.plan) {
|
|
142
|
-
details.push({ label: 'mode', value: 'plan (dry run)' });
|
|
143
|
-
}
|
|
144
|
-
const header = formatStyledHeader({
|
|
145
|
-
command: 'db init',
|
|
146
|
-
description: 'Bootstrap a database to match the current contract',
|
|
147
|
-
url: 'https://pris.ly/db-init',
|
|
148
|
-
details,
|
|
149
|
-
flags,
|
|
150
|
-
});
|
|
151
|
-
console.log(header);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Load contract file
|
|
155
|
-
let contractJsonContent: string;
|
|
156
|
-
try {
|
|
157
|
-
contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
160
|
-
return notOk(
|
|
161
|
-
errorFileNotFound(contractPathAbsolute, {
|
|
162
|
-
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
163
|
-
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`,
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
return notOk(
|
|
168
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
169
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
170
|
-
}),
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
let contractJson: Record<string, unknown>;
|
|
175
|
-
try {
|
|
176
|
-
contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
return notOk(
|
|
179
|
-
errorContractValidationFailed(
|
|
180
|
-
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
181
|
-
{ where: { path: contractPathAbsolute } },
|
|
182
|
-
),
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Resolve database connection (--db flag or config.db.connection)
|
|
187
|
-
const dbConnection = options.db ?? config.db?.connection;
|
|
188
|
-
if (!dbConnection) {
|
|
189
|
-
return notOk(
|
|
190
|
-
errorDatabaseConnectionRequired({
|
|
191
|
-
why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
192
|
-
}),
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check for driver
|
|
197
|
-
if (!config.driver) {
|
|
198
|
-
return notOk(errorDriverRequired({ why: 'Config.driver is required for db init' }));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check target supports migrations via the migrations capability
|
|
202
|
-
if (!config.target.migrations) {
|
|
203
|
-
return notOk(
|
|
204
|
-
errorTargetMigrationNotSupported({
|
|
205
|
-
why: `Target "${config.target.id}" does not support migrations`,
|
|
206
|
-
}),
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Create control client
|
|
211
|
-
const client = createControlClient({
|
|
212
|
-
family: config.family,
|
|
213
|
-
target: config.target,
|
|
214
|
-
adapter: config.adapter,
|
|
215
|
-
driver: config.driver,
|
|
216
|
-
extensionPacks: config.extensionPacks ?? [],
|
|
104
|
+
): Promise<Result<MigrationCommandResult, CliStructuredError>> {
|
|
105
|
+
// Prepare shared migration context (config, contract, connection, client)
|
|
106
|
+
const ctxResult = await prepareMigrationContext(options, flags, ui, {
|
|
107
|
+
commandName: 'db init',
|
|
108
|
+
description: 'Bootstrap a database to match the current contract',
|
|
109
|
+
url: 'https://pris.ly/db-init',
|
|
217
110
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
111
|
+
if (!ctxResult.ok) {
|
|
112
|
+
return ctxResult;
|
|
113
|
+
}
|
|
114
|
+
const { client, contractJson, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
|
|
221
115
|
|
|
222
116
|
try {
|
|
223
117
|
// Call dbInit with connection and progress callback
|
|
224
|
-
// Connection happens inside dbInit with a 'connect' progress span
|
|
225
118
|
const result = await client.dbInit({
|
|
226
|
-
|
|
227
|
-
mode: options.
|
|
119
|
+
contract: contractJson,
|
|
120
|
+
mode: options.dryRun ? 'plan' : 'apply',
|
|
228
121
|
connection: dbConnection,
|
|
229
122
|
onProgress,
|
|
230
123
|
});
|
|
@@ -235,15 +128,14 @@ async function executeDbInitCommand(
|
|
|
235
128
|
}
|
|
236
129
|
|
|
237
130
|
// Convert success result to CLI output format
|
|
238
|
-
const
|
|
239
|
-
const dbInitResult: DbInitResult = {
|
|
131
|
+
const dbInitResult: MigrationCommandResult = {
|
|
240
132
|
ok: true,
|
|
241
133
|
mode: result.value.mode,
|
|
242
134
|
plan: {
|
|
243
|
-
targetId: config.target.targetId,
|
|
135
|
+
targetId: ctxResult.value.config.target.targetId,
|
|
244
136
|
destination: {
|
|
245
|
-
|
|
246
|
-
...(profileHash
|
|
137
|
+
storageHash: result.value.destination.storageHash,
|
|
138
|
+
...ifDefined('profileHash', result.value.destination.profileHash),
|
|
247
139
|
},
|
|
248
140
|
operations: result.value.plan.operations.map((op) => ({
|
|
249
141
|
id: op.id,
|
|
@@ -262,10 +154,8 @@ async function executeDbInitCommand(
|
|
|
262
154
|
...(result.value.marker
|
|
263
155
|
? {
|
|
264
156
|
marker: {
|
|
265
|
-
|
|
266
|
-
...(result.value.marker.profileHash
|
|
267
|
-
? { profileHash: result.value.marker.profileHash }
|
|
268
|
-
: {}),
|
|
157
|
+
storageHash: result.value.marker.storageHash,
|
|
158
|
+
...ifDefined('profileHash', result.value.marker.profileHash),
|
|
269
159
|
},
|
|
270
160
|
}
|
|
271
161
|
: {}),
|
|
@@ -276,15 +166,26 @@ async function executeDbInitCommand(
|
|
|
276
166
|
return ok(dbInitResult);
|
|
277
167
|
} catch (error) {
|
|
278
168
|
// Driver already throws CliStructuredError for connection failures
|
|
279
|
-
// Use static type guard to work across module boundaries
|
|
280
169
|
if (CliStructuredError.is(error)) {
|
|
281
170
|
return notOk(error);
|
|
282
171
|
}
|
|
283
172
|
|
|
284
|
-
|
|
173
|
+
if (error instanceof ContractValidationError) {
|
|
174
|
+
return notOk(
|
|
175
|
+
errorContractValidationFailed(`Contract validation failed: ${error.message}`, {
|
|
176
|
+
where: { path: contractPathAbsolute },
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
182
|
+
const safeMessage = sanitizeErrorMessage(
|
|
183
|
+
rawMessage,
|
|
184
|
+
typeof dbConnection === 'string' ? dbConnection : undefined,
|
|
185
|
+
);
|
|
285
186
|
return notOk(
|
|
286
|
-
errorUnexpected(
|
|
287
|
-
why: `Unexpected error during db init: ${
|
|
187
|
+
errorUnexpected(safeMessage, {
|
|
188
|
+
why: `Unexpected error during db init: ${safeMessage}`,
|
|
288
189
|
}),
|
|
289
190
|
);
|
|
290
191
|
} finally {
|
|
@@ -296,65 +197,42 @@ export function createDbInitCommand(): Command {
|
|
|
296
197
|
const command = new Command('init');
|
|
297
198
|
setCommandDescriptions(
|
|
298
199
|
command,
|
|
299
|
-
'Bootstrap a database to match the current contract and
|
|
200
|
+
'Bootstrap a database to match the current contract and sign it',
|
|
300
201
|
'Initializes a database to match your emitted contract using additive-only operations.\n' +
|
|
301
202
|
'Creates any missing tables, columns, indexes, and constraints defined in your contract.\n' +
|
|
302
203
|
'Leaves existing compatible structures in place, surfaces conflicts when destructive changes\n' +
|
|
303
|
-
'would be required, and
|
|
204
|
+
'would be required, and signs the database to track contract state. Use --dry-run to\n' +
|
|
304
205
|
'preview changes without applying.',
|
|
305
206
|
);
|
|
306
|
-
command
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const result = notOk(
|
|
330
|
-
errorJsonFormatNotSupported({
|
|
331
|
-
command: 'db init',
|
|
332
|
-
format: 'ndjson',
|
|
333
|
-
supportedFormats: ['object'],
|
|
334
|
-
}),
|
|
335
|
-
);
|
|
336
|
-
const exitCode = handleResult(result, flags);
|
|
337
|
-
process.exit(exitCode);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const result = await executeDbInitCommand(options, flags, startTime);
|
|
341
|
-
|
|
342
|
-
const exitCode = handleResult(result, flags, (dbInitResult) => {
|
|
343
|
-
if (flags.json === 'object') {
|
|
344
|
-
console.log(formatDbInitJson(dbInitResult));
|
|
345
|
-
} else {
|
|
346
|
-
const output =
|
|
347
|
-
dbInitResult.mode === 'plan'
|
|
348
|
-
? formatDbInitPlanOutput(dbInitResult, flags)
|
|
349
|
-
: formatDbInitApplyOutput(dbInitResult, flags);
|
|
350
|
-
if (output) {
|
|
351
|
-
console.log(output);
|
|
352
|
-
}
|
|
207
|
+
setCommandExamples(command, [
|
|
208
|
+
'prisma-next db init --db $DATABASE_URL',
|
|
209
|
+
'prisma-next db init --db $DATABASE_URL --dry-run',
|
|
210
|
+
]);
|
|
211
|
+
addMigrationCommandOptions(command);
|
|
212
|
+
command.action(async (options: DbInitOptions) => {
|
|
213
|
+
const flags = parseGlobalFlags(options);
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
|
|
216
|
+
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
217
|
+
|
|
218
|
+
const result = await executeDbInitCommand(options, flags, ui, startTime);
|
|
219
|
+
|
|
220
|
+
const exitCode = handleResult(result, flags, ui, (dbInitResult) => {
|
|
221
|
+
if (flags.json) {
|
|
222
|
+
ui.output(formatMigrationJson(dbInitResult));
|
|
223
|
+
} else {
|
|
224
|
+
const output =
|
|
225
|
+
dbInitResult.mode === 'plan'
|
|
226
|
+
? formatMigrationPlanOutput(dbInitResult, flags)
|
|
227
|
+
: formatMigrationApplyOutput(dbInitResult, flags);
|
|
228
|
+
if (output) {
|
|
229
|
+
ui.log(output);
|
|
353
230
|
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
process.exit(exitCode);
|
|
231
|
+
}
|
|
357
232
|
});
|
|
358
233
|
|
|
234
|
+
process.exit(exitCode);
|
|
235
|
+
});
|
|
236
|
+
|
|
359
237
|
return command;
|
|
360
238
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { IntrospectSchemaResult } from '@prisma-next/framework-components/control';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import {
|
|
4
|
+
addGlobalOptions,
|
|
5
|
+
setCommandDescriptions,
|
|
6
|
+
setCommandExamples,
|
|
7
|
+
} from '../utils/command-helpers';
|
|
8
|
+
import { formatIntrospectJson, formatIntrospectOutput } from '../utils/formatters/verify';
|
|
9
|
+
import { parseGlobalFlags } from '../utils/global-flags';
|
|
10
|
+
import { handleResult } from '../utils/result-handler';
|
|
11
|
+
import { TerminalUI } from '../utils/terminal-ui';
|
|
12
|
+
import {
|
|
13
|
+
type InspectLiveSchemaOptions,
|
|
14
|
+
type InspectLiveSchemaResult,
|
|
15
|
+
inspectLiveSchema,
|
|
16
|
+
} from './inspect-live-schema';
|
|
17
|
+
|
|
18
|
+
function toIntrospectSchemaResult(
|
|
19
|
+
result: InspectLiveSchemaResult,
|
|
20
|
+
): IntrospectSchemaResult<unknown> {
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
summary: 'Schema read successfully',
|
|
24
|
+
target: result.target,
|
|
25
|
+
schema: result.schema,
|
|
26
|
+
meta: result.meta,
|
|
27
|
+
timings: result.timings,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createDbSchemaCommand(): Command {
|
|
32
|
+
const command = new Command('schema');
|
|
33
|
+
setCommandDescriptions(
|
|
34
|
+
command,
|
|
35
|
+
'Inspect the live database schema',
|
|
36
|
+
'Reads the live database schema and prints it as a tree by default or as JSON with\n' +
|
|
37
|
+
'--json. This command is always read-only and never writes files. To save machine-\n' +
|
|
38
|
+
'readable output, use shell redirection, for example `prisma-next db schema --json > schema.json`.',
|
|
39
|
+
);
|
|
40
|
+
setCommandExamples(command, [
|
|
41
|
+
'prisma-next db schema --db $DATABASE_URL',
|
|
42
|
+
'prisma-next db schema --db $DATABASE_URL --json',
|
|
43
|
+
'prisma-next db schema --db $DATABASE_URL --json > schema.json',
|
|
44
|
+
]);
|
|
45
|
+
addGlobalOptions(command)
|
|
46
|
+
.option('--db <url>', 'Database connection string')
|
|
47
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
48
|
+
.action(async (options: InspectLiveSchemaOptions) => {
|
|
49
|
+
const flags = parseGlobalFlags(options);
|
|
50
|
+
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
|
|
53
|
+
const result = await inspectLiveSchema(options, flags, ui, startTime, {
|
|
54
|
+
commandName: 'db schema',
|
|
55
|
+
description: 'Inspect the live database schema',
|
|
56
|
+
url: 'https://pris.ly/db-schema',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const exitCode = handleResult(result, flags, ui, (value) => {
|
|
60
|
+
const introspectResult = toIntrospectSchemaResult(value);
|
|
61
|
+
|
|
62
|
+
if (flags.json) {
|
|
63
|
+
ui.output(formatIntrospectJson(introspectResult));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const output = formatIntrospectOutput(introspectResult, value.schemaView, flags);
|
|
68
|
+
if (output) {
|
|
69
|
+
ui.log(output);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
process.exit(exitCode);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return command;
|
|
77
|
+
}
|