@prisma-next/cli 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +1 -1
  2. package/dist/{cli-errors-CF60g2cG.mjs → cli-errors-Djtz98Vm.mjs} +3 -3
  3. package/dist/cli-errors-Djtz98Vm.mjs.map +1 -0
  4. package/dist/cli.mjs +151 -12
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-Brv4qlfB.mjs → client-oXO2WCPD.mjs} +6 -5
  7. package/dist/client-oXO2WCPD.mjs.map +1 -0
  8. package/dist/{command-helpers-D3vL5yi8.mjs → command-helpers-BSb0tRC8.mjs} +104 -10
  9. package/dist/command-helpers-BSb0tRC8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.d.mts.map +1 -1
  13. package/dist/commands/db-init.mjs +19 -20
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.mjs +6 -10
  16. package/dist/commands/db-schema.mjs.map +1 -1
  17. package/dist/commands/db-sign.mjs +7 -11
  18. package/dist/commands/db-sign.mjs.map +1 -1
  19. package/dist/commands/db-update.d.mts.map +1 -1
  20. package/dist/commands/db-update.mjs +16 -17
  21. package/dist/commands/db-update.mjs.map +1 -1
  22. package/dist/commands/db-verify.mjs +1 -1
  23. package/dist/commands/migrate.d.mts +1 -1
  24. package/dist/commands/migrate.mjs +7 -11
  25. package/dist/commands/migrate.mjs.map +1 -1
  26. package/dist/commands/migration-check.mjs +4 -7
  27. package/dist/commands/migration-check.mjs.map +1 -1
  28. package/dist/commands/migration-graph.d.mts +1 -1
  29. package/dist/commands/migration-graph.mjs +6 -10
  30. package/dist/commands/migration-graph.mjs.map +1 -1
  31. package/dist/commands/migration-list.mjs +5 -9
  32. package/dist/commands/migration-list.mjs.map +1 -1
  33. package/dist/commands/migration-log.d.mts.map +1 -1
  34. package/dist/commands/migration-log.mjs +7 -10
  35. package/dist/commands/migration-log.mjs.map +1 -1
  36. package/dist/commands/migration-new.mjs +6 -10
  37. package/dist/commands/migration-new.mjs.map +1 -1
  38. package/dist/commands/migration-plan.d.mts +1 -1
  39. package/dist/commands/migration-plan.mjs +1 -1
  40. package/dist/commands/migration-show.d.mts +1 -1
  41. package/dist/commands/migration-show.mjs +8 -12
  42. package/dist/commands/migration-show.mjs.map +1 -1
  43. package/dist/commands/migration-status.d.mts +1 -1
  44. package/dist/commands/migration-status.d.mts.map +1 -1
  45. package/dist/commands/migration-status.mjs +36 -14
  46. package/dist/commands/migration-status.mjs.map +1 -1
  47. package/dist/commands/ref.d.mts +1 -1
  48. package/dist/commands/ref.mjs +9 -19
  49. package/dist/commands/ref.mjs.map +1 -1
  50. package/dist/{contract-emit-iynA3BCA.mjs → contract-emit-bcrpT-wD.mjs} +3 -3
  51. package/dist/{contract-emit-iynA3BCA.mjs.map → contract-emit-bcrpT-wD.mjs.map} +1 -1
  52. package/dist/{contract-emit-C3STUIBg.mjs → contract-emit-r4y8Zhf1.mjs} +7 -12
  53. package/dist/contract-emit-r4y8Zhf1.mjs.map +1 -0
  54. package/dist/{contract-infer-Cnj8G1E2.mjs → contract-infer-BmySmqVT.mjs} +8 -13
  55. package/dist/contract-infer-BmySmqVT.mjs.map +1 -0
  56. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs → contract-space-aggregate-loader-BmNQwlws.mjs} +2 -2
  57. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs.map → contract-space-aggregate-loader-BmNQwlws.mjs.map} +1 -1
  58. package/dist/{db-verify-D7cyH_zz.mjs → db-verify-BClPs3ph.mjs} +9 -13
  59. package/dist/db-verify-BClPs3ph.mjs.map +1 -0
  60. package/dist/exports/control-api.d.mts +1 -1
  61. package/dist/exports/control-api.mjs +2 -2
  62. package/dist/exports/index.mjs +2 -2
  63. package/dist/exports/init-output.mjs +1 -1
  64. package/dist/{framework-components-xFLFpZUO.mjs → framework-components-65gOHkHB.mjs} +2 -2
  65. package/dist/{framework-components-xFLFpZUO.mjs.map → framework-components-65gOHkHB.mjs.map} +1 -1
  66. package/dist/{global-flags-DGmw6Kqg.d.mts → global-flags-CdE7M0d9.d.mts} +4 -1
  67. package/dist/global-flags-CdE7M0d9.d.mts.map +1 -0
  68. package/dist/{graph-render-eJDcLWny.mjs → graph-render-DJVv0_uf.mjs} +1 -1
  69. package/dist/{graph-render-eJDcLWny.mjs.map → graph-render-DJVv0_uf.mjs.map} +1 -1
  70. package/dist/{init-eh2z5Tl6.mjs → init-BCJZPWE1.mjs} +547 -399
  71. package/dist/init-BCJZPWE1.mjs.map +1 -0
  72. package/dist/{inspect-live-schema-CWLK_lgs.mjs → inspect-live-schema-DSRbFoOL.mjs} +4 -4
  73. package/dist/{inspect-live-schema-CWLK_lgs.mjs.map → inspect-live-schema-DSRbFoOL.mjs.map} +1 -1
  74. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs → migration-command-scaffold-Bzd9La5c.mjs} +4 -4
  75. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs.map → migration-command-scaffold-Bzd9La5c.mjs.map} +1 -1
  76. package/dist/{migration-plan-CHyUlBV0.mjs → migration-plan-CFwqw3Gk.mjs} +8 -12
  77. package/dist/migration-plan-CFwqw3Gk.mjs.map +1 -0
  78. package/dist/{migration-types-D2FW63pr.d.mts → migration-types-BXWvz12q.d.mts} +1 -1
  79. package/dist/{migration-types-D2FW63pr.d.mts.map → migration-types-BXWvz12q.d.mts.map} +1 -1
  80. package/dist/{migrations-DyUf5lTt.mjs → migrations-CwZMa1Ck.mjs} +2 -2
  81. package/dist/{migrations-DyUf5lTt.mjs.map → migrations-CwZMa1Ck.mjs.map} +1 -1
  82. package/dist/{output-B60Gw5fu.mjs → output-BlsrGMEF.mjs} +1 -1
  83. package/dist/{output-B60Gw5fu.mjs.map → output-BlsrGMEF.mjs.map} +1 -1
  84. package/dist/quick-reference-mongo.md +1 -1
  85. package/dist/quick-reference-postgres.md +1 -1
  86. package/dist/readme-mongo.md +35 -0
  87. package/dist/readme-postgres.md +34 -0
  88. package/dist/{terminal-ui-XtOQsqe9.mjs → terminal-ui-BiB_8KNo.mjs} +131 -24
  89. package/dist/terminal-ui-BiB_8KNo.mjs.map +1 -0
  90. package/dist/{types-0aS865QN.d.mts → types--CqjMdk0.d.mts} +2 -2
  91. package/dist/{types-0aS865QN.d.mts.map → types--CqjMdk0.d.mts.map} +1 -1
  92. package/dist/{verify-D7ypCCe6.mjs → verify-Bom75OYI.mjs} +2 -2
  93. package/dist/{verify-D7ypCCe6.mjs.map → verify-Bom75OYI.mjs.map} +1 -1
  94. package/package.json +19 -17
  95. package/src/cli.ts +42 -0
  96. package/src/commands/contract-emit.ts +4 -4
  97. package/src/commands/contract-infer.ts +6 -6
  98. package/src/commands/db-init.ts +13 -5
  99. package/src/commands/db-schema.ts +4 -4
  100. package/src/commands/db-sign.ts +4 -4
  101. package/src/commands/db-update.ts +13 -5
  102. package/src/commands/db-verify.ts +5 -5
  103. package/src/commands/init/detect-package-manager.ts +15 -0
  104. package/src/commands/init/errors.ts +33 -2
  105. package/src/commands/init/index.ts +13 -5
  106. package/src/commands/init/init.ts +61 -32
  107. package/src/commands/init/inputs.ts +82 -5
  108. package/src/commands/init/output.ts +1 -1
  109. package/src/commands/init/{agent-skill-install.ts → skill-install.ts} +42 -31
  110. package/src/commands/init/templates/code-templates.ts +22 -22
  111. package/src/commands/init/templates/env.ts +8 -1
  112. package/src/commands/init/templates/quick-reference-mongo.md +1 -1
  113. package/src/commands/init/templates/quick-reference-postgres.md +1 -1
  114. package/src/commands/init/templates/readme-mongo.md +35 -0
  115. package/src/commands/init/templates/readme-postgres.md +34 -0
  116. package/src/commands/init/templates/readme.ts +62 -0
  117. package/src/commands/migrate.ts +4 -7
  118. package/src/commands/migration-check.ts +4 -4
  119. package/src/commands/migration-graph.ts +4 -4
  120. package/src/commands/migration-list.ts +4 -4
  121. package/src/commands/migration-log.ts +6 -5
  122. package/src/commands/migration-new.ts +4 -4
  123. package/src/commands/migration-plan.ts +4 -4
  124. package/src/commands/migration-show.ts +4 -4
  125. package/src/commands/migration-status.ts +49 -6
  126. package/src/commands/ref.ts +8 -8
  127. package/src/control-api/operations/apply-aggregate.ts +1 -0
  128. package/src/utils/cli-errors.ts +4 -0
  129. package/src/utils/command-helpers.ts +6 -2
  130. package/src/utils/global-flags.ts +105 -16
  131. package/src/utils/is-ci.ts +18 -0
  132. package/src/utils/telemetry.ts +141 -0
  133. package/src/utils/terminal-ui.ts +44 -23
  134. package/dist/cli-errors-CF60g2cG.mjs.map +0 -1
  135. package/dist/client-Brv4qlfB.mjs.map +0 -1
  136. package/dist/command-helpers-D3vL5yi8.mjs.map +0 -1
  137. package/dist/contract-emit-C3STUIBg.mjs.map +0 -1
  138. package/dist/contract-infer-Cnj8G1E2.mjs.map +0 -1
  139. package/dist/db-verify-D7cyH_zz.mjs.map +0 -1
  140. package/dist/errors-Cw6kyTyV.mjs +0 -56
  141. package/dist/errors-Cw6kyTyV.mjs.map +0 -1
  142. package/dist/global-flags-DGmw6Kqg.d.mts.map +0 -1
  143. package/dist/helpers-eqdN8tH6.mjs +0 -25
  144. package/dist/helpers-eqdN8tH6.mjs.map +0 -1
  145. package/dist/init-eh2z5Tl6.mjs.map +0 -1
  146. package/dist/migration-plan-CHyUlBV0.mjs.map +0 -1
  147. package/dist/result-handler-Bm_6dDYg.mjs +0 -25
  148. package/dist/result-handler-Bm_6dDYg.mjs.map +0 -1
  149. package/dist/terminal-ui-XtOQsqe9.mjs.map +0 -1
  150. /package/dist/{cli-errors-DdcjVLJV.d.mts → cli-errors-Czmx92Zy.d.mts} +0 -0
