prisma-next 0.7.0-dev.6 → 0.7.0-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +5 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{init-CRbEuP9W.mjs → init-B-k3a1Qw.mjs} +333 -204
- package/dist/init-B-k3a1Qw.mjs.map +1 -0
- package/dist/{output-B16Kefzx.mjs → output-BVj6a971.mjs} +3 -3
- package/dist/{output-B16Kefzx.mjs.map → output-BVj6a971.mjs.map} +1 -1
- package/package.json +10 -10
- package/dist/agent-skill-mongo.md +0 -138
- package/dist/agent-skill-postgres.md +0 -106
- package/dist/init-CRbEuP9W.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as CliStructuredError } from "./cli-errors-D3_sMh2K.mjs";
|
|
2
2
|
import { i as formatErrorOutput, r as formatErrorJson, t as TerminalUI } from "./terminal-ui-C_hFNbAn.mjs";
|
|
3
|
-
import { i as renderInitOutro, n as buildNextSteps, r as formatInitJson, t as InitOutputSchema } from "./output-
|
|
3
|
+
import { i as renderInitOutro, n as buildNextSteps, r as formatInitJson, t as InitOutputSchema } from "./output-BVj6a971.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { basename, dirname, extname, isAbsolute, join, normalize } from "pathe";
|
|
6
6
|
import * as clack from "@clack/prompts";
|
|
@@ -9,159 +9,6 @@ import { execFile } from "node:child_process";
|
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
10
|
import { detect, getUserAgent } from "package-manager-detector/detect";
|
|
11
11
|
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
12
|
-
//#region src/commands/init/detect-package-manager.ts
|
|
13
|
-
const KNOWN = new Set([
|
|
14
|
-
"pnpm",
|
|
15
|
-
"npm",
|
|
16
|
-
"yarn",
|
|
17
|
-
"bun",
|
|
18
|
-
"deno"
|
|
19
|
-
]);
|
|
20
|
-
/**
|
|
21
|
-
* Resolves the package manager `init` should drive for `add` / `install`
|
|
22
|
-
* commands. Tries, in order:
|
|
23
|
-
*
|
|
24
|
-
* 1. **`detect()`** — walks up from `cwd` looking for a lockfile, the
|
|
25
|
-
* `packageManager` field, the `devEngines.packageManager` field, or
|
|
26
|
-
* install metadata. This is the right answer whenever the user is
|
|
27
|
-
* anywhere inside an existing project, including a deep workspace
|
|
28
|
-
* subdirectory.
|
|
29
|
-
*
|
|
30
|
-
* 2. **`getUserAgent()`** — parses `npm_config_user_agent`, the env var
|
|
31
|
-
* every PM sets when it spawns a script. This catches the
|
|
32
|
-
* bare-directory case where there's no project to walk up to but the
|
|
33
|
-
* user invoked us via `pnpm dlx prisma-next init` / `bunx
|
|
34
|
-
* prisma-next init` / `yarn dlx …`. Same signal used by every
|
|
35
|
-
* `create-*` tool in the ecosystem (`create-vite`, `create-next-app`,
|
|
36
|
-
* `create-astro`, `@antfu/ni`, …).
|
|
37
|
-
*
|
|
38
|
-
* 3. **`npm`** — final fallback. Always present alongside Node.
|
|
39
|
-
*/
|
|
40
|
-
async function detectPackageManager(cwd) {
|
|
41
|
-
const detected = await detect({ cwd });
|
|
42
|
-
if (detected && KNOWN.has(detected.name)) return detected.name;
|
|
43
|
-
const userAgent = getUserAgent();
|
|
44
|
-
if (userAgent !== null && KNOWN.has(userAgent)) return userAgent;
|
|
45
|
-
return "npm";
|
|
46
|
-
}
|
|
47
|
-
function hasProjectManifest(cwd) {
|
|
48
|
-
return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
|
|
49
|
-
}
|
|
50
|
-
function formatRunCommand(pm, bin, args) {
|
|
51
|
-
if (pm === "npm") return `npx ${bin} ${args}`;
|
|
52
|
-
if (pm === "deno") return `deno run npm:${bin} ${args}`;
|
|
53
|
-
return `${pm} ${bin} ${args}`;
|
|
54
|
-
}
|
|
55
|
-
function formatAddArgs(pm, packages) {
|
|
56
|
-
if (pm === "deno") return ["add", ...packages.map((p) => `npm:${p}`)];
|
|
57
|
-
return ["add", ...packages];
|
|
58
|
-
}
|
|
59
|
-
function formatAddDevArgs(pm, packages) {
|
|
60
|
-
if (pm === "deno") return [
|
|
61
|
-
"add",
|
|
62
|
-
"--dev",
|
|
63
|
-
...packages.map((p) => `npm:${p}`)
|
|
64
|
-
];
|
|
65
|
-
return [
|
|
66
|
-
"add",
|
|
67
|
-
"-D",
|
|
68
|
-
...packages
|
|
69
|
-
];
|
|
70
|
-
}
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/commands/init/detect-pnpm-catalog.ts
|
|
73
|
-
/**
|
|
74
|
-
* Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
|
|
75
|
-
* its top-level `catalog:` block for entries that match any of `packages`.
|
|
76
|
-
*
|
|
77
|
-
* Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
|
|
78
|
-
* inside a pnpm workspace whose catalog overrides one of the packages it
|
|
79
|
-
* installs, surface a structured warning so the user knows the catalog
|
|
80
|
-
* version (not the published `latest`) is what ended up in their
|
|
81
|
-
* `node_modules`. pnpm itself does this silently; the warning closes the
|
|
82
|
-
* "looks fine, must be wrong version six months later" gap.
|
|
83
|
-
*
|
|
84
|
-
* Notes / scope:
|
|
85
|
-
*
|
|
86
|
-
* - We only inspect the unnamed top-level `catalog:` block. pnpm also
|
|
87
|
-
* supports `catalogs:` (plural — *named* catalogs referenced via
|
|
88
|
-
* `catalog:foo` specifiers); those don't apply to a vanilla
|
|
89
|
-
* `pnpm add prisma-next` invocation, so we skip them.
|
|
90
|
-
* - We don't validate YAML syntax exhaustively. The file format pnpm
|
|
91
|
-
* ships is line-oriented and well-known; a minimal regex is more
|
|
92
|
-
* robust than depending on a YAML parser for one warning.
|
|
93
|
-
* - We don't compare against the registry's `latest` — pnpm uses the
|
|
94
|
-
* catalog version regardless, so the warning fires whenever a match
|
|
95
|
-
* exists. The user-facing copy explains how to opt out.
|
|
96
|
-
*/
|
|
97
|
-
function detectPnpmCatalogOverrides(baseDir, packages) {
|
|
98
|
-
const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
|
|
99
|
-
if (workspaceFile === null) return null;
|
|
100
|
-
const catalog = extractCatalogBlock(readFileSync(workspaceFile, "utf-8"));
|
|
101
|
-
if (catalog === null) return {
|
|
102
|
-
workspaceFile,
|
|
103
|
-
entries: []
|
|
104
|
-
};
|
|
105
|
-
const wanted = new Set(packages);
|
|
106
|
-
const entries = [];
|
|
107
|
-
for (const [name, version] of catalog) if (wanted.has(name)) entries.push({
|
|
108
|
-
name,
|
|
109
|
-
version
|
|
110
|
-
});
|
|
111
|
-
return {
|
|
112
|
-
workspaceFile,
|
|
113
|
-
entries
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function findNearestPnpmWorkspaceFile(baseDir) {
|
|
117
|
-
let dir = baseDir;
|
|
118
|
-
let prev = "";
|
|
119
|
-
while (dir !== prev) {
|
|
120
|
-
const candidate = join(dir, "pnpm-workspace.yaml");
|
|
121
|
-
if (existsSync(candidate)) return candidate;
|
|
122
|
-
prev = dir;
|
|
123
|
-
dir = dirname(dir);
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Returns the entries inside the top-level `catalog:` block as `[name, version]`
|
|
129
|
-
* pairs in document order, or `null` when no `catalog:` block exists.
|
|
130
|
-
*
|
|
131
|
-
* The parser is intentionally minimal: it reads line-by-line, locates the
|
|
132
|
-
* top-level `catalog:` line (no leading whitespace), then collects every
|
|
133
|
-
* subsequent indented line of the form `<key>: <value>` until the next
|
|
134
|
-
* top-level key (or end of file). Quotes around `<key>` and `<value>`
|
|
135
|
-
* are stripped; comments (`#…`) are ignored.
|
|
136
|
-
*/
|
|
137
|
-
function extractCatalogBlock(contents) {
|
|
138
|
-
const lines = contents.split(/\r?\n/);
|
|
139
|
-
const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
|
|
140
|
-
if (startIdx === -1) return null;
|
|
141
|
-
const entries = [];
|
|
142
|
-
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
143
|
-
const raw = lines[i] ?? "";
|
|
144
|
-
if (raw.trim() === "" || /^\s*#/.test(raw)) continue;
|
|
145
|
-
if (!/^\s/.test(raw)) break;
|
|
146
|
-
const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
|
|
147
|
-
if (!match) continue;
|
|
148
|
-
const name = match[1] ?? match[2] ?? match[3];
|
|
149
|
-
if (name === void 0) continue;
|
|
150
|
-
const version = stripQuotes((match[4] ?? "").trim());
|
|
151
|
-
if (version === "") continue;
|
|
152
|
-
entries.push([name, version]);
|
|
153
|
-
}
|
|
154
|
-
return entries;
|
|
155
|
-
}
|
|
156
|
-
function stripQuotes(value) {
|
|
157
|
-
if (value.length >= 2) {
|
|
158
|
-
const first = value[0];
|
|
159
|
-
const last = value[value.length - 1];
|
|
160
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'") return value.slice(1, -1);
|
|
161
|
-
}
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
//#endregion
|
|
165
12
|
//#region src/commands/init/errors.ts
|
|
166
13
|
/**
|
|
167
14
|
* Re-init in non-interactive mode without `--force`. Distinct from the
|
|
@@ -364,6 +211,296 @@ function errorInitEmitFailed(options) {
|
|
|
364
211
|
}
|
|
365
212
|
});
|
|
366
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* The project-level agent-skill install (`npx skills add
|
|
216
|
+
* @prisma-next/agent-skill`) failed after a successful dependency
|
|
217
|
+
* install + emit. The project's scaffold remains on disk; the user
|
|
218
|
+
* can either fix the underlying issue (network, registry, PATH) and
|
|
219
|
+
* run the install command manually, or re-run `init --no-skill` to
|
|
220
|
+
* proceed without the skill.
|
|
221
|
+
*
|
|
222
|
+
* Non-rolling-back, matching the existing install/emit failure
|
|
223
|
+
* semantics. Maps to exit code `6 = SKILL_INSTALL_FAILED`.
|
|
224
|
+
*/
|
|
225
|
+
function errorInitSkillInstallFailed(options) {
|
|
226
|
+
return new CliStructuredError("5013", "Failed to install @prisma-next/agent-skill", {
|
|
227
|
+
domain: "CLI",
|
|
228
|
+
why: `\`${options.skillInstallCommand}\` exited with an error: ${options.cause}`,
|
|
229
|
+
fix: `Either:
|
|
230
|
+
- 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}`,
|
|
231
|
+
docsUrl: "https://prisma-next.dev/docs/cli/init#agent-skill",
|
|
232
|
+
meta: {
|
|
233
|
+
filesWritten: options.filesWritten,
|
|
234
|
+
skillInstallCommand: options.skillInstallCommand,
|
|
235
|
+
cause: options.cause
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/commands/init/agent-skill-install.ts
|
|
241
|
+
const exec = promisify(execFile);
|
|
242
|
+
/**
|
|
243
|
+
* The npm package the agent-skill install dispatches to. Version-locked
|
|
244
|
+
* to Prisma Next via the consumer project's `package.json`; the install
|
|
245
|
+
* subprocess picks up whatever version is resolvable at install time.
|
|
246
|
+
*
|
|
247
|
+
* The skill is **always** installed at the project level — never the
|
|
248
|
+
* user level — precisely so the skill version tracks the project's
|
|
249
|
+
* Prisma Next version. A user-level (global) install of an
|
|
250
|
+
* agent-skills CLI package would have to pick a single version for
|
|
251
|
+
* every project on the host, which breaks the version-locking invariant
|
|
252
|
+
* the rest of the framework relies on (skills, CLI, runtime, and
|
|
253
|
+
* extension packs all ship at the same version per release).
|
|
254
|
+
*/
|
|
255
|
+
const AGENT_SKILL_PACKAGE = "@prisma-next/agent-skill";
|
|
256
|
+
/**
|
|
257
|
+
* The skill-install command, formatted for the project's detected
|
|
258
|
+
* package manager. `npx`/`pnpm dlx`/`bunx` are interchangeable to the
|
|
259
|
+
* user; we pick the variant that matches the rest of the install step
|
|
260
|
+
* so a single project consistently uses one runner.
|
|
261
|
+
*
|
|
262
|
+
* `--all` auto-selects every skill in the cluster and every detected
|
|
263
|
+
* agent runtime, skipping the multi-select prompts the `skills` CLI
|
|
264
|
+
* shows by default. A non-interactive scaffold step cannot present
|
|
265
|
+
* prompts, and the cluster is designed to be installed as a unit (the
|
|
266
|
+
* router skill routes between the workflow-scoped siblings). Users who
|
|
267
|
+
* want a narrower install run `npx skills add @prisma-next/agent-skill`
|
|
268
|
+
* themselves after `init` with the flags they want.
|
|
269
|
+
*
|
|
270
|
+
* Exported for unit tests so the per-PM dispatch can be asserted
|
|
271
|
+
* without a live subprocess.
|
|
272
|
+
*/
|
|
273
|
+
function formatSkillInstallCommand(pm) {
|
|
274
|
+
const args = [
|
|
275
|
+
"skills",
|
|
276
|
+
"add",
|
|
277
|
+
AGENT_SKILL_PACKAGE,
|
|
278
|
+
"--all"
|
|
279
|
+
];
|
|
280
|
+
switch (pm) {
|
|
281
|
+
case "pnpm": return `pnpm dlx ${args.join(" ")}`;
|
|
282
|
+
case "yarn": return `yarn dlx ${args.join(" ")}`;
|
|
283
|
+
case "bun": return `bunx ${args.join(" ")}`;
|
|
284
|
+
case "deno": return `deno run -A npm:${args.join(" ")}`;
|
|
285
|
+
case "npm": return `npx ${args.join(" ")}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Parse the project-pm-formatted command into an exec call. The
|
|
290
|
+
* format-then-parse split keeps the user-facing command string the same
|
|
291
|
+
* as the surface the structured error advertises, so a user who copies
|
|
292
|
+
* the error's `fix` line gets the same invocation that init just
|
|
293
|
+
* attempted.
|
|
294
|
+
*/
|
|
295
|
+
function commandToExec(command) {
|
|
296
|
+
const tokens = command.split(/\s+/);
|
|
297
|
+
return {
|
|
298
|
+
file: tokens[0] ?? "npx",
|
|
299
|
+
args: tokens.slice(1)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Runs the project-level skill install. Returns `{ ok: true, command }`
|
|
304
|
+
* on success; throws a structured `errorInitSkillInstallFailed` on
|
|
305
|
+
* failure. The throw is intentionally fatal — project-level skill
|
|
306
|
+
* install is unconditional (modulo `--no-skill`) and the user opted
|
|
307
|
+
* into Prisma Next by running `init`. A silent skip would defeat the
|
|
308
|
+
* onboarding-to-zero contract.
|
|
309
|
+
*/
|
|
310
|
+
async function runProjectLevelSkillInstall(ctx) {
|
|
311
|
+
const command = formatSkillInstallCommand(ctx.pm);
|
|
312
|
+
const { file, args } = commandToExec(command);
|
|
313
|
+
try {
|
|
314
|
+
await exec(file, args, { cwd: ctx.baseDir });
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
command
|
|
318
|
+
};
|
|
319
|
+
} catch (err) {
|
|
320
|
+
throw errorInitSkillInstallFailed({
|
|
321
|
+
skillInstallCommand: command,
|
|
322
|
+
filesWritten: ctx.filesWritten,
|
|
323
|
+
cause: redactSecrets$1(readChildStderr$1(err)) || (err instanceof Error ? err.message : String(err))
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function readChildStderr$1(err) {
|
|
328
|
+
if (err instanceof Error && "stderr" in err) return String(err.stderr ?? "");
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Strips credentials from a `scheme://user:pass@host/...` URL anywhere
|
|
333
|
+
* in `stderr`. Package-manager stderr regularly contains credentialed
|
|
334
|
+
* registry URLs (private npm registries, GitHub Packages tokens), and
|
|
335
|
+
* those bubble into the structured `errorInitSkillInstallFailed`
|
|
336
|
+
* envelope, which ends up in logs and CI output. Redact at the
|
|
337
|
+
* boundary so we never re-emit a secret.
|
|
338
|
+
*
|
|
339
|
+
* Exported for unit tests.
|
|
340
|
+
*/
|
|
341
|
+
function redactSecrets$1(stderr) {
|
|
342
|
+
if (!stderr) return stderr;
|
|
343
|
+
return stderr.replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s]+)@/g, "$1***@");
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Hand-rolled skill stub path that init must not leave behind. Removed
|
|
347
|
+
* on every init run so a project's `.agents/skills/prisma-next/` does
|
|
348
|
+
* not shadow the published `@prisma-next/agent-skill` package.
|
|
349
|
+
*/
|
|
350
|
+
const LEGACY_SKILL_FILE = ".agents/skills/prisma-next/SKILL.md";
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/commands/init/detect-package-manager.ts
|
|
353
|
+
const KNOWN = new Set([
|
|
354
|
+
"pnpm",
|
|
355
|
+
"npm",
|
|
356
|
+
"yarn",
|
|
357
|
+
"bun",
|
|
358
|
+
"deno"
|
|
359
|
+
]);
|
|
360
|
+
/**
|
|
361
|
+
* Resolves the package manager `init` should drive for `add` / `install`
|
|
362
|
+
* commands. Tries, in order:
|
|
363
|
+
*
|
|
364
|
+
* 1. **`detect()`** — walks up from `cwd` looking for a lockfile, the
|
|
365
|
+
* `packageManager` field, the `devEngines.packageManager` field, or
|
|
366
|
+
* install metadata. This is the right answer whenever the user is
|
|
367
|
+
* anywhere inside an existing project, including a deep workspace
|
|
368
|
+
* subdirectory.
|
|
369
|
+
*
|
|
370
|
+
* 2. **`getUserAgent()`** — parses `npm_config_user_agent`, the env var
|
|
371
|
+
* every PM sets when it spawns a script. This catches the
|
|
372
|
+
* bare-directory case where there's no project to walk up to but the
|
|
373
|
+
* user invoked us via `pnpm dlx prisma-next init` / `bunx
|
|
374
|
+
* prisma-next init` / `yarn dlx …`. Same signal used by every
|
|
375
|
+
* `create-*` tool in the ecosystem (`create-vite`, `create-next-app`,
|
|
376
|
+
* `create-astro`, `@antfu/ni`, …).
|
|
377
|
+
*
|
|
378
|
+
* 3. **`npm`** — final fallback. Always present alongside Node.
|
|
379
|
+
*/
|
|
380
|
+
async function detectPackageManager(cwd) {
|
|
381
|
+
const detected = await detect({ cwd });
|
|
382
|
+
if (detected && KNOWN.has(detected.name)) return detected.name;
|
|
383
|
+
const userAgent = getUserAgent();
|
|
384
|
+
if (userAgent !== null && KNOWN.has(userAgent)) return userAgent;
|
|
385
|
+
return "npm";
|
|
386
|
+
}
|
|
387
|
+
function hasProjectManifest(cwd) {
|
|
388
|
+
return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
|
|
389
|
+
}
|
|
390
|
+
function formatRunCommand(pm, bin, args) {
|
|
391
|
+
if (pm === "npm") return `npx ${bin} ${args}`;
|
|
392
|
+
if (pm === "deno") return `deno run npm:${bin} ${args}`;
|
|
393
|
+
return `${pm} ${bin} ${args}`;
|
|
394
|
+
}
|
|
395
|
+
function formatAddArgs(pm, packages) {
|
|
396
|
+
if (pm === "deno") return ["add", ...packages.map((p) => `npm:${p}`)];
|
|
397
|
+
return ["add", ...packages];
|
|
398
|
+
}
|
|
399
|
+
function formatAddDevArgs(pm, packages) {
|
|
400
|
+
if (pm === "deno") return [
|
|
401
|
+
"add",
|
|
402
|
+
"--dev",
|
|
403
|
+
...packages.map((p) => `npm:${p}`)
|
|
404
|
+
];
|
|
405
|
+
return [
|
|
406
|
+
"add",
|
|
407
|
+
"-D",
|
|
408
|
+
...packages
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/commands/init/detect-pnpm-catalog.ts
|
|
413
|
+
/**
|
|
414
|
+
* Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
|
|
415
|
+
* its top-level `catalog:` block for entries that match any of `packages`.
|
|
416
|
+
*
|
|
417
|
+
* Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
|
|
418
|
+
* inside a pnpm workspace whose catalog overrides one of the packages it
|
|
419
|
+
* installs, surface a structured warning so the user knows the catalog
|
|
420
|
+
* version (not the published `latest`) is what ended up in their
|
|
421
|
+
* `node_modules`. pnpm itself does this silently; the warning closes the
|
|
422
|
+
* "looks fine, must be wrong version six months later" gap.
|
|
423
|
+
*
|
|
424
|
+
* Notes / scope:
|
|
425
|
+
*
|
|
426
|
+
* - We only inspect the unnamed top-level `catalog:` block. pnpm also
|
|
427
|
+
* supports `catalogs:` (plural — *named* catalogs referenced via
|
|
428
|
+
* `catalog:foo` specifiers); those don't apply to a vanilla
|
|
429
|
+
* `pnpm add prisma-next` invocation, so we skip them.
|
|
430
|
+
* - We don't validate YAML syntax exhaustively. The file format pnpm
|
|
431
|
+
* ships is line-oriented and well-known; a minimal regex is more
|
|
432
|
+
* robust than depending on a YAML parser for one warning.
|
|
433
|
+
* - We don't compare against the registry's `latest` — pnpm uses the
|
|
434
|
+
* catalog version regardless, so the warning fires whenever a match
|
|
435
|
+
* exists. The user-facing copy explains how to opt out.
|
|
436
|
+
*/
|
|
437
|
+
function detectPnpmCatalogOverrides(baseDir, packages) {
|
|
438
|
+
const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
|
|
439
|
+
if (workspaceFile === null) return null;
|
|
440
|
+
const catalog = extractCatalogBlock(readFileSync(workspaceFile, "utf-8"));
|
|
441
|
+
if (catalog === null) return {
|
|
442
|
+
workspaceFile,
|
|
443
|
+
entries: []
|
|
444
|
+
};
|
|
445
|
+
const wanted = new Set(packages);
|
|
446
|
+
const entries = [];
|
|
447
|
+
for (const [name, version] of catalog) if (wanted.has(name)) entries.push({
|
|
448
|
+
name,
|
|
449
|
+
version
|
|
450
|
+
});
|
|
451
|
+
return {
|
|
452
|
+
workspaceFile,
|
|
453
|
+
entries
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function findNearestPnpmWorkspaceFile(baseDir) {
|
|
457
|
+
let dir = baseDir;
|
|
458
|
+
let prev = "";
|
|
459
|
+
while (dir !== prev) {
|
|
460
|
+
const candidate = join(dir, "pnpm-workspace.yaml");
|
|
461
|
+
if (existsSync(candidate)) return candidate;
|
|
462
|
+
prev = dir;
|
|
463
|
+
dir = dirname(dir);
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Returns the entries inside the top-level `catalog:` block as `[name, version]`
|
|
469
|
+
* pairs in document order, or `null` when no `catalog:` block exists.
|
|
470
|
+
*
|
|
471
|
+
* The parser is intentionally minimal: it reads line-by-line, locates the
|
|
472
|
+
* top-level `catalog:` line (no leading whitespace), then collects every
|
|
473
|
+
* subsequent indented line of the form `<key>: <value>` until the next
|
|
474
|
+
* top-level key (or end of file). Quotes around `<key>` and `<value>`
|
|
475
|
+
* are stripped; comments (`#…`) are ignored.
|
|
476
|
+
*/
|
|
477
|
+
function extractCatalogBlock(contents) {
|
|
478
|
+
const lines = contents.split(/\r?\n/);
|
|
479
|
+
const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
|
|
480
|
+
if (startIdx === -1) return null;
|
|
481
|
+
const entries = [];
|
|
482
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
483
|
+
const raw = lines[i] ?? "";
|
|
484
|
+
if (raw.trim() === "" || /^\s*#/.test(raw)) continue;
|
|
485
|
+
if (!/^\s/.test(raw)) break;
|
|
486
|
+
const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
|
|
487
|
+
if (!match) continue;
|
|
488
|
+
const name = match[1] ?? match[2] ?? match[3];
|
|
489
|
+
if (name === void 0) continue;
|
|
490
|
+
const version = stripQuotes((match[4] ?? "").trim());
|
|
491
|
+
if (version === "") continue;
|
|
492
|
+
entries.push([name, version]);
|
|
493
|
+
}
|
|
494
|
+
return entries;
|
|
495
|
+
}
|
|
496
|
+
function stripQuotes(value) {
|
|
497
|
+
if (value.length >= 2) {
|
|
498
|
+
const first = value[0];
|
|
499
|
+
const last = value[value.length - 1];
|
|
500
|
+
if (first === "\"" && last === "\"" || first === "'" && last === "'") return value.slice(1, -1);
|
|
501
|
+
}
|
|
502
|
+
return value;
|
|
503
|
+
}
|
|
367
504
|
//#endregion
|
|
368
505
|
//#region src/commands/init/hygiene-gitattributes.ts
|
|
369
506
|
/**
|
|
@@ -885,6 +1022,7 @@ async function resolveInitInputs(ctx) {
|
|
|
885
1022
|
canPrompt,
|
|
886
1023
|
autoAcceptPrompts
|
|
887
1024
|
});
|
|
1025
|
+
const installProjectSkill = options.skill !== false;
|
|
888
1026
|
return {
|
|
889
1027
|
target: finalTarget,
|
|
890
1028
|
authoring: finalAuthoring,
|
|
@@ -894,7 +1032,8 @@ async function resolveInitInputs(ctx) {
|
|
|
894
1032
|
probeDb: Boolean(options.probeDb),
|
|
895
1033
|
strictProbe: Boolean(options.strictProbe),
|
|
896
1034
|
reinit,
|
|
897
|
-
removePreviousFacade
|
|
1035
|
+
removePreviousFacade,
|
|
1036
|
+
installProjectSkill
|
|
898
1037
|
};
|
|
899
1038
|
}
|
|
900
1039
|
async function resolveWriteEnv(opts) {
|
|
@@ -1305,47 +1444,6 @@ function removeDependency(existing, depName) {
|
|
|
1305
1444
|
return `${JSON.stringify(parsed, null, 2)}${trailingNewline}`;
|
|
1306
1445
|
}
|
|
1307
1446
|
//#endregion
|
|
1308
|
-
//#region src/commands/init/templates/render.ts
|
|
1309
|
-
function renderTemplate(templateFile, variableNames, vars) {
|
|
1310
|
-
let result = readFileSync(join(import.meta.dirname, templateFile), "utf-8");
|
|
1311
|
-
for (const key of variableNames) {
|
|
1312
|
-
const value = vars[key];
|
|
1313
|
-
if (value === void 0) throw new Error(`Template variable '${key}' is not defined`);
|
|
1314
|
-
result = result.replaceAll(`{{${key}}}`, value);
|
|
1315
|
-
}
|
|
1316
|
-
return result;
|
|
1317
|
-
}
|
|
1318
|
-
//#endregion
|
|
1319
|
-
//#region src/commands/init/templates/agent-skill.ts
|
|
1320
|
-
const variables$1 = [
|
|
1321
|
-
"schemaPath",
|
|
1322
|
-
"schemaDir",
|
|
1323
|
-
"dbImportPath",
|
|
1324
|
-
"pkgRun",
|
|
1325
|
-
"authoringLabel"
|
|
1326
|
-
];
|
|
1327
|
-
/**
|
|
1328
|
-
* Renders the per-project agent skill (FR5.2). The skill template is
|
|
1329
|
-
* target-specific (Postgres vs Mongo query syntax differs); the authoring
|
|
1330
|
-
* style enters via:
|
|
1331
|
-
*
|
|
1332
|
-
* - `schemaPath` — already routed through {@link agentSkillMd}'s caller
|
|
1333
|
-
* (the AC says a TS-authoring scaffold must reference `prisma/contract.ts`).
|
|
1334
|
-
* - `authoringLabel` — a short human-readable note (`PSL` / `TypeScript`)
|
|
1335
|
-
* the skill template uses when describing the contract file.
|
|
1336
|
-
*/
|
|
1337
|
-
function agentSkillMd(target, authoring, schemaPath, pkgRun) {
|
|
1338
|
-
const schemaDir = dirname(schemaPath);
|
|
1339
|
-
const vars = {
|
|
1340
|
-
schemaPath,
|
|
1341
|
-
schemaDir,
|
|
1342
|
-
dbImportPath: `./${schemaDir}/db`,
|
|
1343
|
-
pkgRun,
|
|
1344
|
-
authoringLabel: authoring === "typescript" ? "TypeScript" : "PSL"
|
|
1345
|
-
};
|
|
1346
|
-
return renderTemplate(`agent-skill-${target}.md`, variables$1, vars);
|
|
1347
|
-
}
|
|
1348
|
-
//#endregion
|
|
1349
1447
|
//#region src/commands/init/templates/env.ts
|
|
1350
1448
|
/**
|
|
1351
1449
|
* The minimum supported server version for each target (FR8.1). The
|
|
@@ -1417,6 +1515,17 @@ function envFileContent(target) {
|
|
|
1417
1515
|
return envPlaceholderBody(target);
|
|
1418
1516
|
}
|
|
1419
1517
|
//#endregion
|
|
1518
|
+
//#region src/commands/init/templates/render.ts
|
|
1519
|
+
function renderTemplate(templateFile, variableNames, vars) {
|
|
1520
|
+
let result = readFileSync(join(import.meta.dirname, templateFile), "utf-8");
|
|
1521
|
+
for (const key of variableNames) {
|
|
1522
|
+
const value = vars[key];
|
|
1523
|
+
if (value === void 0) throw new Error(`Template variable '${key}' is not defined`);
|
|
1524
|
+
result = result.replaceAll(`{{${key}}}`, value);
|
|
1525
|
+
}
|
|
1526
|
+
return result;
|
|
1527
|
+
}
|
|
1528
|
+
//#endregion
|
|
1420
1529
|
//#region src/commands/init/templates/quick-reference.ts
|
|
1421
1530
|
const variables = [
|
|
1422
1531
|
"schemaPath",
|
|
@@ -1656,16 +1765,13 @@ async function runInit(baseDir, runOptions) {
|
|
|
1656
1765
|
path: "prisma-next.md",
|
|
1657
1766
|
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun)
|
|
1658
1767
|
},
|
|
1659
|
-
{
|
|
1660
|
-
path: ".agents/skills/prisma-next/SKILL.md",
|
|
1661
|
-
content: agentSkillMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun)
|
|
1662
|
-
},
|
|
1663
1768
|
{
|
|
1664
1769
|
path: ".env.example",
|
|
1665
1770
|
content: envExampleContent(inputs.target)
|
|
1666
1771
|
}
|
|
1667
1772
|
];
|
|
1668
1773
|
const filesToDelete = inputs.reinit ? [...findStaleArtefacts(baseDir, schemaDir)] : [];
|
|
1774
|
+
if (existsSync(join(baseDir, ".agents/skills/prisma-next/SKILL.md"))) filesToDelete.push(LEGACY_SKILL_FILE);
|
|
1669
1775
|
if (inputs.writeEnv) if (!existsSync(join(baseDir, ".env"))) filesToWrite.push({
|
|
1670
1776
|
path: ".env",
|
|
1671
1777
|
content: envFileContent(inputs.target)
|
|
@@ -1810,6 +1916,25 @@ async function runInit(baseDir, runOptions) {
|
|
|
1810
1916
|
filesWritten
|
|
1811
1917
|
}));
|
|
1812
1918
|
}
|
|
1919
|
+
const manualProjectSkillCommand = formatSkillInstallCommand(install.effectivePm);
|
|
1920
|
+
if (!inputs.installProjectSkill) warnings.push(`Skipped @prisma-next/agent-skill install (--no-skill). To install later, run \`${manualProjectSkillCommand}\` in this project.`);
|
|
1921
|
+
else if (install.skipped) warnings.push(`Skipped @prisma-next/agent-skill install because --no-install was passed. Once you run install manually, register the skill with \`${manualProjectSkillCommand}\`.`);
|
|
1922
|
+
else {
|
|
1923
|
+
const spinner = ui.spinner();
|
|
1924
|
+
spinner.start("Registering @prisma-next/agent-skill with the agent runtime...");
|
|
1925
|
+
try {
|
|
1926
|
+
const project = await runProjectLevelSkillInstall({
|
|
1927
|
+
baseDir,
|
|
1928
|
+
pm: install.effectivePm,
|
|
1929
|
+
filesWritten
|
|
1930
|
+
});
|
|
1931
|
+
spinner.stop(`Registered @prisma-next/agent-skill (project level) — ran \`${project.command}\``);
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
spinner.stop("Agent-skill install failed");
|
|
1934
|
+
if (CliStructuredError.is(error)) return emitError(ui, flags, error);
|
|
1935
|
+
throw error;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1813
1938
|
const output = {
|
|
1814
1939
|
ok: true,
|
|
1815
1940
|
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
@@ -1887,6 +2012,7 @@ function exitCodeForError(error) {
|
|
|
1887
2012
|
case "5007": return 4;
|
|
1888
2013
|
case "5008": return 5;
|
|
1889
2014
|
case "5009": return 1;
|
|
2015
|
+
case "5013": return 6;
|
|
1890
2016
|
default: return 1;
|
|
1891
2017
|
}
|
|
1892
2018
|
}
|
|
@@ -1958,7 +2084,8 @@ async function runInstall(ctx) {
|
|
|
1958
2084
|
skipped: true,
|
|
1959
2085
|
deps: [],
|
|
1960
2086
|
devDeps: [],
|
|
1961
|
-
warnings: catalogWarnings
|
|
2087
|
+
warnings: catalogWarnings,
|
|
2088
|
+
effectivePm: pm
|
|
1962
2089
|
};
|
|
1963
2090
|
}
|
|
1964
2091
|
const exec = promisify(execFile);
|
|
@@ -1976,7 +2103,8 @@ async function runInstall(ctx) {
|
|
|
1976
2103
|
skipped: false,
|
|
1977
2104
|
deps,
|
|
1978
2105
|
devDeps,
|
|
1979
|
-
warnings: catalogWarnings
|
|
2106
|
+
warnings: catalogWarnings,
|
|
2107
|
+
effectivePm: pm
|
|
1980
2108
|
};
|
|
1981
2109
|
} catch (err) {
|
|
1982
2110
|
const stderrText = redactSecrets(readChildStderr(err));
|
|
@@ -1994,7 +2122,8 @@ async function runInstall(ctx) {
|
|
|
1994
2122
|
"Falling back to `npm install` so init can complete.",
|
|
1995
2123
|
stderrText ? ` pnpm error: ${stderrText.trim().split("\n")[0]}` : "",
|
|
1996
2124
|
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
1997
|
-
].filter(Boolean).join("\n")]
|
|
2125
|
+
].filter(Boolean).join("\n")],
|
|
2126
|
+
effectivePm: "npm"
|
|
1998
2127
|
};
|
|
1999
2128
|
} catch (npmErr) {
|
|
2000
2129
|
spinner.stop("Installation failed");
|
|
@@ -2153,4 +2282,4 @@ function sanitisePackageName(raw) {
|
|
|
2153
2282
|
//#endregion
|
|
2154
2283
|
export { runInit };
|
|
2155
2284
|
|
|
2156
|
-
//# sourceMappingURL=init-
|
|
2285
|
+
//# sourceMappingURL=init-B-k3a1Qw.mjs.map
|