@prisma-next/cli 0.7.0 → 0.8.0-dev.2
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 +10 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-BCnP7cHo.mjs → client-4d26awB-.mjs} +8 -5
- package/dist/client-4d26awB-.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +2 -2
- package/dist/commands/db-schema.mjs +1 -1
- package/dist/commands/db-sign.mjs +1 -1
- package/dist/commands/db-update.mjs +2 -2
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migration-apply.mjs +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/{contract-emit-9DBda5Ou.mjs → contract-emit-BhKR-D9Y.mjs} +2 -2
- package/dist/{contract-emit-9DBda5Ou.mjs.map → contract-emit-BhKR-D9Y.mjs.map} +1 -1
- package/dist/{contract-emit-B77TsJqf.mjs → contract-emit-DLc5GYbr.mjs} +6 -2
- package/dist/contract-emit-DLc5GYbr.mjs.map +1 -0
- package/dist/{contract-infer-ByxhPjpW.mjs → contract-infer-Bnla2kuK.mjs} +2 -2
- package/dist/{contract-infer-ByxhPjpW.mjs.map → contract-infer-Bnla2kuK.mjs.map} +1 -1
- package/dist/{db-verify-Czm5T-J4.mjs → db-verify-DitNxDiE.mjs} +2 -2
- package/dist/{db-verify-Czm5T-J4.mjs.map → db-verify-DitNxDiE.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{init-BRKnARU6.mjs → init-CVBXQidr.mjs} +342 -207
- package/dist/init-CVBXQidr.mjs.map +1 -0
- package/dist/{inspect-live-schema-DxdBd4Er.mjs → inspect-live-schema-CyzAzPzF.mjs} +2 -2
- package/dist/{inspect-live-schema-DxdBd4Er.mjs.map → inspect-live-schema-CyzAzPzF.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs → migration-command-scaffold-Jp1rosw8.mjs} +2 -2
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs.map → migration-command-scaffold-Jp1rosw8.mjs.map} +1 -1
- package/dist/{migration-plan-mRu5K81L.mjs → migration-plan-q1pPoOCf.mjs} +9 -3
- package/dist/migration-plan-q1pPoOCf.mjs.map +1 -0
- package/dist/{migration-status-By9G5p2H.mjs → migration-status-Do4Ei0i_.mjs} +2 -2
- package/dist/{migration-status-By9G5p2H.mjs.map → migration-status-Do4Ei0i_.mjs.map} +1 -1
- package/dist/{output-B16Kefzx.mjs → output-nBJ6NvsE.mjs} +12 -11
- package/dist/{output-B16Kefzx.mjs.map → output-nBJ6NvsE.mjs.map} +1 -1
- package/package.json +19 -19
- package/src/commands/init/agent-skill-install.ts +130 -0
- package/src/commands/init/errors.ts +32 -0
- package/src/commands/init/exit-codes.ts +10 -0
- package/src/commands/init/index.ts +9 -1
- package/src/commands/init/init.ts +74 -7
- package/src/commands/init/inputs.ts +23 -0
- package/src/commands/init/output.ts +22 -17
- package/src/commands/init/templates/code-templates.ts +4 -1
- package/src/commands/migration-plan.ts +12 -1
- package/src/control-api/client.ts +11 -4
- package/src/control-api/operations/contract-emit.ts +13 -1
- package/src/control-api/operations/db-verify.ts +1 -1
- package/src/utils/contract-space-seed-phase.ts +1 -1
- package/dist/agent-skill-mongo.md +0 -138
- package/dist/agent-skill-postgres.md +0 -106
- package/dist/client-BCnP7cHo.mjs.map +0 -1
- package/dist/contract-emit-B77TsJqf.mjs.map +0 -1
- package/dist/init-BRKnARU6.mjs.map +0 -1
- package/dist/migration-plan-mRu5K81L.mjs.map +0 -1
- package/src/commands/init/templates/agent-skill-mongo.md +0 -138
- package/src/commands/init/templates/agent-skill-postgres.md +0 -106
- package/src/commands/init/templates/agent-skill.ts +0 -41
|
@@ -90,20 +90,21 @@ function renderInitOutro(ui, output, flags) {
|
|
|
90
90
|
*/
|
|
91
91
|
function buildNextSteps(options) {
|
|
92
92
|
const steps = [];
|
|
93
|
-
|
|
93
|
+
let stepNumber = 1;
|
|
94
|
+
const push = (text) => {
|
|
95
|
+
steps.push(`${stepNumber}. ${text}`);
|
|
96
|
+
stepNumber += 1;
|
|
97
|
+
};
|
|
98
|
+
push("Set DATABASE_URL in your environment (export it or add it to .env).");
|
|
94
99
|
if (!options.contractEmitted) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
steps.push(`2. Edit your schema at ${options.schemaPath}, then re-run \`${options.emitCommand}\`.`);
|
|
101
|
-
steps.push("3. Open prisma-next.md for a quick reference on how to write your first typed query.");
|
|
102
|
-
steps.push("4. The .agents/skills/prisma-next/SKILL.md file is wired up for AI-coding agents in this project.");
|
|
103
|
-
}
|
|
100
|
+
push(`Emit the contract: \`${options.emitCommand}\``);
|
|
101
|
+
push(`Edit your schema at ${options.schemaPath}, then re-run the emit command.`);
|
|
102
|
+
} else push(`Edit your schema at ${options.schemaPath}, then re-run \`${options.emitCommand}\`.`);
|
|
103
|
+
push("Open prisma-next.md for a quick reference on how to write your first typed query.");
|
|
104
|
+
if (options.skillRegistered) push("@prisma-next/skills is registered with your agent runtime — open the project in your IDE and ask the agent to add a model, run a query, or plan a migration.");
|
|
104
105
|
return steps;
|
|
105
106
|
}
|
|
106
107
|
//#endregion
|
|
107
108
|
export { renderInitOutro as i, buildNextSteps as n, formatInitJson as r, InitOutputSchema as t };
|
|
108
109
|
|
|
109
|
-
//# sourceMappingURL=output-
|
|
110
|
+
//# sourceMappingURL=output-nBJ6NvsE.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-
|
|
1
|
+
{"version":3,"file":"output-nBJ6NvsE.mjs","names":[],"sources":["../src/commands/init/output.ts"],"sourcesContent":["import { type } from 'arktype';\nimport type { GlobalFlags } from '../../utils/global-flags';\nimport type { TerminalUI } from '../../utils/terminal-ui';\n\n/**\n * arktype schema for the structured success document `init --json` writes\n * to stdout (FR1.5). The same shape backs the human-readable outro\n * renderer (FR10), so the two output modes carry identical information.\n *\n * `target` is normalised to the user-facing flag value (`mongodb` rather\n * than the internal `mongo`) so consumers can round-trip the document\n * straight into a follow-up `--target` invocation.\n *\n * The `ok: true` literal is the documented success/error discriminator —\n * see [Style Guide § JSON Semantics](../../../../../../../docs/CLI%20Style%20Guide.md#json-semantics).\n * Error envelopes (`CliErrorEnvelope`) carry `ok: false` so consumers can\n * branch with `if (doc.ok)` without inspecting the rest of the structure.\n */\nexport const InitOutputSchema = type({\n ok: 'true',\n target: \"'postgres'|'mongodb'\",\n authoring: \"'psl'|'typescript'\",\n schemaPath: 'string',\n filesWritten: 'string[]',\n /**\n * FR9.1 — files removed from disk during this run. Populated only on\n * re-init when previously-emitted contract artefacts (`contract.json`,\n * `contract.d.ts`, `start-/end-contract.*`, `ops.json`,\n * `migration.json`) were left behind by an earlier run. Empty on a\n * green-field init.\n */\n filesDeleted: 'string[]',\n packagesInstalled: {\n skipped: 'boolean',\n deps: 'string[]',\n devDeps: 'string[]',\n },\n contractEmitted: 'boolean',\n nextSteps: 'string[]',\n warnings: 'string[]',\n});\n\nexport type InitOutput = typeof InitOutputSchema.infer;\n\n/**\n * Serialises the output document for `--json`. Sorted keys are not enforced\n * — `JSON.stringify` preserves insertion order, and the schema field order\n * is the documented order, which matches what users will see when they\n * `jq .` the result.\n */\nexport function formatInitJson(output: InitOutput): string {\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Renders the human-readable outro on stderr (FR10.1). Re-uses the same\n * data structure as the JSON output so the two stay in lock-step.\n *\n * Warnings come before \"Next steps\" because they describe state the user\n * needs to be aware of before acting on the next-steps list.\n */\nexport function renderInitOutro(ui: TerminalUI, output: InitOutput, flags: GlobalFlags): void {\n if (flags.quiet || flags.json) {\n return;\n }\n\n for (const warning of output.warnings) {\n ui.warn(warning);\n }\n\n const lines: string[] = [];\n lines.push(`Target: ${output.target}`);\n lines.push(`Authoring: ${output.authoring}`);\n lines.push(`Schema: ${output.schemaPath}`);\n lines.push('');\n lines.push('Files written:');\n for (const file of output.filesWritten) {\n lines.push(` • ${file}`);\n }\n\n if (output.filesDeleted.length > 0) {\n lines.push('');\n lines.push('Files deleted (stale contract artefacts):');\n for (const file of output.filesDeleted) {\n lines.push(` • ${file}`);\n }\n }\n\n if (!output.packagesInstalled.skipped) {\n lines.push('');\n lines.push('Packages installed:');\n for (const dep of output.packagesInstalled.deps) {\n lines.push(` • ${dep}`);\n }\n for (const dep of output.packagesInstalled.devDeps) {\n lines.push(` • ${dep} (dev)`);\n }\n }\n\n lines.push('');\n lines.push('Next steps:');\n for (const step of output.nextSteps) {\n lines.push(` ${step}`);\n }\n\n ui.note(lines.join('\\n'), 'Done');\n}\n\n/**\n * Builds the `nextSteps` array from the resolved scaffold state. Steps are\n * ordered by the workflow a user needs to follow: configure connection →\n * (emit if not yet done) → run a starter query → docs / agent skill.\n *\n * The strings are stable and human-readable; agents wanting to act on them\n * should match on substrings (e.g. \"DATABASE_URL\") rather than exact text,\n * since copy may evolve.\n */\nexport function buildNextSteps(options: {\n readonly target: 'postgres' | 'mongodb';\n readonly contractEmitted: boolean;\n readonly emitCommand: string;\n readonly schemaPath: string;\n /**\n * Whether the project-level `@prisma-next/skills` install actually ran\n * and succeeded during this `init`. When false (the user passed\n * `--no-skill` or `--no-install`, so the install was skipped), the\n * \"registered with your agent runtime\" step is omitted — the skip is\n * already surfaced in the warnings array with a manual-install hint.\n */\n readonly skillRegistered: boolean;\n}): string[] {\n const steps: string[] = [];\n let stepNumber = 1;\n const push = (text: string): void => {\n steps.push(`${stepNumber}. ${text}`);\n stepNumber += 1;\n };\n push('Set DATABASE_URL in your environment (export it or add it to .env).');\n if (!options.contractEmitted) {\n push(`Emit the contract: \\`${options.emitCommand}\\``);\n push(`Edit your schema at ${options.schemaPath}, then re-run the emit command.`);\n } else {\n push(`Edit your schema at ${options.schemaPath}, then re-run \\`${options.emitCommand}\\`.`);\n }\n push('Open prisma-next.md for a quick reference on how to write your first typed query.');\n if (options.skillRegistered) {\n push(\n '@prisma-next/skills is registered with your agent runtime — open the project in your IDE and ask the agent to add a model, run a query, or plan a migration.',\n );\n }\n return steps;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,mBAAmB,KAAK;CACnC,IAAI;CACJ,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,cAAc;;;;;;;;CAQd,cAAc;CACd,mBAAmB;EACjB,SAAS;EACT,MAAM;EACN,SAAS;EACV;CACD,iBAAiB;CACjB,WAAW;CACX,UAAU;CACX,CAAC;;;;;;;AAUF,SAAgB,eAAe,QAA4B;CACzD,OAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;;;;;;;;;AAUxC,SAAgB,gBAAgB,IAAgB,QAAoB,OAA0B;CAC5F,IAAI,MAAM,SAAS,MAAM,MACvB;CAGF,KAAK,MAAM,WAAW,OAAO,UAC3B,GAAG,KAAK,QAAQ;CAGlB,MAAM,QAAkB,EAAE;CAC1B,MAAM,KAAK,cAAc,OAAO,SAAS;CACzC,MAAM,KAAK,cAAc,OAAO,YAAY;CAC5C,MAAM,KAAK,cAAc,OAAO,aAAa;CAC7C,MAAM,KAAK,GAAG;CACd,MAAM,KAAK,iBAAiB;CAC5B,KAAK,MAAM,QAAQ,OAAO,cACxB,MAAM,KAAK,OAAO,OAAO;CAG3B,IAAI,OAAO,aAAa,SAAS,GAAG;EAClC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,4CAA4C;EACvD,KAAK,MAAM,QAAQ,OAAO,cACxB,MAAM,KAAK,OAAO,OAAO;;CAI7B,IAAI,CAAC,OAAO,kBAAkB,SAAS;EACrC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,sBAAsB;EACjC,KAAK,MAAM,OAAO,OAAO,kBAAkB,MACzC,MAAM,KAAK,OAAO,MAAM;EAE1B,KAAK,MAAM,OAAO,OAAO,kBAAkB,SACzC,MAAM,KAAK,OAAO,IAAI,QAAQ;;CAIlC,MAAM,KAAK,GAAG;CACd,MAAM,KAAK,cAAc;CACzB,KAAK,MAAM,QAAQ,OAAO,WACxB,MAAM,KAAK,KAAK,OAAO;CAGzB,GAAG,KAAK,MAAM,KAAK,KAAK,EAAE,OAAO;;;;;;;;;;;AAYnC,SAAgB,eAAe,SAalB;CACX,MAAM,QAAkB,EAAE;CAC1B,IAAI,aAAa;CACjB,MAAM,QAAQ,SAAuB;EACnC,MAAM,KAAK,GAAG,WAAW,IAAI,OAAO;EACpC,cAAc;;CAEhB,KAAK,sEAAsE;CAC3E,IAAI,CAAC,QAAQ,iBAAiB;EAC5B,KAAK,wBAAwB,QAAQ,YAAY,IAAI;EACrD,KAAK,uBAAuB,QAAQ,WAAW,iCAAiC;QAEhF,KAAK,uBAAuB,QAAQ,WAAW,kBAAkB,QAAQ,YAAY,KAAK;CAE5F,KAAK,oFAAoF;CACzF,IAAI,QAAQ,iBACV,KACE,+JACD;CAEH,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-dev.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^1.3.0",
|
|
16
16
|
"@dagrejs/dagre": "^3.0.0",
|
|
17
|
+
"@prisma-next/config": "0.8.0-dev.2",
|
|
18
|
+
"@prisma-next/contract": "0.8.0-dev.2",
|
|
19
|
+
"@prisma-next/emitter": "0.8.0-dev.2",
|
|
20
|
+
"@prisma-next/errors": "0.8.0-dev.2",
|
|
21
|
+
"@prisma-next/framework-components": "0.8.0-dev.2",
|
|
22
|
+
"@prisma-next/migration-tools": "0.8.0-dev.2",
|
|
23
|
+
"@prisma-next/psl-printer": "0.8.0-dev.2",
|
|
24
|
+
"@prisma-next/utils": "0.8.0-dev.2",
|
|
17
25
|
"arktype": "^2.1.29",
|
|
18
26
|
"c12": "^3.3.4",
|
|
19
27
|
"clipanion": "4.0.0-rc.4",
|
|
@@ -26,29 +34,21 @@
|
|
|
26
34
|
"pathe": "^2.0.3",
|
|
27
35
|
"string-width": "^8.2.1",
|
|
28
36
|
"strip-ansi": "^7.2.0",
|
|
29
|
-
"wrap-ansi": "^10.0.0"
|
|
30
|
-
"@prisma-next/config": "0.7.0",
|
|
31
|
-
"@prisma-next/contract": "0.7.0",
|
|
32
|
-
"@prisma-next/emitter": "0.7.0",
|
|
33
|
-
"@prisma-next/errors": "0.7.0",
|
|
34
|
-
"@prisma-next/framework-components": "0.7.0",
|
|
35
|
-
"@prisma-next/migration-tools": "0.7.0",
|
|
36
|
-
"@prisma-next/psl-printer": "0.7.0",
|
|
37
|
-
"@prisma-next/utils": "0.7.0"
|
|
37
|
+
"wrap-ansi": "^10.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
+
"@prisma-next/sql-contract": "0.8.0-dev.2",
|
|
41
|
+
"@prisma-next/sql-contract-emitter": "0.8.0-dev.2",
|
|
42
|
+
"@prisma-next/sql-contract-ts": "0.8.0-dev.2",
|
|
43
|
+
"@prisma-next/sql-operations": "0.8.0-dev.2",
|
|
44
|
+
"@prisma-next/sql-runtime": "0.8.0-dev.2",
|
|
45
|
+
"@prisma-next/test-utils": "0.8.0-dev.2",
|
|
46
|
+
"@prisma-next/tsconfig": "0.8.0-dev.2",
|
|
47
|
+
"@prisma-next/tsdown": "0.8.0-dev.2",
|
|
40
48
|
"@types/node": "24.10.4",
|
|
41
49
|
"tsdown": "0.22.0",
|
|
42
50
|
"typescript": "5.9.3",
|
|
43
|
-
"vitest": "4.1.5"
|
|
44
|
-
"@prisma-next/sql-contract": "0.7.0",
|
|
45
|
-
"@prisma-next/sql-contract-emitter": "0.7.0",
|
|
46
|
-
"@prisma-next/sql-contract-ts": "0.7.0",
|
|
47
|
-
"@prisma-next/sql-operations": "0.7.0",
|
|
48
|
-
"@prisma-next/sql-runtime": "0.7.0",
|
|
49
|
-
"@prisma-next/test-utils": "0.7.0",
|
|
50
|
-
"@prisma-next/tsconfig": "0.7.0",
|
|
51
|
-
"@prisma-next/tsdown": "0.7.0"
|
|
51
|
+
"vitest": "4.1.5"
|
|
52
52
|
},
|
|
53
53
|
"exports": {
|
|
54
54
|
".": {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import type { PackageManager } from './detect-package-manager';
|
|
4
|
+
import { errorInitSkillInstallFailed } from './errors';
|
|
5
|
+
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The npm package the agent-skill install dispatches to. Version-locked
|
|
10
|
+
* to Prisma Next via the consumer project's `package.json`; the install
|
|
11
|
+
* subprocess picks up whatever version is resolvable at install time.
|
|
12
|
+
*
|
|
13
|
+
* The skill is **always** installed at the project level — never the
|
|
14
|
+
* user level — precisely so the skill version tracks the project's
|
|
15
|
+
* Prisma Next version. A user-level (global) install of an
|
|
16
|
+
* agent-skills CLI package would have to pick a single version for
|
|
17
|
+
* every project on the host, which breaks the version-locking invariant
|
|
18
|
+
* the rest of the framework relies on (skills, CLI, runtime, and
|
|
19
|
+
* extension packs all ship at the same version per release).
|
|
20
|
+
*/
|
|
21
|
+
export const AGENT_SKILL_PACKAGE = '@prisma-next/skills';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The skill-install command, formatted for the project's detected
|
|
25
|
+
* package manager. `npx`/`pnpm dlx`/`bunx` are interchangeable to the
|
|
26
|
+
* user; we pick the variant that matches the rest of the install step
|
|
27
|
+
* so a single project consistently uses one runner.
|
|
28
|
+
*
|
|
29
|
+
* `--all` auto-selects every skill in the cluster and every detected
|
|
30
|
+
* agent runtime, skipping the multi-select prompts the `skills` CLI
|
|
31
|
+
* shows by default. A non-interactive scaffold step cannot present
|
|
32
|
+
* prompts, and the cluster is designed to be installed as a unit (the
|
|
33
|
+
* router skill routes between the workflow-scoped siblings). Users who
|
|
34
|
+
* want a narrower install run `npx skills add @prisma-next/skills`
|
|
35
|
+
* themselves after `init` with the flags they want.
|
|
36
|
+
*
|
|
37
|
+
* Exported for unit tests so the per-PM dispatch can be asserted
|
|
38
|
+
* without a live subprocess.
|
|
39
|
+
*/
|
|
40
|
+
export function formatSkillInstallCommand(pm: PackageManager): string {
|
|
41
|
+
const args = ['skills', 'add', AGENT_SKILL_PACKAGE, '--all'];
|
|
42
|
+
switch (pm) {
|
|
43
|
+
case 'pnpm':
|
|
44
|
+
return `pnpm dlx ${args.join(' ')}`;
|
|
45
|
+
case 'yarn':
|
|
46
|
+
return `yarn dlx ${args.join(' ')}`;
|
|
47
|
+
case 'bun':
|
|
48
|
+
return `bunx ${args.join(' ')}`;
|
|
49
|
+
case 'deno':
|
|
50
|
+
return `deno run -A npm:${args.join(' ')}`;
|
|
51
|
+
case 'npm':
|
|
52
|
+
return `npx ${args.join(' ')}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse the project-pm-formatted command into an exec call. The
|
|
58
|
+
* format-then-parse split keeps the user-facing command string the same
|
|
59
|
+
* as the surface the structured error advertises, so a user who copies
|
|
60
|
+
* the error's `fix` line gets the same invocation that init just
|
|
61
|
+
* attempted.
|
|
62
|
+
*/
|
|
63
|
+
function commandToExec(command: string): {
|
|
64
|
+
readonly file: string;
|
|
65
|
+
readonly args: readonly string[];
|
|
66
|
+
} {
|
|
67
|
+
const tokens = command.split(/\s+/);
|
|
68
|
+
return { file: tokens[0] ?? 'npx', args: tokens.slice(1) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runs the project-level skill install. Returns `{ ok: true, command }`
|
|
73
|
+
* on success; throws a structured `errorInitSkillInstallFailed` on
|
|
74
|
+
* failure. The throw is intentionally fatal — project-level skill
|
|
75
|
+
* install is unconditional (modulo `--no-skill`) and the user opted
|
|
76
|
+
* into Prisma Next by running `init`. A silent skip would defeat the
|
|
77
|
+
* onboarding-to-zero contract.
|
|
78
|
+
*/
|
|
79
|
+
export async function runProjectLevelSkillInstall(ctx: {
|
|
80
|
+
readonly baseDir: string;
|
|
81
|
+
readonly pm: PackageManager;
|
|
82
|
+
readonly filesWritten: readonly string[];
|
|
83
|
+
}): Promise<{ readonly ok: true; readonly command: string }> {
|
|
84
|
+
const command = formatSkillInstallCommand(ctx.pm);
|
|
85
|
+
const { file, args } = commandToExec(command);
|
|
86
|
+
try {
|
|
87
|
+
await exec(file, args, { cwd: ctx.baseDir });
|
|
88
|
+
return { ok: true, command };
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw errorInitSkillInstallFailed({
|
|
91
|
+
skillInstallCommand: command,
|
|
92
|
+
filesWritten: ctx.filesWritten,
|
|
93
|
+
cause:
|
|
94
|
+
redactSecrets(readChildStderr(err)) || (err instanceof Error ? err.message : String(err)),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readChildStderr(err: unknown): string {
|
|
100
|
+
if (err instanceof Error && 'stderr' in err) {
|
|
101
|
+
return String((err as { stderr: string }).stderr ?? '');
|
|
102
|
+
}
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Strips credentials from a `scheme://user:pass@host/...` URL anywhere
|
|
108
|
+
* in `stderr`. Package-manager stderr regularly contains credentialed
|
|
109
|
+
* registry URLs (private npm registries, GitHub Packages tokens), and
|
|
110
|
+
* those bubble into the structured `errorInitSkillInstallFailed`
|
|
111
|
+
* envelope, which ends up in logs and CI output. Redact at the
|
|
112
|
+
* boundary so we never re-emit a secret.
|
|
113
|
+
*
|
|
114
|
+
* Exported for unit tests.
|
|
115
|
+
*/
|
|
116
|
+
export function redactSecrets(stderr: string): string {
|
|
117
|
+
if (!stderr) return stderr;
|
|
118
|
+
return stderr.replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s]+)@/g, '$1***@');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// -------------------------------------------------------------------
|
|
122
|
+
// Legacy file cleanup
|
|
123
|
+
// -------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Hand-rolled skill stub path that init must not leave behind. Removed
|
|
127
|
+
* on every init run so a project's `.agents/skills/prisma-next/` does
|
|
128
|
+
* not shadow the published `@prisma-next/skills` package.
|
|
129
|
+
*/
|
|
130
|
+
export const LEGACY_SKILL_FILE = '.agents/skills/prisma-next/SKILL.md';
|
|
@@ -236,3 +236,35 @@ export function errorInitEmitFailed(options: {
|
|
|
236
236
|
},
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The project-level agent-skill install (`npx skills add
|
|
242
|
+
* @prisma-next/skills`) failed after a successful dependency
|
|
243
|
+
* install + emit. The project's scaffold remains on disk; the user
|
|
244
|
+
* can either fix the underlying issue (network, registry, PATH) and
|
|
245
|
+
* run the install command manually, or re-run `init --no-skill` to
|
|
246
|
+
* proceed without the skill.
|
|
247
|
+
*
|
|
248
|
+
* Non-rolling-back, matching the existing install/emit failure
|
|
249
|
+
* semantics. Maps to exit code `6 = SKILL_INSTALL_FAILED`.
|
|
250
|
+
*/
|
|
251
|
+
export function errorInitSkillInstallFailed(options: {
|
|
252
|
+
readonly skillInstallCommand: string;
|
|
253
|
+
readonly filesWritten: readonly string[];
|
|
254
|
+
readonly cause: string;
|
|
255
|
+
}): CliStructuredError {
|
|
256
|
+
return new CliStructuredError('5013', 'Failed to install @prisma-next/skills', {
|
|
257
|
+
domain: 'CLI',
|
|
258
|
+
why: `\`${options.skillInstallCommand}\` exited with an error: ${options.cause}`,
|
|
259
|
+
fix:
|
|
260
|
+
'Either:\n' +
|
|
261
|
+
` - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? ' --force' : ''}\` to skip the skill install for this run, or\n` +
|
|
262
|
+
` - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
|
|
263
|
+
docsUrl: 'https://prisma-next.dev/docs/cli/init#agent-skill',
|
|
264
|
+
meta: {
|
|
265
|
+
filesWritten: options.filesWritten,
|
|
266
|
+
skillInstallCommand: options.skillInstallCommand,
|
|
267
|
+
cause: options.cause,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
@@ -60,3 +60,13 @@ export const INIT_EXIT_INSTALL_FAILED = 4;
|
|
|
60
60
|
* emit` manually.
|
|
61
61
|
*/
|
|
62
62
|
export const INIT_EXIT_EMIT_FAILED = 5;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The project-level `@prisma-next/skills` install (`npx skills add
|
|
66
|
+
* @prisma-next/skills`) failed after a successful dependency
|
|
67
|
+
* install + emit. The scaffolded project files remain on disk; the
|
|
68
|
+
* user can fix the underlying issue (network, registry reachability,
|
|
69
|
+
* `npx skills` not on PATH) and run the install manually, or re-run
|
|
70
|
+
* `init` with `--no-skill` to skip it.
|
|
71
|
+
*/
|
|
72
|
+
export const INIT_EXIT_SKILL_INSTALL_FAILED = 6;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
INIT_EXIT_INTERNAL_ERROR,
|
|
12
12
|
INIT_EXIT_OK,
|
|
13
13
|
INIT_EXIT_PRECONDITION,
|
|
14
|
+
INIT_EXIT_SKILL_INSTALL_FAILED,
|
|
14
15
|
INIT_EXIT_USER_ABORTED,
|
|
15
16
|
} from './exit-codes';
|
|
16
17
|
|
|
@@ -33,6 +34,7 @@ interface InitCommandOptions extends CommonCommandOptions {
|
|
|
33
34
|
readonly probeDb?: boolean;
|
|
34
35
|
readonly strictProbe?: boolean;
|
|
35
36
|
readonly install?: boolean;
|
|
37
|
+
readonly skill?: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function createInitCommand(): Command {
|
|
@@ -52,7 +54,8 @@ export function createInitCommand(): Command {
|
|
|
52
54
|
` ${INIT_EXIT_PRECONDITION} PRECONDITION Bad flags / missing prerequisite (e.g. no package.json).\n` +
|
|
53
55
|
` ${INIT_EXIT_USER_ABORTED} USER_ABORTED User cancelled an interactive prompt.\n` +
|
|
54
56
|
` ${INIT_EXIT_INSTALL_FAILED} INSTALL_FAILED Dependency installation failed (init-specific).\n` +
|
|
55
|
-
` ${INIT_EXIT_EMIT_FAILED} EMIT_FAILED \`contract emit\` failed after install (init-specific)
|
|
57
|
+
` ${INIT_EXIT_EMIT_FAILED} EMIT_FAILED \`contract emit\` failed after install (init-specific).\n` +
|
|
58
|
+
` ${INIT_EXIT_SKILL_INSTALL_FAILED} SKILL_INSTALL_FAILED Agent-skill install failed (re-run with --no-skill to skip).`,
|
|
56
59
|
);
|
|
57
60
|
setCommandExamples(command, [
|
|
58
61
|
'prisma-next init',
|
|
@@ -60,6 +63,7 @@ export function createInitCommand(): Command {
|
|
|
60
63
|
'prisma-next init --yes --target mongodb --authoring typescript --json',
|
|
61
64
|
'prisma-next init --yes --force --target postgres --authoring psl # overwrite an existing scaffold',
|
|
62
65
|
'prisma-next init --no-install # skip pnpm/npm install + emit',
|
|
66
|
+
'prisma-next init --no-skill # skip the agent-skill install (air-gapped / restricted env)',
|
|
63
67
|
]);
|
|
64
68
|
|
|
65
69
|
return addGlobalOptions(command)
|
|
@@ -83,6 +87,10 @@ export function createInitCommand(): Command {
|
|
|
83
87
|
'Treat a failed --probe-db as fatal (no-op without --probe-db; init is offline-by-default)',
|
|
84
88
|
)
|
|
85
89
|
.option('--no-install', 'Skip dependency installation and contract emission')
|
|
90
|
+
.option(
|
|
91
|
+
'--no-skill',
|
|
92
|
+
'Skip the @prisma-next/skills install (air-gapped CI, restricted registries, etc.)',
|
|
93
|
+
)
|
|
86
94
|
.action(async (options: InitCommandOptions) => {
|
|
87
95
|
const { runInit } = await import('./init');
|
|
88
96
|
const flags = parseGlobalFlags(options);
|
|
@@ -7,6 +7,11 @@ import { CliStructuredError } from '../../utils/cli-errors';
|
|
|
7
7
|
import { formatErrorJson, formatErrorOutput } from '../../utils/formatters/errors';
|
|
8
8
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
9
9
|
import { TerminalUI } from '../../utils/terminal-ui';
|
|
10
|
+
import {
|
|
11
|
+
formatSkillInstallCommand,
|
|
12
|
+
LEGACY_SKILL_FILE,
|
|
13
|
+
runProjectLevelSkillInstall,
|
|
14
|
+
} from './agent-skill-install';
|
|
10
15
|
import {
|
|
11
16
|
detectPackageManager,
|
|
12
17
|
formatAddArgs,
|
|
@@ -29,6 +34,7 @@ import {
|
|
|
29
34
|
INIT_EXIT_INTERNAL_ERROR,
|
|
30
35
|
INIT_EXIT_OK,
|
|
31
36
|
INIT_EXIT_PRECONDITION,
|
|
37
|
+
INIT_EXIT_SKILL_INSTALL_FAILED,
|
|
32
38
|
INIT_EXIT_USER_ABORTED,
|
|
33
39
|
} from './exit-codes';
|
|
34
40
|
import { mergeGitattributes, requiredGitattributesLines } from './hygiene-gitattributes';
|
|
@@ -48,7 +54,6 @@ import {
|
|
|
48
54
|
} from './output';
|
|
49
55
|
import { type ProbeOutcome, type ProbeOverrides, probeServerVersion } from './probe-db';
|
|
50
56
|
import { findStaleArtefacts, removeDependency } from './reinit-cleanup';
|
|
51
|
-
import { agentSkillMd } from './templates/agent-skill';
|
|
52
57
|
import { configFile, dbFile, starterSchema, targetPackageName } from './templates/code-templates';
|
|
53
58
|
import { envExampleContent, envFileContent, MIN_SERVER_VERSION } from './templates/env';
|
|
54
59
|
import { quickReferenceMd } from './templates/quick-reference';
|
|
@@ -72,6 +77,15 @@ interface InstallReport {
|
|
|
72
77
|
readonly deps: readonly string[];
|
|
73
78
|
readonly devDeps: readonly string[];
|
|
74
79
|
readonly warnings: readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* The package manager that actually ran. Equal to the detected `pm`
|
|
82
|
+
* on the common path; differs when the FR7.2 pnpm → npm fallback
|
|
83
|
+
* fired, in which case it's `'npm'`. Threaded into the agent-skill
|
|
84
|
+
* install so the runner stays consistent with the install we just
|
|
85
|
+
* ran — re-trying through `pnpm dlx` when `pnpm install` just failed
|
|
86
|
+
* for workspace/catalog reasons would fail again for the same reason.
|
|
87
|
+
*/
|
|
88
|
+
readonly effectivePm: PackageManager;
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/**
|
|
@@ -156,10 +170,6 @@ export async function runInit(
|
|
|
156
170
|
path: 'prisma-next.md',
|
|
157
171
|
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun),
|
|
158
172
|
},
|
|
159
|
-
{
|
|
160
|
-
path: '.agents/skills/prisma-next/SKILL.md',
|
|
161
|
-
content: agentSkillMd(inputs.target, inputs.authoring, inputs.schemaPath, pkgRun),
|
|
162
|
-
},
|
|
163
173
|
{ path: '.env.example', content: envExampleContent(inputs.target) },
|
|
164
174
|
];
|
|
165
175
|
|
|
@@ -172,6 +182,14 @@ export async function runInit(
|
|
|
172
182
|
// and missing-on-disk-at-write-time is tolerated.
|
|
173
183
|
const filesToDelete: string[] = inputs.reinit ? [...findStaleArtefacts(baseDir, schemaDir)] : [];
|
|
174
184
|
|
|
185
|
+
// `init` delegates the skill to `npx skills add @prisma-next/skills`,
|
|
186
|
+
// so a hand-rolled `.agents/skills/prisma-next/SKILL.md` in the project
|
|
187
|
+
// would shadow the published package. Queue it for deletion on every
|
|
188
|
+
// run (not gated on `--reinit`).
|
|
189
|
+
if (existsSync(join(baseDir, LEGACY_SKILL_FILE))) {
|
|
190
|
+
filesToDelete.push(LEGACY_SKILL_FILE);
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
// FR3.2: a real `.env` is only written when the user opted in. Never
|
|
176
194
|
// overwrite an existing `.env` — secrets live there and clobbering
|
|
177
195
|
// them is the most damaging possible side-effect of `init`.
|
|
@@ -416,6 +434,51 @@ export async function runInit(
|
|
|
416
434
|
}
|
|
417
435
|
}
|
|
418
436
|
|
|
437
|
+
// Agent-skill install. Project-level is unconditional modulo
|
|
438
|
+
// `--no-skill`. We deliberately do **not** offer a user-level
|
|
439
|
+
// (global) install path: the skill's behaviour and surface track
|
|
440
|
+
// the project's Prisma Next version, and a host-wide install would
|
|
441
|
+
// have to pick a single version for every project on the machine,
|
|
442
|
+
// which breaks the version-locking invariant the rest of the
|
|
443
|
+
// framework relies on. A project-level failure is fatal
|
|
444
|
+
// (`INIT_EXIT_SKILL_INSTALL_FAILED`).
|
|
445
|
+
//
|
|
446
|
+
// Runs after install + emit because (1) `node_modules` is in place,
|
|
447
|
+
// so any consumers downstream are coherent, and (2) the user has
|
|
448
|
+
// seen the scaffolding succeed before this network-bound step
|
|
449
|
+
// potentially fails. We skip the install when the user passed
|
|
450
|
+
// `--no-install` for the same reason — no `node_modules` means the
|
|
451
|
+
// workspace isn't ready to consume the skill yet anyway.
|
|
452
|
+
const manualProjectSkillCommand = formatSkillInstallCommand(install.effectivePm);
|
|
453
|
+
let skillRegistered = false;
|
|
454
|
+
if (!inputs.installProjectSkill) {
|
|
455
|
+
warnings.push(
|
|
456
|
+
`Skipped @prisma-next/skills install (--no-skill). To install later, run \`${manualProjectSkillCommand}\` in this project.`,
|
|
457
|
+
);
|
|
458
|
+
} else if (install.skipped) {
|
|
459
|
+
warnings.push(
|
|
460
|
+
`Skipped @prisma-next/skills install because --no-install was passed. Once you run install manually, register the skill with \`${manualProjectSkillCommand}\`.`,
|
|
461
|
+
);
|
|
462
|
+
} else {
|
|
463
|
+
const spinner = ui.spinner();
|
|
464
|
+
spinner.start('Registering @prisma-next/skills with the agent runtime...');
|
|
465
|
+
try {
|
|
466
|
+
const project = await runProjectLevelSkillInstall({
|
|
467
|
+
baseDir,
|
|
468
|
+
pm: install.effectivePm,
|
|
469
|
+
filesWritten,
|
|
470
|
+
});
|
|
471
|
+
spinner.stop(`Registered @prisma-next/skills (project level) — ran \`${project.command}\``);
|
|
472
|
+
skillRegistered = true;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
spinner.stop('Agent-skill install failed');
|
|
475
|
+
if (CliStructuredError.is(error)) {
|
|
476
|
+
return emitError(ui, flags, error);
|
|
477
|
+
}
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
419
482
|
const output: InitOutput = {
|
|
420
483
|
ok: true,
|
|
421
484
|
target: inputs.target === 'mongo' ? 'mongodb' : 'postgres',
|
|
@@ -434,6 +497,7 @@ export async function runInit(
|
|
|
434
497
|
contractEmitted,
|
|
435
498
|
emitCommand,
|
|
436
499
|
schemaPath: inputs.schemaPath,
|
|
500
|
+
skillRegistered,
|
|
437
501
|
}),
|
|
438
502
|
warnings,
|
|
439
503
|
};
|
|
@@ -525,6 +589,8 @@ export function exitCodeForError(error: { readonly code: string }): number {
|
|
|
525
589
|
return INIT_EXIT_EMIT_FAILED;
|
|
526
590
|
case '5009': // invalid output document — internal bug in prisma-next
|
|
527
591
|
return INIT_EXIT_INTERNAL_ERROR;
|
|
592
|
+
case '5013': // agent-skill install failed
|
|
593
|
+
return INIT_EXIT_SKILL_INSTALL_FAILED;
|
|
528
594
|
default:
|
|
529
595
|
// Any unexpected code is treated as an internal bug rather than
|
|
530
596
|
// mis-routed to PRECONDITION. Adding a new code requires an
|
|
@@ -646,7 +712,7 @@ async function runInstall(ctx: {
|
|
|
646
712
|
'Manual steps',
|
|
647
713
|
);
|
|
648
714
|
}
|
|
649
|
-
return { skipped: true, deps: [], devDeps: [], warnings: catalogWarnings };
|
|
715
|
+
return { skipped: true, deps: [], devDeps: [], warnings: catalogWarnings, effectivePm: pm };
|
|
650
716
|
}
|
|
651
717
|
|
|
652
718
|
const exec = promisify(execFile);
|
|
@@ -661,7 +727,7 @@ async function runInstall(ctx: {
|
|
|
661
727
|
try {
|
|
662
728
|
await runPair(pm);
|
|
663
729
|
spinner.stop(`Installed ${allPackages}`);
|
|
664
|
-
return { skipped: false, deps, devDeps, warnings: catalogWarnings };
|
|
730
|
+
return { skipped: false, deps, devDeps, warnings: catalogWarnings, effectivePm: pm };
|
|
665
731
|
} catch (err) {
|
|
666
732
|
const stderrText = redactSecrets(readChildStderr(err));
|
|
667
733
|
|
|
@@ -693,6 +759,7 @@ async function runInstall(ctx: {
|
|
|
693
759
|
// catalog-honour warning to avoid a contradictory message
|
|
694
760
|
// pair.
|
|
695
761
|
warnings: [fallbackWarning],
|
|
762
|
+
effectivePm: 'npm',
|
|
696
763
|
};
|
|
697
764
|
} catch (npmErr) {
|
|
698
765
|
spinner.stop('Installation failed');
|
|
@@ -31,6 +31,13 @@ export interface InitFlagOptions {
|
|
|
31
31
|
readonly probeDb?: boolean;
|
|
32
32
|
readonly strictProbe?: boolean;
|
|
33
33
|
readonly install?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* `--no-skill` — skip the project-level skill install entirely.
|
|
36
|
+
* Documented escape hatch for air-gapped CI, restricted registries,
|
|
37
|
+
* and any environment where `npx skills` is
|
|
38
|
+
* not reachable.
|
|
39
|
+
*/
|
|
40
|
+
readonly skill?: boolean;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
@@ -61,6 +68,14 @@ export interface ResolvedInitInputs {
|
|
|
61
68
|
* is added separately via the install step.
|
|
62
69
|
*/
|
|
63
70
|
readonly removePreviousFacade: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Whether to run `npx skills add @prisma-next/skills` at the
|
|
73
|
+
* project level after install + emit. True by default; `--no-skill`
|
|
74
|
+
* sets it to `false`. The skill is always project-level (never
|
|
75
|
+
* user-level / global) so its version stays locked to the project's
|
|
76
|
+
* Prisma Next version — see `agent-skill-install.ts`.
|
|
77
|
+
*/
|
|
78
|
+
readonly installProjectSkill: boolean;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
81
|
const TARGET_ALIASES: ReadonlyMap<string, TargetId> = new Map([
|
|
@@ -161,6 +176,13 @@ export async function resolveInitInputs(ctx: {
|
|
|
161
176
|
autoAcceptPrompts,
|
|
162
177
|
});
|
|
163
178
|
|
|
179
|
+
// Skill-install gating. `--no-skill` (commander parses
|
|
180
|
+
// `options.skill === false`) is the only escape hatch; otherwise
|
|
181
|
+
// project-level install is unconditional. The skill is always
|
|
182
|
+
// installed at the project level so its version tracks the
|
|
183
|
+
// project's Prisma Next release.
|
|
184
|
+
const installProjectSkill = options.skill !== false;
|
|
185
|
+
|
|
164
186
|
return {
|
|
165
187
|
target: finalTarget,
|
|
166
188
|
authoring: finalAuthoring,
|
|
@@ -171,6 +193,7 @@ export async function resolveInitInputs(ctx: {
|
|
|
171
193
|
strictProbe: Boolean(options.strictProbe),
|
|
172
194
|
reinit,
|
|
173
195
|
removePreviousFacade,
|
|
196
|
+
installProjectSkill,
|
|
174
197
|
};
|
|
175
198
|
}
|
|
176
199
|
|
|
@@ -120,27 +120,32 @@ export function buildNextSteps(options: {
|
|
|
120
120
|
readonly contractEmitted: boolean;
|
|
121
121
|
readonly emitCommand: string;
|
|
122
122
|
readonly schemaPath: string;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the project-level `@prisma-next/skills` install actually ran
|
|
125
|
+
* and succeeded during this `init`. When false (the user passed
|
|
126
|
+
* `--no-skill` or `--no-install`, so the install was skipped), the
|
|
127
|
+
* "registered with your agent runtime" step is omitted — the skip is
|
|
128
|
+
* already surfaced in the warnings array with a manual-install hint.
|
|
129
|
+
*/
|
|
130
|
+
readonly skillRegistered: boolean;
|
|
123
131
|
}): string[] {
|
|
124
132
|
const steps: string[] = [];
|
|
125
|
-
|
|
133
|
+
let stepNumber = 1;
|
|
134
|
+
const push = (text: string): void => {
|
|
135
|
+
steps.push(`${stepNumber}. ${text}`);
|
|
136
|
+
stepNumber += 1;
|
|
137
|
+
};
|
|
138
|
+
push('Set DATABASE_URL in your environment (export it or add it to .env).');
|
|
126
139
|
if (!options.contractEmitted) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
steps.push(
|
|
130
|
-
'4. Open prisma-next.md for a quick reference on how to write your first typed query.',
|
|
131
|
-
);
|
|
132
|
-
steps.push(
|
|
133
|
-
'5. The .agents/skills/prisma-next/SKILL.md file is wired up for AI-coding agents in this project.',
|
|
134
|
-
);
|
|
140
|
+
push(`Emit the contract: \`${options.emitCommand}\``);
|
|
141
|
+
push(`Edit your schema at ${options.schemaPath}, then re-run the emit command.`);
|
|
135
142
|
} else {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
steps.push(
|
|
143
|
-
'4. The .agents/skills/prisma-next/SKILL.md file is wired up for AI-coding agents in this project.',
|
|
143
|
+
push(`Edit your schema at ${options.schemaPath}, then re-run \`${options.emitCommand}\`.`);
|
|
144
|
+
}
|
|
145
|
+
push('Open prisma-next.md for a quick reference on how to write your first typed query.');
|
|
146
|
+
if (options.skillRegistered) {
|
|
147
|
+
push(
|
|
148
|
+
'@prisma-next/skills is registered with your agent runtime — open the project in your IDE and ask the agent to add a model, run a query, or plan a migration.',
|
|
144
149
|
);
|
|
145
150
|
}
|
|
146
151
|
return steps;
|
|
@@ -253,7 +253,10 @@ export function dbFile(target: TargetId): string {
|
|
|
253
253
|
import type { Contract } from './contract.d';
|
|
254
254
|
import contractJson from './contract.json' with { type: 'json' };
|
|
255
255
|
|
|
256
|
-
export const db = postgres<Contract>({
|
|
256
|
+
export const db = postgres<Contract>({
|
|
257
|
+
contractJson,
|
|
258
|
+
url: process.env['DATABASE_URL']!,
|
|
259
|
+
});
|
|
257
260
|
`;
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -289,10 +289,21 @@ async function executeMigrationPlanCommand(
|
|
|
289
289
|
// declaredButUnmigrated precheck always passes here.
|
|
290
290
|
const stack = createControlStack(config);
|
|
291
291
|
const familyInstance = config.family.create(stack);
|
|
292
|
+
let validatedAppContract: Contract;
|
|
293
|
+
try {
|
|
294
|
+
validatedAppContract = familyInstance.validateContract(toContractJson);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return notOk(
|
|
297
|
+
errorContractValidationFailed(
|
|
298
|
+
`Contract validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
299
|
+
{ where: { path: contractPathAbsolute } },
|
|
300
|
+
),
|
|
301
|
+
);
|
|
302
|
+
}
|
|
292
303
|
const aggregateResult = await buildContractSpaceAggregate({
|
|
293
304
|
targetId: config.target.targetId,
|
|
294
305
|
migrationsDir,
|
|
295
|
-
appContract:
|
|
306
|
+
appContract: validatedAppContract,
|
|
296
307
|
extensionPacks: config.extensionPacks ?? [],
|
|
297
308
|
validateContract: (json: unknown) => familyInstance.validateContract(json),
|
|
298
309
|
});
|