@prisma-next/cli 0.3.0-dev.3 → 0.3.0-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/commands/contract-emit.d.ts +2 -4
- package/dist/commands/contract-emit.d.ts.map +1 -0
- package/dist/commands/db-init.d.ts +2 -4
- package/dist/commands/db-init.d.ts.map +1 -0
- package/dist/commands/db-introspect.d.ts +2 -4
- package/dist/commands/db-introspect.d.ts.map +1 -0
- package/dist/commands/db-schema-verify.d.ts +2 -4
- package/dist/commands/db-schema-verify.d.ts.map +1 -0
- package/dist/commands/db-sign.d.ts +2 -4
- package/dist/commands/db-sign.d.ts.map +1 -0
- package/dist/commands/db-verify.d.ts +2 -4
- package/dist/commands/db-verify.d.ts.map +1 -0
- package/dist/config-loader.d.ts +3 -5
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/exports/config-types.d.ts +3 -0
- package/dist/exports/config-types.d.ts.map +1 -0
- package/dist/exports/config-types.js.map +1 -0
- package/dist/exports/index.d.ts +4 -0
- package/dist/exports/index.d.ts.map +1 -0
- package/dist/{index.js → exports/index.js} +3 -3
- package/dist/exports/index.js.map +1 -0
- package/dist/{index.d.ts → load-ts-contract.d.ts} +4 -8
- package/dist/load-ts-contract.d.ts.map +1 -0
- package/dist/utils/action.d.ts +16 -0
- package/dist/utils/action.d.ts.map +1 -0
- package/dist/utils/cli-errors.d.ts +7 -0
- package/dist/utils/cli-errors.d.ts.map +1 -0
- package/dist/utils/command-helpers.d.ts +12 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/framework-components.d.ts +81 -0
- package/dist/utils/framework-components.d.ts.map +1 -0
- package/dist/utils/global-flags.d.ts +25 -0
- package/dist/utils/global-flags.d.ts.map +1 -0
- package/dist/utils/output.d.ts +142 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/result-handler.d.ts +15 -0
- package/dist/utils/result-handler.d.ts.map +1 -0
- package/dist/utils/spinner.d.ts +29 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/package.json +21 -21
- package/src/cli.ts +260 -0
- package/src/commands/contract-emit.ts +189 -0
- package/src/commands/db-init.ts +456 -0
- package/src/commands/db-introspect.ts +260 -0
- package/src/commands/db-schema-verify.ts +236 -0
- package/src/commands/db-sign.ts +277 -0
- package/src/commands/db-verify.ts +233 -0
- package/src/config-loader.ts +76 -0
- package/src/exports/config-types.ts +6 -0
- package/src/exports/index.ts +4 -0
- package/src/load-ts-contract.ts +217 -0
- package/src/utils/action.ts +43 -0
- package/src/utils/cli-errors.ts +26 -0
- package/src/utils/command-helpers.ts +26 -0
- package/src/utils/framework-components.ts +196 -0
- package/src/utils/global-flags.ts +75 -0
- package/src/utils/output.ts +1471 -0
- package/src/utils/result-handler.ts +44 -0
- package/src/utils/spinner.ts +67 -0
- package/dist/config-types.d.ts +0 -1
- package/dist/config-types.js.map +0 -1
- package/dist/index.js.map +0 -1
- /package/dist/{config-types.js → exports/config-types.js} +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/cli",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
7
|
-
"dist"
|
|
7
|
+
"dist",
|
|
8
|
+
"src"
|
|
8
9
|
],
|
|
9
10
|
"bin": {
|
|
10
11
|
"prisma-next": "./dist/cli.js"
|
|
@@ -19,33 +20,32 @@
|
|
|
19
20
|
"string-width": "^7.2.0",
|
|
20
21
|
"strip-ansi": "^7.1.2",
|
|
21
22
|
"wrap-ansi": "^9.0.2",
|
|
22
|
-
"@prisma-next/contract": "0.3.0-dev.
|
|
23
|
-
"@prisma-next/
|
|
24
|
-
"@prisma-next/
|
|
25
|
-
"@prisma-next/
|
|
23
|
+
"@prisma-next/contract": "0.3.0-dev.5",
|
|
24
|
+
"@prisma-next/emitter": "0.3.0-dev.5",
|
|
25
|
+
"@prisma-next/utils": "0.3.0-dev.5",
|
|
26
|
+
"@prisma-next/core-control-plane": "0.3.0-dev.5"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/node": "24.10.4",
|
|
29
|
-
"@vitest/coverage-v8": "
|
|
30
|
-
"tsup": "
|
|
31
|
-
"typescript": "
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"@prisma-next/sql-contract": "0.3.0-dev.
|
|
35
|
-
"@prisma-next/sql-contract-
|
|
36
|
-
"@prisma-next/sql-
|
|
37
|
-
"@prisma-next/sql-
|
|
38
|
-
"@prisma-next/sql-runtime": "0.3.0-dev.3",
|
|
30
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
31
|
+
"tsup": "8.5.1",
|
|
32
|
+
"typescript": "5.9.3",
|
|
33
|
+
"vitest": "4.0.16",
|
|
34
|
+
"@prisma-next/sql-contract": "0.3.0-dev.5",
|
|
35
|
+
"@prisma-next/sql-contract-emitter": "0.3.0-dev.5",
|
|
36
|
+
"@prisma-next/sql-contract-ts": "0.3.0-dev.5",
|
|
37
|
+
"@prisma-next/sql-operations": "0.3.0-dev.5",
|
|
38
|
+
"@prisma-next/sql-runtime": "0.3.0-dev.5",
|
|
39
39
|
"@prisma-next/test-utils": "0.0.1"
|
|
40
40
|
},
|
|
41
41
|
"exports": {
|
|
42
42
|
".": {
|
|
43
|
-
"types": "./dist/index.d.ts",
|
|
44
|
-
"import": "./dist/index.js"
|
|
43
|
+
"types": "./dist/exports/index.d.ts",
|
|
44
|
+
"import": "./dist/exports/index.js"
|
|
45
45
|
},
|
|
46
46
|
"./config-types": {
|
|
47
|
-
"types": "./dist/config-types.d.ts",
|
|
48
|
-
"import": "./dist/config-types.js"
|
|
47
|
+
"types": "./dist/exports/config-types.d.ts",
|
|
48
|
+
"import": "./dist/exports/config-types.js"
|
|
49
49
|
},
|
|
50
50
|
"./commands/db-init": {
|
|
51
51
|
"types": "./dist/commands/db-init.d.ts",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
-
"build": "tsup --config tsup.config.ts",
|
|
80
|
+
"build": "tsup --config tsup.config.ts && tsc --project tsconfig.build.json",
|
|
81
81
|
"test": "vitest run",
|
|
82
82
|
"test:coverage": "vitest run --coverage",
|
|
83
83
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createContractEmitCommand } from './commands/contract-emit';
|
|
3
|
+
import { createDbInitCommand } from './commands/db-init';
|
|
4
|
+
import { createDbIntrospectCommand } from './commands/db-introspect';
|
|
5
|
+
import { createDbSchemaVerifyCommand } from './commands/db-schema-verify';
|
|
6
|
+
import { createDbSignCommand } from './commands/db-sign';
|
|
7
|
+
import { createDbVerifyCommand } from './commands/db-verify';
|
|
8
|
+
import { setCommandDescriptions } from './utils/command-helpers';
|
|
9
|
+
import { parseGlobalFlags } from './utils/global-flags';
|
|
10
|
+
import { formatCommandHelp, formatRootHelp } from './utils/output';
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program.name('prisma-next').description('Prisma Next CLI').version('0.0.1');
|
|
15
|
+
|
|
16
|
+
// Override version option description to match capitalization style
|
|
17
|
+
const versionOption = program.options.find((opt) => opt.flags.includes('--version'));
|
|
18
|
+
if (versionOption) {
|
|
19
|
+
versionOption.description = 'Output the version number';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
program.configureOutput({
|
|
23
|
+
writeErr: () => {
|
|
24
|
+
// Suppress all default error output - we handle errors in exitOverride
|
|
25
|
+
},
|
|
26
|
+
writeOut: () => {
|
|
27
|
+
// Suppress all default output - our custom formatters handle everything
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Customize root help output to use our styled format
|
|
32
|
+
const rootHelpFormatter = (cmd: Command) => {
|
|
33
|
+
const flags = parseGlobalFlags({});
|
|
34
|
+
return formatRootHelp({ program: cmd, flags });
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
program.configureHelp({
|
|
38
|
+
formatHelp: rootHelpFormatter,
|
|
39
|
+
subcommandDescription: () => '',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Override exit to handle unhandled errors (fail fast cases)
|
|
43
|
+
// Commands handle structured errors themselves via process.exit()
|
|
44
|
+
program.exitOverride((err) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
// Help requests are not errors - allow Commander to output help and exit normally
|
|
47
|
+
// Commander throws errors with codes like 'commander.help', 'commander.helpDisplayed', or 'outputHelp'
|
|
48
|
+
const errorCode = (err as { code?: string }).code;
|
|
49
|
+
const errorMessage = String(err.message ?? '');
|
|
50
|
+
const errorName = err.name ?? '';
|
|
51
|
+
|
|
52
|
+
// Check for unknown command errors first (before other checks)
|
|
53
|
+
// Commander.js uses code 'commander.unknownCommand' or error message contains 'unknown command'
|
|
54
|
+
const isUnknownCommandError =
|
|
55
|
+
errorCode === 'commander.unknownCommand' ||
|
|
56
|
+
errorCode === 'commander.unknownArgument' ||
|
|
57
|
+
(errorName === 'CommanderError' &&
|
|
58
|
+
(errorMessage.includes('unknown command') || errorMessage.includes('unknown argument')));
|
|
59
|
+
if (isUnknownCommandError) {
|
|
60
|
+
const flags = parseGlobalFlags({});
|
|
61
|
+
// Extract the command/subcommand name from the error message
|
|
62
|
+
// Error message format: "unknown command 'command-name'"
|
|
63
|
+
const match = errorMessage.match(/unknown command ['"]([^'"]+)['"]/);
|
|
64
|
+
const commandName = match ? match[1] : process.argv[3] || process.argv[2] || 'unknown';
|
|
65
|
+
|
|
66
|
+
// Determine which command context we're in
|
|
67
|
+
// Check if the first argument is a recognized parent command
|
|
68
|
+
const firstArg = process.argv[2];
|
|
69
|
+
const parentCommand = firstArg
|
|
70
|
+
? program.commands.find((cmd) => cmd.name() === firstArg)
|
|
71
|
+
: undefined;
|
|
72
|
+
|
|
73
|
+
if (parentCommand && commandName !== firstArg) {
|
|
74
|
+
// Unrecognized subcommand - show parent command help
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.error(`Unknown command: ${commandName}`);
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error('');
|
|
79
|
+
const helpText = formatCommandHelp({ command: parentCommand, flags });
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.log(helpText);
|
|
82
|
+
} else {
|
|
83
|
+
// Unrecognized top-level command - show root help
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error(`Unknown command: ${commandName}`);
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.error('');
|
|
88
|
+
const helpText = formatRootHelp({ program, flags });
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(helpText);
|
|
91
|
+
}
|
|
92
|
+
process.exit(1);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const isHelpError =
|
|
96
|
+
errorCode === 'commander.help' ||
|
|
97
|
+
errorCode === 'commander.helpDisplayed' ||
|
|
98
|
+
errorCode === 'outputHelp' ||
|
|
99
|
+
errorMessage === '(outputHelp)' ||
|
|
100
|
+
errorMessage.includes('outputHelp') ||
|
|
101
|
+
(errorName === 'CommanderError' && errorMessage.includes('outputHelp'));
|
|
102
|
+
if (isHelpError) {
|
|
103
|
+
process.exit(0);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Missing required arguments/subcommands - show help and exit with 0
|
|
107
|
+
// Commander throws errors with code 'commander.missingArgument' or 'commander.missingMandatoryOptionValue'
|
|
108
|
+
// or when a command with subcommands is called without a subcommand
|
|
109
|
+
const isMissingArgumentError =
|
|
110
|
+
errorCode === 'commander.missingArgument' ||
|
|
111
|
+
errorCode === 'commander.missingMandatoryOptionValue' ||
|
|
112
|
+
(errorName === 'CommanderError' &&
|
|
113
|
+
(errorMessage.includes('missing') || errorMessage.includes('required')));
|
|
114
|
+
if (isMissingArgumentError) {
|
|
115
|
+
// Help was already displayed by Commander.js, just exit with 0
|
|
116
|
+
process.exit(0);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Unhandled error - fail fast with exit code 1
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.error(`Unhandled error: ${err.message}`);
|
|
122
|
+
if (err.stack) {
|
|
123
|
+
// eslint-disable-next-line no-console
|
|
124
|
+
console.error(err.stack);
|
|
125
|
+
}
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Register contract subcommand
|
|
132
|
+
const contractCommand = new Command('contract');
|
|
133
|
+
setCommandDescriptions(
|
|
134
|
+
contractCommand,
|
|
135
|
+
'Contract management commands',
|
|
136
|
+
'Define and emit your application data contract. The contract describes your schema as a\n' +
|
|
137
|
+
'declarative data structure that can be signed and verified against your database.',
|
|
138
|
+
);
|
|
139
|
+
contractCommand.configureHelp({
|
|
140
|
+
formatHelp: (cmd) => {
|
|
141
|
+
const flags = parseGlobalFlags({});
|
|
142
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
143
|
+
},
|
|
144
|
+
subcommandDescription: () => '',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Add emit subcommand to contract
|
|
148
|
+
const contractEmitCommand = createContractEmitCommand();
|
|
149
|
+
contractCommand.addCommand(contractEmitCommand);
|
|
150
|
+
|
|
151
|
+
// Register contract command
|
|
152
|
+
program.addCommand(contractCommand);
|
|
153
|
+
|
|
154
|
+
// Register db subcommand
|
|
155
|
+
const dbCommand = new Command('db');
|
|
156
|
+
setCommandDescriptions(
|
|
157
|
+
dbCommand,
|
|
158
|
+
'Database management commands',
|
|
159
|
+
'Verify and sign your database with your contract. Ensure your database schema matches\n' +
|
|
160
|
+
'your contract, and sign it to record the contract hash for future verification.',
|
|
161
|
+
);
|
|
162
|
+
dbCommand.configureHelp({
|
|
163
|
+
formatHelp: (cmd) => {
|
|
164
|
+
const flags = parseGlobalFlags({});
|
|
165
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
166
|
+
},
|
|
167
|
+
subcommandDescription: () => '',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Add verify subcommand to db
|
|
171
|
+
const dbVerifyCommand = createDbVerifyCommand();
|
|
172
|
+
dbCommand.addCommand(dbVerifyCommand);
|
|
173
|
+
|
|
174
|
+
// Add init subcommand to db
|
|
175
|
+
const dbInitCommand = createDbInitCommand();
|
|
176
|
+
dbCommand.addCommand(dbInitCommand);
|
|
177
|
+
|
|
178
|
+
// Add introspect subcommand to db
|
|
179
|
+
const dbIntrospectCommand = createDbIntrospectCommand();
|
|
180
|
+
dbCommand.addCommand(dbIntrospectCommand);
|
|
181
|
+
|
|
182
|
+
// Add schema-verify subcommand to db
|
|
183
|
+
const dbSchemaVerifyCommand = createDbSchemaVerifyCommand();
|
|
184
|
+
dbCommand.addCommand(dbSchemaVerifyCommand);
|
|
185
|
+
|
|
186
|
+
// Add sign subcommand to db
|
|
187
|
+
const dbSignCommand = createDbSignCommand();
|
|
188
|
+
dbCommand.addCommand(dbSignCommand);
|
|
189
|
+
|
|
190
|
+
// Register db command
|
|
191
|
+
program.addCommand(dbCommand);
|
|
192
|
+
|
|
193
|
+
// Create help command
|
|
194
|
+
const helpCommand = new Command('help')
|
|
195
|
+
.description('Show usage instructions')
|
|
196
|
+
.configureHelp({
|
|
197
|
+
formatHelp: (cmd) => {
|
|
198
|
+
const flags = parseGlobalFlags({});
|
|
199
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
.action(() => {
|
|
203
|
+
const flags = parseGlobalFlags({});
|
|
204
|
+
const helpText = formatRootHelp({ program, flags });
|
|
205
|
+
// eslint-disable-next-line no-console
|
|
206
|
+
console.log(helpText);
|
|
207
|
+
process.exit(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
program.addCommand(helpCommand);
|
|
211
|
+
|
|
212
|
+
// Set help as the default action when no command is provided
|
|
213
|
+
program.action(() => {
|
|
214
|
+
const flags = parseGlobalFlags({});
|
|
215
|
+
const helpText = formatRootHelp({ program, flags });
|
|
216
|
+
// eslint-disable-next-line no-console
|
|
217
|
+
console.log(helpText);
|
|
218
|
+
process.exit(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Check if a command was invoked with no arguments (just the command name)
|
|
222
|
+
// or if an unrecognized command was provided
|
|
223
|
+
const args = process.argv.slice(2);
|
|
224
|
+
if (args.length > 0) {
|
|
225
|
+
const commandName = args[0];
|
|
226
|
+
// Handle version option explicitly since we suppress default output
|
|
227
|
+
if (commandName === '--version' || commandName === '-V') {
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.log(program.version());
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
// Skip command check for global options like --help, -h
|
|
233
|
+
const isGlobalOption = commandName === '--help' || commandName === '-h';
|
|
234
|
+
if (!isGlobalOption) {
|
|
235
|
+
// Check if this is a recognized command
|
|
236
|
+
const command = program.commands.find((cmd) => cmd.name() === commandName);
|
|
237
|
+
|
|
238
|
+
if (!command) {
|
|
239
|
+
// Unrecognized command - show error message and usage
|
|
240
|
+
const flags = parseGlobalFlags({});
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.error(`Unknown command: ${commandName}`);
|
|
243
|
+
// eslint-disable-next-line no-console
|
|
244
|
+
console.error('');
|
|
245
|
+
const helpText = formatRootHelp({ program, flags });
|
|
246
|
+
// eslint-disable-next-line no-console
|
|
247
|
+
console.log(helpText);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
} else if (command.commands.length > 0 && args.length === 1) {
|
|
250
|
+
// Parent command called with no subcommand - show help and exit with 0
|
|
251
|
+
const flags = parseGlobalFlags({});
|
|
252
|
+
const helpText = formatCommandHelp({ command, flags });
|
|
253
|
+
// eslint-disable-next-line no-console
|
|
254
|
+
console.log(helpText);
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
program.parse();
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
3
|
+
import { errorContractConfigMissing } from '@prisma-next/core-control-plane/errors';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { loadConfig } from '../config-loader';
|
|
6
|
+
import { performAction } from '../utils/action';
|
|
7
|
+
import { setCommandDescriptions } from '../utils/command-helpers';
|
|
8
|
+
import { parseGlobalFlags } from '../utils/global-flags';
|
|
9
|
+
import {
|
|
10
|
+
formatCommandHelp,
|
|
11
|
+
formatEmitJson,
|
|
12
|
+
formatEmitOutput,
|
|
13
|
+
formatStyledHeader,
|
|
14
|
+
formatSuccessMessage,
|
|
15
|
+
} from '../utils/output';
|
|
16
|
+
import { handleResult } from '../utils/result-handler';
|
|
17
|
+
import { withSpinner } from '../utils/spinner';
|
|
18
|
+
|
|
19
|
+
interface ContractEmitOptions {
|
|
20
|
+
readonly config?: string;
|
|
21
|
+
readonly json?: string | boolean;
|
|
22
|
+
readonly quiet?: boolean;
|
|
23
|
+
readonly q?: boolean;
|
|
24
|
+
readonly verbose?: boolean;
|
|
25
|
+
readonly v?: boolean;
|
|
26
|
+
readonly vv?: boolean;
|
|
27
|
+
readonly trace?: boolean;
|
|
28
|
+
readonly timestamps?: boolean;
|
|
29
|
+
readonly color?: boolean;
|
|
30
|
+
readonly 'no-color'?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createContractEmitCommand(): Command {
|
|
34
|
+
const command = new Command('emit');
|
|
35
|
+
setCommandDescriptions(
|
|
36
|
+
command,
|
|
37
|
+
'Write your contract to JSON and sign it',
|
|
38
|
+
'Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\n' +
|
|
39
|
+
'contract.d.ts. The contract.json contains the canonical contract structure, and\n' +
|
|
40
|
+
'contract.d.ts provides TypeScript types for type-safe query building.',
|
|
41
|
+
);
|
|
42
|
+
command
|
|
43
|
+
.configureHelp({
|
|
44
|
+
formatHelp: (cmd) => {
|
|
45
|
+
const flags = parseGlobalFlags({});
|
|
46
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
50
|
+
.option('--json [format]', 'Output as JSON (object or ndjson)', false)
|
|
51
|
+
.option('-q, --quiet', 'Quiet mode: errors only')
|
|
52
|
+
.option('-v, --verbose', 'Verbose output: debug info, timings')
|
|
53
|
+
.option('-vv, --trace', 'Trace output: deep internals, stack traces')
|
|
54
|
+
.option('--timestamps', 'Add timestamps to output')
|
|
55
|
+
.option('--color', 'Force color output')
|
|
56
|
+
.option('--no-color', 'Disable color output')
|
|
57
|
+
.action(async (options: ContractEmitOptions) => {
|
|
58
|
+
const flags = parseGlobalFlags(options);
|
|
59
|
+
|
|
60
|
+
const result = await performAction(async () => {
|
|
61
|
+
// Load config
|
|
62
|
+
const config = await loadConfig(options.config);
|
|
63
|
+
|
|
64
|
+
// Resolve contract from config
|
|
65
|
+
if (!config.contract) {
|
|
66
|
+
throw errorContractConfigMissing({
|
|
67
|
+
why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Contract config is already normalized by defineConfig() with defaults applied
|
|
72
|
+
const contractConfig = config.contract;
|
|
73
|
+
|
|
74
|
+
// Resolve artifact paths from config (already normalized by defineConfig() with defaults)
|
|
75
|
+
if (!contractConfig.output || !contractConfig.types) {
|
|
76
|
+
throw errorContractConfigMissing({
|
|
77
|
+
why: 'Contract config must have output and types paths. This should not happen if defineConfig() was used.',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const outputJsonPath = resolve(contractConfig.output);
|
|
81
|
+
const outputDtsPath = resolve(contractConfig.types);
|
|
82
|
+
|
|
83
|
+
// Output header (only for human-readable output)
|
|
84
|
+
if (flags.json !== 'object' && !flags.quiet) {
|
|
85
|
+
// Normalize config path for display (match contract path format - no ./ prefix)
|
|
86
|
+
const configPath = options.config
|
|
87
|
+
? relative(process.cwd(), resolve(options.config))
|
|
88
|
+
: 'prisma-next.config.ts';
|
|
89
|
+
// Convert absolute paths to relative paths for display
|
|
90
|
+
const contractPath = relative(process.cwd(), outputJsonPath);
|
|
91
|
+
const typesPath = relative(process.cwd(), outputDtsPath);
|
|
92
|
+
const header = formatStyledHeader({
|
|
93
|
+
command: 'contract emit',
|
|
94
|
+
description: 'Write your contract to JSON and sign it',
|
|
95
|
+
url: 'https://pris.ly/contract-emit',
|
|
96
|
+
details: [
|
|
97
|
+
{ label: 'config', value: configPath },
|
|
98
|
+
{ label: 'contract', value: contractPath },
|
|
99
|
+
{ label: 'types', value: typesPath },
|
|
100
|
+
],
|
|
101
|
+
flags,
|
|
102
|
+
});
|
|
103
|
+
console.log(header);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Create family instance (assembles operation registry, type imports, extension IDs)
|
|
107
|
+
// Note: emit command doesn't need driver, but ControlFamilyDescriptor.create() requires it
|
|
108
|
+
// We'll need to provide a minimal driver descriptor or make driver optional for emit
|
|
109
|
+
// For now, we'll require driver to be present in config even for emit
|
|
110
|
+
if (!config.driver) {
|
|
111
|
+
throw errorContractConfigMissing({
|
|
112
|
+
why: 'Config.driver is required. Even though emit does not use the driver, it is required by ControlFamilyDescriptor.create()',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const familyInstance = config.family.create({
|
|
116
|
+
target: config.target,
|
|
117
|
+
adapter: config.adapter,
|
|
118
|
+
driver: config.driver,
|
|
119
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Resolve contract source from config (user's config handles loading)
|
|
123
|
+
let contractRaw: unknown;
|
|
124
|
+
if (typeof contractConfig.source === 'function') {
|
|
125
|
+
contractRaw = await contractConfig.source();
|
|
126
|
+
} else {
|
|
127
|
+
contractRaw = contractConfig.source;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Call emitContract on family instance (handles stripping mappings and validation internally)
|
|
131
|
+
const emitResult = await withSpinner(
|
|
132
|
+
() => familyInstance.emitContract({ contractIR: contractRaw }),
|
|
133
|
+
{
|
|
134
|
+
message: 'Emitting contract...',
|
|
135
|
+
flags,
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Create directories if needed
|
|
140
|
+
mkdirSync(dirname(outputJsonPath), { recursive: true });
|
|
141
|
+
mkdirSync(dirname(outputDtsPath), { recursive: true });
|
|
142
|
+
|
|
143
|
+
// Write the results to files
|
|
144
|
+
writeFileSync(outputJsonPath, emitResult.contractJson, 'utf-8');
|
|
145
|
+
writeFileSync(outputDtsPath, emitResult.contractDts, 'utf-8');
|
|
146
|
+
|
|
147
|
+
// Add blank line after all async operations if spinners were shown
|
|
148
|
+
if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Return result with file paths for output formatting
|
|
153
|
+
return {
|
|
154
|
+
coreHash: emitResult.coreHash,
|
|
155
|
+
profileHash: emitResult.profileHash,
|
|
156
|
+
outDir: dirname(outputJsonPath),
|
|
157
|
+
files: {
|
|
158
|
+
json: outputJsonPath,
|
|
159
|
+
dts: outputDtsPath,
|
|
160
|
+
},
|
|
161
|
+
timings: {
|
|
162
|
+
total: 0, // Timing is handled by emitContract internally if needed
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Handle result - formats output and returns exit code
|
|
168
|
+
const exitCode = handleResult(result, flags, (emitResult) => {
|
|
169
|
+
// Output based on flags
|
|
170
|
+
if (flags.json === 'object') {
|
|
171
|
+
// JSON output to stdout
|
|
172
|
+
console.log(formatEmitJson(emitResult));
|
|
173
|
+
} else {
|
|
174
|
+
// Human-readable output to stdout
|
|
175
|
+
const output = formatEmitOutput(emitResult, flags);
|
|
176
|
+
if (output) {
|
|
177
|
+
console.log(output);
|
|
178
|
+
}
|
|
179
|
+
// Output success message
|
|
180
|
+
if (!flags.quiet) {
|
|
181
|
+
console.log(formatSuccessMessage(flags));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
process.exit(exitCode);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return command;
|
|
189
|
+
}
|