@prisma-next/cli 0.7.0 → 0.8.0-dev.10
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/README.md +7 -8
- package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
- package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
- package/dist/cli.mjs +70 -21
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-BCnP7cHo.mjs → client-XkUw4xD0.mjs} +17 -13
- package/dist/client-XkUw4xD0.mjs.map +1 -0
- package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
- package/dist/command-helpers-D3vL5yi8.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +7 -7
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +67 -25
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +37 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +28 -0
- package/dist/commands/migrate.d.mts.map +1 -0
- package/dist/commands/{migration-apply.mjs → migrate.mjs} +54 -36
- package/dist/commands/migrate.mjs.map +1 -0
- package/dist/commands/migration-check.d.mts +18 -0
- package/dist/commands/migration-check.d.mts.map +1 -0
- package/dist/commands/migration-check.mjs +284 -0
- package/dist/commands/migration-check.mjs.map +1 -0
- package/dist/commands/migration-graph.d.mts +16 -0
- package/dist/commands/migration-graph.d.mts.map +1 -0
- package/dist/commands/migration-graph.mjs +141 -0
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +20 -0
- package/dist/commands/migration-list.d.mts.map +1 -0
- package/dist/commands/migration-list.mjs +107 -0
- package/dist/commands/migration-list.mjs.map +1 -0
- package/dist/commands/migration-log.d.mts +21 -0
- package/dist/commands/migration-log.d.mts.map +1 -0
- package/dist/commands/migration-log.mjs +146 -0
- package/dist/commands/migration-log.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +21 -20
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +2 -2
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +85 -47
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +3 -15
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +732 -1
- package/dist/commands/migration-status.mjs.map +1 -0
- package/dist/commands/ref.d.mts +34 -0
- package/dist/commands/ref.d.mts.map +1 -0
- package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
- package/dist/commands/ref.mjs.map +1 -0
- package/dist/{contract-emit-B77TsJqf.mjs → contract-emit-CgoFk9AU.mjs} +8 -4
- package/dist/contract-emit-CgoFk9AU.mjs.map +1 -0
- package/dist/{contract-emit-9DBda5Ou.mjs → contract-emit-GpxW5RLe.mjs} +6 -6
- package/dist/{contract-emit-9DBda5Ou.mjs.map → contract-emit-GpxW5RLe.mjs.map} +1 -1
- package/dist/{contract-infer-ByxhPjpW.mjs → contract-infer-D8edZOCi.mjs} +5 -5
- package/dist/{contract-infer-ByxhPjpW.mjs.map → contract-infer-D8edZOCi.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-D68YpuPR.mjs} +3 -3
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-D68YpuPR.mjs.map} +1 -1
- package/dist/{db-verify-Czm5T-J4.mjs → db-verify-DtRB9iHJ.mjs} +7 -7
- package/dist/{db-verify-Czm5T-J4.mjs.map → db-verify-DtRB9iHJ.mjs.map} +1 -1
- package/dist/errors-Cw6kyTyV.mjs +56 -0
- package/dist/errors-Cw6kyTyV.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
- package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
- package/dist/{migration-status-By9G5p2H.mjs → graph-render-eJDcLWny.mjs} +3 -692
- package/dist/graph-render-eJDcLWny.mjs.map +1 -0
- package/dist/{init-BRKnARU6.mjs → init-Dm0QZPUA.mjs} +412 -208
- package/dist/init-Dm0QZPUA.mjs.map +1 -0
- package/dist/{inspect-live-schema-DxdBd4Er.mjs → inspect-live-schema-CPPqCips.mjs} +4 -4
- package/dist/{inspect-live-schema-DxdBd4Er.mjs.map → inspect-live-schema-CPPqCips.mjs.map} +1 -1
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs → migration-command-scaffold-B_ezTTwX.mjs} +4 -4
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs.map → migration-command-scaffold-B_ezTTwX.mjs.map} +1 -1
- package/dist/{migration-plan-mRu5K81L.mjs → migration-plan-DWB-NTxH.mjs} +62 -30
- package/dist/migration-plan-DWB-NTxH.mjs.map +1 -0
- package/dist/migration-types-D2FW63pr.d.mts +15 -0
- package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
- package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
- package/dist/migrations-DyUf5lTt.mjs.map +1 -0
- package/dist/{output-B16Kefzx.mjs → output-B60Gw5fu.mjs} +12 -11
- package/dist/{output-B16Kefzx.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
- package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
- package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
- package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
- package/dist/{types-LItU7E4l.d.mts → types-BS_wpjAY.d.mts} +2 -2
- package/dist/{types-LItU7E4l.d.mts.map → types-BS_wpjAY.d.mts.map} +1 -1
- package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
- package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
- package/package.json +41 -25
- package/src/cli.ts +78 -15
- package/src/commands/db-sign.ts +102 -32
- package/src/commands/db-update.ts +56 -4
- package/src/commands/init/agent-skill-install.ts +232 -0
- package/src/commands/init/errors.ts +32 -0
- package/src/commands/init/exit-codes.ts +10 -0
- package/src/commands/init/index.ts +9 -1
- package/src/commands/init/init.ts +82 -7
- package/src/commands/init/inputs.ts +23 -0
- package/src/commands/init/output.ts +22 -17
- package/src/commands/init/templates/code-templates.ts +4 -1
- package/src/commands/{migration-apply.ts → migrate.ts} +54 -70
- package/src/commands/migration-check/exit-codes.ts +3 -0
- package/src/commands/migration-check.ts +369 -0
- package/src/commands/migration-graph.ts +184 -0
- package/src/commands/migration-list.ts +155 -0
- package/src/commands/migration-log.ts +218 -0
- package/src/commands/migration-new.ts +17 -9
- package/src/commands/migration-plan.ts +77 -27
- package/src/commands/migration-show.ts +132 -60
- package/src/commands/migration-status.ts +77 -64
- package/src/commands/{migration-ref.ts → ref.ts} +32 -86
- package/src/control-api/client.ts +11 -4
- package/src/control-api/operations/apply-aggregate.ts +4 -4
- package/src/control-api/operations/contract-emit.ts +13 -1
- package/src/control-api/operations/db-apply-aggregate.ts +3 -2
- package/src/control-api/operations/db-verify.ts +1 -1
- package/src/control-api/operations/migration-apply.ts +4 -3
- package/src/control-api/types.ts +1 -2
- package/src/migration-cli.ts +1 -1
- package/src/utils/cli-errors.ts +37 -0
- package/src/utils/command-helpers.ts +28 -3
- package/src/utils/contract-space-aggregate-loader.ts +2 -2
- package/src/utils/contract-space-seed-phase.ts +2 -2
- package/src/utils/formatters/help.ts +12 -2
- package/src/utils/formatters/migrations.ts +2 -2
- package/dist/agent-skill-mongo.md +0 -138
- package/dist/agent-skill-postgres.md +0 -106
- package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
- package/dist/client-BCnP7cHo.mjs.map +0 -1
- package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
- package/dist/commands/migration-apply.d.mts +0 -51
- package/dist/commands/migration-apply.d.mts.map +0 -1
- package/dist/commands/migration-apply.mjs.map +0 -1
- package/dist/commands/migration-ref.d.mts +0 -45
- package/dist/commands/migration-ref.d.mts.map +0 -1
- package/dist/commands/migration-ref.mjs.map +0 -1
- package/dist/contract-emit-B77TsJqf.mjs.map +0 -1
- package/dist/init-BRKnARU6.mjs.map +0 -1
- package/dist/migration-plan-mRu5K81L.mjs.map +0 -1
- package/dist/migration-status-By9G5p2H.mjs.map +0 -1
- package/dist/migrations-CTsyBXCA.mjs.map +0 -1
- package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
- package/src/commands/init/templates/agent-skill-mongo.md +0 -138
- package/src/commands/init/templates/agent-skill-postgres.md +0 -106
- package/src/commands/init/templates/agent-skill.ts +0 -41
- /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
package/src/cli.ts
CHANGED
|
@@ -12,17 +12,44 @@ import { createDbSchemaCommand } from './commands/db-schema';
|
|
|
12
12
|
import { createDbSignCommand } from './commands/db-sign';
|
|
13
13
|
import { createDbUpdateCommand } from './commands/db-update';
|
|
14
14
|
import { createDbVerifyCommand } from './commands/db-verify';
|
|
15
|
-
import {
|
|
15
|
+
import { createMigrateCommand } from './commands/migrate';
|
|
16
|
+
import { createMigrationCheckCommand } from './commands/migration-check';
|
|
17
|
+
import { createMigrationGraphCommand } from './commands/migration-graph';
|
|
18
|
+
import { createMigrationListCommand } from './commands/migration-list';
|
|
19
|
+
import { createMigrationLogCommand } from './commands/migration-log';
|
|
16
20
|
import { createMigrationNewCommand } from './commands/migration-new';
|
|
17
21
|
import { createMigrationPlanCommand } from './commands/migration-plan';
|
|
18
|
-
import { createMigrationRefCommand } from './commands/migration-ref';
|
|
19
22
|
import { createMigrationShowCommand } from './commands/migration-show';
|
|
20
23
|
import { createMigrationStatusCommand } from './commands/migration-status';
|
|
24
|
+
import { createRefCommand } from './commands/ref';
|
|
21
25
|
import { setCommandDescriptions } from './utils/command-helpers';
|
|
22
26
|
import { formatCommandHelp, formatRootHelp } from './utils/formatters/help';
|
|
23
27
|
import { parseGlobalFlags } from './utils/global-flags';
|
|
24
28
|
import { suggestCommands } from './utils/suggest-command';
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Lookup table mapping removed subcommands to their replacement verbs.
|
|
32
|
+
* Keyed by `<parent>:<subcommand>` (e.g. `migration:apply`).
|
|
33
|
+
* The handler consults this before falling back to the fuzzy suggest engine.
|
|
34
|
+
*/
|
|
35
|
+
const removedVerbRedirects: Record<string, string> = {
|
|
36
|
+
'migration:apply': 'Use `prisma-next migrate --to <contract>` instead.',
|
|
37
|
+
'migration:ref': 'Use `prisma-next ref set|list|delete` instead.',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Removed flags on specific subcommands. Keyed by `<parent>:<sub>:<flag>`.
|
|
42
|
+
* Checked during the pre-parse argv scan before commander sees the flags.
|
|
43
|
+
*/
|
|
44
|
+
const removedFlagRedirects: Record<string, string> = {
|
|
45
|
+
'migration:status:graph': 'Use `prisma-next migration graph` to view the migration graph.',
|
|
46
|
+
'migration:status:all':
|
|
47
|
+
'Use `prisma-next migration log --db <url>` to view the full execution history.',
|
|
48
|
+
'migration:status:limit':
|
|
49
|
+
'Use `prisma-next migration log --db <url>` to view the full execution history.',
|
|
50
|
+
'migration:status:ref': 'Use `--to <contract>` instead of `--ref`.',
|
|
51
|
+
};
|
|
52
|
+
|
|
26
53
|
/**
|
|
27
54
|
* Formats the "Did you mean ...?" hint for an unknown command.
|
|
28
55
|
*/
|
|
@@ -177,9 +204,6 @@ contractCommand.addCommand(contractEmitCommand);
|
|
|
177
204
|
const contractInferCommand = createContractInferCommand();
|
|
178
205
|
contractCommand.addCommand(contractInferCommand);
|
|
179
206
|
|
|
180
|
-
// Register contract command
|
|
181
|
-
program.addCommand(contractCommand);
|
|
182
|
-
|
|
183
207
|
// Register db subcommand
|
|
184
208
|
const dbCommand = new Command('db');
|
|
185
209
|
setCommandDescriptions(
|
|
@@ -216,9 +240,6 @@ dbCommand.addCommand(dbSchemaCommand);
|
|
|
216
240
|
const dbSignCommand = createDbSignCommand();
|
|
217
241
|
dbCommand.addCommand(dbSignCommand);
|
|
218
242
|
|
|
219
|
-
// Register db command
|
|
220
|
-
program.addCommand(dbCommand);
|
|
221
|
-
|
|
222
243
|
// Register migration subcommand
|
|
223
244
|
const migrationCommand = new Command('migration');
|
|
224
245
|
setCommandDescriptions(
|
|
@@ -247,17 +268,38 @@ migrationCommand.addCommand(migrationShowCommand);
|
|
|
247
268
|
const migrationStatusCommand = createMigrationStatusCommand();
|
|
248
269
|
migrationCommand.addCommand(migrationStatusCommand);
|
|
249
270
|
|
|
250
|
-
const
|
|
251
|
-
migrationCommand.addCommand(
|
|
271
|
+
const migrationLogCommand = createMigrationLogCommand();
|
|
272
|
+
migrationCommand.addCommand(migrationLogCommand);
|
|
252
273
|
|
|
253
|
-
const
|
|
254
|
-
migrationCommand.addCommand(
|
|
274
|
+
const migrationListCommand = createMigrationListCommand();
|
|
275
|
+
migrationCommand.addCommand(migrationListCommand);
|
|
255
276
|
|
|
256
|
-
|
|
277
|
+
const migrationGraphCommand = createMigrationGraphCommand();
|
|
278
|
+
migrationCommand.addCommand(migrationGraphCommand);
|
|
279
|
+
|
|
280
|
+
const migrationCheckCommand = createMigrationCheckCommand();
|
|
281
|
+
migrationCommand.addCommand(migrationCheckCommand);
|
|
282
|
+
|
|
283
|
+
// Top-level migrate command
|
|
284
|
+
const migrateCommand = createMigrateCommand();
|
|
257
285
|
|
|
258
|
-
//
|
|
286
|
+
// Top-level ref command (replaces `migration ref`)
|
|
287
|
+
const refCommand = createRefCommand();
|
|
288
|
+
|
|
289
|
+
// Top-level init command
|
|
259
290
|
const initCommand = createInitCommand();
|
|
291
|
+
|
|
292
|
+
// Register top-level commands in the order the spec's intended-surface
|
|
293
|
+
// diagram lists them: verbs (init, migrate) first, then subject
|
|
294
|
+
// namespaces (contract, db, migration, ref). The order shows up in
|
|
295
|
+
// `prisma-next --help` and is the first thing a new user sees, so it
|
|
296
|
+
// matches the order spec.md uses to introduce the surface.
|
|
260
297
|
program.addCommand(initCommand);
|
|
298
|
+
program.addCommand(migrateCommand);
|
|
299
|
+
program.addCommand(contractCommand);
|
|
300
|
+
program.addCommand(dbCommand);
|
|
301
|
+
program.addCommand(migrationCommand);
|
|
302
|
+
program.addCommand(refCommand);
|
|
261
303
|
|
|
262
304
|
// Create help command
|
|
263
305
|
const helpCommand = new Command('help')
|
|
@@ -324,7 +366,28 @@ if (args.length > 0) {
|
|
|
324
366
|
const helpText = formatRootHelp({ program, flags });
|
|
325
367
|
process.stderr.write(`${helpText}\n`);
|
|
326
368
|
process.exit(2);
|
|
327
|
-
} else if (command.commands.length > 0 && args.length
|
|
369
|
+
} else if (command.commands.length > 0 && args.length >= 2) {
|
|
370
|
+
const subcommandName = args[1];
|
|
371
|
+
const redirectKey = `${commandName}:${subcommandName}`;
|
|
372
|
+
const redirect = removedVerbRedirects[redirectKey];
|
|
373
|
+
if (redirect) {
|
|
374
|
+
process.stderr.write(`Unknown command: ${subcommandName}\n${redirect}\n`);
|
|
375
|
+
process.exit(2);
|
|
376
|
+
}
|
|
377
|
+
for (let i = 2; i < args.length; i++) {
|
|
378
|
+
const arg = args[i]!;
|
|
379
|
+
if (!arg.startsWith('--')) continue;
|
|
380
|
+
const flagName = arg.slice(2);
|
|
381
|
+
const flagKey = `${commandName}:${subcommandName}:${flagName}`;
|
|
382
|
+
const flagRedirect = removedFlagRedirects[flagKey];
|
|
383
|
+
if (flagRedirect) {
|
|
384
|
+
process.stderr.write(`Unknown option: ${arg}\n${flagRedirect}\n`);
|
|
385
|
+
process.exit(2);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (command.commands.length > 0 && args.length === 1) {
|
|
328
391
|
// Parent command called with no subcommand. Same shape as the
|
|
329
392
|
// no-args case above: the user did not request help, we are
|
|
330
393
|
// voluntarily rendering it as decoration around an underspecified
|
package/src/commands/db-sign.ts
CHANGED
|
@@ -3,9 +3,12 @@ import type {
|
|
|
3
3
|
SignDatabaseResult,
|
|
4
4
|
VerifyDatabaseSchemaResult,
|
|
5
5
|
} from '@prisma-next/framework-components/control';
|
|
6
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
7
|
+
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
8
|
+
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
6
9
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
7
10
|
import { Command } from 'commander';
|
|
8
|
-
import { relative, resolve } from 'pathe';
|
|
11
|
+
import { join, relative, resolve } from 'pathe';
|
|
9
12
|
import { loadConfig } from '../config-loader';
|
|
10
13
|
import { createControlClient } from '../control-api/client';
|
|
11
14
|
import { ContractValidationError } from '../control-api/errors';
|
|
@@ -15,12 +18,17 @@ import {
|
|
|
15
18
|
errorDatabaseConnectionRequired,
|
|
16
19
|
errorDriverRequired,
|
|
17
20
|
errorFileNotFound,
|
|
21
|
+
errorRuntime,
|
|
18
22
|
errorUnexpected,
|
|
23
|
+
mapMigrationToolsError,
|
|
24
|
+
mapRefResolutionError,
|
|
19
25
|
} from '../utils/cli-errors';
|
|
20
26
|
import {
|
|
21
27
|
addGlobalOptions,
|
|
28
|
+
loadMigrationPackages,
|
|
22
29
|
maskConnectionUrl,
|
|
23
30
|
resolveContractPath,
|
|
31
|
+
resolveMigrationPaths,
|
|
24
32
|
setCommandDescriptions,
|
|
25
33
|
setCommandExamples,
|
|
26
34
|
} from '../utils/command-helpers';
|
|
@@ -40,6 +48,7 @@ import { TerminalUI } from '../utils/terminal-ui';
|
|
|
40
48
|
interface DbSignOptions extends CommonCommandOptions {
|
|
41
49
|
readonly db?: string;
|
|
42
50
|
readonly config?: string;
|
|
51
|
+
readonly contract?: string;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
@@ -54,11 +63,11 @@ type DbSignFailure = CliStructuredError | VerifyDatabaseSchemaResult;
|
|
|
54
63
|
* Failure: CliStructuredError (infra error) or VerifyDatabaseSchemaResult (schema mismatch)
|
|
55
64
|
*/
|
|
56
65
|
async function executeDbSignCommand(
|
|
66
|
+
contractArg: string | undefined,
|
|
57
67
|
options: DbSignOptions,
|
|
58
68
|
flags: GlobalFlags,
|
|
59
69
|
ui: TerminalUI,
|
|
60
70
|
): Promise<Result<SignDatabaseResult, DbSignFailure>> {
|
|
61
|
-
// Load config
|
|
62
71
|
const config = await loadConfig(options.config);
|
|
63
72
|
const configPath = options.config
|
|
64
73
|
? relative(process.cwd(), resolve(options.config))
|
|
@@ -66,11 +75,12 @@ async function executeDbSignCommand(
|
|
|
66
75
|
const contractPathAbsolute = resolveContractPath(config);
|
|
67
76
|
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
const effectiveContractArg = contractArg ?? options.contract;
|
|
79
|
+
|
|
70
80
|
if (!flags.json && !flags.quiet) {
|
|
71
81
|
const details: Array<{ label: string; value: string }> = [
|
|
72
82
|
{ label: 'config', value: configPath },
|
|
73
|
-
{ label: 'contract', value: contractPath },
|
|
83
|
+
{ label: 'contract', value: effectiveContractArg ?? contractPath },
|
|
74
84
|
];
|
|
75
85
|
if (options.db) {
|
|
76
86
|
details.push({ label: 'database', value: maskConnectionUrl(options.db) });
|
|
@@ -85,36 +95,79 @@ async function executeDbSignCommand(
|
|
|
85
95
|
ui.stderr(header);
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
let contractJson: Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
if (effectiveContractArg) {
|
|
101
|
+
try {
|
|
102
|
+
const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
|
|
103
|
+
const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
|
|
104
|
+
const refs = await readRefs(refsDir);
|
|
105
|
+
const refResult = parseContractRef(effectiveContractArg, { graph, refs });
|
|
106
|
+
if (!refResult.ok) {
|
|
107
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
108
|
+
}
|
|
109
|
+
const targetHash = refResult.value.hash;
|
|
110
|
+
const matchingBundle = bundles.find((p) => p.metadata.to === targetHash);
|
|
111
|
+
if (matchingBundle) {
|
|
112
|
+
const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
|
|
113
|
+
const raw = await readFile(endContractPath, 'utf-8');
|
|
114
|
+
contractJson = JSON.parse(raw) as Record<string, unknown>;
|
|
115
|
+
} else {
|
|
116
|
+
const defaultRaw = await readFile(contractPathAbsolute, 'utf-8');
|
|
117
|
+
const defaultContract = JSON.parse(defaultRaw) as Record<string, unknown>;
|
|
118
|
+
const storageHash = (defaultContract['storage'] as Record<string, unknown> | undefined)?.[
|
|
119
|
+
'storageHash'
|
|
120
|
+
];
|
|
121
|
+
if (storageHash === targetHash) {
|
|
122
|
+
contractJson = defaultContract;
|
|
123
|
+
} else {
|
|
124
|
+
return notOk(
|
|
125
|
+
errorRuntime(`No contract file found for hash "${targetHash}"`, {
|
|
126
|
+
why: `Resolved contract reference "${effectiveContractArg}" to hash "${targetHash}" but no migration produces that hash and the emitted contract does not match.`,
|
|
127
|
+
fix: 'Ensure the target contract exists on disk — either as a migration endpoint or as the emitted contract.json.',
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
134
|
+
if (error instanceof CliStructuredError) return notOk(error);
|
|
94
135
|
return notOk(
|
|
95
|
-
|
|
96
|
-
why: `
|
|
97
|
-
|
|
136
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
137
|
+
why: `Failed to resolve contract reference: ${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
let contractJsonContent: string;
|
|
143
|
+
try {
|
|
144
|
+
contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
147
|
+
return notOk(
|
|
148
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
149
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
150
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return notOk(
|
|
155
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
156
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
98
157
|
}),
|
|
99
158
|
);
|
|
100
159
|
}
|
|
101
|
-
return notOk(
|
|
102
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
103
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
160
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
161
|
+
try {
|
|
162
|
+
contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return notOk(
|
|
165
|
+
errorContractValidationFailed(
|
|
166
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
167
|
+
{ where: { path: contractPathAbsolute } },
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
118
171
|
}
|
|
119
172
|
|
|
120
173
|
// Resolve database connection (--db flag or config.db.connection)
|
|
@@ -203,16 +256,33 @@ export function createDbSignCommand(): Command {
|
|
|
203
256
|
'in CI/deployment pipelines. The signature records that this database instance is aligned\n' +
|
|
204
257
|
'with a specific contract version.',
|
|
205
258
|
);
|
|
206
|
-
setCommandExamples(command, [
|
|
259
|
+
setCommandExamples(command, [
|
|
260
|
+
'prisma-next db sign --db $DATABASE_URL',
|
|
261
|
+
'prisma-next db sign production --db $DATABASE_URL',
|
|
262
|
+
'prisma-next db sign --contract production --db $DATABASE_URL',
|
|
263
|
+
]);
|
|
207
264
|
addGlobalOptions(command)
|
|
265
|
+
.argument('[contract]', 'Contract reference (hash, prefix, ref name, or migration dir name)')
|
|
208
266
|
.option('--db <url>', 'Database connection string')
|
|
209
267
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
210
|
-
.
|
|
268
|
+
.option(
|
|
269
|
+
'--contract <contract>',
|
|
270
|
+
'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
271
|
+
)
|
|
272
|
+
.action(async (positionalContract: string | undefined, options: DbSignOptions) => {
|
|
211
273
|
const flags = parseGlobalFlags(options);
|
|
212
274
|
|
|
275
|
+
if (positionalContract && options.contract) {
|
|
276
|
+
process.stderr.write(
|
|
277
|
+
'Cannot specify both a positional contract argument and --contract flag.\n',
|
|
278
|
+
);
|
|
279
|
+
process.exit(2);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
213
283
|
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
214
284
|
|
|
215
|
-
const result = await executeDbSignCommand(options, flags, ui);
|
|
285
|
+
const result = await executeDbSignCommand(positionalContract, options, flags, ui);
|
|
216
286
|
|
|
217
287
|
if (result.ok) {
|
|
218
288
|
// Success - format sign output
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
|
+
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
4
|
+
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
1
5
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
2
6
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
7
|
import { Command } from 'commander';
|
|
8
|
+
import { join } from 'pathe';
|
|
4
9
|
import { ContractValidationError } from '../control-api/errors';
|
|
5
10
|
import type { DbUpdateFailure } from '../control-api/types';
|
|
6
11
|
import {
|
|
@@ -11,9 +16,12 @@ import {
|
|
|
11
16
|
errorMigrationPlanningFailed,
|
|
12
17
|
errorRunnerFailed,
|
|
13
18
|
errorUnexpected,
|
|
19
|
+
mapMigrationToolsError,
|
|
20
|
+
mapRefResolutionError,
|
|
14
21
|
} from '../utils/cli-errors';
|
|
15
22
|
import type { MigrationCommandOptions } from '../utils/command-helpers';
|
|
16
23
|
import {
|
|
24
|
+
loadMigrationPackages,
|
|
17
25
|
resolveMigrationPaths,
|
|
18
26
|
sanitizeErrorMessage,
|
|
19
27
|
setCommandDescriptions,
|
|
@@ -33,7 +41,9 @@ import {
|
|
|
33
41
|
import { handleResult } from '../utils/result-handler';
|
|
34
42
|
import { TerminalUI } from '../utils/terminal-ui';
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
interface DbUpdateOptions extends MigrationCommandOptions {
|
|
45
|
+
readonly to?: string;
|
|
46
|
+
}
|
|
37
47
|
|
|
38
48
|
/**
|
|
39
49
|
* Maps a DbUpdateFailure to a CliStructuredError for consistent error handling.
|
|
@@ -81,9 +91,47 @@ async function executeDbUpdateCommand(
|
|
|
81
91
|
if (!ctxResult.ok) {
|
|
82
92
|
return ctxResult;
|
|
83
93
|
}
|
|
84
|
-
const { client, config,
|
|
85
|
-
|
|
86
|
-
const { migrationsDir } = resolveMigrationPaths(
|
|
94
|
+
const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
|
|
95
|
+
let { contractJson } = ctxResult.value;
|
|
96
|
+
const { migrationsDir, appMigrationsDir, refsDir } = resolveMigrationPaths(
|
|
97
|
+
options.config,
|
|
98
|
+
config,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (options.to) {
|
|
102
|
+
try {
|
|
103
|
+
const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
|
|
104
|
+
const refs = await readRefs(refsDir);
|
|
105
|
+
const refResult = parseContractRef(options.to, { graph, refs });
|
|
106
|
+
if (!refResult.ok) {
|
|
107
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
108
|
+
}
|
|
109
|
+
const targetHash = refResult.value.hash;
|
|
110
|
+
const matchingBundle = bundles.find((p) => p.metadata.to === targetHash);
|
|
111
|
+
if (!matchingBundle) {
|
|
112
|
+
return notOk(
|
|
113
|
+
errorUnexpected(
|
|
114
|
+
`No migration bundle found for --to "${options.to}" (resolved hash: ${targetHash})`,
|
|
115
|
+
{
|
|
116
|
+
why: `The ref resolved successfully but no on-disk migration package has an end-contract hash matching ${targetHash}.`,
|
|
117
|
+
fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',
|
|
118
|
+
},
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
|
|
123
|
+
const raw = await readFile(endContractPath, 'utf-8');
|
|
124
|
+
contractJson = JSON.parse(raw) as Record<string, unknown>;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (MigrationToolsError.is(error)) {
|
|
127
|
+
return notOk(mapMigrationToolsError(error));
|
|
128
|
+
}
|
|
129
|
+
if (CliStructuredError.is(error)) {
|
|
130
|
+
return notOk(error);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
87
135
|
|
|
88
136
|
try {
|
|
89
137
|
await client.connect(dbConnection);
|
|
@@ -185,6 +233,10 @@ export function createDbUpdateCommand(): Command {
|
|
|
185
233
|
'prisma-next db update --db $DATABASE_URL --dry-run',
|
|
186
234
|
]);
|
|
187
235
|
addMigrationCommandOptions(command);
|
|
236
|
+
command.option(
|
|
237
|
+
'--to <contract>',
|
|
238
|
+
'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
239
|
+
);
|
|
188
240
|
command.action(async (options: DbUpdateOptions) => {
|
|
189
241
|
const flags = parseGlobalFlags(options);
|
|
190
242
|
const startTime = Date.now();
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { version as cliVersion } from '../../../package.json' with { type: 'json' };
|
|
4
|
+
import type { PackageManager } from './detect-package-manager';
|
|
5
|
+
import { errorInitSkillInstallFailed } from './errors';
|
|
6
|
+
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default base for the GitHub-URL form `<owner>/<repo>` consumed by
|
|
11
|
+
* upstream `skills add`. Each `SkillSource` joins this base with its
|
|
12
|
+
* own subpath (and optional `#ref` for version-pinned clusters).
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_AGENT_SKILL_BASE = 'prisma/prisma-next';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* One discovery scope inside the Prisma Next monorepo. The CLI emits
|
|
18
|
+
* one `skills add <base>/<subpath>[#ref] --all` invocation per source
|
|
19
|
+
* during `init`.
|
|
20
|
+
*
|
|
21
|
+
* `ref` semantics:
|
|
22
|
+
* - `cli`: pin to the CLI's own package version (lockstep with the
|
|
23
|
+
* skills' SPI). Used for the version-locked usage cluster — the
|
|
24
|
+
* skills under `skills/<X>/SKILL.md`, which describe the public
|
|
25
|
+
* package API and are pinned to the version of `@prisma-next/*`
|
|
26
|
+
* currently installed in the consumer's project.
|
|
27
|
+
* - `null`: no ref. The cluster is "always-latest" — the cumulative
|
|
28
|
+
* instruction set is the source of truth, and the latest revision
|
|
29
|
+
* on `main` includes bug fixes for every prior transition. Used
|
|
30
|
+
* for the upgrade and extension-author clusters.
|
|
31
|
+
*/
|
|
32
|
+
export interface SkillSource {
|
|
33
|
+
readonly subpath: string;
|
|
34
|
+
readonly ref: 'cli' | null;
|
|
35
|
+
readonly description: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_AGENT_SKILL_SOURCES: readonly SkillSource[] = [
|
|
39
|
+
{
|
|
40
|
+
subpath: 'skills',
|
|
41
|
+
ref: 'cli',
|
|
42
|
+
description: 'usage skills (version-locked to installed Prisma Next)',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
subpath: 'skills/upgrade',
|
|
46
|
+
ref: null,
|
|
47
|
+
description: 'upgrade skill (always tracks `main`)',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
subpath: 'skills/extension-author',
|
|
51
|
+
ref: null,
|
|
52
|
+
description: 'extension-author skill (always tracks `main`)',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Test-only escape hatch for pinning the install base to a local
|
|
58
|
+
* checkout. Production runs leave this unset, so installs always use
|
|
59
|
+
* `DEFAULT_AGENT_SKILL_BASE`.
|
|
60
|
+
*
|
|
61
|
+
* When set to an absolute filesystem path (typical for tests), the
|
|
62
|
+
* `#ref` fragment is dropped — local-path mode in upstream's CLI does
|
|
63
|
+
* not accept refs, and the local clone has whatever content the test
|
|
64
|
+
* checked into it anyway. When set to anything else (e.g. a fork name
|
|
65
|
+
* `myuser/prisma-next`), the ref policy is preserved.
|
|
66
|
+
*/
|
|
67
|
+
function resolveAgentSkillBase(): string {
|
|
68
|
+
const override = process.env['PRISMA_NEXT_SKILLS_BASE']?.trim();
|
|
69
|
+
return override && override.length > 0 ? override : DEFAULT_AGENT_SKILL_BASE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isLocalPath(base: string): boolean {
|
|
73
|
+
return base.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(base);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the `<base>/<subpath>[#ref]` URL the `skills` CLI will
|
|
78
|
+
* resolve. Exported for unit tests so the per-source format can be
|
|
79
|
+
* asserted without going through the full install loop.
|
|
80
|
+
*/
|
|
81
|
+
export function formatSkillSourceUrl(source: SkillSource): string {
|
|
82
|
+
const base = resolveAgentSkillBase();
|
|
83
|
+
const url = `${base}/${source.subpath}`;
|
|
84
|
+
if (source.ref === null) return url;
|
|
85
|
+
if (isLocalPath(base)) return url;
|
|
86
|
+
if (source.ref === 'cli') return `${url}#v${cliVersion}`;
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The skill-install command for one source, formatted for the
|
|
92
|
+
* project's detected package manager. `npx`/`pnpm dlx`/`bunx` are
|
|
93
|
+
* interchangeable to the user; we pick the variant that matches the
|
|
94
|
+
* rest of the install step so a single project consistently uses one
|
|
95
|
+
* runner.
|
|
96
|
+
*
|
|
97
|
+
* `--all` auto-selects every skill in the cluster and every detected
|
|
98
|
+
* agent runtime, skipping the multi-select prompts the `skills` CLI
|
|
99
|
+
* shows by default. A non-interactive scaffold step cannot present
|
|
100
|
+
* prompts.
|
|
101
|
+
*
|
|
102
|
+
* Exported for unit tests so the per-PM dispatch can be asserted
|
|
103
|
+
* without a live subprocess.
|
|
104
|
+
*/
|
|
105
|
+
export function formatSkillInstallCommand(pm: PackageManager, source: SkillSource): string {
|
|
106
|
+
const args = ['skills@latest', 'add', formatSkillSourceUrl(source), '--all'];
|
|
107
|
+
return formatPackageManagerCommand(pm, args);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `skills add --all` should cover Claude Code, but upstream currently skips
|
|
112
|
+
* project-local Claude symlinks when `.claude/` does not already exist. Run
|
|
113
|
+
* the explicit Claude Code install as well so fresh projects get
|
|
114
|
+
* `.claude/skills` without asking users to create that folder first.
|
|
115
|
+
*/
|
|
116
|
+
export function formatClaudeSkillInstallCommand(pm: PackageManager, source: SkillSource): string {
|
|
117
|
+
const args = [
|
|
118
|
+
'skills@latest',
|
|
119
|
+
'add',
|
|
120
|
+
formatSkillSourceUrl(source),
|
|
121
|
+
'--agent',
|
|
122
|
+
'claude-code',
|
|
123
|
+
'--skill',
|
|
124
|
+
"'*'",
|
|
125
|
+
'-y',
|
|
126
|
+
];
|
|
127
|
+
return formatPackageManagerCommand(pm, args);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatPackageManagerCommand(pm: PackageManager, args: readonly string[]): string {
|
|
131
|
+
switch (pm) {
|
|
132
|
+
case 'pnpm':
|
|
133
|
+
return `pnpm dlx ${args.join(' ')}`;
|
|
134
|
+
case 'yarn':
|
|
135
|
+
return `yarn dlx ${args.join(' ')}`;
|
|
136
|
+
case 'bun':
|
|
137
|
+
return `bunx ${args.join(' ')}`;
|
|
138
|
+
case 'deno':
|
|
139
|
+
return `deno run -A npm:${args.join(' ')}`;
|
|
140
|
+
case 'npm':
|
|
141
|
+
return `npx ${args.join(' ')}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parse the project-pm-formatted command into an exec call. The
|
|
147
|
+
* format-then-parse split keeps the user-facing command string the same
|
|
148
|
+
* as the surface the structured error advertises, so a user who copies
|
|
149
|
+
* the error's `fix` line gets the same invocation that init just
|
|
150
|
+
* attempted. Single quotes are preserved in the display form so `*` is
|
|
151
|
+
* safe to copy into a shell, then stripped before `execFile`.
|
|
152
|
+
*/
|
|
153
|
+
function commandToExec(command: string): {
|
|
154
|
+
readonly file: string;
|
|
155
|
+
readonly args: readonly string[];
|
|
156
|
+
} {
|
|
157
|
+
const tokens = (command.match(/'[^']*'|\S+/g) ?? []).map((token) =>
|
|
158
|
+
token.startsWith("'") && token.endsWith("'") ? token.slice(1, -1) : token,
|
|
159
|
+
);
|
|
160
|
+
return { file: tokens[0] ?? 'npx', args: tokens.slice(1) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Runs the project-level skill install for every source in
|
|
165
|
+
* `DEFAULT_AGENT_SKILL_SOURCES`, in order. Returns
|
|
166
|
+
* `{ ok: true, commands }` on success; throws a structured
|
|
167
|
+
* `errorInitSkillInstallFailed` on the first failure (subsequent
|
|
168
|
+
* sources are not attempted — the user opted into Prisma Next by
|
|
169
|
+
* running `init` and a partial install would leave the project in an
|
|
170
|
+
* ambiguous state). The throw is intentionally fatal — project-level
|
|
171
|
+
* skill install is unconditional (modulo `--no-skill`).
|
|
172
|
+
*/
|
|
173
|
+
export async function runProjectLevelSkillInstall(ctx: {
|
|
174
|
+
readonly baseDir: string;
|
|
175
|
+
readonly pm: PackageManager;
|
|
176
|
+
readonly filesWritten: readonly string[];
|
|
177
|
+
}): Promise<{ readonly ok: true; readonly commands: readonly string[] }> {
|
|
178
|
+
const commands: string[] = [];
|
|
179
|
+
const installCommands = DEFAULT_AGENT_SKILL_SOURCES.flatMap((source) => [
|
|
180
|
+
formatSkillInstallCommand(ctx.pm, source),
|
|
181
|
+
formatClaudeSkillInstallCommand(ctx.pm, source),
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
for (const command of installCommands) {
|
|
185
|
+
const { file, args } = commandToExec(command);
|
|
186
|
+
try {
|
|
187
|
+
await exec(file, args, { cwd: ctx.baseDir });
|
|
188
|
+
commands.push(command);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
throw errorInitSkillInstallFailed({
|
|
191
|
+
skillInstallCommand: command,
|
|
192
|
+
filesWritten: ctx.filesWritten,
|
|
193
|
+
cause:
|
|
194
|
+
redactSecrets(readChildStderr(err)) || (err instanceof Error ? err.message : String(err)),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { ok: true, commands };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function readChildStderr(err: unknown): string {
|
|
202
|
+
if (err instanceof Error && 'stderr' in err) {
|
|
203
|
+
return String((err as { stderr: string }).stderr ?? '');
|
|
204
|
+
}
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Strips credentials from a `scheme://user:pass@host/...` URL anywhere
|
|
210
|
+
* in `stderr`. Package-manager stderr regularly contains credentialed
|
|
211
|
+
* registry URLs (private npm registries, GitHub Packages tokens), and
|
|
212
|
+
* those bubble into the structured `errorInitSkillInstallFailed`
|
|
213
|
+
* envelope, which ends up in logs and CI output. Redact at the
|
|
214
|
+
* boundary so we never re-emit a secret.
|
|
215
|
+
*
|
|
216
|
+
* Exported for unit tests.
|
|
217
|
+
*/
|
|
218
|
+
export function redactSecrets(stderr: string): string {
|
|
219
|
+
if (!stderr) return stderr;
|
|
220
|
+
return stderr.replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s]+)@/g, '$1***@');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// -------------------------------------------------------------------
|
|
224
|
+
// Legacy file cleanup
|
|
225
|
+
// -------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Hand-rolled skill stub path that init must not leave behind. Removed
|
|
229
|
+
* on every init run so a project's `.agents/skills/prisma-next/` does
|
|
230
|
+
* not shadow the installed Prisma Next skill cluster.
|
|
231
|
+
*/
|
|
232
|
+
export const LEGACY_SKILL_FILE = '.agents/skills/prisma-next/SKILL.md';
|