@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,16 +1,178 @@
1
- import { t as CliStructuredError } from "./cli-errors-CF60g2cG.mjs";
2
- import { n as formatErrorOutput, t as formatErrorJson } from "./errors-Cw6kyTyV.mjs";
3
- import { t as TerminalUI } from "./terminal-ui-XtOQsqe9.mjs";
1
+ import { t as CliStructuredError } from "./cli-errors-Djtz98Vm.mjs";
2
+ import { a as isCI, i as formatErrorOutput, r as formatErrorJson, t as createTerminalUI } from "./terminal-ui-BiB_8KNo.mjs";
4
3
  import { t as version } from "./cli.mjs";
5
- import { i as renderInitOutro, n as buildNextSteps, r as formatInitJson, t as InitOutputSchema } from "./output-B60Gw5fu.mjs";
4
+ import { i as renderInitOutro, n as buildNextSteps, r as formatInitJson, t as InitOutputSchema } from "./output-BlsrGMEF.mjs";
6
5
  import { createRequire } from "node:module";
7
6
  import { basename, dirname, extname, isAbsolute, join, normalize } from "pathe";
8
7
  import * as clack from "@clack/prompts";
9
8
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { readUserConfig, resolveGating, writeUserConfig } from "@prisma-next/cli-telemetry";
10
10
  import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
12
12
  import { detect, getUserAgent } from "package-manager-detector/detect";
13
13
  import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
