@prisma-next/cli 0.9.0 → 0.10.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/dist/cli.mjs +154 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{command-helpers-D3vL5yi8.mjs → command-helpers-Dvgul7UA.mjs} +3 -3
- package/dist/command-helpers-Dvgul7UA.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 +4 -4
- package/dist/commands/db-schema.mjs +4 -4
- package/dist/commands/db-sign.mjs +3 -3
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +3 -3
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/commands/migration-list.mjs +2 -2
- package/dist/commands/migration-log.mjs +2 -2
- package/dist/commands/migration-new.mjs +2 -2
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.mjs +3 -3
- package/dist/commands/ref.mjs +2 -2
- package/dist/{contract-emit-C3STUIBg.mjs → contract-emit-BDBzHlaC.mjs} +4 -4
- package/dist/{contract-emit-C3STUIBg.mjs.map → contract-emit-BDBzHlaC.mjs.map} +1 -1
- package/dist/{contract-infer-Cnj8G1E2.mjs → contract-infer-Dm8pBZMR.mjs} +4 -4
- package/dist/{contract-infer-Cnj8G1E2.mjs.map → contract-infer-Dm8pBZMR.mjs.map} +1 -1
- package/dist/{db-verify-D7cyH_zz.mjs → db-verify-CW8DR5Ei.mjs} +4 -4
- package/dist/{db-verify-D7cyH_zz.mjs.map → db-verify-CW8DR5Ei.mjs.map} +1 -1
- package/dist/{errors-Cw6kyTyV.mjs → errors-BYAXmyRJ.mjs} +2 -2
- package/dist/{errors-Cw6kyTyV.mjs.map → errors-BYAXmyRJ.mjs.map} +1 -1
- package/dist/exports/index.mjs +2 -2
- package/dist/global-flags-DGmw6Kqg.d.mts.map +1 -1
- package/dist/{init-eh2z5Tl6.mjs → init-CxS9eqbQ.mjs} +435 -373
- package/dist/init-CxS9eqbQ.mjs.map +1 -0
- package/dist/{inspect-live-schema-CWLK_lgs.mjs → inspect-live-schema-iETRZ_59.mjs} +2 -2
- package/dist/{inspect-live-schema-CWLK_lgs.mjs.map → inspect-live-schema-iETRZ_59.mjs.map} +1 -1
- package/dist/is-ci-YyvQBBke.mjs +44 -0
- package/dist/is-ci-YyvQBBke.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CmXXC1UZ.mjs → migration-command-scaffold-BlgVj_Pn.mjs} +2 -2
- package/dist/{migration-command-scaffold-CmXXC1UZ.mjs.map → migration-command-scaffold-BlgVj_Pn.mjs.map} +1 -1
- package/dist/{migration-plan-CHyUlBV0.mjs → migration-plan-BSzcWsvm.mjs} +3 -3
- package/dist/{migration-plan-CHyUlBV0.mjs.map → migration-plan-BSzcWsvm.mjs.map} +1 -1
- package/dist/{migrations-DyUf5lTt.mjs → migrations-CgANWI0w.mjs} +2 -2
- package/dist/{migrations-DyUf5lTt.mjs.map → migrations-CgANWI0w.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +1 -1
- package/dist/quick-reference-postgres.md +1 -1
- package/dist/{result-handler-Bm_6dDYg.mjs → result-handler-CG3vVoKf.mjs} +2 -2
- package/dist/{result-handler-Bm_6dDYg.mjs.map → result-handler-CG3vVoKf.mjs.map} +1 -1
- package/dist/{verify-D7ypCCe6.mjs → verify-nlzO0uIY.mjs} +2 -2
- package/dist/{verify-D7ypCCe6.mjs.map → verify-nlzO0uIY.mjs.map} +1 -1
- package/package.json +19 -17
- package/src/cli.ts +15 -0
- package/src/commands/init/errors.ts +2 -2
- package/src/commands/init/index.ts +11 -3
- package/src/commands/init/init.ts +31 -18
- package/src/commands/init/inputs.ts +76 -1
- package/src/commands/init/{agent-skill-install.ts → skill-install.ts} +6 -6
- package/src/commands/init/templates/code-templates.ts +18 -10
- package/src/commands/init/templates/quick-reference-mongo.md +1 -1
- package/src/commands/init/templates/quick-reference-postgres.md +1 -1
- package/src/utils/global-flags.ts +6 -2
- package/src/utils/is-ci.ts +18 -0
- package/src/utils/telemetry.ts +166 -0
- package/dist/command-helpers-D3vL5yi8.mjs.map +0 -1
- package/dist/helpers-eqdN8tH6.mjs +0 -25
- package/dist/helpers-eqdN8tH6.mjs.map +0 -1
- package/dist/init-eh2z5Tl6.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-dev.10",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -14,16 +14,18 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^1.3.0",
|
|
16
16
|
"@dagrejs/dagre": "^3.0.0",
|
|
17
|
-
"@prisma-next/config": "0.
|
|
18
|
-
"@prisma-next/contract": "0.
|
|
19
|
-
"@prisma-next/emitter": "0.
|
|
20
|
-
"@prisma-next/errors": "0.
|
|
21
|
-
"@prisma-next/framework-components": "0.
|
|
22
|
-
"@prisma-next/migration-tools": "0.
|
|
23
|
-
"@prisma-next/psl-printer": "0.
|
|
24
|
-
"@prisma-next/
|
|
17
|
+
"@prisma-next/config": "0.10.0-dev.10",
|
|
18
|
+
"@prisma-next/contract": "0.10.0-dev.10",
|
|
19
|
+
"@prisma-next/emitter": "0.10.0-dev.10",
|
|
20
|
+
"@prisma-next/errors": "0.10.0-dev.10",
|
|
21
|
+
"@prisma-next/framework-components": "0.10.0-dev.10",
|
|
22
|
+
"@prisma-next/migration-tools": "0.10.0-dev.10",
|
|
23
|
+
"@prisma-next/psl-printer": "0.10.0-dev.10",
|
|
24
|
+
"@prisma-next/cli-telemetry": "0.10.0-dev.10",
|
|
25
|
+
"@prisma-next/utils": "0.10.0-dev.10",
|
|
25
26
|
"arktype": "^2.2.0",
|
|
26
27
|
"c12": "^3.3.4",
|
|
28
|
+
"ci-info": "^4.3.1",
|
|
27
29
|
"clipanion": "4.0.0-rc.4",
|
|
28
30
|
"closest-match": "^1.3.3",
|
|
29
31
|
"colorette": "^2.0.20",
|
|
@@ -37,14 +39,14 @@
|
|
|
37
39
|
"wrap-ansi": "^10.0.0"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@prisma-next/sql-contract": "0.
|
|
41
|
-
"@prisma-next/sql-contract-emitter": "0.
|
|
42
|
-
"@prisma-next/sql-contract-ts": "0.
|
|
43
|
-
"@prisma-next/sql-operations": "0.
|
|
44
|
-
"@prisma-next/sql-runtime": "0.
|
|
45
|
-
"@prisma-next/test-utils": "0.
|
|
46
|
-
"@prisma-next/tsconfig": "0.
|
|
47
|
-
"@prisma-next/tsdown": "0.
|
|
42
|
+
"@prisma-next/sql-contract": "0.10.0-dev.10",
|
|
43
|
+
"@prisma-next/sql-contract-emitter": "0.10.0-dev.10",
|
|
44
|
+
"@prisma-next/sql-contract-ts": "0.10.0-dev.10",
|
|
45
|
+
"@prisma-next/sql-operations": "0.10.0-dev.10",
|
|
46
|
+
"@prisma-next/sql-runtime": "0.10.0-dev.10",
|
|
47
|
+
"@prisma-next/test-utils": "0.10.0-dev.10",
|
|
48
|
+
"@prisma-next/tsconfig": "0.10.0-dev.10",
|
|
49
|
+
"@prisma-next/tsdown": "0.10.0-dev.10",
|
|
48
50
|
"@types/node": "24.10.4",
|
|
49
51
|
"tsdown": "0.22.0",
|
|
50
52
|
"typescript": "5.9.3",
|
package/src/cli.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { setCommandDescriptions } from './utils/command-helpers';
|
|
|
27
27
|
import { formatCommandHelp, formatRootHelp } from './utils/formatters/help';
|
|
28
28
|
import { parseGlobalFlags } from './utils/global-flags';
|
|
29
29
|
import { suggestCommands } from './utils/suggest-command';
|
|
30
|
+
import { fireTelemetryFromPreAction } from './utils/telemetry';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Lookup table mapping removed subcommands to their replacement verbs.
|
|
@@ -68,6 +69,20 @@ const program = new Command();
|
|
|
68
69
|
|
|
69
70
|
program.name('prisma-next').description('Prisma Next CLI').version(packageJson.version);
|
|
70
71
|
|
|
72
|
+
// Telemetry hook — fires at command start, before the action body
|
|
73
|
+
// runs. Every failure mode is swallowed inside `runTelemetry`; the
|
|
74
|
+
// hook never throws and never blocks long enough to be perceptible.
|
|
75
|
+
//
|
|
76
|
+
// Fire-and-forget: `fireTelemetryFromPreAction` `await`s a c12 config
|
|
77
|
+
// load before forking the sender, so awaiting it here would block the
|
|
78
|
+
// command's action body behind telemetry. Use `void` to dispatch in
|
|
79
|
+
// parallel; the existing `.catch(() => {})` keeps errors swallowed.
|
|
80
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
81
|
+
void fireTelemetryFromPreAction(actionCommand).catch(() => {
|
|
82
|
+
// defence-in-depth — runTelemetry already swallows internally.
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
71
86
|
// Override version option description to match capitalization style
|
|
72
87
|
const versionOption = program.options.find((opt) => opt.flags.includes('--version'));
|
|
73
88
|
if (versionOption) {
|
|
@@ -238,7 +238,7 @@ export function errorInitEmitFailed(options: {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
|
-
* The project-level
|
|
241
|
+
* The project-level skills install (`npx skills add
|
|
242
242
|
* prisma/prisma-next#v<version>`) failed after a successful dependency
|
|
243
243
|
* install + emit. The project's scaffold remains on disk; the user
|
|
244
244
|
* can either fix the underlying issue (network, registry, PATH) and
|
|
@@ -260,7 +260,7 @@ export function errorInitSkillInstallFailed(options: {
|
|
|
260
260
|
'Either:\n' +
|
|
261
261
|
` - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? ' --force' : ''}\` to skip the skill install for this run, or\n` +
|
|
262
262
|
` - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
|
|
263
|
-
docsUrl: 'https://prisma-next.dev/docs/cli/init#
|
|
263
|
+
docsUrl: 'https://prisma-next.dev/docs/cli/init#skills',
|
|
264
264
|
meta: {
|
|
265
265
|
filesWritten: options.filesWritten,
|
|
266
266
|
skillInstallCommand: options.skillInstallCommand,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
setCommandExamples,
|
|
6
6
|
} from '../../utils/command-helpers';
|
|
7
7
|
import { type CommonCommandOptions, parseGlobalFlags } from '../../utils/global-flags';
|
|
8
|
+
import { fireTelemetryAfterInitConsent } from '../../utils/telemetry';
|
|
8
9
|
import {
|
|
9
10
|
INIT_EXIT_EMIT_FAILED,
|
|
10
11
|
INIT_EXIT_INSTALL_FAILED,
|
|
@@ -63,7 +64,7 @@ export function createInitCommand(): Command {
|
|
|
63
64
|
'prisma-next init --yes --target mongodb --authoring typescript --json',
|
|
64
65
|
'prisma-next init --yes --force --target postgres --authoring psl # overwrite an existing scaffold',
|
|
65
66
|
'prisma-next init --no-install # skip pnpm/npm install + emit',
|
|
66
|
-
'prisma-next init --no-skill # skip the
|
|
67
|
+
'prisma-next init --no-skill # skip the skills install (air-gapped / restricted env)',
|
|
67
68
|
]);
|
|
68
69
|
|
|
69
70
|
return addGlobalOptions(command)
|
|
@@ -91,7 +92,7 @@ export function createInitCommand(): Command {
|
|
|
91
92
|
'--no-skill',
|
|
92
93
|
'Skip Prisma Next skills install (air-gapped CI, restricted registries, etc.)',
|
|
93
94
|
)
|
|
94
|
-
.action(async (options: InitCommandOptions) => {
|
|
95
|
+
.action(async (options: InitCommandOptions, actionCommand: Command) => {
|
|
95
96
|
const { runInit } = await import('./init');
|
|
96
97
|
const flags = parseGlobalFlags(options);
|
|
97
98
|
const canPrompt = deriveCanPrompt({
|
|
@@ -99,7 +100,14 @@ export function createInitCommand(): Command {
|
|
|
99
100
|
optionInteractive: options.interactive,
|
|
100
101
|
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
101
102
|
});
|
|
102
|
-
const exitCode = await runInit(process.cwd(), {
|
|
103
|
+
const exitCode = await runInit(process.cwd(), {
|
|
104
|
+
options,
|
|
105
|
+
flags,
|
|
106
|
+
canPrompt,
|
|
107
|
+
afterFirstTelemetryConsent: (inputs) => {
|
|
108
|
+
fireTelemetryAfterInitConsent(actionCommand, { databaseTarget: inputs.target });
|
|
109
|
+
},
|
|
110
|
+
});
|
|
103
111
|
process.exit(exitCode);
|
|
104
112
|
});
|
|
105
113
|
}
|
|
@@ -7,13 +7,6 @@ import { CliStructuredError } from '../../utils/cli-errors';
|
|
|
7
7
|
import { formatErrorJson, formatErrorOutput } from '../../utils/formatters/errors';
|
|
8
8
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
9
9
|
import { TerminalUI } from '../../utils/terminal-ui';
|
|
10
|
-
import {
|
|
11
|
-
DEFAULT_AGENT_SKILL_SOURCES,
|
|
12
|
-
formatClaudeSkillInstallCommand,
|
|
13
|
-
formatSkillInstallCommand,
|
|
14
|
-
LEGACY_SKILL_FILE,
|
|
15
|
-
runProjectLevelSkillInstall,
|
|
16
|
-
} from './agent-skill-install';
|
|
17
10
|
import {
|
|
18
11
|
detectPackageManager,
|
|
19
12
|
formatAddArgs,
|
|
@@ -56,6 +49,13 @@ import {
|
|
|
56
49
|
} from './output';
|
|
57
50
|
import { type ProbeOutcome, type ProbeOverrides, probeServerVersion } from './probe-db';
|
|
58
51
|
import { findStaleArtefacts, removeDependency } from './reinit-cleanup';
|
|
52
|
+
import {
|
|
53
|
+
DEFAULT_SKILL_SOURCES,
|
|
54
|
+
formatClaudeSkillInstallCommand,
|
|
55
|
+
formatSkillInstallCommand,
|
|
56
|
+
LEGACY_SKILL_FILE,
|
|
57
|
+
runProjectLevelSkillInstall,
|
|
58
|
+
} from './skill-install';
|
|
59
59
|
import { configFile, dbFile, starterSchema, targetPackageName } from './templates/code-templates';
|
|
60
60
|
import { envExampleContent, envFileContent, MIN_SERVER_VERSION } from './templates/env';
|
|
61
61
|
import { quickReferenceMd } from './templates/quick-reference';
|
|
@@ -81,11 +81,11 @@ interface InstallReport {
|
|
|
81
81
|
readonly warnings: readonly string[];
|
|
82
82
|
/**
|
|
83
83
|
* The package manager that actually ran. Equal to the detected `pm`
|
|
84
|
-
* on the common path; differs when the
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
84
|
+
* on the common path; differs when the pnpm → npm fallback fired, in
|
|
85
|
+
* which case it's `'npm'`. Threaded into the skills install so the
|
|
86
|
+
* runner stays consistent with the install we just ran — re-trying
|
|
87
|
+
* through `pnpm dlx` when `pnpm install` just failed for
|
|
88
|
+
* workspace/catalog reasons would fail again for the same reason.
|
|
89
89
|
*/
|
|
90
90
|
readonly effectivePm: PackageManager;
|
|
91
91
|
}
|
|
@@ -95,7 +95,7 @@ interface InstallReport {
|
|
|
95
95
|
* structured CLI errors raised at every phase (input resolution, install,
|
|
96
96
|
* emit) and renders them via the same UI surface as success output
|
|
97
97
|
* (`--json` to stdout, human to stderr). Exit codes follow the documented
|
|
98
|
-
* stable set in `./exit-codes.ts`
|
|
98
|
+
* stable set in `./exit-codes.ts` and the
|
|
99
99
|
* [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes).
|
|
100
100
|
*
|
|
101
101
|
* Layered for testability: the action handler in `./index.ts` is
|
|
@@ -113,6 +113,11 @@ export async function runInit(
|
|
|
113
113
|
* mode) — see [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity).
|
|
114
114
|
*/
|
|
115
115
|
readonly canPrompt: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Called once after the first affirmative telemetry consent is persisted.
|
|
118
|
+
* Failures are swallowed by the caller; init success must not depend on telemetry.
|
|
119
|
+
*/
|
|
120
|
+
readonly afterFirstTelemetryConsent?: (inputs: ResolvedInitInputs) => void | Promise<void>;
|
|
116
121
|
/**
|
|
117
122
|
* FR8.3 — test-only seam for the optional database version probe.
|
|
118
123
|
* Production callers omit this; tests inject stub `probePostgres` /
|
|
@@ -124,7 +129,7 @@ export async function runInit(
|
|
|
124
129
|
readonly probeOverrides?: ProbeOverrides;
|
|
125
130
|
},
|
|
126
131
|
): Promise<number> {
|
|
127
|
-
const { options, flags, canPrompt, probeOverrides } = runOptions;
|
|
132
|
+
const { options, flags, canPrompt, probeOverrides, afterFirstTelemetryConsent } = runOptions;
|
|
128
133
|
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
129
134
|
const warnings: string[] = [];
|
|
130
135
|
const filesWritten: string[] = [];
|
|
@@ -144,6 +149,14 @@ export async function runInit(
|
|
|
144
149
|
throw error;
|
|
145
150
|
}
|
|
146
151
|
|
|
152
|
+
if (inputs.enableTelemetry === true && afterFirstTelemetryConsent !== undefined) {
|
|
153
|
+
try {
|
|
154
|
+
await afterFirstTelemetryConsent(inputs);
|
|
155
|
+
} catch {
|
|
156
|
+
// telemetry is best-effort and must never affect init
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
const pm = await detectPackageManager(baseDir);
|
|
148
161
|
const pkgRun = formatRunCommand(pm, 'prisma-next', '').trimEnd();
|
|
149
162
|
|
|
@@ -451,7 +464,7 @@ export async function runInit(
|
|
|
451
464
|
// potentially fails. We skip the install when the user passed
|
|
452
465
|
// `--no-install` for the same reason — no `node_modules` means the
|
|
453
466
|
// workspace isn't ready to consume the skill yet anyway.
|
|
454
|
-
const manualProjectSkillCommands =
|
|
467
|
+
const manualProjectSkillCommands = DEFAULT_SKILL_SOURCES.flatMap((source) => [
|
|
455
468
|
formatSkillInstallCommand(install.effectivePm, source),
|
|
456
469
|
formatClaudeSkillInstallCommand(install.effectivePm, source),
|
|
457
470
|
]);
|
|
@@ -459,11 +472,11 @@ export async function runInit(
|
|
|
459
472
|
let skillRegistered = false;
|
|
460
473
|
if (!inputs.installProjectSkill) {
|
|
461
474
|
warnings.push(
|
|
462
|
-
`Skipped Prisma Next
|
|
475
|
+
`Skipped Prisma Next skills install (--no-skill). To install the skills later, run: ${manualProjectSkillSummary}`,
|
|
463
476
|
);
|
|
464
477
|
} else if (install.skipped) {
|
|
465
478
|
warnings.push(
|
|
466
|
-
`Skipped Prisma Next
|
|
479
|
+
`Skipped Prisma Next skills install because --no-install was passed. After you run install manually, install the skills with: ${manualProjectSkillSummary}`,
|
|
467
480
|
);
|
|
468
481
|
} else {
|
|
469
482
|
const spinner = ui.spinner();
|
|
@@ -597,7 +610,7 @@ export function exitCodeForError(error: { readonly code: string }): number {
|
|
|
597
610
|
return INIT_EXIT_EMIT_FAILED;
|
|
598
611
|
case '5009': // invalid output document — internal bug in prisma-next
|
|
599
612
|
return INIT_EXIT_INTERNAL_ERROR;
|
|
600
|
-
case '5013': //
|
|
613
|
+
case '5013': // skill install failed
|
|
601
614
|
return INIT_EXIT_SKILL_INSTALL_FAILED;
|
|
602
615
|
default:
|
|
603
616
|
// Any unexpected code is treated as an internal bug rather than
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import * as clack from '@clack/prompts';
|
|
3
|
+
import { readUserConfig, resolveGating, writeUserConfig } from '@prisma-next/cli-telemetry';
|
|
3
4
|
import { extname, join, normalize } from 'pathe';
|
|
4
5
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
6
|
+
import { isCI } from '../../utils/is-ci';
|
|
5
7
|
import {
|
|
6
8
|
errorInitInvalidFlagValue,
|
|
7
9
|
errorInitMissingFlags,
|
|
@@ -68,12 +70,24 @@ export interface ResolvedInitInputs {
|
|
|
68
70
|
* is added separately via the install step.
|
|
69
71
|
*/
|
|
70
72
|
readonly removePreviousFacade: string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Telemetry consent answer recorded during this `init` run, or `null`
|
|
75
|
+
* when no prompt was shown. The prompt fires only on the
|
|
76
|
+
* canPrompt + !autoAcceptPrompts + no env/CI opt-out +
|
|
77
|
+
* `enableTelemetry === undefined` intersection; outside that window
|
|
78
|
+
* the field is `null` and the stored preference (if any) stays
|
|
79
|
+
* unchanged. The answer has already been persisted to
|
|
80
|
+
* `$XDG_CONFIG_HOME/prisma-next/config.json` by
|
|
81
|
+
* the time `runInit` sees this value — it's surfaced here purely so
|
|
82
|
+
* the post-init summary can mention what the user chose.
|
|
83
|
+
*/
|
|
84
|
+
readonly enableTelemetry: boolean | null;
|
|
71
85
|
/**
|
|
72
86
|
* Whether to run `npx skills add prisma/prisma-next#v<version>` at the
|
|
73
87
|
* project level after install + emit. True by default; `--no-skill`
|
|
74
88
|
* sets it to `false`. The skill is always project-level (never
|
|
75
89
|
* user-level / global) so its version stays locked to the project's
|
|
76
|
-
* Prisma Next version — see `
|
|
90
|
+
* Prisma Next version — see `skill-install.ts`.
|
|
77
91
|
*/
|
|
78
92
|
readonly installProjectSkill: boolean;
|
|
79
93
|
}
|
|
@@ -91,6 +105,12 @@ const AUTHORING_VALUES: ReadonlyMap<string, AuthoringId> = new Map([
|
|
|
91
105
|
['ts', 'typescript'],
|
|
92
106
|
]);
|
|
93
107
|
|
|
108
|
+
export const TELEMETRY_CONSENT_MESSAGE = [
|
|
109
|
+
'Help us prioritize features by sharing anonymous CLI usage data?',
|
|
110
|
+
'The telemetry implementation is open source and fully transparent.',
|
|
111
|
+
'(packages/1-framework/3-tooling/cli-telemetry and apps/telemetry-backend).',
|
|
112
|
+
].join(' ');
|
|
113
|
+
|
|
94
114
|
/**
|
|
95
115
|
* Resolves every required input for `runInit`. In interactive mode, missing
|
|
96
116
|
* inputs are prompted via clack; in non-interactive mode, missing required
|
|
@@ -176,6 +196,11 @@ export async function resolveInitInputs(ctx: {
|
|
|
176
196
|
autoAcceptPrompts,
|
|
177
197
|
});
|
|
178
198
|
|
|
199
|
+
const enableTelemetry = await resolveTelemetryConsent({
|
|
200
|
+
canPrompt,
|
|
201
|
+
autoAcceptPrompts,
|
|
202
|
+
});
|
|
203
|
+
|
|
179
204
|
// Skill-install gating. `--no-skill` (commander parses
|
|
180
205
|
// `options.skill === false`) is the only escape hatch; otherwise
|
|
181
206
|
// project-level install is unconditional. The skill is always
|
|
@@ -193,10 +218,60 @@ export async function resolveInitInputs(ctx: {
|
|
|
193
218
|
strictProbe: Boolean(options.strictProbe),
|
|
194
219
|
reinit,
|
|
195
220
|
removePreviousFacade,
|
|
221
|
+
enableTelemetry,
|
|
196
222
|
installProjectSkill,
|
|
197
223
|
};
|
|
198
224
|
}
|
|
199
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The interactive telemetry consent prompt. Shown as the last
|
|
228
|
+
* question of the `init` sequence iff:
|
|
229
|
+
* 1. `canPrompt === true` (interactive stdin / stdout combo),
|
|
230
|
+
* 2. `autoAcceptPrompts === false` (the user did not pass `--yes`),
|
|
231
|
+
* 3. neither telemetry env opt-out is active,
|
|
232
|
+
* 4. the process is not running in CI,
|
|
233
|
+
* 5. the stored `enableTelemetry` value is `undefined` (the user
|
|
234
|
+
* has never been asked, or skipped the prompt previously).
|
|
235
|
+
*
|
|
236
|
+
* Outside that intersection the function returns `null` and leaves the
|
|
237
|
+
* stored preference untouched. Inside it, the user's answer is
|
|
238
|
+
* persisted via `writeUserConfig({ enableTelemetry })` before this
|
|
239
|
+
* function returns; on an affirmative answer `writeUserConfig`
|
|
240
|
+
* generates and stores the v4 `installationId` in the same write.
|
|
241
|
+
*
|
|
242
|
+
* The wording names CLI usage data and points to the open-source
|
|
243
|
+
* client/backend paths. Default value is `true` to match precedent
|
|
244
|
+
* and to make the consent answer a single keystroke once it's been
|
|
245
|
+
* disclosed.
|
|
246
|
+
*/
|
|
247
|
+
async function resolveTelemetryConsent(opts: {
|
|
248
|
+
readonly canPrompt: boolean;
|
|
249
|
+
readonly autoAcceptPrompts: boolean;
|
|
250
|
+
}): Promise<boolean | null> {
|
|
251
|
+
if (!opts.canPrompt || opts.autoAcceptPrompts || isCI()) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const config = readUserConfig();
|
|
255
|
+
const gating = resolveGating({ env: process.env, config });
|
|
256
|
+
if (!gating.enabled && gating.reason === 'env-override') {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const stored = config.enableTelemetry;
|
|
260
|
+
if (stored !== undefined) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const result = await clack.confirm({
|
|
264
|
+
message: TELEMETRY_CONSENT_MESSAGE,
|
|
265
|
+
initialValue: true,
|
|
266
|
+
output: process.stderr,
|
|
267
|
+
});
|
|
268
|
+
if (clack.isCancel(result)) {
|
|
269
|
+
throw errorInitUserAborted();
|
|
270
|
+
}
|
|
271
|
+
writeUserConfig({ enableTelemetry: Boolean(result) });
|
|
272
|
+
return Boolean(result);
|
|
273
|
+
}
|
|
274
|
+
|
|
200
275
|
async function resolveWriteEnv(opts: {
|
|
201
276
|
readonly flag: boolean | undefined;
|
|
202
277
|
readonly canPrompt: boolean;
|
|
@@ -11,7 +11,7 @@ const exec = promisify(execFile);
|
|
|
11
11
|
* upstream `skills add`. Each `SkillSource` joins this base with its
|
|
12
12
|
* own subpath (and optional `#ref` for version-pinned clusters).
|
|
13
13
|
*/
|
|
14
|
-
export const
|
|
14
|
+
export const DEFAULT_SKILL_BASE = 'prisma/prisma-next';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* One discovery scope inside the Prisma Next monorepo. The CLI emits
|
|
@@ -35,7 +35,7 @@ export interface SkillSource {
|
|
|
35
35
|
readonly description: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export const
|
|
38
|
+
export const DEFAULT_SKILL_SOURCES: readonly SkillSource[] = [
|
|
39
39
|
{
|
|
40
40
|
subpath: 'skills',
|
|
41
41
|
ref: 'cli',
|
|
@@ -56,7 +56,7 @@ export const DEFAULT_AGENT_SKILL_SOURCES: readonly SkillSource[] = [
|
|
|
56
56
|
/**
|
|
57
57
|
* Test-only escape hatch for pinning the install base to a local
|
|
58
58
|
* checkout. Production runs leave this unset, so installs always use
|
|
59
|
-
* `
|
|
59
|
+
* `DEFAULT_SKILL_BASE`.
|
|
60
60
|
*
|
|
61
61
|
* When set to an absolute filesystem path (typical for tests), the
|
|
62
62
|
* `#ref` fragment is dropped — local-path mode in upstream's CLI does
|
|
@@ -66,7 +66,7 @@ export const DEFAULT_AGENT_SKILL_SOURCES: readonly SkillSource[] = [
|
|
|
66
66
|
*/
|
|
67
67
|
function resolveAgentSkillBase(): string {
|
|
68
68
|
const override = process.env['PRISMA_NEXT_SKILLS_BASE']?.trim();
|
|
69
|
-
return override && override.length > 0 ? override :
|
|
69
|
+
return override && override.length > 0 ? override : DEFAULT_SKILL_BASE;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function isLocalPath(base: string): boolean {
|
|
@@ -162,7 +162,7 @@ function commandToExec(command: string): {
|
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
164
|
* Runs the project-level skill install for every source in
|
|
165
|
-
* `
|
|
165
|
+
* `DEFAULT_SKILL_SOURCES`, in order. Returns
|
|
166
166
|
* `{ ok: true, commands }` on success; throws a structured
|
|
167
167
|
* `errorInitSkillInstallFailed` on the first failure (subsequent
|
|
168
168
|
* sources are not attempted — the user opted into Prisma Next by
|
|
@@ -176,7 +176,7 @@ export async function runProjectLevelSkillInstall(ctx: {
|
|
|
176
176
|
readonly filesWritten: readonly string[];
|
|
177
177
|
}): Promise<{ readonly ok: true; readonly commands: readonly string[] }> {
|
|
178
178
|
const commands: string[] = [];
|
|
179
|
-
const installCommands =
|
|
179
|
+
const installCommands = DEFAULT_SKILL_SOURCES.flatMap((source) => [
|
|
180
180
|
formatSkillInstallCommand(ctx.pm, source),
|
|
181
181
|
formatClaudeSkillInstallCommand(ctx.pm, source),
|
|
182
182
|
]);
|
|
@@ -42,9 +42,10 @@ export function schemaSample(target: TargetId, authoring: AuthoringId): string {
|
|
|
42
42
|
function schemaSamplePslPostgres(): string {
|
|
43
43
|
return `\`\`\`prisma
|
|
44
44
|
model User {
|
|
45
|
-
id
|
|
46
|
-
email
|
|
47
|
-
|
|
45
|
+
id Int @id @default(autoincrement())
|
|
46
|
+
email String @unique
|
|
47
|
+
username String?
|
|
48
|
+
name String?
|
|
48
49
|
}
|
|
49
50
|
\`\`\``;
|
|
50
51
|
}
|
|
@@ -52,9 +53,10 @@ model User {
|
|
|
52
53
|
function schemaSamplePslMongo(): string {
|
|
53
54
|
return `\`\`\`prisma
|
|
54
55
|
model User {
|
|
55
|
-
id
|
|
56
|
-
email
|
|
57
|
-
|
|
56
|
+
id ObjectId @id @map("_id")
|
|
57
|
+
email String @unique
|
|
58
|
+
username String?
|
|
59
|
+
name String?
|
|
58
60
|
@@map("users")
|
|
59
61
|
}
|
|
60
62
|
\`\`\``;
|
|
@@ -74,6 +76,7 @@ export const contract = defineContract(
|
|
|
74
76
|
fields: {
|
|
75
77
|
id: field.id.uuidv7(),
|
|
76
78
|
email: field.text().unique(),
|
|
79
|
+
username: field.text().optional(),
|
|
77
80
|
name: field.text().optional(),
|
|
78
81
|
},
|
|
79
82
|
}),
|
|
@@ -98,6 +101,7 @@ export const contract = defineContract(
|
|
|
98
101
|
fields: {
|
|
99
102
|
_id: field.objectId(),
|
|
100
103
|
email: field.string(),
|
|
104
|
+
username: field.string().optional(),
|
|
101
105
|
name: field.string().optional(),
|
|
102
106
|
},
|
|
103
107
|
}),
|
|
@@ -113,6 +117,7 @@ function starterSchemaPslPostgres(): string {
|
|
|
113
117
|
model User {
|
|
114
118
|
id Int @id @default(autoincrement())
|
|
115
119
|
email String @unique
|
|
120
|
+
username String?
|
|
116
121
|
name String?
|
|
117
122
|
posts Post[]
|
|
118
123
|
createdAt DateTime @default(now())
|
|
@@ -135,10 +140,11 @@ function starterSchemaPslMongo(): string {
|
|
|
135
140
|
return `// use prisma-next
|
|
136
141
|
|
|
137
142
|
model User {
|
|
138
|
-
id
|
|
139
|
-
email
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
id ObjectId @id @map("_id")
|
|
144
|
+
email String @unique
|
|
145
|
+
username String?
|
|
146
|
+
name String?
|
|
147
|
+
posts Post[]
|
|
142
148
|
@@map("users")
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -166,6 +172,7 @@ export const contract = defineContract(
|
|
|
166
172
|
fields: {
|
|
167
173
|
id: field.id.uuidv7(),
|
|
168
174
|
email: field.text().unique(),
|
|
175
|
+
username: field.text().optional(),
|
|
169
176
|
name: field.text().optional(),
|
|
170
177
|
createdAt: field.temporal.createdAt(),
|
|
171
178
|
updatedAt: field.temporal.updatedAt(),
|
|
@@ -208,6 +215,7 @@ export const contract = defineContract(
|
|
|
208
215
|
fields: {
|
|
209
216
|
_id: field.objectId(),
|
|
210
217
|
email: field.string(),
|
|
218
|
+
username: field.string().optional(),
|
|
211
219
|
name: field.string().optional(),
|
|
212
220
|
},
|
|
213
221
|
relations: {
|
|
@@ -22,7 +22,7 @@ const user = await db.orm.users
|
|
|
22
22
|
.first();
|
|
23
23
|
|
|
24
24
|
// Your editor will show the type of user as
|
|
25
|
-
// { _id: ObjectId; email: string; name: string | null; posts: Post[] } | null
|
|
25
|
+
// { _id: ObjectId; email: string; username: string | null; name: string | null; posts: Post[] } | null
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
`db` connects to MongoDB lazily on the first query, so there is no manual `connect(...)` step in your application code. Call `await db.close()` if you need to release the underlying connection (typically only in tests or short-lived scripts). After `close()` the client is single-shot — any further query, `connect()`, or `runtime()` call rejects with `"Mongo client is closed"`. Construct a new `mongo({...})` if you need to use it again.
|
|
@@ -22,7 +22,7 @@ const user = await db.orm.User
|
|
|
22
22
|
.first();
|
|
23
23
|
|
|
24
24
|
// Your editor will show the type of user as
|
|
25
|
-
// { id: number; email: string;
|
|
25
|
+
// { id: number; email: string; username: string | null; name: string | null; createdAt: Date; posts: Post[] } | null
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Your contract has two companion files in the same directory:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isCI } from './is-ci';
|
|
2
|
+
|
|
1
3
|
export interface GlobalFlags {
|
|
2
4
|
readonly json?: boolean;
|
|
3
5
|
readonly quiet?: boolean;
|
|
@@ -70,8 +72,10 @@ export function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {
|
|
|
70
72
|
} else if (options.color !== undefined) {
|
|
71
73
|
flags.color = options.color;
|
|
72
74
|
} else {
|
|
73
|
-
// Default: enable color if TTY
|
|
74
|
-
|
|
75
|
+
// Default: enable color if TTY and not in CI. Uses the consolidated
|
|
76
|
+
// `isCI()` helper (`./is-ci.ts`) — the single source of truth shared
|
|
77
|
+
// with telemetry-skip detection.
|
|
78
|
+
flags.color = process.stdout.isTTY && !isCI();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
// Interactivity: --interactive/--no-interactive
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isCI as ciInfoIsCI } from 'ci-info';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when the process is running in any CI environment recognised
|
|
5
|
+
* by the `ci-info` package. The single source of truth for CI detection
|
|
6
|
+
* across this CLI — colour-output suppression and telemetry-skip both call
|
|
7
|
+
* this helper, so neither path drifts from the other when a new CI provider
|
|
8
|
+
* is added upstream.
|
|
9
|
+
*
|
|
10
|
+
* `ci-info` checks the standard `CI=true` marker plus dozens of
|
|
11
|
+
* provider-specific environment variables (Buildkite, Jenkins, Drone,
|
|
12
|
+
* Bitbucket Pipelines, Azure Pipelines, AWS CodeBuild, …) that the raw
|
|
13
|
+
* `process.env.CI` read misses.
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
export function isCI(): boolean {
|
|
17
|
+
return ciInfoIsCI;
|
|
18
|
+
}
|