prisma-next 0.7.0 → 0.8.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.
- package/dist/cli.mjs +5 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{init-BRKnARU6.mjs → init-B-k3a1Qw.mjs} +337 -205
- 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 +11 -11
- package/dist/agent-skill-mongo.md +0 -138
- package/dist/agent-skill-postgres.md +0 -106
- package/dist/init-BRKnARU6.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
|
/**
|
|
@@ -810,7 +947,10 @@ function dbFile(target) {
|
|
|
810
947
|
import type { Contract } from './contract.d';
|
|
811
948
|
import contractJson from './contract.json' with { type: 'json' };
|
|
812
949
|
|
|
813
|
-
export const db = postgres<Contract>({
|
|
950
|
+
export const db = postgres<Contract>({
|
|
951
|
+
contractJson,
|
|
952
|
+
url: process.env['DATABASE_URL']!,
|
|
953
|
+
});
|
|
814
954
|
`;
|
|
815
955
|
return `import mongo from '@prisma-next/mongo/runtime';
|
|
816
956
|
import type { Contract } from './contract.d';
|
|
@@ -882,6 +1022,7 @@ async function resolveInitInputs(ctx) {
|
|
|
882
1022
|
canPrompt,
|
|
883
1023
|
autoAcceptPrompts
|
|
884
1024
|
});
|
|
1025
|
+
const installProjectSkill = options.skill !== false;
|
|
885
1026
|
return {
|
|
886
1027
|
target: finalTarget,
|
|
887
1028
|
authoring: finalAuthoring,
|
|
@@ -891,7 +1032,8 @@ async function resolveInitInputs(ctx) {
|
|
|
891
1032
|
probeDb: Boolean(options.probeDb),
|
|
892
1033
|
strictProbe: Boolean(options.strictProbe),
|
|
893
1034
|
reinit,
|
|
894
|
-
removePreviousFacade
|
|
1035
|
+
removePreviousFacade,
|
|
1036
|
+
installProjectSkill
|
|
895
1037
|
};
|
|
896
1038
|
}
|
|
897
1039
|
async function resolveWriteEnv(opts) {
|
|
@@ -1302,47 +1444,6 @@ function removeDependency(existing, depName) {
|
|
|
1302
1444
|
return `${JSON.stringify(parsed, null, 2)}${trailingNewline}`;
|
|
1303
1445
|
}
|
|
1304
1446
|
//#endregion
|
|
1305
|
-
//#region src/commands/init/templates/render.ts
|
|
1306
|
-
function renderTemplate(templateFile, variableNames, vars) {
|
|
1307
|
-
let result = readFileSync(join(import.meta.dirname, templateFile), "utf-8");
|
|
1308
|
-
for (const key of variableNames) {
|
|
1309
|
-
const value = vars[key];
|
|
1310
|
-
if (value === void 0) throw new Error(`Template variable '${key}' is not defined`);
|
|
1311
|
-
result = result.replaceAll(`{{${key}}}`, value);
|
|
1312
|
-
}
|
|
1313
|
-
return result;
|
|
1314
|
-
}
|
|
1315
|
-
//#endregion
|
|
1316
|
-
//#region src/commands/init/templates/agent-skill.ts
|
|
1317
|
-
const variables$1 = [
|
|
1318
|
-
"schemaPath",
|
|
1319
|
-
"schemaDir",
|
|
1320
|
-
"dbImportPath",
|
|
1321
|
-
"pkgRun",
|
|
1322
|
-
"authoringLabel"
|
|
1323
|
-
];
|
|
1324
|
-
/**
|
|
1325
|
-
* Renders the per-project agent skill (FR5.2). The skill template is
|
|
1326
|
-
* target-specific (Postgres vs Mongo query syntax differs); the authoring
|
|
1327
|
-
* style enters via:
|
|
1328
|
-
*
|
|
1329
|
-
* - `schemaPath` — already routed through {@link agentSkillMd}'s caller
|
|
1330
|
-
* (the AC says a TS-authoring scaffold must reference `prisma/contract.ts`).
|
|
1331
|
-
* - `authoringLabel` — a short human-readable note (`PSL` / `TypeScript`)
|
|
1332
|
-
* the skill template uses when describing the contract file.
|
|
1333
|
-
*/
|
|
1334
|
-
function agentSkillMd(target, authoring, schemaPath, pkgRun) {
|
|
1335
|
-
const schemaDir = dirname(schemaPath);
|
|
1336
|
-
const vars = {
|
|
1337
|
-
schemaPath,
|
|
1338
|
-
schemaDir,
|
|
1339
|
-
dbImportPath: `./${schemaDir}/db`,
|
|
1340
|
-
pkgRun,
|
|
1341
|
-
authoringLabel: authoring === "typescript" ? "TypeScript" : "PSL"
|
|
1342
|
-
};
|
|
1343
|
-
return renderTemplate(`agent-skill-${target}.md`, variables$1, vars);
|
|
1344
|
-
}
|
|
1345
|
-
//#endregion
|
|
1346
1447
|
//#region src/commands/init/templates/env.ts
|
|
1347
1448
|
/**
|
|
1348
1449
|
* The minimum supported server version for each target (FR8.1). The
|
|
@@ -1414,6 +1515,17 @@ function envFileContent(target) {
|
|
|
1414
1515
|
return envPlaceholderBody(target);
|
|
1415
1516
|
}
|
|
1416
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
|
|
1417
1529
|
//#region src/commands/init/templates/quick-reference.ts
|
|
1418
1530
|
const variables = [
|
|
1419
1531
|
"schemaPath",
|
|
@@ -1653,16 +1765,13 @@ async function runInit(baseDir, runOptions) {
|
|
|
1653
1765
|
path: "prisma-next.md",
|
|
1654
1766
|
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun)
|
|
1655
1767
|
},
|
|
1656
|
-
{
|
|
1657
|
-
path: ".agents/skills/prisma-next/SKILL.md",
|
|
1658
|
-
content: agentSkillMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun)
|
|
1659
|
-
},
|
|
1660
1768
|
{
|
|
1661
1769
|
path: ".env.example",
|
|
1662
1770
|
content: envExampleContent(inputs.target)
|
|
1663
1771
|
}
|
|
1664
1772
|
];
|
|
1665
1773
|
const filesToDelete = inputs.reinit ? [...findStaleArtefacts(baseDir, schemaDir)] : [];
|
|
1774
|
+
if (existsSync(join(baseDir, ".agents/skills/prisma-next/SKILL.md"))) filesToDelete.push(LEGACY_SKILL_FILE);
|
|
1666
1775
|
if (inputs.writeEnv) if (!existsSync(join(baseDir, ".env"))) filesToWrite.push({
|
|
1667
1776
|
path: ".env",
|
|
1668
1777
|
content: envFileContent(inputs.target)
|
|
@@ -1807,6 +1916,25 @@ async function runInit(baseDir, runOptions) {
|
|
|
1807
1916
|
filesWritten
|
|
1808
1917
|
}));
|
|
1809
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
|
+
}
|
|
1810
1938
|
const output = {
|
|
1811
1939
|
ok: true,
|
|
1812
1940
|
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
@@ -1884,6 +2012,7 @@ function exitCodeForError(error) {
|
|
|
1884
2012
|
case "5007": return 4;
|
|
1885
2013
|
case "5008": return 5;
|
|
1886
2014
|
case "5009": return 1;
|
|
2015
|
+
case "5013": return 6;
|
|
1887
2016
|
default: return 1;
|
|
1888
2017
|
}
|
|
1889
2018
|
}
|
|
@@ -1955,7 +2084,8 @@ async function runInstall(ctx) {
|
|
|
1955
2084
|
skipped: true,
|
|
1956
2085
|
deps: [],
|
|
1957
2086
|
devDeps: [],
|
|
1958
|
-
warnings: catalogWarnings
|
|
2087
|
+
warnings: catalogWarnings,
|
|
2088
|
+
effectivePm: pm
|
|
1959
2089
|
};
|
|
1960
2090
|
}
|
|
1961
2091
|
const exec = promisify(execFile);
|
|
@@ -1973,7 +2103,8 @@ async function runInstall(ctx) {
|
|
|
1973
2103
|
skipped: false,
|
|
1974
2104
|
deps,
|
|
1975
2105
|
devDeps,
|
|
1976
|
-
warnings: catalogWarnings
|
|
2106
|
+
warnings: catalogWarnings,
|
|
2107
|
+
effectivePm: pm
|
|
1977
2108
|
};
|
|
1978
2109
|
} catch (err) {
|
|
1979
2110
|
const stderrText = redactSecrets(readChildStderr(err));
|
|
@@ -1991,7 +2122,8 @@ async function runInstall(ctx) {
|
|
|
1991
2122
|
"Falling back to `npm install` so init can complete.",
|
|
1992
2123
|
stderrText ? ` pnpm error: ${stderrText.trim().split("\n")[0]}` : "",
|
|
1993
2124
|
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
1994
|
-
].filter(Boolean).join("\n")]
|
|
2125
|
+
].filter(Boolean).join("\n")],
|
|
2126
|
+
effectivePm: "npm"
|
|
1995
2127
|
};
|
|
1996
2128
|
} catch (npmErr) {
|
|
1997
2129
|
spinner.stop("Installation failed");
|
|
@@ -2150,4 +2282,4 @@ function sanitisePackageName(raw) {
|
|
|
2150
2282
|
//#endregion
|
|
2151
2283
|
export { runInit };
|
|
2152
2284
|
|
|
2153
|
-
//# sourceMappingURL=init-
|
|
2285
|
+
//# sourceMappingURL=init-B-k3a1Qw.mjs.map
|