14
+ //#region src/commands/init/detect-package-manager.ts
15
+ const KNOWN = new Set([
16
+ "pnpm",
17
+ "npm",
18
+ "yarn",
19
+ "bun",
20
+ "deno"
21
+ ]);
22
+ /**
23
+ * Resolves the package manager `init` should drive for `add` / `install`
24
+ * commands. Tries, in order:
25
+ *
26
+ * 1. **`detect()`** — walks up from `cwd` looking for a lockfile, the
27
+ * `packageManager` field, the `devEngines.packageManager` field, or
28
+ * install metadata. This is the right answer whenever the user is
29
+ * anywhere inside an existing project, including a deep workspace
30
+ * subdirectory.
31
+ *
32
+ * 2. **`getUserAgent()`** — parses `npm_config_user_agent`, the env var
33
+ * every PM sets when it spawns a script. This catches the
34
+ * bare-directory case where there's no project to walk up to but the
35
+ * user invoked us via `pnpm dlx prisma-next init` / `bunx
36
+ * prisma-next init` / `yarn dlx …`. Same signal used by every
37
+ * `create-*` tool in the ecosystem (`create-vite`, `create-next-app`,
38
+ * `create-astro`, `@antfu/ni`, …).
39
+ *
40
+ * 3. **`npm`** — final fallback. Always present alongside Node.
41
+ */
42
+ async function detectPackageManager(cwd) {
43
+ const detected = await detect({ cwd });
44
+ if (detected && KNOWN.has(detected.name)) return detected.name;
45
+ const userAgent = getUserAgent();
46
+ if (userAgent !== null && KNOWN.has(userAgent)) return userAgent;
47
+ return "npm";
48
+ }
49
+ function hasProjectManifest(cwd) {
50
+ return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
51
+ }
52
+ function formatRunCommand(pm, bin, args) {
53
+ if (pm === "npm") return `npx ${bin} ${args}`;
54
+ if (pm === "deno") return `deno run npm:${bin} ${args}`;
55
+ return `${pm} ${bin} ${args}`;
56
+ }
57
+ function formatRunScriptCommand(pm, scriptName) {
58
+ switch (pm) {
59
+ case "deno": return `deno task ${scriptName}`;
60
+ case "bun": return `bun run ${scriptName}`;
61
+ case "pnpm": return `pnpm run ${scriptName}`;
62
+ case "yarn": return `yarn run ${scriptName}`;
63
+ default: return `npm run ${scriptName}`;
64
+ }
65
+ }
66
+ function formatAddArgs(pm, packages) {
67
+ if (pm === "deno") return ["add", ...packages.map((p) => `npm:${p}`)];
68
+ return ["add", ...packages];
69
+ }
70
+ function formatAddDevArgs(pm, packages) {
71
+ if (pm === "deno") return [
72
+ "add",
73
+ "--dev",
74
+ ...packages.map((p) => `npm:${p}`)
75
+ ];
76
+ return [
77
+ "add",
78
+ "-D",
79
+ ...packages
80
+ ];
81
+ }
82
+ //#endregion
83
+ //#region src/commands/init/detect-pnpm-catalog.ts
84
+ /**
85
+ * Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
86
+ * its top-level `catalog:` block for entries that match any of `packages`.
87
+ *
88
+ * Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
89
+ * inside a pnpm workspace whose catalog overrides one of the packages it
90
+ * installs, surface a structured warning so the user knows the catalog
91
+ * version (not the published `latest`) is what ended up in their
92
+ * `node_modules`. pnpm itself does this silently; the warning closes the
93
+ * "looks fine, must be wrong version six months later" gap.
94
+ *
95
+ * Notes / scope:
96
+ *
97
+ * - We only inspect the unnamed top-level `catalog:` block. pnpm also
98
+ * supports `catalogs:` (plural — *named* catalogs referenced via
99
+ * `catalog:foo` specifiers); those don't apply to a vanilla
100
+ * `pnpm add prisma-next` invocation, so we skip them.
101
+ * - We don't validate YAML syntax exhaustively. The file format pnpm
102
+ * ships is line-oriented and well-known; a minimal regex is more
103
+ * robust than depending on a YAML parser for one warning.
104
+ * - We don't compare against the registry's `latest` — pnpm uses the
105
+ * catalog version regardless, so the warning fires whenever a match
106
+ * exists. The user-facing copy explains how to opt out.
107
+ */
108
+ function detectPnpmCatalogOverrides(baseDir, packages) {
109
+ const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
110
+ if (workspaceFile === null) return null;
111
+ const catalog = extractCatalogBlock(readFileSync(workspaceFile, "utf-8"));
112
+ if (catalog === null) return {
113
+ workspaceFile,
114
+ entries: []
115
+ };
116
+ const wanted = new Set(packages);
117
+ const entries = [];
118
+ for (const [name, version] of catalog) if (wanted.has(name)) entries.push({
119
+ name,
120
+ version
121
+ });
122
+ return {
123
+ workspaceFile,
124
+ entries
125
+ };
126
+ }
127
+ function findNearestPnpmWorkspaceFile(baseDir) {
128
+ let dir = baseDir;
129
+ let prev = "";
130
+ while (dir !== prev) {
131
+ const candidate = join(dir, "pnpm-workspace.yaml");
132
+ if (existsSync(candidate)) return candidate;
133
+ prev = dir;
134
+ dir = dirname(dir);
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Returns the entries inside the top-level `catalog:` block as `[name, version]`
140
+ * pairs in document order, or `null` when no `catalog:` block exists.
141
+ *
142
+ * The parser is intentionally minimal: it reads line-by-line, locates the
143
+ * top-level `catalog:` line (no leading whitespace), then collects every
144
+ * subsequent indented line of the form `<key>: <value>` until the next
145
+ * top-level key (or end of file). Quotes around `<key>` and `<value>`
146
+ * are stripped; comments (`#…`) are ignored.
147
+ */
148
+ function extractCatalogBlock(contents) {
149
+ const lines = contents.split(/\r?\n/);
150
+ const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
151
+ if (startIdx === -1) return null;
152
+ const entries = [];
153
+ for (let i = startIdx + 1; i < lines.length; i++) {
154
+ const raw = lines[i] ?? "";
155
+ if (raw.trim() === "" || /^\s*#/.test(raw)) continue;
156
+ if (!/^\s/.test(raw)) break;
157
+ const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
158
+ if (!match) continue;
159
+ const name = match[1] ?? match[2] ?? match[3];
160
+ if (name === void 0) continue;
161
+ const version = stripQuotes((match[4] ?? "").trim());
162
+ if (version === "") continue;
163
+ entries.push([name, version]);
164
+ }
165
+ return entries;
166
+ }
167
+ function stripQuotes(value) {
168
+ if (value.length >= 2) {
169
+ const first = value[0];
170
+ const last = value[value.length - 1];
171
+ if (first === "\"" && last === "\"" || first === "'" && last === "'") return value.slice(1, -1);
172
+ }
173
+ return value;
174
+ }
175
+ //#endregion
14
176
  //#region src/commands/init/errors.ts
15
177
  /**
16
178
  * Re-init in non-interactive mode without `--force`. Distinct from the
@@ -70,6 +232,26 @@ function errorInitInvalidFlagValue(options) {
70
232
  });
71
233
  }
72
234
  /**
235
+ * `--authoring` and `--schema-path` disagree on file extension (e.g. PSL
236
+ * authoring with a `.ts` path). Surfaces before any scaffold files are
237
+ * written so the project tree stays untouched.
238
+ */
239
+ function errorInitAuthoringSchemaPathMismatch(options) {
240
+ const expectedAuthoring = options.expectedExtension === ".ts" ? "typescript" : "psl";
241
+ return new CliStructuredError("5014", "Authoring and schema path do not match", {
242
+ domain: "CLI",
243
+ why: `\`--authoring ${options.authoring}\` requires a schema file ending in ${options.expectedExtension}, but \`--schema-path ${options.schemaPath}\` ends in ${options.actualExtension}.`,
244
+ fix: `Use a matching pair, for example \`--authoring ${expectedAuthoring} --schema-path <path>${options.expectedExtension}\`, or change \`--authoring\` to match the path you supplied. You can also omit \`--schema-path\` to use the default for the chosen authoring.`,
245
+ docsUrl: "https://prisma-next.dev/docs/cli/init",
246
+ meta: {
247
+ authoring: options.authoring,
248
+ schemaPath: options.schemaPath,
249
+ actualExtension: options.actualExtension,
250
+ expectedExtension: options.expectedExtension
251
+ }
252
+ });
253
+ }
254
+ /**
73
255
  * The user cancelled an interactive prompt (Ctrl-C, escape, declined a
74
256
  * selection). Distinct from `errorInitReinitNeedsForce` because that path
75
257
  * applies to non-interactive mode where the user was never given the
@@ -206,367 +388,37 @@ function errorInitEmitFailed(options) {
206
388
  domain: "CLI",
207
389
  why: `\`prisma-next contract emit\` failed: ${options.cause}`,
208
390
  fix: `Inspect your contract file, fix the underlying issue, then re-run \`${options.emitCommand}\`. Pass \`-v\` for the full error envelope.`,
209
- docsUrl: "https://prisma-next.dev/docs/cli/contract-emit",
210
- meta: {
211
- filesWritten: options.filesWritten,
212
- cause: options.cause
213
- }
214
- });
215
- }
216
- /**
217
- * The project-level agent-skill install (`npx skills add
218
- * prisma/prisma-next#v<version>`) failed after a successful dependency
219
- * install + emit. The project's scaffold remains on disk; the user
220
- * can either fix the underlying issue (network, registry, PATH) and
221
- * run the install command manually, or re-run `init --no-skill` to
222
- * proceed without the skill.
223
- *
224
- * Non-rolling-back, matching the existing install/emit failure
225
- * semantics. Maps to exit code `6 = SKILL_INSTALL_FAILED`.
226
- */
227
- function errorInitSkillInstallFailed(options) {
228
- return new CliStructuredError("5013", "Failed to install Prisma Next skills", {
229
- domain: "CLI",
230
- why: `\`${options.skillInstallCommand}\` exited with an error: ${options.cause}`,
231
- fix: `Either:
232
- - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? " --force" : ""}\` to skip the skill install for this run, or\n - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
233
- docsUrl: "https://prisma-next.dev/docs/cli/init#agent-skill",
234
- meta: {
235
- filesWritten: options.filesWritten,
236
- skillInstallCommand: options.skillInstallCommand,
237
- cause: options.cause
238
- }
239
- });
240
- }
241
- //#endregion
242
- //#region src/commands/init/agent-skill-install.ts
243
- const exec = promisify(execFile);
244
- /**
245
- * Default base for the GitHub-URL form `<owner>/<repo>` consumed by
246
- * upstream `skills add`. Each `SkillSource` joins this base with its
247
- * own subpath (and optional `#ref` for version-pinned clusters).
248
- */
249
- const DEFAULT_AGENT_SKILL_BASE = "prisma/prisma-next";
250
- const DEFAULT_AGENT_SKILL_SOURCES = [
251
- {
252
- subpath: "skills",
253
- ref: "cli",
254
- description: "usage skills (version-locked to installed Prisma Next)"
255
- },
256
- {
257
- subpath: "skills/upgrade",
258
- ref: null,
259
- description: "upgrade skill (always tracks `main`)"
260
- },
261
- {
262
- subpath: "skills/extension-author",
263
- ref: null,
264
- description: "extension-author skill (always tracks `main`)"
265
- }
266
- ];
267
- /**
268
- * Test-only escape hatch for pinning the install base to a local
269
- * checkout. Production runs leave this unset, so installs always use
270
- * `DEFAULT_AGENT_SKILL_BASE`.
271
- *
272
- * When set to an absolute filesystem path (typical for tests), the
273
- * `#ref` fragment is dropped — local-path mode in upstream's CLI does
274
- * not accept refs, and the local clone has whatever content the test
275
- * checked into it anyway. When set to anything else (e.g. a fork name
276
- * `myuser/prisma-next`), the ref policy is preserved.
277
- */
278
- function resolveAgentSkillBase() {
279
- const override = process.env["PRISMA_NEXT_SKILLS_BASE"]?.trim();
280
- return override && override.length > 0 ? override : DEFAULT_AGENT_SKILL_BASE;
281
- }
282
- function isLocalPath(base) {
283
- return base.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(base);
284
- }
285
- /**
286
- * Build the `<base>/<subpath>[#ref]` URL the `skills` CLI will
287
- * resolve. Exported for unit tests so the per-source format can be
288
- * asserted without going through the full install loop.
289
- */
290
- function formatSkillSourceUrl(source) {
291
- const base = resolveAgentSkillBase();
292
- const url = `${base}/${source.subpath}`;
293
- if (source.ref === null) return url;
294
- if (isLocalPath(base)) return url;
295
- if (source.ref === "cli") return `${url}#v${version}`;
296
- return url;
297
- }
298
- /**
299
- * The skill-install command for one source, formatted for the
300
- * project's detected package manager. `npx`/`pnpm dlx`/`bunx` are
301
- * interchangeable to the user; we pick the variant that matches the
302
- * rest of the install step so a single project consistently uses one
303
- * runner.
304
- *
305
- * `--all` auto-selects every skill in the cluster and every detected
306
- * agent runtime, skipping the multi-select prompts the `skills` CLI
307
- * shows by default. A non-interactive scaffold step cannot present
308
- * prompts.
309
- *
310
- * Exported for unit tests so the per-PM dispatch can be asserted
311
- * without a live subprocess.
312
- */
313
- function formatSkillInstallCommand(pm, source) {
314
- return formatPackageManagerCommand(pm, [
315
- "skills@latest",
316
- "add",
317
- formatSkillSourceUrl(source),
318
- "--all"
319
- ]);
320
- }
321
- /**
322
- * `skills add --all` should cover Claude Code, but upstream currently skips
323
- * project-local Claude symlinks when `.claude/` does not already exist. Run
324
- * the explicit Claude Code install as well so fresh projects get
325
- * `.claude/skills` without asking users to create that folder first.
326
- */
327
- function formatClaudeSkillInstallCommand(pm, source) {
328
- return formatPackageManagerCommand(pm, [
329
- "skills@latest",
330
- "add",
331
- formatSkillSourceUrl(source),
332
- "--agent",
333
- "claude-code",
334
- "--skill",
335
- "'*'",
336
- "-y"
337
- ]);
338
- }
339
- function formatPackageManagerCommand(pm, args) {
340
- switch (pm) {
341
- case "pnpm": return `pnpm dlx ${args.join(" ")}`;
342
- case "yarn": return `yarn dlx ${args.join(" ")}`;
343
- case "bun": return `bunx ${args.join(" ")}`;
344
- case "deno": return `deno run -A npm:${args.join(" ")}`;
345
- case "npm": return `npx ${args.join(" ")}`;
346
- }
347
- }
348
- /**
349
- * Parse the project-pm-formatted command into an exec call. The
350
- * format-then-parse split keeps the user-facing command string the same
351
- * as the surface the structured error advertises, so a user who copies
352
- * the error's `fix` line gets the same invocation that init just
353
- * attempted. Single quotes are preserved in the display form so `*` is
354
- * safe to copy into a shell, then stripped before `execFile`.
355
- */
356
- function commandToExec(command) {
357
- const tokens = (command.match(/'[^']*'|\S+/g) ?? []).map((token) => token.startsWith("'") && token.endsWith("'") ? token.slice(1, -1) : token);
358
- return {
359
- file: tokens[0] ?? "npx",
360
- args: tokens.slice(1)
361
- };
362
- }
363
- /**
364
- * Runs the project-level skill install for every source in
365
- * `DEFAULT_AGENT_SKILL_SOURCES`, in order. Returns
366
- * `{ ok: true, commands }` on success; throws a structured
367
- * `errorInitSkillInstallFailed` on the first failure (subsequent
368
- * sources are not attempted — the user opted into Prisma Next by
369
- * running `init` and a partial install would leave the project in an
370
- * ambiguous state). The throw is intentionally fatal — project-level
371
- * skill install is unconditional (modulo `--no-skill`).
372
- */
373
- async function runProjectLevelSkillInstall(ctx) {
374
- const commands = [];
375
- const installCommands = DEFAULT_AGENT_SKILL_SOURCES.flatMap((source) => [formatSkillInstallCommand(ctx.pm, source), formatClaudeSkillInstallCommand(ctx.pm, source)]);
376
- for (const command of installCommands) {
377
- const { file, args } = commandToExec(command);
378
- try {
379
- await exec(file, args, { cwd: ctx.baseDir });
380
- commands.push(command);
381
- } catch (err) {
382
- throw errorInitSkillInstallFailed({
383
- skillInstallCommand: command,
384
- filesWritten: ctx.filesWritten,
385
- cause: redactSecrets$1(readChildStderr$1(err)) || (err instanceof Error ? err.message : String(err))
386
- });
387
- }
388
- }
389
- return {
390
- ok: true,
391
- commands
392
- };
393
- }
394
- function readChildStderr$1(err) {
395
- if (err instanceof Error && "stderr" in err) return String(err.stderr ?? "");
396
- return "";
397
- }
398
- /**
399
- * Strips credentials from a `scheme://user:pass@host/...` URL anywhere
400
- * in `stderr`. Package-manager stderr regularly contains credentialed
401
- * registry URLs (private npm registries, GitHub Packages tokens), and
402
- * those bubble into the structured `errorInitSkillInstallFailed`
403
- * envelope, which ends up in logs and CI output. Redact at the
404
- * boundary so we never re-emit a secret.
405
- *
406
- * Exported for unit tests.
407
- */
408
- function redactSecrets$1(stderr) {
409
- if (!stderr) return stderr;
410
- return stderr.replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s]+)@/g, "$1***@");
411
- }
412
- /**
413
- * Hand-rolled skill stub path that init must not leave behind. Removed
414
- * on every init run so a project's `.agents/skills/prisma-next/` does
415
- * not shadow the installed Prisma Next skill cluster.
416
- */
417
- const LEGACY_SKILL_FILE = ".agents/skills/prisma-next/SKILL.md";
418
- //#endregion
419
- //#region src/commands/init/detect-package-manager.ts
420
- const KNOWN = new Set([
421
- "pnpm",
422
- "npm",
423
- "yarn",
424
- "bun",
425
- "deno"
426
- ]);
427
- /**
428
- * Resolves the package manager `init` should drive for `add` / `install`
429
- * commands. Tries, in order:
430
- *
431
- * 1. **`detect()`** — walks up from `cwd` looking for a lockfile, the
432
- * `packageManager` field, the `devEngines.packageManager` field, or
433
- * install metadata. This is the right answer whenever the user is
434
- * anywhere inside an existing project, including a deep workspace
435
- * subdirectory.
436
- *
437
- * 2. **`getUserAgent()`** — parses `npm_config_user_agent`, the env var
438
- * every PM sets when it spawns a script. This catches the
439
- * bare-directory case where there's no project to walk up to but the
440
- * user invoked us via `pnpm dlx prisma-next init` / `bunx
441
- * prisma-next init` / `yarn dlx …`. Same signal used by every
442
- * `create-*` tool in the ecosystem (`create-vite`, `create-next-app`,
443
- * `create-astro`, `@antfu/ni`, …).
444
- *
445
- * 3. **`npm`** — final fallback. Always present alongside Node.
446
- */
447
- async function detectPackageManager(cwd) {
448
- const detected = await detect({ cwd });
449
- if (detected && KNOWN.has(detected.name)) return detected.name;
450
- const userAgent = getUserAgent();
451
- if (userAgent !== null && KNOWN.has(userAgent)) return userAgent;
452
- return "npm";
453
- }
454
- function hasProjectManifest(cwd) {
455
- return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
456
- }
457
- function formatRunCommand(pm, bin, args) {
458
- if (pm === "npm") return `npx ${bin} ${args}`;
459
- if (pm === "deno") return `deno run npm:${bin} ${args}`;
460
- return `${pm} ${bin} ${args}`;
461
- }
462
- function formatAddArgs(pm, packages) {
463
- if (pm === "deno") return ["add", ...packages.map((p) => `npm:${p}`)];
464
- return ["add", ...packages];
465
- }
466
- function formatAddDevArgs(pm, packages) {
467
- if (pm === "deno") return [
468
- "add",
469
- "--dev",
470
- ...packages.map((p) => `npm:${p}`)
471
- ];
472
- return [
473
- "add",
474
- "-D",
475
- ...packages
476
- ];
477
- }
478
- //#endregion
479
- //#region src/commands/init/detect-pnpm-catalog.ts
480
- /**
481
- * Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
482
- * its top-level `catalog:` block for entries that match any of `packages`.
483
- *
484
- * Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
485
- * inside a pnpm workspace whose catalog overrides one of the packages it
486
- * installs, surface a structured warning so the user knows the catalog
487
- * version (not the published `latest`) is what ended up in their
488
- * `node_modules`. pnpm itself does this silently; the warning closes the
489
- * "looks fine, must be wrong version six months later" gap.
490
- *
491
- * Notes / scope:
492
- *
493
- * - We only inspect the unnamed top-level `catalog:` block. pnpm also
494
- * supports `catalogs:` (plural — *named* catalogs referenced via
495
- * `catalog:foo` specifiers); those don't apply to a vanilla
496
- * `pnpm add prisma-next` invocation, so we skip them.
497
- * - We don't validate YAML syntax exhaustively. The file format pnpm
498
- * ships is line-oriented and well-known; a minimal regex is more
499
- * robust than depending on a YAML parser for one warning.
500
- * - We don't compare against the registry's `latest` — pnpm uses the
501
- * catalog version regardless, so the warning fires whenever a match
502
- * exists. The user-facing copy explains how to opt out.
503
- */
504
- function detectPnpmCatalogOverrides(baseDir, packages) {
505
- const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
506
- if (workspaceFile === null) return null;
507
- const catalog = extractCatalogBlock(readFileSync(workspaceFile, "utf-8"));
508
- if (catalog === null) return {
509
- workspaceFile,
510
- entries: []
511
- };
512
- const wanted = new Set(packages);
513
- const entries = [];
514
- for (const [name, version] of catalog) if (wanted.has(name)) entries.push({
515
- name,
516
- version
391
+ docsUrl: "https://prisma-next.dev/docs/cli/contract-emit",
392
+ meta: {
393
+ filesWritten: options.filesWritten,
394
+ cause: options.cause
395
+ }
517
396
  });
518
- return {
519
- workspaceFile,
520
- entries
521
- };
522
- }
523
- function findNearestPnpmWorkspaceFile(baseDir) {
524
- let dir = baseDir;
525
- let prev = "";
526
- while (dir !== prev) {
527
- const candidate = join(dir, "pnpm-workspace.yaml");
528
- if (existsSync(candidate)) return candidate;
529
- prev = dir;
530
- dir = dirname(dir);
531
- }
532
- return null;
533
397
  }
534
398
  /**
535
- * Returns the entries inside the top-level `catalog:` block as `[name, version]`
536
- * pairs in document order, or `null` when no `catalog:` block exists.
399
+ * The project-level skills install (`npx skills add
400
+ * prisma/prisma-next#v<version>`) failed after a successful dependency
401
+ * install + emit. The project's scaffold remains on disk; the user
402
+ * can either fix the underlying issue (network, registry, PATH) and
403
+ * run the install command manually, or re-run `init --no-skill` to
404
+ * proceed without the skill.
537
405
  *
538
- * The parser is intentionally minimal: it reads line-by-line, locates the
539
- * top-level `catalog:` line (no leading whitespace), then collects every
540
- * subsequent indented line of the form `<key>: <value>` until the next
541
- * top-level key (or end of file). Quotes around `<key>` and `<value>`
542
- * are stripped; comments (`#…`) are ignored.
406
+ * Non-rolling-back, matching the existing install/emit failure
407
+ * semantics. Maps to exit code `6 = SKILL_INSTALL_FAILED`.
543
408
  */
544
- function extractCatalogBlock(contents) {
545
- const lines = contents.split(/\r?\n/);
546
- const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
547
- if (startIdx === -1) return null;
548
- const entries = [];
549
- for (let i = startIdx + 1; i < lines.length; i++) {
550
- const raw = lines[i] ?? "";
551
- if (raw.trim() === "" || /^\s*#/.test(raw)) continue;
552
- if (!/^\s/.test(raw)) break;
553
- const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
554
- if (!match) continue;
555
- const name = match[1] ?? match[2] ?? match[3];
556
- if (name === void 0) continue;
557
- const version = stripQuotes((match[4] ?? "").trim());
558
- if (version === "") continue;
559
- entries.push([name, version]);
560
- }
561
- return entries;
562
- }
563
- function stripQuotes(value) {
564
- if (value.length >= 2) {
565
- const first = value[0];
566
- const last = value[value.length - 1];
567
- if (first === "\"" && last === "\"" || first === "'" && last === "'") return value.slice(1, -1);
568
- }
569
- return value;
409
+ function errorInitSkillInstallFailed(options) {
410
+ return new CliStructuredError("5013", "Failed to install Prisma Next skills", {
411
+ domain: "CLI",
412
+ why: `\`${options.skillInstallCommand}\` exited with an error: ${options.cause}`,
413
+ fix: `Either:
414
+ - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? " --force" : ""}\` to skip the skill install for this run, or\n - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
415
+ docsUrl: "https://prisma-next.dev/docs/cli/init#skills",
416
+ meta: {
417
+ filesWritten: options.filesWritten,
418
+ skillInstallCommand: options.skillInstallCommand,
419
+ cause: options.cause
420
+ }
421
+ });
570
422
  }
571
423
  //#endregion
572
424
  //#region src/commands/init/hygiene-gitattributes.ts
@@ -814,18 +666,20 @@ function schemaSample(target, authoring) {
814
666
  function schemaSamplePslPostgres() {
815
667
  return `\`\`\`prisma
816
668
  model User {
817
- id Int @id @default(autoincrement())
818
- email String @unique
819
- name String?
669
+ id Int @id @default(autoincrement())
670
+ email String @unique
671
+ username String?
672
+ name String?
820
673
  }
821
674
  \`\`\``;
822
675
  }
823
676
  function schemaSamplePslMongo() {
824
677
  return `\`\`\`prisma
825
678
  model User {
826
- id ObjectId @id @map("_id")
827
- email String @unique
828
- name String?
679
+ id ObjectId @id @map("_id")
680
+ email String @unique
681
+ username String?
682
+ name String?
829
683
  @@map("users")
830
684
  }
831
685
  \`\`\``;
@@ -833,17 +687,16 @@ model User {
833
687
  function schemaSampleTsPostgres() {
834
688
  return `\`\`\`typescript
835
689
  import { defineContract } from '@prisma-next/postgres/contract-builder';
836
- import sqlFamily from '@prisma-next/postgres/family';
837
- import postgresTarget from '@prisma-next/postgres/target';
838
690
 
839
691
  export const contract = defineContract(
840
- { family: sqlFamily, target: postgresTarget },
692
+ {},
841
693
  ({ field, model }) => ({
842
694
  models: {
843
695
  User: model('User', {
844
696
  fields: {
845
697
  id: field.id.uuidv7(),
846
698
  email: field.text().unique(),
699
+ username: field.text().optional(),
847
700
  name: field.text().optional(),
848
701
  },
849
702
  }),
@@ -855,11 +708,9 @@ export const contract = defineContract(
855
708
  function schemaSampleTsMongo() {
856
709
  return `\`\`\`typescript
857
710
  import { defineContract } from '@prisma-next/mongo/contract-builder';
858
- import mongoFamily from '@prisma-next/mongo/family';
859
- import mongoTarget from '@prisma-next/mongo/target';
860
711
 
861
712
  export const contract = defineContract(
862
- { family: mongoFamily, target: mongoTarget },
713
+ {},
863
714
  ({ field, model }) => ({
864
715
  models: {
865
716
  User: model('User', {
@@ -867,6 +718,7 @@ export const contract = defineContract(
867
718
  fields: {
868
719
  _id: field.objectId(),
869
720
  email: field.string(),
721
+ username: field.string().optional(),
870
722
  name: field.string().optional(),
871
723
  },
872
724
  }),
@@ -881,6 +733,7 @@ function starterSchemaPslPostgres() {
881
733
  model User {
882
734
  id Int @id @default(autoincrement())
883
735
  email String @unique
736
+ username String?
884
737
  name String?
885
738
  posts Post[]
886
739
  createdAt DateTime @default(now())
@@ -902,10 +755,11 @@ function starterSchemaPslMongo() {
902
755
  return `// use prisma-next
903
756
 
904
757
  model User {
905
- id ObjectId @id @map("_id")
906
- email String @unique
907
- name String?
908
- posts Post[]
758
+ id ObjectId @id @map("_id")
759
+ email String @unique
760
+ username String?
761
+ name String?
762
+ posts Post[]
909
763
  @@map("users")
910
764
  }
911
765
 
@@ -921,17 +775,16 @@ model Post {
921
775
  }
922
776
  function starterSchemaTsPostgres() {
923
777
  return `import { defineContract } from '@prisma-next/postgres/contract-builder';
924
- import sqlFamily from '@prisma-next/postgres/family';
925
- import postgresTarget from '@prisma-next/postgres/target';
926
778
 
927
779
  export const contract = defineContract(
928
- { family: sqlFamily, target: postgresTarget },
780
+ {},
929
781
  ({ field, model, rel }) => ({
930
782
  models: {
931
783
  User: model('User', {
932
784
  fields: {
933
785
  id: field.id.uuidv7(),
934
786
  email: field.text().unique(),
787
+ username: field.text().optional(),
935
788
  name: field.text().optional(),
936
789
  createdAt: field.temporal.createdAt(),
937
790
  updatedAt: field.temporal.updatedAt(),
@@ -961,11 +814,9 @@ export const contract = defineContract(
961
814
  }
962
815
  function starterSchemaTsMongo() {
963
816
  return `import { defineContract } from '@prisma-next/mongo/contract-builder';
964
- import mongoFamily from '@prisma-next/mongo/family';
965
- import mongoTarget from '@prisma-next/mongo/target';
966
817
 
967
818
  export const contract = defineContract(
968
- { family: mongoFamily, target: mongoTarget },
819
+ {},
969
820
  ({ field, model, rel }) => ({
970
821
  models: {
971
822
  User: model('User', {
@@ -973,6 +824,7 @@ export const contract = defineContract(
973
824
  fields: {
974
825
  _id: field.objectId(),
975
826
  email: field.string(),
827
+ username: field.string().optional(),
976
828
  name: field.string().optional(),
977
829
  },
978
830
  relations: {
@@ -1042,6 +894,11 @@ const AUTHORING_VALUES = new Map([
1042
894
  ["typescript", "typescript"],
1043
895
  ["ts", "typescript"]
1044
896
  ]);
897
+ const TELEMETRY_CONSENT_MESSAGE = [
898
+ "Help us prioritize features by sharing anonymous CLI usage data?",
899
+ "The telemetry implementation is open source and fully transparent.",
900
+ "(packages/1-framework/3-tooling/cli-telemetry and apps/telemetry-backend)."
901
+ ].join(" ");
1045
902
  /**
1046
903
  * Resolves every required input for `runInit`. In interactive mode, missing
1047
904
  * inputs are prompted via clack; in non-interactive mode, missing required
@@ -1089,6 +946,10 @@ async function resolveInitInputs(ctx) {
1089
946
  canPrompt,
1090
947
  autoAcceptPrompts
1091
948
  });
949
+ const enableTelemetry = await resolveTelemetryConsent({
950
+ canPrompt,
951
+ autoAcceptPrompts
952
+ });
1092
953
  const installProjectSkill = options.skill !== false;
1093
954
  return {
1094
955
  target: finalTarget,
@@ -1100,9 +961,49 @@ async function resolveInitInputs(ctx) {
1100
961
  strictProbe: Boolean(options.strictProbe),
1101
962
  reinit,
1102
963
  removePreviousFacade,
964
+ enableTelemetry,
1103
965
  installProjectSkill
1104
966
  };
1105
967
  }
968
+ /**
969
+ * The interactive telemetry consent prompt. Shown as the last
970
+ * question of the `init` sequence iff:
971
+ * 1. `canPrompt === true` (interactive stdin / stdout combo),
972
+ * 2. `autoAcceptPrompts === false` (the user did not pass `--yes`),
973
+ * 3. neither telemetry env opt-out is active,
974
+ * 4. the process is not running in CI,
975
+ * 5. the stored `enableTelemetry` value is `undefined` (the user
976
+ * has never been asked, or skipped the prompt previously).
977
+ *
978
+ * Outside that intersection the function returns `null` and leaves the
979
+ * stored preference untouched. Inside it, the user's answer is
980
+ * persisted via `writeUserConfig({ enableTelemetry })` before this
981
+ * function returns; on an affirmative answer `writeUserConfig`
982
+ * generates and stores the v4 `installationId` in the same write.
983
+ *
984
+ * The wording names CLI usage data and points to the open-source
985
+ * client/backend paths. Default value is `true` to match precedent
986
+ * and to make the consent answer a single keystroke once it's been
987
+ * disclosed.
988
+ */
989
+ async function resolveTelemetryConsent(opts) {
990
+ if (!opts.canPrompt || opts.autoAcceptPrompts || isCI()) return null;
991
+ const config = readUserConfig();
992
+ const gating = resolveGating({
993
+ env: process.env,
994
+ config
995
+ });
996
+ if (!gating.enabled && gating.reason === "env-override") return null;
997
+ if (config.enableTelemetry !== void 0) return null;
998
+ const result = await clack.confirm({
999
+ message: TELEMETRY_CONSENT_MESSAGE,
1000
+ initialValue: true,
1001
+ output: process.stderr
1002
+ });
1003
+ if (clack.isCancel(result)) throw errorInitUserAborted();
1004
+ writeUserConfig({ enableTelemetry: Boolean(result) });
1005
+ return Boolean(result);
1006
+ }
1106
1007
  async function resolveWriteEnv(opts) {
1107
1008
  if (opts.flag !== void 0) return Boolean(opts.flag);
1108
1009
  if (!opts.canPrompt || opts.autoAcceptPrompts) return false;
@@ -1214,10 +1115,11 @@ function validateSchemaPath(value, authoring) {
1214
1115
  });
1215
1116
  const ext = extname(trimmed).toLowerCase();
1216
1117
  const expected = authoring === "typescript" ? ".ts" : ".prisma";
1217
- if (ext !== expected) throw errorInitInvalidFlagValue({
1218
- flag: "schema-path",
1219
- value,
1220
- allowed: [`<file path ending in ${expected} for --authoring ${authoring}>`]
1118
+ if (ext !== expected) throw errorInitAuthoringSchemaPathMismatch({
1119
+ authoring,
1120
+ schemaPath: trimmed,
1121
+ actualExtension: ext.length > 0 ? ext : "(none)",
1122
+ expectedExtension: expected
1221
1123
  });
1222
1124
  return normalize(trimmed);
1223
1125
  }
@@ -1511,6 +1413,189 @@ function removeDependency(existing, depName) {
1511
1413
  return `${JSON.stringify(parsed, null, 2)}${trailingNewline}`;
1512
1414
  }
1513
1415
  //#endregion
1416
+ //#region src/commands/init/skill-install.ts
1417
+ const exec = promisify(execFile);
1418
+ /**
1419
+ * Default base for the GitHub-URL form `<owner>/<repo>` consumed by
1420
+ * upstream `skills add`. Each `SkillSource` joins this base with its
1421
+ * own subpath (and optional `#ref` for version-pinned clusters).
1422
+ */
1423
+ const DEFAULT_SKILL_BASE = "prisma/prisma-next";
1424
+ const DEFAULT_SKILL_SOURCES = [
1425
+ {
1426
+ subpath: "skills",
1427
+ ref: "cli",
1428
+ description: "usage skills (version-locked to installed Prisma Next)"
1429
+ },
1430
+ {
1431
+ subpath: "skills/upgrade",
1432
+ ref: null,
1433
+ description: "upgrade skill (always tracks `main`)"
1434
+ },
1435
+ {
1436
+ subpath: "skills/extension-author",
1437
+ ref: null,
1438
+ description: "extension-author skill (always tracks `main`)"
1439
+ }
1440
+ ];
1441
+ /**
1442
+ * Test-only escape hatch for pinning the install base to a local
1443
+ * checkout. Production runs leave this unset, so installs always use
1444
+ * `DEFAULT_SKILL_BASE`.
1445
+ *
1446
+ * When set to an absolute filesystem path (typical for tests), the
1447
+ * `#ref` fragment is dropped — local-path mode in upstream's CLI does
1448
+ * not accept refs, and the local clone has whatever content the test
1449
+ * checked into it anyway. When set to anything else (e.g. a fork name
1450
+ * `myuser/prisma-next`), the ref policy is preserved.
1451
+ */
1452
+ function resolveAgentSkillBase() {
1453
+ const override = process.env["PRISMA_NEXT_SKILLS_BASE"]?.trim();
1454
+ return override && override.length > 0 ? override : DEFAULT_SKILL_BASE;
1455
+ }
1456
+ function isLocalPath(base) {
1457
+ return base.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(base);
1458
+ }
1459
+ /**
1460
+ * Agents passed to every project-level init install. Upstream `skills add`
1461
+ * is the source of truth for per-agent install behaviour; the CLI lists
1462
+ * every supported runtime on one invocation and delegates the rest.
1463
+ */
1464
+ const DEFAULT_SKILL_AGENTS = [
1465
+ "cursor",
1466
+ "claude-code",
1467
+ "codex",
1468
+ "windsurf"
1469
+ ];
1470
+ /**
1471
+ * Build the `<base>/<subpath>[#ref]` URL the `skills` CLI will
1472
+ * resolve. Exported for unit tests so the per-source format can be
1473
+ * asserted without going through the full install loop.
1474
+ */
1475
+ function formatSkillSourceUrl(source) {
1476
+ const base = resolveAgentSkillBase();
1477
+ const url = `${base}/${source.subpath}`;
1478
+ if (source.ref === null) return url;
1479
+ if (isLocalPath(base)) return url;
1480
+ if (source.ref === "cli") return `${url}#v${version}`;
1481
+ return url;
1482
+ }
1483
+ /**
1484
+ * The skill-install command for one source, formatted for the
1485
+ * project's detected package manager. `npx`/`pnpm dlx`/`bunx` are
1486
+ * interchangeable to the user; we pick the variant that matches the
1487
+ * rest of the install step so a single project consistently uses one
1488
+ * runner.
1489
+ *
1490
+ * `--agent` takes space-separated slugs on one flag; `--skill '*'` and `-y`
1491
+ * skip the multi-select prompts a non-interactive scaffold step cannot show.
1492
+ *
1493
+ * Exported for unit tests so the per-PM dispatch can be asserted
1494
+ * without a live subprocess.
1495
+ */
1496
+ function formatSkillInstallCommand(args) {
1497
+ const agents = args.agents ?? DEFAULT_SKILL_AGENTS;
1498
+ const cliArgs = [
1499
+ "skills@latest",
1500
+ "add",
1501
+ formatSkillSourceUrl(args.source),
1502
+ "--agent",
1503
+ ...agents,
1504
+ "--skill",
1505
+ "'*'",
1506
+ "-y"
1507
+ ];
1508
+ return formatPackageManagerCommand(args.pm, cliArgs);
1509
+ }
1510
+ /**
1511
+ * Ordered skill-install commands for one init run. Exported for unit tests.
1512
+ */
1513
+ function resolveProjectSkillInstallCommands(pm) {
1514
+ return DEFAULT_SKILL_SOURCES.map((source) => formatSkillInstallCommand({
1515
+ pm,
1516
+ source
1517
+ }));
1518
+ }
1519
+ function formatPackageManagerCommand(pm, args) {
1520
+ switch (pm) {
1521
+ case "pnpm": return `pnpm dlx ${args.join(" ")}`;
1522
+ case "yarn": return `yarn dlx ${args.join(" ")}`;
1523
+ case "bun": return `bunx ${args.join(" ")}`;
1524
+ case "deno": return `deno run -A npm:${args.join(" ")}`;
1525
+ case "npm": return `npx ${args.join(" ")}`;
1526
+ }
1527
+ }
1528
+ /**
1529
+ * Parse the project-pm-formatted command into an exec call. The
1530
+ * format-then-parse split keeps the user-facing command string the same
1531
+ * as the surface the structured error advertises, so a user who copies
1532
+ * the error's `fix` line gets the same invocation that init just
1533
+ * attempted. Single quotes are preserved in the display form so `*` is
1534
+ * safe to copy into a shell, then stripped before `execFile`.
1535
+ */
1536
+ function commandToExec(command) {
1537
+ const tokens = (command.match(/'[^']*'|\S+/g) ?? []).map((token) => token.startsWith("'") && token.endsWith("'") ? token.slice(1, -1) : token);
1538
+ return {
1539
+ file: tokens[0] ?? "npx",
1540
+ args: tokens.slice(1)
1541
+ };
1542
+ }
1543
+ /**
1544
+ * Runs the project-level skill install for every source in
1545
+ * `DEFAULT_SKILL_SOURCES`, in order. Returns
1546
+ * `{ ok: true, commands }` on success; throws a structured
1547
+ * `errorInitSkillInstallFailed` on the first failure (subsequent
1548
+ * sources are not attempted — the user opted into Prisma Next by
1549
+ * running `init` and a partial install would leave the project in an
1550
+ * ambiguous state). The throw is intentionally fatal — project-level
1551
+ * skill install is unconditional (modulo `--no-skill`).
1552
+ */
1553
+ async function runProjectLevelSkillInstall(ctx) {
1554
+ const commands = [];
1555
+ const installCommands = resolveProjectSkillInstallCommands(ctx.pm);
1556
+ for (const command of installCommands) {
1557
+ const { file, args } = commandToExec(command);
1558
+ try {
1559
+ await exec(file, args, { cwd: ctx.baseDir });
1560
+ commands.push(command);
1561
+ } catch (err) {
1562
+ throw errorInitSkillInstallFailed({
1563
+ skillInstallCommand: command,
1564
+ filesWritten: ctx.filesWritten,
1565
+ cause: redactSecrets$1(readChildStderr$1(err)) || (err instanceof Error ? err.message : String(err))
1566
+ });
1567
+ }
1568
+ }
1569
+ return {
1570
+ ok: true,
1571
+ commands
1572
+ };
1573
+ }
1574
+ function readChildStderr$1(err) {
1575
+ if (err instanceof Error && "stderr" in err) return String(err.stderr ?? "");
1576
+ return "";
1577
+ }
1578
+ /**
1579
+ * Strips credentials from a `scheme://user:pass@host/...` URL anywhere
1580
+ * in `stderr`. Package-manager stderr regularly contains credentialed
1581
+ * registry URLs (private npm registries, GitHub Packages tokens), and
1582
+ * those bubble into the structured `errorInitSkillInstallFailed`
1583
+ * envelope, which ends up in logs and CI output. Redact at the
1584
+ * boundary so we never re-emit a secret.
1585
+ *
1586
+ * Exported for unit tests.
1587
+ */
1588
+ function redactSecrets$1(stderr) {
1589
+ if (!stderr) return stderr;
1590
+ return stderr.replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s]+)@/g, "$1***@");
1591
+ }
1592
+ /**
1593
+ * Hand-rolled skill stub path that init must not leave behind. Removed
1594
+ * on every init run so a project's `.agents/skills/prisma-next/` does
1595
+ * not shadow the installed Prisma Next skill cluster.
1596
+ */
1597
+ const LEGACY_SKILL_FILE = ".agents/skills/prisma-next/SKILL.md";
1598
+ //#endregion
1514
1599
  //#region src/commands/init/templates/env.ts
1515
1600
  /**
1516
1601
  * The minimum supported server version for each target (FR8.1). The
@@ -1548,7 +1633,12 @@ function envPlaceholderBody(target) {
1548
1633
  lines.push(`# Requires ${label} >= ${minVersion}.`);
1549
1634
  lines.push("");
1550
1635
  if (target === "postgres") lines.push("DATABASE_URL=\"postgresql://user:password@localhost:5432/mydb\"");
1551
- else lines.push("DATABASE_URL=\"mongodb://localhost:27017/mydb\"");
1636
+ else {
1637
+ lines.push("# Standalone local mongod / `docker run mongo:7` — no replica set required for first-run queries.");
1638
+ lines.push("# Transactions and change streams need a replica set; add ?replicaSet=... only after initiating one.");
1639
+ lines.push("");
1640
+ lines.push("DATABASE_URL=\"mongodb://user:password@localhost:27017/mydb\"");
1641
+ }
1552
1642
  lines.push("");
1553
1643
  return lines.join("\n");
1554
1644
  }
@@ -1634,6 +1724,54 @@ function requirementsBlock(target) {
1634
1724
  ].join("\n");
1635
1725
  }
1636
1726
  //#endregion
1727
+ //#region src/commands/init/templates/readme.ts
1728
+ const sharedVariables = [
1729
+ "projectName",
1730
+ "contractPath",
1731
+ "runDev",
1732
+ "runContractEmit"
1733
+ ];
1734
+ const postgresVariables = [
1735
+ ...sharedVariables,
1736
+ "runDbInit",
1737
+ "runDbUpdate",
1738
+ "runMigrationPlan",
1739
+ "runMigrate",
1740
+ "runDbSeed"
1741
+ ];
1742
+ const mongoVariables = [
1743
+ ...sharedVariables,
1744
+ "runDbUp",
1745
+ "runDbDown",
1746
+ "runDbReset",
1747
+ "runMigrationPlan",
1748
+ "runMigrate",
1749
+ "runDbSeed"
1750
+ ];
1751
+ function minimalProjectReadmeMd(target, schemaPath, projectName, pm) {
1752
+ const run = (script) => formatRunScriptCommand(pm, script);
1753
+ const shared = {
1754
+ projectName,
1755
+ contractPath: schemaPath,
1756
+ runDev: run("dev"),
1757
+ runContractEmit: run("contract:emit"),
1758
+ runMigrationPlan: run("migration:plan"),
1759
+ runMigrate: run("migrate"),
1760
+ runDbSeed: run("db:seed")
1761
+ };
1762
+ if (target === "mongo") return renderTemplate("readme-mongo.md", mongoVariables, {
1763
+ ...shared,
1764
+ runDbUp: run("db:up"),
1765
+ runDbDown: run("db:down"),
1766
+ runDbReset: run("db:reset")
1767
+ });
1768
+ return renderTemplate("readme-postgres.md", postgresVariables, {
1769
+ ...shared,
1770
+ runDbInit: run("db:init"),
1771
+ runDbUpdate: run("db:update")
1772
+ });
1773
+ }
1774
+ //#endregion
1637
1775
  //#region src/commands/init/templates/tsconfig.ts
1638
1776
  /**
1639
1777
  * Compiler options the scaffolded `prisma-next.config.ts` and `db.ts` need
@@ -1782,7 +1920,7 @@ function mergeTypesArray(existing) {
1782
1920
  * structured CLI errors raised at every phase (input resolution, install,
1783
1921
  * emit) and renders them via the same UI surface as success output
1784
1922
  * (`--json` to stdout, human to stderr). Exit codes follow the documented
1785
- * stable set in `./exit-codes.ts` (FR1.6) and the
1923
+ * stable set in `./exit-codes.ts` and the
1786
1924
  * [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes).
1787
1925
  *
1788
1926
  * Layered for testability: the action handler in `./index.ts` is
@@ -1790,11 +1928,8 @@ function mergeTypesArray(existing) {
1790
1928
  * function does no flag parsing of its own.
1791
1929
  */
1792
1930
  async function runInit(baseDir, runOptions) {
1793
- const { options, flags, canPrompt, probeOverrides } = runOptions;
1794
- const ui = new TerminalUI({
1795
- color: flags.color,
1796
- interactive: flags.interactive
1797
- });
1931
+ const { options, flags, canPrompt, probeOverrides, afterFirstTelemetryConsent } = runOptions;
1932
+ const ui = createTerminalUI(flags);
1798
1933
  const warnings = [];
1799
1934
  const filesWritten = [];
1800
1935
  const filesDeleted = [];
@@ -1811,6 +1946,9 @@ async function runInit(baseDir, runOptions) {
1811
1946
  if (CliStructuredError.is(error)) return emitError(ui, flags, error);
1812
1947
  throw error;
1813
1948
  }
1949
+ if (inputs.enableTelemetry === true && afterFirstTelemetryConsent !== void 0) try {
1950
+ await afterFirstTelemetryConsent(inputs);
1951
+ } catch {}
1814
1952
  const pm = await detectPackageManager(baseDir);
1815
1953
  const pkgRun = formatRunCommand(pm, "prisma-next", "").trimEnd();
1816
1954
  const schemaDir = dirname(inputs.schemaPath);
@@ -1920,6 +2058,13 @@ async function runInit(baseDir, runOptions) {
1920
2058
  if (typeWarning !== null) warnings.push(typeWarning);
1921
2059
  if (synthesisePackageJson) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
1922
2060
  }
2061
+ if (existsSync(join(baseDir, "src/index.ts"))) if (!existsSync(join(baseDir, "README.md"))) {
2062
+ const rawName = parsedPackageJson !== null && typeof parsedPackageJson["name"] === "string" ? parsedPackageJson["name"] : basename(baseDir);
2063
+ filesToWrite.push({
2064
+ path: "README.md",
2065
+ content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(rawName), pm)
2066
+ });
2067
+ } else warnings.push("README.md already exists; leaving it untouched.");
1923
2068
  for (const file of filesToWrite) {
1924
2069
  const fullPath = join(baseDir, file.path);
1925
2070
  mkdirSync(dirname(fullPath), { recursive: true });
@@ -1983,10 +2128,12 @@ async function runInit(baseDir, runOptions) {
1983
2128
  filesWritten
1984
2129
  }));
1985
2130
  }
1986
- const manualProjectSkillSummary = DEFAULT_AGENT_SKILL_SOURCES.flatMap((source) => [formatSkillInstallCommand(install.effectivePm, source), formatClaudeSkillInstallCommand(install.effectivePm, source)]).map((c) => `\`${c}\``).join(" && ");
2131
+ const manualProjectSkillSummary = DEFAULT_SKILL_SOURCES.map((source) => formatSkillInstallCommand({
2132
+ pm: install.effectivePm,
2133
+ source
2134
+ })).map((c) => `\`${c}\``).join(" && ");
1987
2135
  let skillRegistered = false;
1988
- if (!inputs.installProjectSkill) warnings.push(`Skipped Prisma Next agent-skill install (--no-skill). To install the skills later, run: ${manualProjectSkillSummary}`);
1989
- else if (install.skipped) warnings.push(`Skipped Prisma Next agent-skill install because --no-install was passed. After you run install manually, install the skills with: ${manualProjectSkillSummary}`);
2136
+ if (!inputs.installProjectSkill) warnings.push(`Skipped Prisma Next skills install (--no-skill). To install the skills later, run: ${manualProjectSkillSummary}`);
1990
2137
  else {
1991
2138
  const spinner = ui.spinner();
1992
2139
  spinner.start("Registering Prisma Next skills with the agent runtime...");
@@ -2077,7 +2224,8 @@ function exitCodeForError(error) {
2077
2224
  case "5005":
2078
2225
  case "5010":
2079
2226
  case "5011":
2080
- case "5012": return 2;
2227
+ case "5012":
2228
+ case "5014": return 2;
2081
2229
  case "5006": return 3;
2082
2230
  case "5007": return 4;
2083
2231
  case "5008": return 5;
@@ -2299,7 +2447,7 @@ async function runEmit(ctx) {
2299
2447
  const spinner = ctx.ui.spinner();
2300
2448
  spinner.start("Emitting contract...");
2301
2449
  try {
2302
- const { executeContractEmit } = await import("./contract-emit-iynA3BCA.mjs").then((n) => n.t);
2450
+ const { executeContractEmit } = await import("./contract-emit-bcrpT-wD.mjs").then((n) => n.t);
2303
2451
  await executeContractEmit({ configPath: join(ctx.baseDir, "prisma-next.config.ts") });
2304
2452
  spinner.stop("Contract emitted");
2305
2453
  } catch (err) {
@@ -2352,4 +2500,4 @@ function sanitisePackageName(raw) {
2352
2500
  //#endregion
2353
2501
  export { runInit };
2354
2502
 
2355
- //# sourceMappingURL=init-eh2z5Tl6.mjs.map
2503
+ //# sourceMappingURL=init-BCJZPWE1.mjs.map