@prisma/orm-toolchain 8.0.0-rc.1-dev.38 → 8.0.0-rc.1-dev.39
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/bin__prisma-next.mjs +8 -301
- package/dist/bin__prisma-next.mjs.map +1 -1
- package/dist/cli.mjs +1944 -256
- package/dist/cli.mjs.map +1 -1
- package/dist/cli__commands__migrate.mjs +1 -1
- package/dist/cli__commands__migration-check.d.mts +1 -1
- package/dist/cli__commands__migration-check.mjs +1 -1
- package/dist/cli__commands__migration-graph.d.mts +1 -1
- package/dist/cli__commands__migration-graph.mjs +1 -1
- package/dist/cli__commands__migration-list.d.mts +1 -1
- package/dist/cli__commands__migration-list.mjs +1 -1
- package/dist/cli__commands__migration-log.d.mts +1 -1
- package/dist/cli__commands__migration-log.mjs +1 -1
- package/dist/cli__commands__migration-show.d.mts +1 -1
- package/dist/cli__commands__migration-status.d.mts +1 -1
- package/dist/cli__commands__migration-status.mjs +2 -2
- package/dist/cli__control-api.d.mts +1 -1
- package/dist/cli__control-api.mjs +1 -1
- package/dist/cli__init-output.d.mts +1 -1
- package/dist/cli__init-output.mjs +1 -1
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs +295 -0
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs.map +1 -0
- package/dist/init-CcJxAIeS-CITTa8YU.mjs +800 -0
- package/dist/init-CcJxAIeS-CITTa8YU.mjs.map +1 -0
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs → migrate-BVEyoBor-CN_EOCzx.mjs} +3 -3
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs.map → migrate-BVEyoBor-CN_EOCzx.mjs.map} +1 -1
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs → migrate-failure-CKMEnA_x-ByHkYjn5.mjs} +3 -3
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs.map → migrate-failure-CKMEnA_x-ByHkYjn5.mjs.map} +1 -1
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs → migration-graph-D4sgTiXa-BLtiLIk7.mjs} +4 -4
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs.map → migration-graph-D4sgTiXa-BLtiLIk7.mjs.map} +1 -1
- package/dist/{migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs → migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs} +17 -3
- package/dist/migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs.map +1 -0
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs} +3 -3
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs.map → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs.map} +1 -1
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs → migration-list-BIL-re1m-CZY4m3d0.mjs} +3 -3
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs.map → migration-list-BIL-re1m-CZY4m3d0.mjs.map} +1 -1
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs → migration-log-BdBKYAUW-DTPxKZtD.mjs} +4 -4
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs.map → migration-log-BdBKYAUW-DTPxKZtD.mjs.map} +1 -1
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs → migration-log-table-eES9lPjv-BCFm8qJr.mjs} +3 -3
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs.map → migration-log-table-eES9lPjv-BCFm8qJr.mjs.map} +1 -1
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs → migration-status-DzPVkkB3-BA7jjITd.mjs} +5 -4
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs.map → migration-status-DzPVkkB3-BA7jjITd.mjs.map} +1 -1
- package/dist/{output-DWNYqZGc-D2sRgLAm.mjs → output-DP3woAjV-BxCr9nHA.mjs} +21 -11
- package/dist/output-DP3woAjV-BxCr9nHA.mjs.map +1 -0
- package/dist/{init-Bco4QkKJ-DqUZVrBU.mjs → redact-secrets-kQ0KKV1u-MnVSKe-u.mjs} +740 -1516
- package/dist/redact-secrets-kQ0KKV1u-MnVSKe-u.mjs.map +1 -0
- package/dist/{schemas-Bm2e3Hk1-qLKF0B36.d.mts → schemas-BGgjiKV0-Rh8RlMCl.d.mts} +4 -2
- package/dist/schemas-BGgjiKV0-Rh8RlMCl.d.mts.map +1 -0
- package/dist/{schemas-BMkqaqvM-CLaqlat4.mjs → schemas-CYSD83Cu-Bff4PPoi.mjs} +3 -2
- package/dist/schemas-CYSD83Cu-Bff4PPoi.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/init-Bco4QkKJ-DqUZVrBU.mjs.map +0 -1
- package/dist/migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs.map +0 -1
- package/dist/output-DWNYqZGc-D2sRgLAm.mjs.map +0 -1
- package/dist/schemas-BMkqaqvM-CLaqlat4.mjs.map +0 -1
- package/dist/schemas-Bm2e3Hk1-qLKF0B36.d.mts.map +0 -1
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import { t as CliStructuredError$1 } from "./cli-errors-BG6bItjG-DwdKIeGz.mjs";
|
|
2
|
+
import { a as formatErrorJson, o as formatErrorOutput, r as createTerminalUI } from "./terminal-ui-Cqz4LVL7-B0mcVdLy.mjs";
|
|
3
|
+
import { c as targetLabel, i as scaffoldSpecifierResolverFor, l as targetPackageName, n as dbFile, o as starterSchema, r as defaultSchemaPath, t as configFile } from "./code-templates-CSLH8721-CWRA4Lru.mjs";
|
|
4
|
+
import { A as isRecognisedPnpmResolutionError, B as removeDependency, C as findStaleArtifacts, D as formatSkillInstallCommand, E as formatRunCommand, F as mergeTsConfig, G as validateSchemaPath, H as resolveAuthoring, I as minimalProjectReadmeMd, L as probeServerVersion, M as mergeGitattributes, N as mergeGitignore, P as mergePackageScripts, R as quickReferenceMd, T as formatAddDevArgs, U as resolveProjectSkillInstallCommands, V as requiredGitattributesLines, W as resolveTarget, _ as errorInitProbeFailed, a as TsConfigParseError, b as errorInitStrictProbeWithoutProbe, c as detectPackageManager, d as envFileContent, f as errorInitEmitFailed, g as errorInitMissingFlags, h as errorInitInvalidTsconfig, i as REQUIRED_SCRIPTS, j as legacySkillDirs, k as hasProjectManifest, l as ensureEsmModuleType, m as errorInitInvalidManifest, n as DEFAULT_SKILL_SOURCES, o as buildCatalogWarnings, p as errorInitInstallFailed, r as MIN_SERVER_VERSION, s as defaultTsConfig, u as envExampleContent, v as errorInitReinitNeedsForce, w as formatAddArgs, x as errorInitUserAborted, y as errorInitSkillInstallFailed, z as redactSecrets } from "./redact-secrets-kQ0KKV1u-MnVSKe-u.mjs";
|
|
5
|
+
import { i as renderInitOutro, n as buildNextSteps, r as formatInitJson, t as InitOutputSchema } from "./output-DP3woAjV-BxCr9nHA.mjs";
|
|
6
|
+
import { basename, dirname, extname, isAbsolute, join } from "pathe";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { docsUrlFor } from "@prisma/orm-framework/utils/structured-error";
|
|
9
|
+
import * as clack from "@clack/prompts";
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
//#region ../../../1-framework/3-tooling/cli/dist/init-CcJxAIeS.mjs
|
|
13
|
+
/**
|
|
14
|
+
* Resolves every required input for `runInit`. In interactive mode, missing
|
|
15
|
+
* inputs are prompted via clack; in non-interactive mode, missing required
|
|
16
|
+
* inputs throw a structured error listing exactly which flags are missing
|
|
17
|
+
* (FR1.4). Throws `CliStructuredError` on any unrecoverable input issue.
|
|
18
|
+
*
|
|
19
|
+
* `canPrompt` is decoupled from `flags.interactive` so the action handler
|
|
20
|
+
* (`./index.ts`) owns the merge of stdout-TTY (decoration) and stdin-TTY
|
|
21
|
+
* (prompts). `flags.interactive` continues to gate `TerminalUI` decoration
|
|
22
|
+
* — see [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity).
|
|
23
|
+
*/
|
|
24
|
+
async function resolveInitInputs(ctx) {
|
|
25
|
+
const { baseDir, options, flags, canPrompt } = ctx;
|
|
26
|
+
const force = Boolean(options.force);
|
|
27
|
+
const autoAcceptPrompts = Boolean(flags.yes);
|
|
28
|
+
if (options.strictProbe && !options.probeDb) throw errorInitStrictProbeWithoutProbe();
|
|
29
|
+
const reinit = await resolveReinit({
|
|
30
|
+
baseDir,
|
|
31
|
+
force,
|
|
32
|
+
canPrompt,
|
|
33
|
+
autoAcceptPrompts
|
|
34
|
+
});
|
|
35
|
+
const target = resolveTarget(options.target);
|
|
36
|
+
const authoring = resolveAuthoring(options.authoring);
|
|
37
|
+
const missing = [];
|
|
38
|
+
if (target === void 0) missing.push("target");
|
|
39
|
+
if (authoring === void 0) missing.push("authoring");
|
|
40
|
+
if (!canPrompt && missing.length > 0) throw errorInitMissingFlags({
|
|
41
|
+
missing,
|
|
42
|
+
why: process.stdin.isTTY ? "Non-interactive mode is active (`--no-interactive` or stdout is piped)." : "stdin is not a TTY, so `init` cannot prompt interactively."
|
|
43
|
+
});
|
|
44
|
+
const finalTarget = target ?? await promptTarget();
|
|
45
|
+
const finalAuthoring = authoring ?? await promptAuthoring();
|
|
46
|
+
const finalSchemaPath = options.schemaPath !== void 0 ? validateSchemaPath(options.schemaPath, finalAuthoring) : canPrompt ? await promptSchemaPath(finalAuthoring) : defaultSchemaPath(finalAuthoring);
|
|
47
|
+
const writeEnv = await resolveWriteEnv({
|
|
48
|
+
flag: options.writeEnv,
|
|
49
|
+
canPrompt,
|
|
50
|
+
autoAcceptPrompts
|
|
51
|
+
});
|
|
52
|
+
const removePreviousFacade = await resolveRemovePreviousFacade({
|
|
53
|
+
baseDir,
|
|
54
|
+
target: finalTarget,
|
|
55
|
+
reinit,
|
|
56
|
+
force,
|
|
57
|
+
canPrompt,
|
|
58
|
+
autoAcceptPrompts
|
|
59
|
+
});
|
|
60
|
+
const installProjectSkill = options.skill !== false;
|
|
61
|
+
return {
|
|
62
|
+
target: finalTarget,
|
|
63
|
+
authoring: finalAuthoring,
|
|
64
|
+
schemaPath: finalSchemaPath,
|
|
65
|
+
install: options.install !== false,
|
|
66
|
+
writeEnv,
|
|
67
|
+
probeDb: Boolean(options.probeDb),
|
|
68
|
+
strictProbe: Boolean(options.strictProbe),
|
|
69
|
+
reinit,
|
|
70
|
+
removePreviousFacade,
|
|
71
|
+
installProjectSkill
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function resolveWriteEnv(opts) {
|
|
75
|
+
if (opts.flag !== void 0) return Boolean(opts.flag);
|
|
76
|
+
if (!opts.canPrompt || opts.autoAcceptPrompts) return false;
|
|
77
|
+
const result = await clack.confirm({
|
|
78
|
+
message: "Also write a .env file from .env.example? (gitignored)",
|
|
79
|
+
initialValue: false,
|
|
80
|
+
output: process.stderr
|
|
81
|
+
});
|
|
82
|
+
if (clack.isCancel(result)) throw errorInitUserAborted();
|
|
83
|
+
return Boolean(result);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* FR9.2 — detects whether re-init is switching targets (the previous
|
|
87
|
+
* facade differs from the chosen target's facade) and resolves the
|
|
88
|
+
* remove-or-keep question.
|
|
89
|
+
*
|
|
90
|
+
* The non-interactive contract is the same as the `--force` re-init
|
|
91
|
+
* gate above: a non-interactive run that reaches this helper always
|
|
92
|
+
* has `--force` (otherwise `resolveReinit` would have thrown 5002), so
|
|
93
|
+
* the removal proceeds without further prompting. Interactive runs see
|
|
94
|
+
* a `clack.confirm` with `initialValue: true` — the destructive default
|
|
95
|
+
* is correct because keeping both facades produces a project that
|
|
96
|
+
* imports from one but pays for both in the lockfile, which is a
|
|
97
|
+
* silent foot-gun the user almost never wants.
|
|
98
|
+
*
|
|
99
|
+
* Returns the previous facade package name when the user consented (or
|
|
100
|
+
* was force-ed) to remove it, otherwise `null`. Parse failures on
|
|
101
|
+
* `package.json` resolve to `null` here — `runInit`'s precondition
|
|
102
|
+
* gate surfaces a structured 5010 error for the same file shortly
|
|
103
|
+
* after, so we avoid double-reporting and keep this helper side-effect
|
|
104
|
+
* free under hostile inputs.
|
|
105
|
+
*/
|
|
106
|
+
async function resolveRemovePreviousFacade(opts) {
|
|
107
|
+
if (!opts.reinit) return null;
|
|
108
|
+
const packageJsonPath = join(opts.baseDir, "package.json");
|
|
109
|
+
if (!existsSync(packageJsonPath)) return null;
|
|
110
|
+
const otherTarget = opts.target === "postgres" ? "mongo" : "postgres";
|
|
111
|
+
const otherFacadeNames = [targetPackageName(otherTarget, scaffoldSpecifierResolverFor(otherTarget)), targetPackageName(otherTarget)];
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const deps = parsed["dependencies"];
|
|
119
|
+
if (deps === null || typeof deps !== "object" || Array.isArray(deps)) return null;
|
|
120
|
+
const otherFacade = otherFacadeNames.find((name) => Object.hasOwn(deps, name));
|
|
121
|
+
if (otherFacade === void 0) return null;
|
|
122
|
+
if (opts.force || opts.canPrompt && opts.autoAcceptPrompts) return otherFacade;
|
|
123
|
+
if (!opts.canPrompt) return otherFacade;
|
|
124
|
+
const result = await clack.confirm({
|
|
125
|
+
message: `Switching from ${targetLabel(otherTarget)} to ${targetLabel(opts.target)} — remove ${otherFacade} from package.json dependencies?`,
|
|
126
|
+
initialValue: true,
|
|
127
|
+
output: process.stderr
|
|
128
|
+
});
|
|
129
|
+
if (clack.isCancel(result)) throw errorInitUserAborted();
|
|
130
|
+
return result === true ? otherFacade : null;
|
|
131
|
+
}
|
|
132
|
+
async function resolveReinit(opts) {
|
|
133
|
+
if (!existsSync(join(opts.baseDir, "prisma-next.config.ts"))) return false;
|
|
134
|
+
if (opts.force) return true;
|
|
135
|
+
if (!opts.canPrompt) throw errorInitReinitNeedsForce();
|
|
136
|
+
if (opts.autoAcceptPrompts) return true;
|
|
137
|
+
const result = await clack.confirm({
|
|
138
|
+
message: "This project is already initialized. Re-initialize? This will overwrite all generated files.",
|
|
139
|
+
initialValue: false,
|
|
140
|
+
output: process.stderr
|
|
141
|
+
});
|
|
142
|
+
if (clack.isCancel(result) || result !== true) throw errorInitUserAborted();
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
async function promptTarget() {
|
|
146
|
+
const result = await clack.select({
|
|
147
|
+
message: "What database are you using?",
|
|
148
|
+
options: [{
|
|
149
|
+
value: "postgres",
|
|
150
|
+
label: "PostgreSQL"
|
|
151
|
+
}, {
|
|
152
|
+
value: "mongo",
|
|
153
|
+
label: "MongoDB"
|
|
154
|
+
}],
|
|
155
|
+
output: process.stderr
|
|
156
|
+
});
|
|
157
|
+
if (clack.isCancel(result)) throw errorInitUserAborted();
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
async function promptAuthoring() {
|
|
161
|
+
const result = await clack.select({
|
|
162
|
+
message: "How do you want to write your schema?",
|
|
163
|
+
options: [{
|
|
164
|
+
value: "psl",
|
|
165
|
+
label: "Prisma Schema Language (.prisma)"
|
|
166
|
+
}, {
|
|
167
|
+
value: "typescript",
|
|
168
|
+
label: "TypeScript (.ts)"
|
|
169
|
+
}],
|
|
170
|
+
output: process.stderr
|
|
171
|
+
});
|
|
172
|
+
if (clack.isCancel(result)) throw errorInitUserAborted();
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
async function promptSchemaPath(authoring) {
|
|
176
|
+
const expectedExt = authoring === "typescript" ? ".ts" : ".prisma";
|
|
177
|
+
const result = await clack.text({
|
|
178
|
+
message: "Where should the schema file go?",
|
|
179
|
+
initialValue: defaultSchemaPath(authoring),
|
|
180
|
+
validate(value = "") {
|
|
181
|
+
const trimmed = value.trim();
|
|
182
|
+
if (trimmed.length === 0) return "Path cannot be empty";
|
|
183
|
+
if (trimmed.endsWith("/") || trimmed.endsWith("\\")) return "Path must be a file, not a directory";
|
|
184
|
+
const ext = extname(trimmed).toLowerCase();
|
|
185
|
+
if (ext === "") return "Path must include a file extension (e.g. .prisma or .ts)";
|
|
186
|
+
if (ext !== expectedExt) return `Schema path must end in ${expectedExt} for --authoring ${authoring} (got ${ext}).`;
|
|
187
|
+
},
|
|
188
|
+
output: process.stderr
|
|
189
|
+
});
|
|
190
|
+
if (clack.isCancel(result)) throw errorInitUserAborted();
|
|
191
|
+
return validateSchemaPath(result, authoring);
|
|
192
|
+
}
|
|
193
|
+
const exec = promisify(execFile);
|
|
194
|
+
/**
|
|
195
|
+
* Parse the project-pm-formatted command into an exec call. The
|
|
196
|
+
* format-then-parse split keeps the user-facing command string the same
|
|
197
|
+
* as the surface the structured error advertises, so a user who copies
|
|
198
|
+
* the error's `fix` line gets the same invocation that init just
|
|
199
|
+
* attempted. Single-quoted tokens are preserved in the display form so
|
|
200
|
+
* shell-sensitive characters stay copy-safe, then stripped before
|
|
201
|
+
* `execFile`.
|
|
202
|
+
*/
|
|
203
|
+
function commandToExec(command) {
|
|
204
|
+
const tokens = (command.match(/'[^']*'|\S+/g) ?? []).map((token) => token.startsWith("'") && token.endsWith("'") ? token.slice(1, -1) : token);
|
|
205
|
+
return {
|
|
206
|
+
file: tokens[0] ?? "npx",
|
|
207
|
+
args: tokens.slice(1)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Runs the project-level skill install for every source in
|
|
212
|
+
* `DEFAULT_SKILL_SOURCES`, in order. Returns
|
|
213
|
+
* `{ ok: true, commands }` on success; throws a structured
|
|
214
|
+
* `errorInitSkillInstallFailed` on the first failure (subsequent
|
|
215
|
+
* sources are not attempted — the user opted into Prisma Next by
|
|
216
|
+
* running `init` and a partial install would leave the project in an
|
|
217
|
+
* ambiguous state). The throw is intentionally fatal — project-level
|
|
218
|
+
* skill install is unconditional (modulo `--no-skill`).
|
|
219
|
+
*/
|
|
220
|
+
async function runProjectLevelSkillInstall(ctx) {
|
|
221
|
+
const commands = [];
|
|
222
|
+
const installCommands = resolveProjectSkillInstallCommands(ctx.pm);
|
|
223
|
+
for (const command of installCommands) {
|
|
224
|
+
const { file, args } = commandToExec(command);
|
|
225
|
+
try {
|
|
226
|
+
await exec(file, args, { cwd: ctx.baseDir });
|
|
227
|
+
commands.push(command);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
throw errorInitSkillInstallFailed({
|
|
230
|
+
skillInstallCommand: command,
|
|
231
|
+
filesWritten: ctx.filesWritten,
|
|
232
|
+
cause: redactSecrets(readChildStderr$1(err)) || (err instanceof Error ? err.message : String(err))
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
commands
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function readChildStderr$1(err) {
|
|
242
|
+
if (err instanceof Error && "stderr" in err) return String(err.stderr ?? "");
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Runs the `init` command end-to-end and returns the exit code. Catches
|
|
247
|
+
* structured CLI errors raised at every phase (input resolution, install,
|
|
248
|
+
* emit) and renders them via the same UI surface as success output
|
|
249
|
+
* (`--json` to stdout, human to stderr). Exit codes follow the documented
|
|
250
|
+
* stable set in `./exit-codes.ts` and the
|
|
251
|
+
* [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes).
|
|
252
|
+
*
|
|
253
|
+
* Layered for testability: the action handler in `./index.ts` is
|
|
254
|
+
* responsible for parsing flags and constructing `runOptions`; this
|
|
255
|
+
* function does no flag parsing of its own.
|
|
256
|
+
*/
|
|
257
|
+
async function runInit(baseDir, runOptions) {
|
|
258
|
+
const { options, flags, canPrompt, probeOverrides } = runOptions;
|
|
259
|
+
const ui = createTerminalUI(flags);
|
|
260
|
+
const warnings = [];
|
|
261
|
+
const filesWritten = [];
|
|
262
|
+
const filesDeleted = [];
|
|
263
|
+
if (!flags.json && !flags.quiet) clack.intro("prisma-next init", { output: process.stderr });
|
|
264
|
+
let inputs;
|
|
265
|
+
try {
|
|
266
|
+
inputs = await resolveInitInputs({
|
|
267
|
+
baseDir,
|
|
268
|
+
options,
|
|
269
|
+
flags,
|
|
270
|
+
canPrompt
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (CliStructuredError$1.is(error)) return emitError(ui, flags, error);
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
const pm = await detectPackageManager(baseDir);
|
|
277
|
+
const pkgRun = formatRunCommand(pm, "prisma-next", "").trimEnd();
|
|
278
|
+
const schemaDir = dirname(inputs.schemaPath);
|
|
279
|
+
const configContractPath = isAbsolute(inputs.schemaPath) ? inputs.schemaPath : `./${inputs.schemaPath}`;
|
|
280
|
+
const resolveImportSpecifier = scaffoldSpecifierResolverFor(inputs.target);
|
|
281
|
+
const filesToWrite = [
|
|
282
|
+
{
|
|
283
|
+
path: inputs.schemaPath,
|
|
284
|
+
content: starterSchema(inputs.target, inputs.authoring, resolveImportSpecifier)
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
path: "prisma-next.config.ts",
|
|
288
|
+
content: configFile(inputs.target, configContractPath, resolveImportSpecifier)
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
path: join(schemaDir, "db.ts"),
|
|
292
|
+
content: dbFile(inputs.target, resolveImportSpecifier)
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
path: "prisma-next.md",
|
|
296
|
+
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun, resolveImportSpecifier)
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
path: ".env.example",
|
|
300
|
+
content: envExampleContent(inputs.target)
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
const filesToDelete = inputs.reinit ? [...findStaleArtifacts(baseDir, schemaDir)] : [];
|
|
304
|
+
const legacyDirsToDelete = legacySkillDirs().filter((rel) => existsSync(join(baseDir, rel)));
|
|
305
|
+
if (inputs.writeEnv) if (!existsSync(join(baseDir, ".env"))) filesToWrite.push({
|
|
306
|
+
path: ".env",
|
|
307
|
+
content: envFileContent(inputs.target)
|
|
308
|
+
});
|
|
309
|
+
else warnings.push(".env already exists; leaving it untouched. Compare with .env.example for any new keys.");
|
|
310
|
+
const tsconfigPath = join(baseDir, "tsconfig.json");
|
|
311
|
+
if (existsSync(tsconfigPath)) {
|
|
312
|
+
const existing = readFileSync(tsconfigPath, "utf-8");
|
|
313
|
+
let merged;
|
|
314
|
+
try {
|
|
315
|
+
merged = mergeTsConfig(existing);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if (err instanceof TsConfigParseError) return emitError(ui, flags, errorInitInvalidTsconfig({
|
|
318
|
+
path: "tsconfig.json",
|
|
319
|
+
cause: err.message
|
|
320
|
+
}));
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
filesToWrite.push({
|
|
324
|
+
path: "tsconfig.json",
|
|
325
|
+
content: merged,
|
|
326
|
+
logMessage: "Updated tsconfig.json with required compiler options."
|
|
327
|
+
});
|
|
328
|
+
} else filesToWrite.push({
|
|
329
|
+
path: "tsconfig.json",
|
|
330
|
+
content: defaultTsConfig()
|
|
331
|
+
});
|
|
332
|
+
const gitignorePath = join(baseDir, ".gitignore");
|
|
333
|
+
const newGitignore = mergeGitignore(existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : void 0);
|
|
334
|
+
if (newGitignore !== null) filesToWrite.push({
|
|
335
|
+
path: ".gitignore",
|
|
336
|
+
content: newGitignore
|
|
337
|
+
});
|
|
338
|
+
const gitattributesPath = join(baseDir, ".gitattributes");
|
|
339
|
+
const newGitattributes = mergeGitattributes(existsSync(gitattributesPath) ? readFileSync(gitattributesPath, "utf-8") : void 0, requiredGitattributesLines(schemaDir, inputs.target));
|
|
340
|
+
if (newGitattributes !== null) filesToWrite.push({
|
|
341
|
+
path: ".gitattributes",
|
|
342
|
+
content: newGitattributes
|
|
343
|
+
});
|
|
344
|
+
const packageJsonPath = join(baseDir, "package.json");
|
|
345
|
+
const packageJsonExisted = existsSync(packageJsonPath);
|
|
346
|
+
const synthesisePackageJson = !packageJsonExisted && !hasProjectManifest(baseDir);
|
|
347
|
+
let parsedPackageJson = null;
|
|
348
|
+
if (packageJsonExisted || synthesisePackageJson) {
|
|
349
|
+
const pkgRaw = packageJsonExisted ? readFileSync(packageJsonPath, "utf-8") : defaultPackageJsonContent(basename(baseDir));
|
|
350
|
+
try {
|
|
351
|
+
parsedPackageJson = JSON.parse(pkgRaw);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (err instanceof SyntaxError) return emitError(ui, flags, errorInitInvalidManifest({
|
|
354
|
+
path: "package.json",
|
|
355
|
+
cause: err.message
|
|
356
|
+
}));
|
|
357
|
+
throw err;
|
|
358
|
+
}
|
|
359
|
+
let workingPkg = pkgRaw;
|
|
360
|
+
let pkgChanged = synthesisePackageJson;
|
|
361
|
+
if (inputs.removePreviousFacade !== null) {
|
|
362
|
+
const next = removeDependency(workingPkg, inputs.removePreviousFacade);
|
|
363
|
+
if (next !== null) {
|
|
364
|
+
workingPkg = next;
|
|
365
|
+
pkgChanged = true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const { content: nextPkg, warnings: scriptWarnings } = mergePackageScripts(workingPkg, REQUIRED_SCRIPTS);
|
|
369
|
+
if (nextPkg !== null) {
|
|
370
|
+
workingPkg = nextPkg;
|
|
371
|
+
pkgChanged = true;
|
|
372
|
+
}
|
|
373
|
+
const { content: typedPkg, warning: typeWarning } = ensureEsmModuleType(workingPkg);
|
|
374
|
+
if (typedPkg !== null) {
|
|
375
|
+
workingPkg = typedPkg;
|
|
376
|
+
pkgChanged = true;
|
|
377
|
+
}
|
|
378
|
+
if (pkgChanged) filesToWrite.push({
|
|
379
|
+
path: "package.json",
|
|
380
|
+
content: workingPkg
|
|
381
|
+
});
|
|
382
|
+
warnings.push(...scriptWarnings);
|
|
383
|
+
if (typeWarning !== null) warnings.push(typeWarning);
|
|
384
|
+
if (synthesisePackageJson) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
|
|
385
|
+
}
|
|
386
|
+
if (existsSync(join(baseDir, "src/index.ts"))) if (!existsSync(join(baseDir, "README.md"))) {
|
|
387
|
+
const rawName = parsedPackageJson !== null && typeof parsedPackageJson["name"] === "string" ? parsedPackageJson["name"] : basename(baseDir);
|
|
388
|
+
filesToWrite.push({
|
|
389
|
+
path: "README.md",
|
|
390
|
+
content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(rawName), pm)
|
|
391
|
+
});
|
|
392
|
+
} else warnings.push("README.md already exists; leaving it untouched.");
|
|
393
|
+
for (const file of filesToWrite) {
|
|
394
|
+
const fullPath = join(baseDir, file.path);
|
|
395
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
396
|
+
writeFileSync(fullPath, file.content, "utf-8");
|
|
397
|
+
filesWritten.push(file.path);
|
|
398
|
+
if (file.logMessage !== void 0 && !flags.json && !flags.quiet) ui.log(file.logMessage);
|
|
399
|
+
}
|
|
400
|
+
for (const rel of filesToDelete) {
|
|
401
|
+
const fullPath = join(baseDir, rel);
|
|
402
|
+
if (!existsSync(fullPath)) continue;
|
|
403
|
+
try {
|
|
404
|
+
unlinkSync(fullPath);
|
|
405
|
+
filesDeleted.push(rel);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const rel of legacyDirsToDelete) {
|
|
411
|
+
rmSync(join(baseDir, rel), {
|
|
412
|
+
recursive: true,
|
|
413
|
+
force: true
|
|
414
|
+
});
|
|
415
|
+
filesDeleted.push(rel);
|
|
416
|
+
}
|
|
417
|
+
const emitCommand = formatRunCommand(pm, "prisma-next", "contract emit");
|
|
418
|
+
let install;
|
|
419
|
+
try {
|
|
420
|
+
install = await runInstall({
|
|
421
|
+
baseDir,
|
|
422
|
+
pm,
|
|
423
|
+
target: inputs.target,
|
|
424
|
+
install: inputs.install,
|
|
425
|
+
flags,
|
|
426
|
+
ui,
|
|
427
|
+
filesWritten,
|
|
428
|
+
resolveImportSpecifier,
|
|
429
|
+
hasTypesNode: parsedPackageJson !== null ? hasDirectDep(parsedPackageJson, "@types/node") : false
|
|
430
|
+
});
|
|
431
|
+
} catch (error) {
|
|
432
|
+
if (CliStructuredError$1.is(error)) return emitError(ui, flags, error);
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
warnings.push(...install.warnings);
|
|
436
|
+
let contractEmitted = false;
|
|
437
|
+
if (!install.skipped) try {
|
|
438
|
+
await runEmit({
|
|
439
|
+
baseDir,
|
|
440
|
+
ui,
|
|
441
|
+
filesWritten,
|
|
442
|
+
emitCommand
|
|
443
|
+
});
|
|
444
|
+
contractEmitted = true;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (CliStructuredError$1.is(error)) return emitError(ui, flags, error);
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
if (inputs.probeDb) {
|
|
450
|
+
const escalated = applyProbeOutcome(await probeServerVersion({
|
|
451
|
+
baseDir,
|
|
452
|
+
target: inputs.target,
|
|
453
|
+
databaseUrl: process.env["DATABASE_URL"],
|
|
454
|
+
minVersion: MIN_SERVER_VERSION[inputs.target]
|
|
455
|
+
}, probeOverrides ?? {}), {
|
|
456
|
+
strictProbe: inputs.strictProbe,
|
|
457
|
+
warnings
|
|
458
|
+
});
|
|
459
|
+
if (escalated !== null) return emitError(ui, flags, errorInitProbeFailed({
|
|
460
|
+
cause: escalated,
|
|
461
|
+
filesWritten
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
const manualProjectSkillSummary = DEFAULT_SKILL_SOURCES.map((source) => formatSkillInstallCommand({
|
|
465
|
+
pm: install.effectivePm,
|
|
466
|
+
source
|
|
467
|
+
})).map((c) => `\`${c}\``).join(" && ");
|
|
468
|
+
let skillRegistered = false;
|
|
469
|
+
if (!inputs.installProjectSkill) warnings.push(`Skipped Prisma Next skills install (--no-skill). To install the skills later, run: ${manualProjectSkillSummary}`);
|
|
470
|
+
else {
|
|
471
|
+
const spinner = ui.spinner();
|
|
472
|
+
spinner.start("Registering Prisma Next skills with the agent runtime...");
|
|
473
|
+
try {
|
|
474
|
+
const project = await runProjectLevelSkillInstall({
|
|
475
|
+
baseDir,
|
|
476
|
+
pm: install.effectivePm,
|
|
477
|
+
filesWritten
|
|
478
|
+
});
|
|
479
|
+
spinner.stop(`Registered Prisma Next skills (project level) — ran ${project.commands.map((c) => `\`${c}\``).join(", ")}`);
|
|
480
|
+
skillRegistered = true;
|
|
481
|
+
} catch (error) {
|
|
482
|
+
spinner.stop("Agent-skill install failed");
|
|
483
|
+
if (CliStructuredError$1.is(error)) return emitError(ui, flags, error);
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const packagesInstalled = install.skipped ? "skipped" : "installed";
|
|
488
|
+
const output = {
|
|
489
|
+
ok: true,
|
|
490
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
491
|
+
authoring: inputs.authoring,
|
|
492
|
+
schemaPath: inputs.schemaPath,
|
|
493
|
+
filesWritten,
|
|
494
|
+
filesDeleted,
|
|
495
|
+
packagesInstalled: {
|
|
496
|
+
status: packagesInstalled,
|
|
497
|
+
deps: [...install.deps],
|
|
498
|
+
devDeps: [...install.devDeps]
|
|
499
|
+
},
|
|
500
|
+
contractEmitted,
|
|
501
|
+
nextSteps: buildNextSteps({
|
|
502
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
503
|
+
packagesInstalled,
|
|
504
|
+
contractEmitted,
|
|
505
|
+
emitCommand,
|
|
506
|
+
schemaPath: inputs.schemaPath,
|
|
507
|
+
skillRegistered
|
|
508
|
+
}),
|
|
509
|
+
warnings
|
|
510
|
+
};
|
|
511
|
+
const validated = InitOutputSchema(output);
|
|
512
|
+
if (validated instanceof Error || validated.problems !== void 0) return emitError(ui, flags, new CliStructuredError$1("CLI.INIT_INVALID_OUTPUT_DOCUMENT", "Init produced an invalid output document", {
|
|
513
|
+
why: `The success document failed schema validation: ${String(validated)}`,
|
|
514
|
+
fix: "This is a bug in prisma-next. Please report it with the full `-v` output.",
|
|
515
|
+
docsUrl: docsUrlFor("CLI.INIT_INVALID_OUTPUT_DOCUMENT")
|
|
516
|
+
}));
|
|
517
|
+
if (flags.json) ui.output(formatInitJson(output));
|
|
518
|
+
else {
|
|
519
|
+
renderInitOutro(ui, output, flags);
|
|
520
|
+
if (!flags.quiet) clack.outro("Done. Open prisma-next.md to get started.", { output: process.stderr });
|
|
521
|
+
}
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Renders a structured CLI error to the right channel and returns the exit
|
|
526
|
+
* code derived from the error's PN code. JSON-mode errors go to stdout
|
|
527
|
+
* (so consumers always parse from one place); human-mode errors go to
|
|
528
|
+
* stderr. Mirrors `handleResult` but returns init-specific exit codes
|
|
529
|
+
* rather than the CLI/RUN binary.
|
|
530
|
+
*/
|
|
531
|
+
function emitError(ui, flags, error) {
|
|
532
|
+
const envelope = error.toEnvelope();
|
|
533
|
+
if (flags.json) ui.output(formatErrorJson(envelope));
|
|
534
|
+
else ui.error(formatErrorOutput(envelope, flags));
|
|
535
|
+
return exitCodeForError(error);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Maps a structured init error to its documented exit code. Centralised so
|
|
539
|
+
* the error → exit-code contract lives next to the codes themselves.
|
|
540
|
+
*
|
|
541
|
+
* `CLI.INIT_INVALID_OUTPUT_DOCUMENT` (and the unknown-code default branch)
|
|
542
|
+
* routes to `INIT_EXIT_INTERNAL_ERROR` because those represent prisma-next
|
|
543
|
+
* bugs the user did not cause — surfacing them as `PRECONDITION` would
|
|
544
|
+
* mislead automation into thinking the caller mis-invoked the CLI.
|
|
545
|
+
*
|
|
546
|
+
* See [exit-codes.ts](./exit-codes.ts) for the canonical list and
|
|
547
|
+
* [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes)
|
|
548
|
+
* for the reservation policy.
|
|
549
|
+
*
|
|
550
|
+
* Exported for unit tests so the mapping can be asserted without
|
|
551
|
+
* round-tripping a full `runInit` invocation.
|
|
552
|
+
*/
|
|
553
|
+
function exitCodeForError(error) {
|
|
554
|
+
switch (error.code) {
|
|
555
|
+
case "CLI.INIT_REINIT_NEEDS_FORCE":
|
|
556
|
+
case "CLI.INIT_MISSING_FLAGS":
|
|
557
|
+
case "CLI.INIT_INVALID_FLAG_VALUE":
|
|
558
|
+
case "CLI.INIT_STRICT_PROBE_WITHOUT_PROBE":
|
|
559
|
+
case "CLI.INIT_INVALID_MANIFEST":
|
|
560
|
+
case "CLI.INIT_INVALID_TSCONFIG":
|
|
561
|
+
case "CLI.INIT_PROBE_FAILED":
|
|
562
|
+
case "CLI.INIT_AUTHORING_SCHEMA_PATH_MISMATCH":
|
|
563
|
+
case "CLI.INIT_WRITE_FAILED": return 2;
|
|
564
|
+
case "CLI.INIT_USER_ABORTED": return 3;
|
|
565
|
+
case "CLI.INIT_INSTALL_FAILED": return 4;
|
|
566
|
+
case "CLI.INIT_EMIT_FAILED": return 5;
|
|
567
|
+
case "CLI.INIT_INVALID_OUTPUT_DOCUMENT": return 1;
|
|
568
|
+
case "CLI.INIT_SKILL_INSTALL_FAILED": return 6;
|
|
569
|
+
default: return 1;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Folds a `ProbeOutcome` into init's warning channel and returns the
|
|
574
|
+
* fatal cause string when `--strict-probe` should escalate. Mirrors
|
|
575
|
+
* the FR8.3 contract:
|
|
576
|
+
*
|
|
577
|
+
* - `ok` — informational; nothing surfaced unless verbose. (We could
|
|
578
|
+
* plumb a `note` here, but the spec only requires the warning side
|
|
579
|
+
* of the contract; an "all good" line would just be noise on the
|
|
580
|
+
* common path.)
|
|
581
|
+
* - `below-minimum` — warning regardless of `--strict-probe`. The
|
|
582
|
+
* probe ran successfully and found an old server; that is not a
|
|
583
|
+
* probe *failure* (which is what `--strict-probe` escalates), it
|
|
584
|
+
* is the probe doing its job.
|
|
585
|
+
* - `no-database-url` / `connection-failed` / `driver-missing` —
|
|
586
|
+
* warning by default, fatal under `--strict-probe`.
|
|
587
|
+
*
|
|
588
|
+
* Exported for unit tests so the branching contract can be asserted
|
|
589
|
+
* without spinning up a full `runInit` round trip.
|
|
590
|
+
*/
|
|
591
|
+
function applyProbeOutcome(outcome, ctx) {
|
|
592
|
+
switch (outcome.kind) {
|
|
593
|
+
case "ok": return null;
|
|
594
|
+
case "below-minimum":
|
|
595
|
+
ctx.warnings.push(outcome.message);
|
|
596
|
+
return null;
|
|
597
|
+
case "no-database-url":
|
|
598
|
+
case "connection-failed":
|
|
599
|
+
case "driver-missing":
|
|
600
|
+
if (ctx.strictProbe) return outcome.message;
|
|
601
|
+
ctx.warnings.push(outcome.message);
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Drives the `pnpm add` / `npm install` step. Failures are escalated to
|
|
607
|
+
* a structured `errorInitInstallFailed` (exit code 4) — the spec treats
|
|
608
|
+
* an unrecoverable install as a hard outcome rather than a warning so
|
|
609
|
+
* CI/agents can branch on the exit code (FR1.6).
|
|
610
|
+
*
|
|
611
|
+
* For pnpm specifically, we additionally implement the FR7.2 fallback:
|
|
612
|
+
* if pnpm fails with a recognised workspace/catalog resolution error
|
|
613
|
+
* class (typically caused by a registry version that leaked
|
|
614
|
+
* `workspace:*` or `catalog:` specifiers), we retry the install using
|
|
615
|
+
* `npm` and surface a non-fatal warning explaining the swap.
|
|
616
|
+
*/
|
|
617
|
+
async function runInstall(ctx) {
|
|
618
|
+
const { baseDir, pm, target, install, flags, ui, filesWritten, hasTypesNode } = ctx;
|
|
619
|
+
const deps = [targetPackageName(target, ctx.resolveImportSpecifier), "dotenv"];
|
|
620
|
+
const devDeps = hasTypesNode ? ["prisma-next"] : ["prisma-next", "@types/node"];
|
|
621
|
+
const addCommand = `${pm} ${formatAddArgs(pm, deps).join(" ")}`;
|
|
622
|
+
const addDevCommand = `${pm} ${formatAddDevArgs(pm, devDeps).join(" ")}`;
|
|
623
|
+
const emitCommand = formatRunCommand(pm, "prisma-next", "contract emit");
|
|
624
|
+
const catalogWarnings = pm === "pnpm" ? buildCatalogWarnings(baseDir, [...deps, ...devDeps]) : [];
|
|
625
|
+
if (!install) {
|
|
626
|
+
if (!flags.json && !flags.quiet) ui.note([
|
|
627
|
+
"Run the following commands to complete setup:",
|
|
628
|
+
"",
|
|
629
|
+
" 1. Install dependencies:",
|
|
630
|
+
` ${addCommand}`,
|
|
631
|
+
` ${addDevCommand}`,
|
|
632
|
+
"",
|
|
633
|
+
" 2. Emit the contract:",
|
|
634
|
+
` ${emitCommand}`
|
|
635
|
+
].join("\n"), "Manual steps");
|
|
636
|
+
return {
|
|
637
|
+
skipped: true,
|
|
638
|
+
deps: [],
|
|
639
|
+
devDeps: [],
|
|
640
|
+
warnings: catalogWarnings,
|
|
641
|
+
effectivePm: pm
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const exec = promisify(execFile);
|
|
645
|
+
const runPair = async (manager) => {
|
|
646
|
+
await exec(manager, formatAddArgs(manager, deps), { cwd: baseDir });
|
|
647
|
+
await exec(manager, formatAddDevArgs(manager, devDeps), { cwd: baseDir });
|
|
648
|
+
};
|
|
649
|
+
const allPackages = [...deps, ...devDeps].join(", ");
|
|
650
|
+
const spinner = ui.spinner();
|
|
651
|
+
spinner.start(`Installing ${allPackages}...`);
|
|
652
|
+
try {
|
|
653
|
+
await runPair(pm);
|
|
654
|
+
spinner.stop(`Installed ${allPackages}`);
|
|
655
|
+
return {
|
|
656
|
+
skipped: false,
|
|
657
|
+
deps,
|
|
658
|
+
devDeps,
|
|
659
|
+
warnings: catalogWarnings,
|
|
660
|
+
effectivePm: pm
|
|
661
|
+
};
|
|
662
|
+
} catch (err) {
|
|
663
|
+
const stderrText = redactSecrets(readChildStderr(err));
|
|
664
|
+
if (pm === "pnpm" && isRecognisedPnpmResolutionError(stderrText)) {
|
|
665
|
+
spinner.message("pnpm could not resolve a workspace/catalog dependency, retrying with npm...");
|
|
666
|
+
try {
|
|
667
|
+
await runPair("npm");
|
|
668
|
+
spinner.stop(`Installed ${allPackages} via npm (pnpm fallback)`);
|
|
669
|
+
return {
|
|
670
|
+
skipped: false,
|
|
671
|
+
deps,
|
|
672
|
+
devDeps,
|
|
673
|
+
warnings: [[
|
|
674
|
+
"pnpm could not install: a published Prisma Next dependency leaked a `workspace:*` or `catalog:` specifier.",
|
|
675
|
+
"Falling back to `npm install` so init can complete.",
|
|
676
|
+
stderrText ? ` pnpm error: ${stderrText.trim().split("\n")[0]}` : "",
|
|
677
|
+
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
678
|
+
].filter(Boolean).join("\n")],
|
|
679
|
+
effectivePm: "npm"
|
|
680
|
+
};
|
|
681
|
+
} catch (npmErr) {
|
|
682
|
+
spinner.stop("Installation failed");
|
|
683
|
+
throw errorInitInstallFailed({
|
|
684
|
+
addCommand,
|
|
685
|
+
addDevCommand,
|
|
686
|
+
emitCommand,
|
|
687
|
+
filesWritten,
|
|
688
|
+
stderrLines: [stderrText, redactSecrets(readChildStderr(npmErr))]
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
spinner.stop("Installation failed");
|
|
693
|
+
throw errorInitInstallFailed({
|
|
694
|
+
addCommand,
|
|
695
|
+
addDevCommand,
|
|
696
|
+
emitCommand,
|
|
697
|
+
filesWritten,
|
|
698
|
+
stderrLines: [stderrText]
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* FR2.1 — true when the parsed `package.json` declares `name` directly
|
|
704
|
+
* in either `dependencies` or `devDependencies`. We deliberately don't
|
|
705
|
+
* inspect `peerDependencies` (irrelevant for a leaf project) or the
|
|
706
|
+
* lockfile (transitive presence is brittle to detect and not the
|
|
707
|
+
* realistic clobber-risk path).
|
|
708
|
+
*
|
|
709
|
+
* Exported for unit tests.
|
|
710
|
+
*/
|
|
711
|
+
function hasDirectDep(parsed, name) {
|
|
712
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
713
|
+
const value = parsed[field];
|
|
714
|
+
if (value !== null && typeof value === "object" && name in value) return true;
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
function readChildStderr(err) {
|
|
719
|
+
if (err instanceof Error && "stderr" in err) return String(err.stderr ?? "");
|
|
720
|
+
return "";
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Drives `prisma-next contract emit` against the freshly scaffolded
|
|
724
|
+
* project. On failure, throws `errorInitEmitFailed` with the underlying
|
|
725
|
+
* cause embedded in `meta.cause` so the user can re-run with `-v` to see
|
|
726
|
+
* the full envelope and follow the fix steps. Maps to exit code
|
|
727
|
+
* `5 = EMIT_FAILED` (FR1.6).
|
|
728
|
+
*/
|
|
729
|
+
async function runEmit(ctx) {
|
|
730
|
+
const spinner = ctx.ui.spinner();
|
|
731
|
+
spinner.start("Emitting contract...");
|
|
732
|
+
try {
|
|
733
|
+
const { executeContractEmit } = await import("./contract-emit-Bzt11N4v-BQ2Pr8KG.mjs");
|
|
734
|
+
const { loadConfigForSections } = await import("./exports-BZLNbtkY.mjs").then((n) => n.t);
|
|
735
|
+
const configFilePath = join(ctx.baseDir, "prisma-next.config.ts");
|
|
736
|
+
const configResult = await loadConfigForSections(configFilePath, [
|
|
737
|
+
"contract",
|
|
738
|
+
"family",
|
|
739
|
+
"target",
|
|
740
|
+
"adapter",
|
|
741
|
+
"extensions"
|
|
742
|
+
]);
|
|
743
|
+
if (!configResult.ok) throw configResult.failure;
|
|
744
|
+
await executeContractEmit({
|
|
745
|
+
config: configResult.value,
|
|
746
|
+
cwd: process.cwd(),
|
|
747
|
+
configPath: configFilePath
|
|
748
|
+
});
|
|
749
|
+
spinner.stop("Contract emitted");
|
|
750
|
+
} catch (err) {
|
|
751
|
+
spinner.stop("Contract emission failed");
|
|
752
|
+
throw errorInitEmitFailed({
|
|
753
|
+
emitCommand: ctx.emitCommand,
|
|
754
|
+
filesWritten: ctx.filesWritten,
|
|
755
|
+
cause: causeMessage(err)
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function causeMessage(err) {
|
|
760
|
+
if (err instanceof Error) return err.message;
|
|
761
|
+
return String(err);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Minimal `package.json` content used when init runs in a directory
|
|
765
|
+
* that has no project manifest (TML-2496). Mirrors the npm 11 `init -y`
|
|
766
|
+
* defaults, with two deliberate deviations:
|
|
767
|
+
*
|
|
768
|
+
* - `"private": true` so a stray `npm publish` cannot leak the
|
|
769
|
+
* placeholder. Users who want to publish have to opt in by removing
|
|
770
|
+
* the field.
|
|
771
|
+
* - `"type": "module"` so the scaffolded ESM imports in
|
|
772
|
+
* `prisma-next.config.ts` and `db.ts` typecheck and run without
|
|
773
|
+
* additional tsconfig coercion.
|
|
774
|
+
*
|
|
775
|
+
* Exported for unit tests so the canonical shape is asserted in one
|
|
776
|
+
* place rather than re-derived at every call site.
|
|
777
|
+
*/
|
|
778
|
+
function defaultPackageJsonContent(rawName) {
|
|
779
|
+
return `${JSON.stringify({
|
|
780
|
+
name: sanitisePackageName(rawName),
|
|
781
|
+
version: "0.0.0",
|
|
782
|
+
private: true,
|
|
783
|
+
type: "module"
|
|
784
|
+
}, null, 2)}\n`;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* npm package names are restricted to lowercase, no leading dot/underscore,
|
|
788
|
+
* and a small URL-safe character set. `basename(cwd)` happily returns
|
|
789
|
+
* "My Project" or ".hidden" — both rejected by `npm install` validation.
|
|
790
|
+
* Coerce to a safe fallback rather than emit a manifest npm refuses to
|
|
791
|
+
* read.
|
|
792
|
+
*/
|
|
793
|
+
function sanitisePackageName(raw) {
|
|
794
|
+
const trimmed = raw.toLowerCase().replace(/[^a-z0-9._~-]/g, "-").replace(/^[._-]+/, "").replace(/-+/g, "-");
|
|
795
|
+
return trimmed.length > 0 ? trimmed : "my-app";
|
|
796
|
+
}
|
|
797
|
+
//#endregion
|
|
798
|
+
export { runInit };
|
|
799
|
+
|
|
800
|
+
//# sourceMappingURL=init-CcJxAIeS-CITTa8YU.mjs.map
|