@@ -1,8 +1,11 @@
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 {
8
+ errorInitAuthoringSchemaPathMismatch,
6
9
  errorInitInvalidFlagValue,
7
10
  errorInitMissingFlags,
8
11
  errorInitReinitNeedsForce,
@@ -68,12 +71,24 @@ export interface ResolvedInitInputs {
68
71
  * is added separately via the install step.
69
72
  */
70
73
  readonly removePreviousFacade: string | null;
74
+ /**
75
+ * Telemetry consent answer recorded during this `init` run, or `null`
76
+ * when no prompt was shown. The prompt fires only on the
77
+ * canPrompt + !autoAcceptPrompts + no env/CI opt-out +
78
+ * `enableTelemetry === undefined` intersection; outside that window
79
+ * the field is `null` and the stored preference (if any) stays
80
+ * unchanged. The answer has already been persisted to
81
+ * `$XDG_CONFIG_HOME/prisma-next/config.json` by
82
+ * the time `runInit` sees this value — it's surfaced here purely so
83
+ * the post-init summary can mention what the user chose.
84
+ */
85
+ readonly enableTelemetry: boolean | null;
71
86
  /**
72
87
  * Whether to run `npx skills add prisma/prisma-next#v<version>` at the
73
88
  * project level after install + emit. True by default; `--no-skill`
74
89
  * sets it to `false`. The skill is always project-level (never
75
90
  * user-level / global) so its version stays locked to the project's
76
- * Prisma Next version — see `agent-skill-install.ts`.
91
+ * Prisma Next version — see `skill-install.ts`.
77
92
  */
78
93
  readonly installProjectSkill: boolean;
79
94
  }
@@ -91,6 +106,12 @@ const AUTHORING_VALUES: ReadonlyMap<string, AuthoringId> = new Map([
91
106
  ['ts', 'typescript'],
92
107
  ]);
93
108
 
109
+ export const TELEMETRY_CONSENT_MESSAGE = [
110
+ 'Help us prioritize features by sharing anonymous CLI usage data?',
111
+ 'The telemetry implementation is open source and fully transparent.',
112
+ '(packages/1-framework/3-tooling/cli-telemetry and apps/telemetry-backend).',
113
+ ].join(' ');
114
+
94
115
  /**
95
116
  * Resolves every required input for `runInit`. In interactive mode, missing
96
117
  * inputs are prompted via clack; in non-interactive mode, missing required
@@ -176,6 +197,11 @@ export async function resolveInitInputs(ctx: {
176
197
  autoAcceptPrompts,
177
198
  });
178
199
 
200
+ const enableTelemetry = await resolveTelemetryConsent({
201
+ canPrompt,
202
+ autoAcceptPrompts,
203
+ });
204
+
179
205
  // Skill-install gating. `--no-skill` (commander parses
180
206
  // `options.skill === false`) is the only escape hatch; otherwise
181
207
  // project-level install is unconditional. The skill is always
@@ -193,10 +219,60 @@ export async function resolveInitInputs(ctx: {
193
219
  strictProbe: Boolean(options.strictProbe),
194
220
  reinit,
195
221
  removePreviousFacade,
222
+ enableTelemetry,
196
223
  installProjectSkill,
197
224
  };
198
225
  }
199
226
 
227
+ /**
228
+ * The interactive telemetry consent prompt. Shown as the last
229
+ * question of the `init` sequence iff:
230
+ * 1. `canPrompt === true` (interactive stdin / stdout combo),
231
+ * 2. `autoAcceptPrompts === false` (the user did not pass `--yes`),
232
+ * 3. neither telemetry env opt-out is active,
233
+ * 4. the process is not running in CI,
234
+ * 5. the stored `enableTelemetry` value is `undefined` (the user
235
+ * has never been asked, or skipped the prompt previously).
236
+ *
237
+ * Outside that intersection the function returns `null` and leaves the
238
+ * stored preference untouched. Inside it, the user's answer is
239
+ * persisted via `writeUserConfig({ enableTelemetry })` before this
240
+ * function returns; on an affirmative answer `writeUserConfig`
241
+ * generates and stores the v4 `installationId` in the same write.
242
+ *
243
+ * The wording names CLI usage data and points to the open-source
244
+ * client/backend paths. Default value is `true` to match precedent
245
+ * and to make the consent answer a single keystroke once it's been
246
+ * disclosed.
247
+ */
248
+ async function resolveTelemetryConsent(opts: {
249
+ readonly canPrompt: boolean;
250
+ readonly autoAcceptPrompts: boolean;
251
+ }): Promise<boolean | null> {
252
+ if (!opts.canPrompt || opts.autoAcceptPrompts || isCI()) {
253
+ return null;
254
+ }
255
+ const config = readUserConfig();
256
+ const gating = resolveGating({ env: process.env, config });
257
+ if (!gating.enabled && gating.reason === 'env-override') {
258
+ return null;
259
+ }
260
+ const stored = config.enableTelemetry;
261
+ if (stored !== undefined) {
262
+ return null;
263
+ }
264
+ const result = await clack.confirm({
265
+ message: TELEMETRY_CONSENT_MESSAGE,
266
+ initialValue: true,
267
+ output: process.stderr,
268
+ });
269
+ if (clack.isCancel(result)) {
270
+ throw errorInitUserAborted();
271
+ }
272
+ writeUserConfig({ enableTelemetry: Boolean(result) });
273
+ return Boolean(result);
274
+ }
275
+
200
276
  async function resolveWriteEnv(opts: {
201
277
  readonly flag: boolean | undefined;
202
278
  readonly canPrompt: boolean;
@@ -376,10 +452,11 @@ function validateSchemaPath(value: string, authoring: AuthoringId): string {
376
452
  const ext = extname(trimmed).toLowerCase();
377
453
  const expected = authoring === 'typescript' ? '.ts' : '.prisma';
378
454
  if (ext !== expected) {
379
- throw errorInitInvalidFlagValue({
380
- flag: 'schema-path',
381
- value,
382
- allowed: [`<file path ending in ${expected} for --authoring ${authoring}>`],
455
+ throw errorInitAuthoringSchemaPathMismatch({
456
+ authoring,
457
+ schemaPath: trimmed,
458
+ actualExtension: ext.length > 0 ? ext : '(none)',
459
+ expectedExtension: expected,
383
460
  });
384
461
  }
385
462
  return normalize(trimmed);
@@ -123,7 +123,7 @@ export function buildNextSteps(options: {
123
123
  /**
124
124
  * Whether the project-level Prisma Next skills install actually ran
125
125
  * and succeeded during this `init`. When false (the user passed
126
- * `--no-skill` or `--no-install`, so the install was skipped), the
126
+ * `--no-skill`, so the install was skipped), the
127
127
  * "registered with your agent runtime" step is omitted — the skip is
128
128
  * already surfaced in the warnings array with a manual-install hint.
129
129
  */
@@ -11,12 +11,12 @@ 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 DEFAULT_AGENT_SKILL_BASE = 'prisma/prisma-next';
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
18
- * one `skills add <base>/<subpath>[#ref] --all` invocation per source
19
- * during `init`.
18
+ * one `skills add <base>/<subpath>[#ref] --agent ... --skill '*' -y`
19
+ * invocation per source during `init`.
20
20
  *
21
21
  * `ref` semantics:
22
22
  * - `cli`: pin to the CLI's own package version (lockstep with the
@@ -35,7 +35,7 @@ export interface SkillSource {
35
35
  readonly description: string;
36
36
  }
37
37
 
38
- export const DEFAULT_AGENT_SKILL_SOURCES: readonly SkillSource[] = [
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
- * `DEFAULT_AGENT_SKILL_BASE`.
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,13 +66,28 @@ 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 : DEFAULT_AGENT_SKILL_BASE;
69
+ return override && override.length > 0 ? override : DEFAULT_SKILL_BASE;
70
70
  }
71
71
 
72
72
  function isLocalPath(base: string): boolean {
73
73
  return base.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(base);
74
74
  }
75
75
 
76
+ /** Agent slugs accepted by the upstream `skills add --agent` flag. */
77
+ export type SkillAgent = 'cursor' | 'claude-code' | 'codex' | 'windsurf';
78
+
79
+ /**
80
+ * Agents passed to every project-level init install. Upstream `skills add`
81
+ * is the source of truth for per-agent install behaviour; the CLI lists
82
+ * every supported runtime on one invocation and delegates the rest.
83
+ */
84
+ export const DEFAULT_SKILL_AGENTS: readonly SkillAgent[] = [
85
+ 'cursor',
86
+ 'claude-code',
87
+ 'codex',
88
+ 'windsurf',
89
+ ];
90
+
76
91
  /**
77
92
  * Build the `<base>/<subpath>[#ref]` URL the `skills` CLI will
78
93
  * resolve. Exported for unit tests so the per-source format can be
@@ -94,37 +109,36 @@ export function formatSkillSourceUrl(source: SkillSource): string {
94
109
  * rest of the install step so a single project consistently uses one
95
110
  * runner.
96
111
  *
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.
112
+ * `--agent` takes space-separated slugs on one flag; `--skill '*'` and `-y`
113
+ * skip the multi-select prompts a non-interactive scaffold step cannot show.
101
114
  *
102
115
  * Exported for unit tests so the per-PM dispatch can be asserted
103
116
  * without a live subprocess.
104
117
  */
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
+ export function formatSkillInstallCommand(args: {
119
+ readonly pm: PackageManager;
120
+ readonly source: SkillSource;
121
+ readonly agents?: readonly SkillAgent[];
122
+ }): string {
123
+ const agents = args.agents ?? DEFAULT_SKILL_AGENTS;
124
+ const cliArgs = [
118
125
  'skills@latest',
119
126
  'add',
120
- formatSkillSourceUrl(source),
127
+ formatSkillSourceUrl(args.source),
121
128
  '--agent',
122
- 'claude-code',
129
+ ...agents,
123
130
  '--skill',
124
131
  "'*'",
125
132
  '-y',
126
133
  ];
127
- return formatPackageManagerCommand(pm, args);
134
+ return formatPackageManagerCommand(args.pm, cliArgs);
135
+ }
136
+
137
+ /**
138
+ * Ordered skill-install commands for one init run. Exported for unit tests.
139
+ */
140
+ export function resolveProjectSkillInstallCommands(pm: PackageManager): readonly string[] {
141
+ return DEFAULT_SKILL_SOURCES.map((source) => formatSkillInstallCommand({ pm, source }));
128
142
  }
129
143
 
130
144
  function formatPackageManagerCommand(pm: PackageManager, args: readonly string[]): string {
@@ -162,7 +176,7 @@ function commandToExec(command: string): {
162
176
 
163
177
  /**
164
178
  * Runs the project-level skill install for every source in
165
- * `DEFAULT_AGENT_SKILL_SOURCES`, in order. Returns
179
+ * `DEFAULT_SKILL_SOURCES`, in order. Returns
166
180
  * `{ ok: true, commands }` on success; throws a structured
167
181
  * `errorInitSkillInstallFailed` on the first failure (subsequent
168
182
  * sources are not attempted — the user opted into Prisma Next by
@@ -176,10 +190,7 @@ export async function runProjectLevelSkillInstall(ctx: {
176
190
  readonly filesWritten: readonly string[];
177
191
  }): Promise<{ readonly ok: true; readonly commands: readonly string[] }> {
178
192
  const commands: string[] = [];
179
- const installCommands = DEFAULT_AGENT_SKILL_SOURCES.flatMap((source) => [
180
- formatSkillInstallCommand(ctx.pm, source),
181
- formatClaudeSkillInstallCommand(ctx.pm, source),
182
- ]);
193
+ const installCommands = resolveProjectSkillInstallCommands(ctx.pm);
183
194
 
184
195
  for (const command of installCommands) {
185
196
  const { file, args } = commandToExec(command);
@@ -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 Int @id @default(autoincrement())
46
- email String @unique
47
- name String?
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 ObjectId @id @map("_id")
56
- email String @unique
57
- name String?
56
+ id ObjectId @id @map("_id")
57
+ email String @unique
58
+ username String?
59
+ name String?
58
60
  @@map("users")
59
61
  }
60
62
  \`\`\``;
@@ -63,17 +65,16 @@ model User {
63
65
  function schemaSampleTsPostgres(): string {
64
66
  return `\`\`\`typescript
65
67
  import { defineContract } from '@prisma-next/postgres/contract-builder';
66
- import sqlFamily from '@prisma-next/postgres/family';
67
- import postgresTarget from '@prisma-next/postgres/target';
68
68
 
69
69
  export const contract = defineContract(
70
- { family: sqlFamily, target: postgresTarget },
70
+ {},
71
71
  ({ field, model }) => ({
72
72
  models: {
73
73
  User: model('User', {
74
74
  fields: {
75
75
  id: field.id.uuidv7(),
76
76
  email: field.text().unique(),
77
+ username: field.text().optional(),
77
78
  name: field.text().optional(),
78
79
  },
79
80
  }),
@@ -86,11 +87,9 @@ export const contract = defineContract(
86
87
  function schemaSampleTsMongo(): string {
87
88
  return `\`\`\`typescript
88
89
  import { defineContract } from '@prisma-next/mongo/contract-builder';
89
- import mongoFamily from '@prisma-next/mongo/family';
90
- import mongoTarget from '@prisma-next/mongo/target';
91
90
 
92
91
  export const contract = defineContract(
93
- { family: mongoFamily, target: mongoTarget },
92
+ {},
94
93
  ({ field, model }) => ({
95
94
  models: {
96
95
  User: model('User', {
@@ -98,6 +97,7 @@ export const contract = defineContract(
98
97
  fields: {
99
98
  _id: field.objectId(),
100
99
  email: field.string(),
100
+ username: field.string().optional(),
101
101
  name: field.string().optional(),
102
102
  },
103
103
  }),
@@ -113,6 +113,7 @@ function starterSchemaPslPostgres(): string {
113
113
  model User {
114
114
  id Int @id @default(autoincrement())
115
115
  email String @unique
116
+ username String?
116
117
  name String?
117
118
  posts Post[]
118
119
  createdAt DateTime @default(now())
@@ -135,10 +136,11 @@ function starterSchemaPslMongo(): string {
135
136
  return `// use prisma-next
136
137
 
137
138
  model User {
138
- id ObjectId @id @map("_id")
139
- email String @unique
140
- name String?
141
- posts Post[]
139
+ id ObjectId @id @map("_id")
140
+ email String @unique
141
+ username String?
142
+ name String?
143
+ posts Post[]
142
144
  @@map("users")
143
145
  }
144
146
 
@@ -155,17 +157,16 @@ model Post {
155
157
 
156
158
  function starterSchemaTsPostgres(): string {
157
159
  return `import { defineContract } from '@prisma-next/postgres/contract-builder';
158
- import sqlFamily from '@prisma-next/postgres/family';
159
- import postgresTarget from '@prisma-next/postgres/target';
160
160
 
161
161
  export const contract = defineContract(
162
- { family: sqlFamily, target: postgresTarget },
162
+ {},
163
163
  ({ field, model, rel }) => ({
164
164
  models: {
165
165
  User: model('User', {
166
166
  fields: {
167
167
  id: field.id.uuidv7(),
168
168
  email: field.text().unique(),
169
+ username: field.text().optional(),
169
170
  name: field.text().optional(),
170
171
  createdAt: field.temporal.createdAt(),
171
172
  updatedAt: field.temporal.updatedAt(),
@@ -196,11 +197,9 @@ export const contract = defineContract(
196
197
 
197
198
  function starterSchemaTsMongo(): string {
198
199
  return `import { defineContract } from '@prisma-next/mongo/contract-builder';
199
- import mongoFamily from '@prisma-next/mongo/family';
200
- import mongoTarget from '@prisma-next/mongo/target';
201
200
 
202
201
  export const contract = defineContract(
203
- { family: mongoFamily, target: mongoTarget },
202
+ {},
204
203
  ({ field, model, rel }) => ({
205
204
  models: {
206
205
  User: model('User', {
@@ -208,6 +207,7 @@ export const contract = defineContract(
208
207
  fields: {
209
208
  _id: field.objectId(),
210
209
  email: field.string(),
210
+ username: field.string().optional(),
211
211
  name: field.string().optional(),
212
212
  },
213
213
  relations: {
@@ -40,7 +40,14 @@ function envPlaceholderBody(target: TargetId): string {
40
40
  if (target === 'postgres') {
41
41
  lines.push('DATABASE_URL="postgresql://user:password@localhost:5432/mydb"');
42
42
  } else {
43
- lines.push('DATABASE_URL="mongodb://localhost:27017/mydb"');
43
+ lines.push(
44
+ '# Standalone local mongod / `docker run mongo:7` — no replica set required for first-run queries.',
45
+ );
46
+ lines.push(
47
+ '# Transactions and change streams need a replica set; add ?replicaSet=... only after initiating one.',
48
+ );
49
+ lines.push('');
50
+ lines.push('DATABASE_URL="mongodb://user:password@localhost:27017/mydb"');
44
51
  }
45
52
  lines.push('');
46
53
  return lines.join('\n');
@@ -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; createdAt: Date, name: string, posts: Post[] } | null
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:
@@ -0,0 +1,35 @@
1
+ # {{projectName}}
2
+
3
+ Generated by `prisma-next init` with the Minimal template.
4
+
5
+ ## First run
6
+
7
+ 1. `{{runDbUp}}` — start the local MongoDB replica set with `mongodb-memory-server`. Data persists in `.mongo-data/` across restarts.
8
+ 2. `{{runDev}}` — runs `src/index.ts`, which writes one row and reads it back.
9
+
10
+ ## Available scripts
11
+
12
+ - `{{runDev}}` — run the Prisma Next sample script
13
+
14
+ ### Database and migrations
15
+
16
+ - `{{runContractEmit}}` — emit contract artifacts after contract changes
17
+ - `{{runDbUp}}` — start the local MongoDB replica set with `mongodb-memory-server`. Data persists in `.mongo-data/` across restarts.
18
+ - `{{runDbDown}}` — stop the local MongoDB process (data preserved)
19
+ - `{{runDbReset}}` — stop and wipe `.mongo-data/` for a clean slate
20
+ - `{{runMigrationPlan}}` — create a MongoDB migration plan
21
+ - `{{runMigrate}}` — apply the planned MongoDB migration
22
+ - `{{runDbSeed}}` — insert sample users manually
23
+
24
+ ## Prisma Next
25
+
26
+ Prisma Next setup is scaffolded in:
27
+
28
+ - `{{contractPath}}`
29
+ - `prisma-next.config.ts`
30
+ - `prisma/db.ts`
31
+ - `src/lib/prisma.ts`
32
+
33
+ For provider-specific Prisma Next reference docs, see `prisma-next.md`. Prisma Next skills live in the upstream `skills/` directory: https://github.com/prisma/prisma-next/tree/main/skills.
34
+
35
+ Node-based Prisma Next projects expect Node.js 24 LTS or newer.
@@ -0,0 +1,34 @@
1
+ # {{projectName}}
2
+
3
+ Generated by `prisma-next init` with the Minimal template.
4
+
5
+ ## First run
6
+
7
+ 1. `{{runDbInit}}` — applies your contract to the database (creates tables, signs the marker).
8
+ 2. `{{runDev}}` — runs `src/index.ts`, which writes one row and reads it back.
9
+
10
+ ## Available scripts
11
+
12
+ - `{{runDev}}` — run the Prisma Next sample script
13
+
14
+ ### Database and migrations
15
+
16
+ - `{{runContractEmit}}` — emit contract artifacts after contract changes
17
+ - `{{runDbInit}}` — initialize database state manually
18
+ - `{{runDbUpdate}}` — update database state manually
19
+ - `{{runMigrationPlan}}` — create a migration plan
20
+ - `{{runMigrate}}` — apply a planned migration
21
+ - `{{runDbSeed}}` — insert sample users manually (run after `db:init`)
22
+
23
+ ## Prisma Next
24
+
25
+ Prisma Next setup is scaffolded in:
26
+
27
+ - `{{contractPath}}`
28
+ - `prisma-next.config.ts`
29
+ - `prisma/db.ts`
30
+ - `src/lib/prisma.ts`
31
+
32
+ For provider-specific Prisma Next reference docs, see `prisma-next.md`. Prisma Next skills live in the upstream `skills/` directory: https://github.com/prisma/prisma-next/tree/main/skills.
33
+
34
+ Node-based Prisma Next projects expect Node.js 24 LTS or newer.
@@ -0,0 +1,62 @@
1
+ import { formatRunScriptCommand, type PackageManager } from '../detect-package-manager';
2
+ import type { TargetId } from './code-templates';
3
+ import { renderTemplate } from './render';
4
+
5
+ const sharedVariables = ['projectName', 'contractPath', 'runDev', 'runContractEmit'] as const;
6
+
7
+ export const postgresVariables = [
8
+ ...sharedVariables,
9
+ 'runDbInit',
10
+ 'runDbUpdate',
11
+ 'runMigrationPlan',
12
+ 'runMigrate',
13
+ 'runDbSeed',
14
+ ] as const;
15
+
16
+ export const mongoVariables = [
17
+ ...sharedVariables,
18
+ 'runDbUp',
19
+ 'runDbDown',
20
+ 'runDbReset',
21
+ 'runMigrationPlan',
22
+ 'runMigrate',
23
+ 'runDbSeed',
24
+ ] as const;
25
+
26
+ type PostgresVars = Record<(typeof postgresVariables)[number], string>;
27
+ type MongoVars = Record<(typeof mongoVariables)[number], string>;
28
+
29
+ export function minimalProjectReadmeMd(
30
+ target: TargetId,
31
+ schemaPath: string,
32
+ projectName: string,
33
+ pm: PackageManager,
34
+ ): string {
35
+ const run = (script: string): string => formatRunScriptCommand(pm, script);
36
+ const shared = {
37
+ projectName,
38
+ contractPath: schemaPath,
39
+ runDev: run('dev'),
40
+ runContractEmit: run('contract:emit'),
41
+ runMigrationPlan: run('migration:plan'),
42
+ runMigrate: run('migrate'),
43
+ runDbSeed: run('db:seed'),
44
+ };
45
+
46
+ if (target === 'mongo') {
47
+ const vars: MongoVars = {
48
+ ...shared,
49
+ runDbUp: run('db:up'),
50
+ runDbDown: run('db:down'),
51
+ runDbReset: run('db:reset'),
52
+ };
53
+ return renderTemplate('readme-mongo.md', mongoVariables, vars);
54
+ }
55
+
56
+ const vars: PostgresVars = {
57
+ ...shared,
58
+ runDbInit: run('db:init'),
59
+ runDbUpdate: run('db:update'),
60
+ };
61
+ return renderTemplate('readme-postgres.md', postgresVariables, vars);
62
+ }
@@ -43,9 +43,9 @@ import {
43
43
  import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
44
44
  import { formatStyledHeader } from '../utils/formatters/styled';
45
45
  import type { CommonCommandOptions } from '../utils/global-flags';
46
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
46
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
47
47
  import { handleResult } from '../utils/result-handler';
48
- import { TerminalUI } from '../utils/terminal-ui';
48
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
49
49
 
50
50
  interface MigrateCommandOptions extends CommonCommandOptions {
51
51
  readonly db?: string;
@@ -319,13 +319,10 @@ export function createMigrateCommand(): Command {
319
319
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
320
320
  )
321
321
  .action(async (options: MigrateCommandOptions) => {
322
- const flags = parseGlobalFlags(options);
322
+ const flags = parseGlobalFlagsOrExit(options);
323
323
  const startTime = Date.now();
324
324
 
325
- const ui = new TerminalUI({
326
- color: flags.color,
327
- interactive: flags.interactive,
328
- });
325
+ const ui = createTerminalUI(flags);
329
326
 
330
327
  const result = await executeMigrateCommand(options, flags, ui, startTime);
331
328
 
@@ -17,8 +17,8 @@ import {
17
17
  } from '../utils/command-helpers';
18
18
  import { formatStyledHeader } from '../utils/formatters/styled';
19
19
  import type { CommonCommandOptions } from '../utils/global-flags';
20
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
21
- import { TerminalUI } from '../utils/terminal-ui';
20
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
21
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
22
22
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
23
23
 
24
24
  interface MigrationCheckOptions extends CommonCommandOptions {
@@ -335,8 +335,8 @@ export function createMigrationCheckCommand(): Command {
335
335
  .argument('[migration]', 'Migration reference (directory name or hash) to check')
336
336
  .option('--config <path>', 'Path to prisma-next.config.ts')
337
337
  .action(async (target: string | undefined, options: MigrationCheckOptions) => {
338
- const flags = parseGlobalFlags(options);
339
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
338
+ const flags = parseGlobalFlagsOrExit(options);
339
+ const ui = createTerminalUI(flags);
340
340
 
341
341
  let result: MigrationCheckResult;
342
342
  let exitCode: number;
@@ -23,10 +23,10 @@ import { migrationGraphToRenderInput } from '../utils/formatters/graph-migration
23
23
  import { graphRenderer } from '../utils/formatters/graph-render';
24
24
  import { formatStyledHeader } from '../utils/formatters/styled';
25
25
  import type { CommonCommandOptions } from '../utils/global-flags';
26
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
26
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
27
27
  import type { StatusRef } from '../utils/migration-types';
28
28
  import { handleResult } from '../utils/result-handler';
29
- import { TerminalUI } from '../utils/terminal-ui';
29
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
30
30
 
31
31
  interface MigrationGraphOptions extends CommonCommandOptions {
32
32
  readonly config?: string;
@@ -130,8 +130,8 @@ export function createMigrationGraphCommand(): Command {
130
130
  .option('--config <path>', 'Path to prisma-next.config.ts')
131
131
  .option('--dot', 'Output in Graphviz DOT format')
132
132
  .action(async (options: MigrationGraphOptions) => {
133
- const flags = parseGlobalFlags(options);
134
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
133
+ const flags = parseGlobalFlagsOrExit(options);
134
+ const ui = createTerminalUI(flags);
135
135
  const result = await executeMigrationGraphCommand(options, flags, ui);
136
136
  const exitCode = handleResult(result, flags, ui, (graphResult) => {
137
137
  // Explicit format flags win over the auto-JSON default. `flags.json`