@prisma-next/cli 0.5.0-dev.1 → 0.5.0-dev.10
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/README.md +17 -18
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-D2NPMaxW.d.mts} +1 -0
- package/dist/cli.mjs +126 -13
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-faKQqcix.mjs} +19 -4
- package/dist/client-faKQqcix.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +7 -2
- package/dist/commands/contract-infer.mjs +8 -2
- package/dist/commands/db-init.mjs +8 -7
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -5
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +8 -7
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +8 -7
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +8 -7
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +1 -1
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +14 -37
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +20 -25
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +6 -3
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +30 -35
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +31 -40
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +4 -4
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +18 -25
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -4
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +7 -2
- package/dist/{config-loader-_W4T21X1.mjs → config-loader-C25b63rJ.mjs} +1 -1
- package/dist/{config-loader-_W4T21X1.mjs.map → config-loader-C25b63rJ.mjs.map} +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/{contract-emit-CNYyzJwF.mjs → contract-emit-B5wnhTuF.mjs} +8 -8
- package/dist/{contract-emit-CNYyzJwF.mjs.map → contract-emit-B5wnhTuF.mjs.map} +1 -1
- package/dist/contract-emit-B9wkchud.mjs +6 -0
- package/dist/{contract-emit-CQfj7xJn.mjs → contract-emit-PeB96eHy.mjs} +6 -6
- package/dist/{contract-emit-CQfj7xJn.mjs.map → contract-emit-PeB96eHy.mjs.map} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-CAOELa-H.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-CAOELa-H.mjs.map} +1 -1
- package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-DnY9fUw0.mjs} +4 -4
- package/dist/{contract-infer-BP3DrGgz.mjs.map → contract-infer-DnY9fUw0.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +6 -4
- package/dist/exports/index.mjs +7 -2
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DsFfxXVZ.mjs} +2 -2
- package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DsFfxXVZ.mjs.map} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-D9UbZDyz.mjs} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-D9UbZDyz.mjs.map} +1 -1
- package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-C6el-5x_.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-C6el-5x_.mjs.map} +1 -1
- package/dist/init-jf33mNQ6.mjs +2062 -0
- package/dist/init-jf33mNQ6.mjs.map +1 -0
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-CqoZhKC1.mjs} +6 -6
- package/dist/{inspect-live-schema-DWzf4Q_m.mjs.map → inspect-live-schema-CqoZhKC1.mjs.map} +1 -1
- package/dist/migration-cli.mjs +14 -7
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-B40VaF-m.mjs} +6 -6
- package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-B40VaF-m.mjs.map} +1 -1
- package/dist/{migration-status-B0HLF7So.mjs → migration-status-CDgFxhAo.mjs} +18 -32
- package/dist/migration-status-CDgFxhAo.mjs.map +1 -0
- package/dist/{migrations-B0dOQlk0.mjs → migrations-CKRMAKka.mjs} +3 -3
- package/dist/migrations-CKRMAKka.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DvQWB1nK.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DvQWB1nK.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-DcV0QoTr.mjs} +9 -90
- package/dist/result-handler-DcV0QoTr.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-C3ZLwQxK.mjs} +76 -2
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +1 -0
- package/dist/{validate-contract-deps-esa-VQ0h.mjs → validate-contract-deps-B_Cs29TL.mjs} +1 -1
- package/dist/{validate-contract-deps-esa-VQ0h.mjs.map → validate-contract-deps-B_Cs29TL.mjs.map} +1 -1
- package/dist/{verify-BxiVp50b.mjs → verify-Bkycc-Tf.mjs} +2 -2
- package/dist/{verify-BxiVp50b.mjs.map → verify-Bkycc-Tf.mjs.map} +1 -1
- package/package.json +19 -14
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/migration-apply.ts +14 -49
- package/src/commands/migration-new.ts +19 -27
- package/src/commands/migration-plan.ts +52 -41
- package/src/commands/migration-ref.ts +40 -54
- package/src/commands/migration-show.ts +23 -27
- package/src/commands/migration-status.ts +29 -46
- package/src/control-api/operations/migration-apply.ts +15 -0
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +16 -9
- package/src/utils/cli-errors.ts +45 -1
- package/src/utils/command-helpers.ts +11 -24
- package/src/utils/formatters/graph-migration-mapper.ts +1 -1
- package/src/utils/formatters/migrations.ts +2 -2
- package/dist/cli-errors-DHq6GQGu.mjs +0 -5
- package/dist/client-TG7rbCWT.mjs.map +0 -1
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/migration-status-B0HLF7So.mjs.map +0 -1
- package/dist/migrations-B0dOQlk0.mjs.map +0 -1
- package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable exit codes for the `init` command.
|
|
3
|
+
*
|
|
4
|
+
* These are part of the command's public contract. AI agents and CI scripts
|
|
5
|
+
* branch on them (FR1.6), so the values must remain stable across versions.
|
|
6
|
+
*
|
|
7
|
+
* Codes 0–3 are the CLI-wide reserved values per the [CLI Style Guide
|
|
8
|
+
* Exit Codes section](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes):
|
|
9
|
+
* `OK = 0`, `INTERNAL_ERROR = 1`, `PRECONDITION = 2`, `USER_ABORTED = 3`.
|
|
10
|
+
* Codes 4 and 5 are command-specific outcomes for `init`'s two fallible
|
|
11
|
+
* side effects (install + emit). Documented in `--help` via
|
|
12
|
+
* `setCommandDescriptions` in `./index.ts`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const INIT_EXIT_OK = 0;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Anything we did not anticipate — a bug in prisma-next, not something
|
|
19
|
+
* the caller did wrong. Includes the structured error code `5009`
|
|
20
|
+
* (invalid output document) and any unrecognised internal error code,
|
|
21
|
+
* so callers can distinguish "tool is broken" from "your invocation
|
|
22
|
+
* was wrong" (`PRECONDITION = 2`). Maps to the generic "RUN" error
|
|
23
|
+
* domain.
|
|
24
|
+
*/
|
|
25
|
+
export const INIT_EXIT_INTERNAL_ERROR = 1;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Preconditions not met. The caller asked for something we cannot do
|
|
29
|
+
* without more input or a different environment. Examples:
|
|
30
|
+
* - missing `package.json` / `deno.json`
|
|
31
|
+
* - non-interactive mode without enough flags to proceed
|
|
32
|
+
* - re-init without `--force` in non-interactive mode
|
|
33
|
+
*/
|
|
34
|
+
export const INIT_EXIT_PRECONDITION = 2;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The user actively aborted an interactive prompt (Ctrl-C, declined the
|
|
38
|
+
* re-init confirmation, etc.). Distinct from PRECONDITION because the user
|
|
39
|
+
* was given the choice and made it; no diagnostic is needed.
|
|
40
|
+
*/
|
|
41
|
+
export const INIT_EXIT_USER_ABORTED = 3;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Dependency installation step failed without a recoverable fallback.
|
|
45
|
+
* `init` automatically falls back from `pnpm` to `npm` on a recognised
|
|
46
|
+
* workspace/catalog leak (FR7.2); this code is returned only when the
|
|
47
|
+
* fallback also fails, or when the package manager is not pnpm and the
|
|
48
|
+
* single attempt failed. Files written before the install step (config,
|
|
49
|
+
* schema, db client, etc.) remain on disk so the user can fix the
|
|
50
|
+
* environment and re-run; the error envelope's `meta.filesWritten` lists
|
|
51
|
+
* them.
|
|
52
|
+
*/
|
|
53
|
+
export const INIT_EXIT_INSTALL_FAILED = 4;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Contract emit step failed after a successful install. Files written
|
|
57
|
+
* before emit (including any installed dependencies) are still on disk;
|
|
58
|
+
* the user can fix the underlying issue (typically a contract syntax
|
|
59
|
+
* error or a missing extension pack) and re-run `prisma-next contract
|
|
60
|
+
* emit` manually.
|
|
61
|
+
*/
|
|
62
|
+
export const INIT_EXIT_EMIT_FAILED = 5;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { TargetId } from './templates/code-templates';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The schema-relative `.gitattributes` entries written for a freshly
|
|
5
|
+
* initialised project (FR3.4). Mirrors the relevant subset of the
|
|
6
|
+
* repo-root [`.gitattributes`](../../../../../../../../.gitattributes):
|
|
7
|
+
*
|
|
8
|
+
* - **Today**: `contract.json`, `contract.d.ts` are emitted on every
|
|
9
|
+
* `prisma-next contract emit`. Marking them `linguist-generated`
|
|
10
|
+
* keeps GitHub's diff stats honest and collapses the file in code
|
|
11
|
+
* review by default.
|
|
12
|
+
* - **Forward-looking**: `end-contract.*`, `start-contract.*`, `ops.json`,
|
|
13
|
+
* `migration.json` are not yet emitted by `init` flows but will be
|
|
14
|
+
* produced by adjacent commands (lower / migration tooling). Adding
|
|
15
|
+
* them now matches Decision 5 (forward-looking subset) so the file
|
|
16
|
+
* does not need to be amended every time a new artefact lands.
|
|
17
|
+
*
|
|
18
|
+
* Patterns are written relative to the schema directory so a user
|
|
19
|
+
* who runs `init --schema-path db/contract.prisma` gets
|
|
20
|
+
* `db/contract.json linguist-generated` — not the workspace-glob form
|
|
21
|
+
* `<glob>/contract.json` (which would over-match any unrelated
|
|
22
|
+
* `contract.json` the user has elsewhere) and not the absolute
|
|
23
|
+
* `prisma/contract.json` (which would silently break for a non-default
|
|
24
|
+
* schema path).
|
|
25
|
+
*/
|
|
26
|
+
const ARTEFACT_FILENAMES: readonly string[] = [
|
|
27
|
+
'contract.json',
|
|
28
|
+
'contract.d.ts',
|
|
29
|
+
'end-contract.json',
|
|
30
|
+
'end-contract.d.ts',
|
|
31
|
+
'start-contract.json',
|
|
32
|
+
'start-contract.d.ts',
|
|
33
|
+
'ops.json',
|
|
34
|
+
'migration.json',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const ATTRIBUTE = 'linguist-generated';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Computes the `.gitattributes` lines this scaffold expects to own. Each
|
|
41
|
+
* line has the shape `<path> linguist-generated`. The `target` parameter
|
|
42
|
+
* is currently unused but accepted for symmetry with the other hygiene
|
|
43
|
+
* helpers and to leave room for target-specific entries (e.g. a future
|
|
44
|
+
* Mongo-only artefact) without a signature break.
|
|
45
|
+
*/
|
|
46
|
+
export function requiredGitattributesLines(
|
|
47
|
+
schemaDir: string,
|
|
48
|
+
_target: TargetId,
|
|
49
|
+
): readonly string[] {
|
|
50
|
+
const dir = schemaDir === '.' ? '' : schemaDir.replace(/\/+$/, '');
|
|
51
|
+
const prefix = dir === '' ? '' : `${dir}/`;
|
|
52
|
+
return ARTEFACT_FILENAMES.map((file) => `${prefix}${file} ${ATTRIBUTE}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Idempotent `.gitattributes` merge (FR3.4 / FR9.3). Returns the new file
|
|
57
|
+
* content given the existing content (or `undefined` if the file does
|
|
58
|
+
* not yet exist).
|
|
59
|
+
*
|
|
60
|
+
* Equivalence is exact-line: a user-customised line like
|
|
61
|
+
* `prisma/*.json linguist-generated` is *not* recognised as covering
|
|
62
|
+
* `prisma/contract.json linguist-generated`. We accept that
|
|
63
|
+
* over-specification — preserving the user's broad pattern *and*
|
|
64
|
+
* appending the narrow one — because the narrow lines are what the
|
|
65
|
+
* acceptance criteria pin (FR3.4 AC).
|
|
66
|
+
*
|
|
67
|
+
* Returns `null` when no changes are required (file already contains
|
|
68
|
+
* every required entry).
|
|
69
|
+
*/
|
|
70
|
+
export function mergeGitattributes(
|
|
71
|
+
existing: string | undefined,
|
|
72
|
+
required: readonly string[],
|
|
73
|
+
): string | null {
|
|
74
|
+
if (existing === undefined) {
|
|
75
|
+
return `${required.join('\n')}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const presentLines = new Set(
|
|
79
|
+
existing
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((line) => line.trim())
|
|
82
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const missing = required.filter((line) => !presentLines.has(line));
|
|
86
|
+
if (missing.length === 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Mirrors `mergeGitignore`: a zero-byte existing file would otherwise
|
|
91
|
+
// gain a leading blank line, because `''.endsWith('\n')` is false. The
|
|
92
|
+
// empty-file case is uncommon (most projects either don't have a
|
|
93
|
+
// `.gitattributes` or have one with content), but symmetric handling
|
|
94
|
+
// keeps the two mergers' invariants identical.
|
|
95
|
+
const separator = existing.length === 0 || existing.endsWith('\n') ? '' : '\n';
|
|
96
|
+
return `${existing}${separator}${missing.join('\n')}\n`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The minimal `.gitignore` lines a Prisma Next scaffold needs (FR3.3).
|
|
3
|
+
* Order matches what Node tooling typically writes today.
|
|
4
|
+
*
|
|
5
|
+
* `node_modules/` first because it's the byte-largest miss; `dist/`
|
|
6
|
+
* because the scaffolded `tsconfig.json` writes there; `.env` last so
|
|
7
|
+
* the secret-bearing file is the one most-recently visible in any diff
|
|
8
|
+
* (a paranoid-correct ordering — humans skim from the top).
|
|
9
|
+
*/
|
|
10
|
+
export const REQUIRED_GITIGNORE_ENTRIES: readonly string[] = ['node_modules/', 'dist/', '.env'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Idempotent `.gitignore` merge (FR3.3 / FR9.3). Returns the new file
|
|
14
|
+
* content given the existing content (or `undefined` if the file does
|
|
15
|
+
* not yet exist). Adds only entries that are not already present and
|
|
16
|
+
* never duplicates a line. Existing comments and blank lines are
|
|
17
|
+
* preserved verbatim — `.gitignore` is parsed by `git` without a tree,
|
|
18
|
+
* so any line modification risks changing semantics.
|
|
19
|
+
*
|
|
20
|
+
* Pattern equivalence is line-literal: `node_modules/` and `node_modules`
|
|
21
|
+
* are treated as different entries. This is intentional — `git` treats
|
|
22
|
+
* them differently (the trailing slash restricts the match to
|
|
23
|
+
* directories), and the AC pins the trailing-slash form.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` when no changes are required (file already contains
|
|
26
|
+
* every required entry). The caller can use this to decide whether to
|
|
27
|
+
* include `.gitignore` in `filesWritten`.
|
|
28
|
+
*/
|
|
29
|
+
export function mergeGitignore(existing: string | undefined): string | null {
|
|
30
|
+
if (existing === undefined) {
|
|
31
|
+
return `${REQUIRED_GITIGNORE_ENTRIES.join('\n')}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const present = new Set(
|
|
35
|
+
existing
|
|
36
|
+
.split('\n')
|
|
37
|
+
.map((line) => line.trim())
|
|
38
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const missing = REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !present.has(entry));
|
|
42
|
+
if (missing.length === 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const separator = existing.length === 0 || existing.endsWith('\n') ? '' : '\n';
|
|
47
|
+
return `${existing}${separator}${missing.join('\n')}\n`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package.json `scripts` entries `init` adds idempotently (FR3.5).
|
|
3
|
+
* The script *name* mirrors the CLI subcommand path (`contract:emit` →
|
|
4
|
+
* `prisma-next contract emit`) so the script is greppable: a user
|
|
5
|
+
* encountering `npm run contract:emit` in CI logs can navigate
|
|
6
|
+
* straight to the equivalent CLI invocation.
|
|
7
|
+
*
|
|
8
|
+
* No watch-mode entry is included (Spec Decision 9) — file-watching is
|
|
9
|
+
* the build tool's job (Vite plugin, `tsc --watch`, etc.).
|
|
10
|
+
*/
|
|
11
|
+
export interface RequiredScript {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly command: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const REQUIRED_SCRIPTS: readonly RequiredScript[] = [
|
|
17
|
+
{ name: 'contract:emit', command: 'prisma-next contract emit' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export interface PackageScriptsMergeResult {
|
|
21
|
+
/**
|
|
22
|
+
* The new package.json content. `null` when no changes are required
|
|
23
|
+
* (every required script is already present with the correct
|
|
24
|
+
* command).
|
|
25
|
+
*/
|
|
26
|
+
readonly content: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Structured warnings raised when an existing script of the same
|
|
29
|
+
* name maps to a different command. Each warning names the script,
|
|
30
|
+
* the existing command, and the command we wanted to write — the
|
|
31
|
+
* user can decide whether to keep their override or update it.
|
|
32
|
+
*/
|
|
33
|
+
readonly warnings: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Idempotent `package.json#scripts` merge with collision detection
|
|
38
|
+
* (FR3.5 / FR9.3):
|
|
39
|
+
*
|
|
40
|
+
* - If a required script is **missing**, append it.
|
|
41
|
+
* - If a required script is **already present and identical**, leave
|
|
42
|
+
* the file alone (idempotency).
|
|
43
|
+
* - If a required script is **present but maps to a different command**,
|
|
44
|
+
* skip the write for that script and surface a structured warning.
|
|
45
|
+
* The user's override is sacred — `init` should never silently
|
|
46
|
+
* overwrite a custom build pipeline.
|
|
47
|
+
*
|
|
48
|
+
* Preserves the existing key order (so a user who has alphabetised
|
|
49
|
+
* their scripts does not see them reshuffled) and appends new entries
|
|
50
|
+
* at the end.
|
|
51
|
+
*
|
|
52
|
+
* The `package.json` is parsed and re-stringified through `JSON` —
|
|
53
|
+
* comments are not preserved (package.json does not support them per
|
|
54
|
+
* spec). Trailing newline matches the original input's trailing
|
|
55
|
+
* newline behaviour.
|
|
56
|
+
*/
|
|
57
|
+
export function mergePackageScripts(
|
|
58
|
+
existing: string,
|
|
59
|
+
required: readonly RequiredScript[] = REQUIRED_SCRIPTS,
|
|
60
|
+
): PackageScriptsMergeResult {
|
|
61
|
+
const parsed = JSON.parse(existing) as Record<string, unknown>;
|
|
62
|
+
const scripts: Record<string, string> =
|
|
63
|
+
typeof parsed['scripts'] === 'object' && parsed['scripts'] !== null
|
|
64
|
+
? { ...(parsed['scripts'] as Record<string, string>) }
|
|
65
|
+
: {};
|
|
66
|
+
|
|
67
|
+
const warnings: string[] = [];
|
|
68
|
+
let mutated = false;
|
|
69
|
+
|
|
70
|
+
for (const { name, command } of required) {
|
|
71
|
+
const existingValue = scripts[name];
|
|
72
|
+
if (existingValue === undefined) {
|
|
73
|
+
scripts[name] = command;
|
|
74
|
+
mutated = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (existingValue !== command) {
|
|
78
|
+
warnings.push(
|
|
79
|
+
`package.json already has a "${name}" script with a different command — keeping yours.\n existing: ${existingValue}\n expected: ${command}\nIf you want the default, remove your "${name}" script and re-run \`init\`.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!mutated) {
|
|
85
|
+
return { content: null, warnings };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
parsed['scripts'] = scripts;
|
|
89
|
+
const trailingNewline = existing.endsWith('\n') ? '\n' : '';
|
|
90
|
+
return { content: `${JSON.stringify(parsed, null, 2)}${trailingNewline}`, warnings };
|
|
91
|
+
}
|
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
addGlobalOptions,
|
|
4
|
+
setCommandDescriptions,
|
|
5
|
+
setCommandExamples,
|
|
6
|
+
} from '../../utils/command-helpers';
|
|
7
|
+
import { type CommonCommandOptions, parseGlobalFlags } from '../../utils/global-flags';
|
|
8
|
+
import {
|
|
9
|
+
INIT_EXIT_EMIT_FAILED,
|
|
10
|
+
INIT_EXIT_INSTALL_FAILED,
|
|
11
|
+
INIT_EXIT_INTERNAL_ERROR,
|
|
12
|
+
INIT_EXIT_OK,
|
|
13
|
+
INIT_EXIT_PRECONDITION,
|
|
14
|
+
INIT_EXIT_USER_ABORTED,
|
|
15
|
+
} from './exit-codes';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Commander.js parsed options for `init`. The init-specific options live
|
|
19
|
+
* alongside the inherited `CommonCommandOptions` global flags.
|
|
20
|
+
*
|
|
21
|
+
* `target` and `authoring` are typed as plain `string` here because
|
|
22
|
+
* Commander.js does not enforce enums at parse time — the validation /
|
|
23
|
+
* normalisation happens in `inputs.ts::resolveInitInputs`, which can
|
|
24
|
+
* raise a structured `errorInitInvalidFlagValue` with the full set of
|
|
25
|
+
* allowed values.
|
|
26
|
+
*/
|
|
27
|
+
interface InitCommandOptions extends CommonCommandOptions {
|
|
28
|
+
readonly target?: string;
|
|
29
|
+
readonly authoring?: string;
|
|
30
|
+
readonly schemaPath?: string;
|
|
31
|
+
readonly force?: boolean;
|
|
32
|
+
readonly writeEnv?: boolean;
|
|
33
|
+
readonly probeDb?: boolean;
|
|
34
|
+
readonly strictProbe?: boolean;
|
|
35
|
+
readonly install?: boolean;
|
|
36
|
+
}
|
|
3
37
|
|
|
4
38
|
export function createInitCommand(): Command {
|
|
5
39
|
const command = new Command('init');
|
|
@@ -7,15 +41,86 @@ export function createInitCommand(): Command {
|
|
|
7
41
|
command,
|
|
8
42
|
'Initialize a new Prisma Next project',
|
|
9
43
|
'Scaffolds config, schema, and runtime files, installs dependencies,\n' +
|
|
10
|
-
'and emits the contract. Gets you from zero to typed queries in one step
|
|
44
|
+
'and emits the contract. Gets you from zero to typed queries in one step.\n' +
|
|
45
|
+
'\n' +
|
|
46
|
+
'Run interactively for a guided experience, or supply --target / --authoring\n' +
|
|
47
|
+
'and --yes for a fully scriptable run (CI, AI coding agents, automation).\n' +
|
|
48
|
+
'\n' +
|
|
49
|
+
'Exit codes (see CLI Style Guide § Exit Codes):\n' +
|
|
50
|
+
` ${INIT_EXIT_OK} OK Init succeeded.\n` +
|
|
51
|
+
` ${INIT_EXIT_INTERNAL_ERROR} INTERNAL_ERROR Unexpected bug in prisma-next (please report).\n` +
|
|
52
|
+
` ${INIT_EXIT_PRECONDITION} PRECONDITION Bad flags / missing prerequisite (e.g. no package.json).\n` +
|
|
53
|
+
` ${INIT_EXIT_USER_ABORTED} USER_ABORTED User cancelled an interactive prompt.\n` +
|
|
54
|
+
` ${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).`,
|
|
11
56
|
);
|
|
12
|
-
setCommandExamples(command, [
|
|
13
|
-
|
|
57
|
+
setCommandExamples(command, [
|
|
58
|
+
'prisma-next init',
|
|
59
|
+
'prisma-next init --yes --target postgres --authoring psl',
|
|
60
|
+
'prisma-next init --yes --target mongodb --authoring typescript --json',
|
|
61
|
+
'prisma-next init --yes --force --target postgres --authoring psl # overwrite an existing scaffold',
|
|
62
|
+
'prisma-next init --no-install # skip pnpm/npm install + emit',
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
return addGlobalOptions(command)
|
|
66
|
+
.option('--target <db>', 'Database target: postgres or mongodb')
|
|
67
|
+
.option('--authoring <style>', 'Schema authoring style: psl or typescript')
|
|
68
|
+
.option(
|
|
69
|
+
'--schema-path <path>',
|
|
70
|
+
'Where to write the starter schema (default: prisma/contract.prisma)',
|
|
71
|
+
)
|
|
72
|
+
.option('--force', 'Overwrite an existing scaffold without prompting')
|
|
73
|
+
.option(
|
|
74
|
+
'--write-env',
|
|
75
|
+
'Write a .env file from .env.example (gitignored; default: only .env.example)',
|
|
76
|
+
)
|
|
77
|
+
.option(
|
|
78
|
+
'--probe-db',
|
|
79
|
+
'Connect to DATABASE_URL once and check the server version against the target minimum (opt-in; off by default)',
|
|
80
|
+
)
|
|
81
|
+
.option(
|
|
82
|
+
'--strict-probe',
|
|
83
|
+
'Treat a failed --probe-db as fatal (no-op without --probe-db; init is offline-by-default)',
|
|
84
|
+
)
|
|
14
85
|
.option('--no-install', 'Skip dependency installation and contract emission')
|
|
15
|
-
.action(async (options:
|
|
86
|
+
.action(async (options: InitCommandOptions) => {
|
|
16
87
|
const { runInit } = await import('./init');
|
|
17
|
-
|
|
88
|
+
const flags = parseGlobalFlags(options);
|
|
89
|
+
const canPrompt = deriveCanPrompt({
|
|
90
|
+
flagsInteractive: flags.interactive,
|
|
91
|
+
optionInteractive: options.interactive,
|
|
92
|
+
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
93
|
+
});
|
|
94
|
+
const exitCode = await runInit(process.cwd(), { options, flags, canPrompt });
|
|
95
|
+
process.exit(exitCode);
|
|
18
96
|
});
|
|
97
|
+
}
|
|
19
98
|
|
|
20
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Bridges the action handler's two TTY checks (stdout via `flags`, stdin
|
|
101
|
+
* via `process.stdin.isTTY`) into the `canPrompt` boolean `runInit`
|
|
102
|
+
* consumes.
|
|
103
|
+
*
|
|
104
|
+
* Per the [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity):
|
|
105
|
+
*
|
|
106
|
+
* - `flags.interactive` governs *decoration* (TerminalUI, intro/outro,
|
|
107
|
+
* spinners) and is derived from stdout-TTY by `parseGlobalFlags`,
|
|
108
|
+
* honouring `--interactive` / `--no-interactive`.
|
|
109
|
+
* - Prompting additionally requires a stdin TTY — closing stdin is a
|
|
110
|
+
* common signal in CI / agent environments even when stdout stays
|
|
111
|
+
* attached.
|
|
112
|
+
* - `--interactive` is the explicit override: when the user passes it,
|
|
113
|
+
* we honour it (e.g. testing flows where stdin is stubbed).
|
|
114
|
+
*
|
|
115
|
+
* Exported so callers and tests can derive the same value without
|
|
116
|
+
* touching `process` globals — F14 of the M1/M2 review.
|
|
117
|
+
*/
|
|
118
|
+
export function deriveCanPrompt(opts: {
|
|
119
|
+
readonly flagsInteractive: boolean | undefined;
|
|
120
|
+
readonly optionInteractive: boolean | undefined;
|
|
121
|
+
readonly stdinIsTTY: boolean;
|
|
122
|
+
}): boolean {
|
|
123
|
+
if (opts.optionInteractive === true) return true;
|
|
124
|
+
if (opts.flagsInteractive === false) return false;
|
|
125
|
+
return opts.stdinIsTTY;
|
|
21
126
|
}
|