@prisma-next/cli 0.14.0-dev.3 → 0.14.0-dev.30

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.
Files changed (134) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +29 -12
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-CJzuo5wX.mjs → client-Ri4Tnfhw.mjs} +7 -5
  5. package/dist/client-Ri4Tnfhw.mjs.map +1 -0
  6. package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-6cNJZ863.mjs} +3 -3
  7. package/dist/{command-helpers-DGMvGBeX.mjs.map → command-helpers-6cNJZ863.mjs.map} +1 -1
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.mjs +1 -1
  10. package/dist/commands/db-init.mjs +3 -3
  11. package/dist/commands/db-schema.mjs +3 -3
  12. package/dist/commands/db-sign.mjs +5 -5
  13. package/dist/commands/db-sign.mjs.map +1 -1
  14. package/dist/commands/db-update.mjs +4 -4
  15. package/dist/commands/db-verify.mjs +1 -1
  16. package/dist/commands/migrate.d.mts +1 -1
  17. package/dist/commands/migrate.mjs +6 -6
  18. package/dist/commands/migrate.mjs.map +1 -1
  19. package/dist/commands/migration-check.d.mts +1 -1
  20. package/dist/commands/migration-check.mjs +1 -1
  21. package/dist/commands/migration-graph.d.mts +1 -1
  22. package/dist/commands/migration-graph.mjs +4 -4
  23. package/dist/commands/migration-graph.mjs.map +1 -1
  24. package/dist/commands/migration-list.d.mts +1 -1
  25. package/dist/commands/migration-list.mjs +1 -1
  26. package/dist/commands/migration-log.d.mts +1 -1
  27. package/dist/commands/migration-log.mjs +1 -1
  28. package/dist/commands/migration-new.mjs +4 -4
  29. package/dist/commands/migration-new.mjs.map +1 -1
  30. package/dist/commands/migration-plan.mjs +1 -1
  31. package/dist/commands/migration-show.mjs +4 -4
  32. package/dist/commands/migration-show.mjs.map +1 -1
  33. package/dist/commands/migration-status.d.mts +1 -1
  34. package/dist/commands/migration-status.mjs +1 -1
  35. package/dist/commands/ref.d.mts +1 -1
  36. package/dist/commands/ref.mjs +3 -3
  37. package/dist/commands/ref.mjs.map +1 -1
  38. package/dist/commands/telemetry/index.mjs +1 -1
  39. package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-DdxWRjMc.mjs} +2 -2
  40. package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-DdxWRjMc.mjs.map} +1 -1
  41. package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-C4i_rUXA.mjs} +6 -6
  42. package/dist/contract-emit-C4i_rUXA.mjs.map +1 -0
  43. package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-CwnzLGfH.mjs} +6 -5
  44. package/dist/contract-emit-CwnzLGfH.mjs.map +1 -0
  45. package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-C5MVYYOB.mjs} +3 -3
  46. package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-C5MVYYOB.mjs.map} +1 -1
  47. package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-Bu2fjSWV.mjs} +2 -2
  48. package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs.map → contract-space-aggregate-loader-Bu2fjSWV.mjs.map} +1 -1
  49. package/dist/{db-verify-C24FKhb7.mjs → db-verify-CHyzYwsM.mjs} +8 -5
  50. package/dist/db-verify-CHyzYwsM.mjs.map +1 -0
  51. package/dist/exports/control-api.d.mts +1 -1
  52. package/dist/exports/control-api.mjs +2 -2
  53. package/dist/exports/index.d.mts +5 -1
  54. package/dist/exports/index.d.mts.map +1 -1
  55. package/dist/exports/index.mjs +4 -3
  56. package/dist/exports/index.mjs.map +1 -1
  57. package/dist/format-C3XdaDuA.mjs +102 -0
  58. package/dist/format-C3XdaDuA.mjs.map +1 -0
  59. package/dist/{framework-components-YVQHhPH7.mjs → framework-components-5RljWT7Q.mjs} +2 -2
  60. package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-5RljWT7Q.mjs.map} +1 -1
  61. package/dist/{init-0HwB-Vh8.mjs → init-DCBYcJXZ.mjs} +12 -8
  62. package/dist/init-DCBYcJXZ.mjs.map +1 -0
  63. package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-DraXO0SQ.mjs} +4 -4
  64. package/dist/inspect-live-schema-DraXO0SQ.mjs.map +1 -0
  65. package/dist/{migration-check-soB5uZEQ.mjs → migration-check-CTcJfCC4.mjs} +5 -5
  66. package/dist/migration-check-CTcJfCC4.mjs.map +1 -0
  67. package/dist/migration-cli.mjs +1 -1
  68. package/dist/migration-cli.mjs.map +1 -1
  69. package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-BZIOhPQO.mjs} +4 -4
  70. package/dist/migration-command-scaffold-BZIOhPQO.mjs.map +1 -0
  71. package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DVHVmMrf.mjs} +4 -4
  72. package/dist/migration-list-DVHVmMrf.mjs.map +1 -0
  73. package/dist/{migration-log-CG0qQAFm.mjs → migration-log-EkG_DZSn.mjs} +4 -4
  74. package/dist/migration-log-EkG_DZSn.mjs.map +1 -0
  75. package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-zK6RG6JY.mjs} +2 -2
  76. package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-zK6RG6JY.mjs.map} +1 -1
  77. package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-o2aqjnfd.mjs} +6 -6
  78. package/dist/migration-plan-o2aqjnfd.mjs.map +1 -0
  79. package/dist/{migration-status-CgWSoI_g.mjs → migration-status-Dlhn-_Cu.mjs} +6 -6
  80. package/dist/migration-status-Dlhn-_Cu.mjs.map +1 -0
  81. package/dist/{telemetry-BIM4beEO.mjs → telemetry-BckOSUWG.mjs} +2 -2
  82. package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-BckOSUWG.mjs.map} +1 -1
  83. package/dist/{types-C_tYiJYx.d.mts → types-qETCCNbe.d.mts} +2 -2
  84. package/dist/{types-C_tYiJYx.d.mts.map → types-qETCCNbe.d.mts.map} +1 -1
  85. package/dist/{verify-DcOYZ1tH.mjs → verify-CLw3YRbl.mjs} +7 -2
  86. package/dist/{verify-DcOYZ1tH.mjs.map → verify-CLw3YRbl.mjs.map} +1 -1
  87. package/package.json +21 -23
  88. package/src/cli.ts +7 -0
  89. package/src/commands/contract-emit.ts +1 -1
  90. package/src/commands/db-sign.ts +1 -1
  91. package/src/commands/db-verify.ts +1 -1
  92. package/src/commands/format.ts +75 -0
  93. package/src/commands/init/detect-package-manager.ts +8 -4
  94. package/src/commands/init/hygiene-package-scripts.ts +16 -3
  95. package/src/commands/inspect-live-schema.ts +1 -1
  96. package/src/commands/lsp.ts +32 -0
  97. package/src/commands/migrate.ts +1 -1
  98. package/src/commands/migration-check.ts +1 -1
  99. package/src/commands/migration-graph.ts +1 -1
  100. package/src/commands/migration-list.ts +1 -1
  101. package/src/commands/migration-log.ts +1 -1
  102. package/src/commands/migration-new.ts +1 -1
  103. package/src/commands/migration-plan.ts +1 -1
  104. package/src/commands/migration-show.ts +1 -1
  105. package/src/commands/migration-status.ts +1 -1
  106. package/src/commands/ref.ts +1 -1
  107. package/src/control-api/client.ts +4 -3
  108. package/src/control-api/operations/contract-emit.ts +7 -7
  109. package/src/control-api/operations/db-verify.ts +1 -0
  110. package/src/control-api/operations/format.ts +98 -0
  111. package/src/exports/index.ts +1 -0
  112. package/src/migration-cli.ts +1 -1
  113. package/src/utils/combine-schema-results.ts +3 -0
  114. package/src/utils/formatters/verify.ts +8 -0
  115. package/src/utils/migration-command-scaffold.ts +1 -1
  116. package/dist/client-CJzuo5wX.mjs.map +0 -1
  117. package/dist/config-loader-p9JMrekQ.mjs +0 -88
  118. package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
  119. package/dist/config-loader.d.mts +0 -16
  120. package/dist/config-loader.d.mts.map +0 -1
  121. package/dist/config-loader.mjs +0 -2
  122. package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
  123. package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
  124. package/dist/db-verify-C24FKhb7.mjs.map +0 -1
  125. package/dist/init-0HwB-Vh8.mjs.map +0 -1
  126. package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
  127. package/dist/migration-check-soB5uZEQ.mjs.map +0 -1
  128. package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
  129. package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
  130. package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
  131. package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
  132. package/dist/migration-status-CgWSoI_g.mjs.map +0 -1
  133. package/src/config-loader.ts +0 -92
  134. package/src/config-path-validation.ts +0 -74
@@ -6,6 +6,10 @@ export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun' | 'deno';
6
6
 
7
7
  const KNOWN: ReadonlySet<string> = new Set<PackageManager>(['pnpm', 'npm', 'yarn', 'bun', 'deno']);
8
8
 
9
+ function isPackageManager(name: string): name is PackageManager {
10
+ return KNOWN.has(name);
11
+ }
12
+
9
13
  /**
10
14
  * Resolves the package manager `init` should drive for `add` / `install`
11
15
  * commands. Tries, in order:
@@ -28,12 +32,12 @@ const KNOWN: ReadonlySet<string> = new Set<PackageManager>(['pnpm', 'npm', 'yarn
28
32
  */
29
33
  export async function detectPackageManager(cwd: string): Promise<PackageManager> {
30
34
  const detected = await detect({ cwd });
31
- if (detected && KNOWN.has(detected.name)) {
32
- return detected.name as PackageManager;
35
+ if (detected && isPackageManager(detected.name)) {
36
+ return detected.name;
33
37
  }
34
38
  const userAgent = getUserAgent();
35
- if (userAgent !== null && KNOWN.has(userAgent)) {
36
- return userAgent as PackageManager;
39
+ if (userAgent !== null && isPackageManager(userAgent)) {
40
+ return userAgent;
37
41
  }
38
42
  return 'npm';
39
43
  }
@@ -1,3 +1,5 @@
1
+ import { blindCast } from '@prisma-next/utils/casts';
2
+
1
3
  /**
2
4
  * The package.json `scripts` entries `init` adds idempotently (FR3.5).
3
5
  * The script *name* mirrors the CLI subcommand path (`contract:emit` →
@@ -58,10 +60,18 @@ export function mergePackageScripts(
58
60
  existing: string,
59
61
  required: readonly RequiredScript[] = REQUIRED_SCRIPTS,
60
62
  ): PackageScriptsMergeResult {
61
- const parsed = JSON.parse(existing) as Record<string, unknown>;
63
+ const parsed = blindCast<
64
+ Record<string, unknown>,
65
+ 'JSON.parse returns `unknown`; package.json is a JSON object so its top level is a string-keyed record'
66
+ >(JSON.parse(existing));
62
67
  const scripts: Record<string, string> =
63
68
  typeof parsed['scripts'] === 'object' && parsed['scripts'] !== null
64
- ? { ...(parsed['scripts'] as Record<string, string>) }
69
+ ? {
70
+ ...blindCast<
71
+ Record<string, string>,
72
+ 'guarded above: parsed.scripts is a non-null object; package.json `scripts` values are command strings'
73
+ >(parsed['scripts']),
74
+ }
65
75
  : {};
66
76
 
67
77
  const warnings: string[] = [];
@@ -131,7 +141,10 @@ export interface EsmModuleTypeResult {
131
141
  * ESM and we don't silently overwrite that.
132
142
  */
133
143
  export function ensureEsmModuleType(existing: string): EsmModuleTypeResult {
134
- const parsed = JSON.parse(existing) as Record<string, unknown>;
144
+ const parsed = blindCast<
145
+ Record<string, unknown>,
146
+ 'JSON.parse returns `unknown`; package.json is a JSON object so its top level is a string-keyed record'
147
+ >(JSON.parse(existing));
135
148
  const currentType = parsed['type'];
136
149
 
137
150
  if (currentType === 'module') {
@@ -1,9 +1,9 @@
1
+ import { loadConfig } from '@prisma-next/config-loader';
1
2
  import type { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';
2
3
  import type { CoreSchemaView } from '@prisma-next/framework-components/control';
3
4
  import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
4
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
6
  import { relative, resolve } from 'pathe';
6
- import { loadConfig } from '../config-loader';
7
7
  import { createControlClient } from '../control-api/client';
8
8
  import {
9
9
  CliStructuredError,
@@ -0,0 +1,32 @@
1
+ import { Command } from 'commander';
2
+ import {
3
+ addGlobalOptions,
4
+ setCommandDescriptions,
5
+ setCommandExamples,
6
+ } from '../utils/command-helpers';
7
+
8
+ export function createLspCommand(): Command {
9
+ const command = new Command('lsp');
10
+ setCommandDescriptions(
11
+ command,
12
+ 'Start the Prisma Next language server',
13
+ 'Launches a Language Server Protocol server that publishes PSL parse diagnostics\n' +
14
+ 'and handles whole-document PSL formatting for the schema inputs declared in\n' +
15
+ 'your config (contract.source.inputs). Formatting uses the Prisma Next PSL\n' +
16
+ 'formatter and the formatter block from the project config.\n' +
17
+ 'Communicates over stdio; intended to be spawned by an\n' +
18
+ 'editor, not run interactively. The server keeps running until the editor client\n' +
19
+ 'disconnects.',
20
+ );
21
+ setCommandExamples(command, ['prisma-next lsp --stdio']);
22
+ addGlobalOptions(command)
23
+ .option('--stdio', 'Communicate with the editor over stdio (the default and only transport)')
24
+ .action(async () => {
25
+ // Lazy import so `vscode-languageserver` stays off every other command's
26
+ // startup path — only `prisma-next lsp` pays its load cost.
27
+ const { startServer } = await import('@prisma-next/language-server');
28
+ startServer();
29
+ });
30
+
31
+ return command;
32
+ }
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { loadConfig } from '@prisma-next/config-loader';
2
3
  import type { Contract } from '@prisma-next/contract/types';
3
4
  import { createControlStack } from '@prisma-next/framework-components/control';
4
5
  import { type ContractSpaceMember, requireHeadRef } from '@prisma-next/migration-tools/aggregate';
@@ -11,7 +12,6 @@ import { readRefs } from '@prisma-next/migration-tools/refs';
11
12
  import { ifDefined } from '@prisma-next/utils/defined';
12
13
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
13
14
  import { Command } from 'commander';
14
- import { loadConfig } from '../config-loader';
15
15
  import { createControlClient } from '../control-api/client';
16
16
  import { planMemberPath } from '../control-api/operations/migrate';
17
17
  import type {
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
+ import { loadConfig } from '@prisma-next/config-loader';
3
4
  import { createControlStack } from '@prisma-next/framework-components/control';
4
5
  import type {
5
6
  ContractSpaceAggregate,
@@ -25,7 +26,6 @@ import { ifDefined } from '@prisma-next/utils/defined';
25
26
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
26
27
  import { Command } from 'commander';
27
28
  import { join, relative } from 'pathe';
28
- import { loadConfig } from '../config-loader';
29
29
  import {
30
30
  type CliStructuredError,
31
31
  errorAmbiguousMigrationRef,
@@ -1,9 +1,9 @@
1
+ import { loadConfig } from '@prisma-next/config-loader';
1
2
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
3
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
3
4
  import { ifDefined } from '@prisma-next/utils/defined';
4
5
  import { ok, type Result } from '@prisma-next/utils/result';
5
6
  import { Command } from 'commander';
6
- import { loadConfig } from '../config-loader';
7
7
  import type { CliStructuredError } from '../utils/cli-errors';
8
8
  import {
9
9
  addGlobalOptions,
@@ -1,3 +1,4 @@
1
+ import { loadConfig } from '@prisma-next/config-loader';
1
2
  import type {
2
3
  ContractSpaceAggregate,
3
4
  ContractSpaceMember,
@@ -13,7 +14,6 @@ import {
13
14
  import { ifDefined } from '@prisma-next/utils/defined';
14
15
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
15
16
  import { Command } from 'commander';
16
- import { loadConfig } from '../config-loader';
17
17
  import {
18
18
  type CliStructuredError,
19
19
  errorInvalidSpaceId,
@@ -1,9 +1,9 @@
1
+ import { loadConfig } from '@prisma-next/config-loader';
1
2
  import type { LedgerEntryRecord } from '@prisma-next/contract/types';
2
3
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
4
  import { ifDefined } from '@prisma-next/utils/defined';
4
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
6
  import { Command } from 'commander';
6
- import { loadConfig } from '../config-loader';
7
7
  import { createControlClient } from '../control-api/client';
8
8
  import {
9
9
  CliStructuredError,
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { readFile } from 'node:fs/promises';
12
+ import { loadConfig } from '@prisma-next/config-loader';
12
13
  import type { Contract } from '@prisma-next/contract/types';
13
14
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
14
15
  import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control';
@@ -25,7 +26,6 @@ import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
25
26
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
26
27
  import { Command } from 'commander';
27
28
  import { join, relative } from 'pathe';
28
- import { loadConfig } from '../config-loader';
29
29
  import {
30
30
  CliStructuredError,
31
31
  errorFileNotFound,
@@ -1,4 +1,5 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { loadConfig } from '@prisma-next/config-loader';
2
3
  import type { Contract } from '@prisma-next/contract/types';
3
4
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
4
5
  import {
@@ -21,7 +22,6 @@ import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
21
22
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
22
23
  import { Command } from 'commander';
23
24
  import { join, relative } from 'pathe';
24
- import { loadConfig } from '../config-loader';
25
25
  import {
26
26
  type CliErrorConflict,
27
27
  CliStructuredError,
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { loadConfig } from '@prisma-next/config-loader';
2
3
  import type { Contract } from '@prisma-next/contract/types';
3
4
  import {
4
5
  APP_SPACE_ID,
@@ -13,7 +14,6 @@ import { ifDefined } from '@prisma-next/utils/defined';
13
14
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
14
15
  import { Command } from 'commander';
15
16
  import { relative } from 'pathe';
16
- import { loadConfig } from '../config-loader';
17
17
  import { createControlClient } from '../control-api/client';
18
18
  import {
19
19
  type CliStructuredError,
@@ -1,3 +1,4 @@
1
+ import { loadConfig } from '@prisma-next/config-loader';
1
2
  import type { LedgerEntryRecord } from '@prisma-next/contract/types';
2
3
  import type {
3
4
  ContractMarkerRecordLike,
@@ -17,7 +18,6 @@ import { ifDefined } from '@prisma-next/utils/defined';
17
18
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
18
19
  import { dim, yellow } from 'colorette';
19
20
  import { Command } from 'commander';
20
- import { loadConfig } from '../config-loader';
21
21
  import { createControlClient } from '../control-api/client';
22
22
  import {
23
23
  CliStructuredError,
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { loadConfig } from '@prisma-next/config-loader';
2
3
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
3
4
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
4
5
  import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
@@ -14,7 +15,6 @@ import {
14
15
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
15
16
  import { Command } from 'commander';
16
17
  import { join } from 'pathe';
17
- import { loadConfig } from '../config-loader';
18
18
  import {
19
19
  CliStructuredError,
20
20
  errorFileNotFound,
@@ -696,17 +696,18 @@ class ControlClientImpl implements ControlClient {
696
696
  // payload — `enrichContract` only adds capability + extension
697
697
  // metadata onto whatever shape it receives. The structural
698
698
  // check happens immediately afterwards via
699
- // `familyInstance.deserializeContract(enrichedIR)`, which is
700
- // the seam-of-record and the only thing that may surface
699
+ // `familyInstance.deserializeContract`, which is the
700
+ // seam-of-record and the only thing that may surface
701
701
  // structural errors to the caller.
702
702
  const enrichedIR = enrichContract(
703
703
  contractRaw as unknown as Contract,
704
704
  this.frameworkComponents ?? [],
705
705
  );
706
+ const rawContractJson = this.options.target.contractSerializer.serializeContract(enrichedIR);
706
707
 
707
708
  let deserializedContract: Contract;
708
709
  try {
709
- deserializedContract = this.familyInstance.deserializeContract(enrichedIR);
710
+ deserializedContract = this.familyInstance.deserializeContract(rawContractJson);
710
711
  } catch (error) {
711
712
  onProgress?.({
712
713
  action: 'emit',
@@ -1,4 +1,5 @@
1
1
  import { mkdir } from 'node:fs/promises';
2
+ import { loadConfig } from '@prisma-next/config-loader';
2
3
  import type { Contract } from '@prisma-next/contract/types';
3
4
  import { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';
4
5
  import { createControlStack } from '@prisma-next/framework-components/control';
@@ -7,7 +8,6 @@ import { blindCast } from '@prisma-next/utils/casts';
7
8
  import { ifDefined } from '@prisma-next/utils/defined';
8
9
  import type { JsonObject } from '@prisma-next/utils/json';
9
10
  import { dirname, join } from 'pathe';
10
- import { loadConfig } from '../../config-loader';
11
11
  import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
12
12
  import { queueEmitByOutput } from '../../utils/emit-queue';
13
13
  import { toExtensionInputs } from '../../utils/extension-pack-inputs';
@@ -258,16 +258,16 @@ export async function executeContractEmit(
258
258
  // Blind cast: `validateProviderResult` upstream has already
259
259
  // pinned `validatedContract.value` to the provider's loose
260
260
  // `Contract` envelope, but the local `Contract` type at this
261
- // call site is the precise structural interface. Re-narrowing
262
- // the envelope into the precise type is exactly what the
263
- // `familyInstance.deserializeContract(enrichedIR)` call on the
264
- // next line does — the cast just defers the structural check
265
- // by one statement so `enrichContract` can decorate first.
261
+ // call site is the precise structural interface. The cast just
262
+ // defers the structural check by one statement so `enrichContract`
263
+ // can decorate first; the subsequent serialize→deserialize round-trip
264
+ // re-narrows the envelope into the precise type.
266
265
  const enrichedIR = enrichContract(
267
266
  validatedContract.value as unknown as Contract,
268
267
  frameworkComponents,
269
268
  );
270
- const deserializedContract = familyInstance.deserializeContract(enrichedIR);
269
+ const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);
270
+ const deserializedContract = familyInstance.deserializeContract(rawContractJson);
271
271
  // Each target's descriptor ships a `contractSerializer` SPI; the
272
272
  // framework canonicalizer threads its `serializeContract` so the
273
273
  // on-disk JSON envelope is constructed by target-owned code
@@ -272,6 +272,7 @@ function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSc
272
272
  target: { expected: contract.target },
273
273
  schema: {
274
274
  issues: [],
275
+ schemaDiffIssues: [],
275
276
  root: {
276
277
  status: 'pass',
277
278
  kind: 'skipped',
@@ -0,0 +1,98 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { EOL } from 'node:os';
3
+ import { loadConfig } from '@prisma-next/config-loader';
4
+ import { type FormatOptions, format, PslFormatError } from '@prisma-next/psl-parser/format';
5
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
6
+ import { CliStructuredError, errorRuntime, errorUnexpected } from '../../utils/cli-errors';
7
+
8
+ export interface FormatOperationOptions {
9
+ readonly configPath?: string;
10
+ readonly eol?: string;
11
+ }
12
+
13
+ export interface FormatOperationResult {
14
+ readonly formatted: boolean;
15
+ readonly path?: string;
16
+ }
17
+
18
+ export function resolveNewline(
19
+ formatterNewline: 'LF' | 'CRLF' | undefined,
20
+ eol: string,
21
+ ): 'LF' | 'CRLF' {
22
+ if (formatterNewline !== undefined) {
23
+ return formatterNewline;
24
+ }
25
+ return eol === '\r\n' ? 'CRLF' : 'LF';
26
+ }
27
+
28
+ export async function executeFormat(
29
+ options: FormatOperationOptions,
30
+ ): Promise<Result<FormatOperationResult, CliStructuredError>> {
31
+ const eol = options.eol ?? EOL;
32
+
33
+ let config: Awaited<ReturnType<typeof loadConfig>>;
34
+ try {
35
+ config = await loadConfig(options.configPath);
36
+ } catch (error) {
37
+ if (CliStructuredError.is(error)) {
38
+ return notOk(error);
39
+ }
40
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
41
+ }
42
+
43
+ const source = config.contract?.source;
44
+ if (source?.sourceFormat !== 'psl') {
45
+ return ok({ formatted: false });
46
+ }
47
+
48
+ const inputPath = source.inputs?.[0];
49
+ if (inputPath === undefined) {
50
+ return ok({ formatted: false });
51
+ }
52
+
53
+ let contents: string;
54
+ try {
55
+ contents = await readFile(inputPath, 'utf-8');
56
+ } catch (error) {
57
+ return notOk(
58
+ errorRuntime('Failed to read contract source file', {
59
+ why: error instanceof Error ? error.message : String(error),
60
+ fix: `Check that ${inputPath} exists and is readable.`,
61
+ }),
62
+ );
63
+ }
64
+
65
+ const formatOptions: FormatOptions = {
66
+ indent: config.formatter?.indent ?? 2,
67
+ newline: resolveNewline(config.formatter?.newline, eol),
68
+ };
69
+
70
+ let formatted: string;
71
+ try {
72
+ formatted = format(contents, formatOptions);
73
+ } catch (error) {
74
+ if (error instanceof PslFormatError) {
75
+ return notOk(
76
+ errorRuntime('Cannot format PSL with parse errors', {
77
+ why: error.message,
78
+ fix: 'Fix the parse errors in your schema and try again.',
79
+ meta: { diagnostics: error.diagnostics },
80
+ }),
81
+ );
82
+ }
83
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
84
+ }
85
+
86
+ try {
87
+ await writeFile(inputPath, formatted, 'utf-8');
88
+ } catch (error) {
89
+ return notOk(
90
+ errorRuntime('Failed to write formatted contract source file', {
91
+ why: error instanceof Error ? error.message : String(error),
92
+ fix: `Check that ${inputPath} is writable.`,
93
+ }),
94
+ );
95
+ }
96
+
97
+ return ok({ formatted: true, path: inputPath });
98
+ }
@@ -1,4 +1,5 @@
1
1
  // CLI-specific exports
2
2
  export { createContractEmitCommand } from '../commands/contract-emit';
3
+ export { createFormatCommand } from '../commands/format';
3
4
  export type { LoadTsContractOptions } from '../load-ts-contract';
4
5
  export { loadContractFromTs } from '../load-ts-contract';
@@ -45,6 +45,7 @@
45
45
  import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
46
46
  import type { Writable } from 'node:stream';
47
47
  import { fileURLToPath } from 'node:url';
48
+ import { loadConfig } from '@prisma-next/config-loader';
48
49
  import {
49
50
  CliStructuredError,
50
51
  errorMigrationCliInvalidConfigArg,
@@ -57,7 +58,6 @@ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
57
58
  import { buildMigrationArtifacts, type Migration } from '@prisma-next/migration-tools/migration';
58
59
  import { Cli, Command, Option, UsageError } from 'clipanion';
59
60
  import { dirname, join } from 'pathe';
60
- import { loadConfig } from './config-loader';
61
61
 
62
62
  /**
63
63
  * Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses
@@ -30,6 +30,7 @@ export function combineSchemaResults(
30
30
  let okAll = true;
31
31
  let firstFailure: VerifyDatabaseSchemaResult | undefined;
32
32
  let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];
33
+ let schemaDiffIssues: VerifyDatabaseSchemaResult['schema']['schemaDiffIssues'] = [];
33
34
  const counts = { pass: 0, warn: 0, fail: 0, totalNodes: 0 };
34
35
  const childRoots: Array<VerifyDatabaseSchemaResult['schema']['root']> = [];
35
36
  for (const [, result] of perSpace) {
@@ -38,6 +39,7 @@ export function combineSchemaResults(
38
39
  if (firstFailure === undefined) firstFailure = result;
39
40
  }
40
41
  issues = [...issues, ...result.schema.issues];
42
+ schemaDiffIssues = [...schemaDiffIssues, ...result.schema.schemaDiffIssues];
41
43
  counts.pass += result.schema.counts.pass;
42
44
  counts.warn += result.schema.counts.warn;
43
45
  counts.fail += result.schema.counts.fail;
@@ -65,6 +67,7 @@ export function combineSchemaResults(
65
67
  target: appResult.target,
66
68
  schema: {
67
69
  issues,
70
+ schemaDiffIssues,
68
71
  root: {
69
72
  status: okAll ? 'pass' : 'fail',
70
73
  kind: 'aggregate',
@@ -539,6 +539,14 @@ export function formatSchemaVerifyOutput(
539
539
  });
540
540
  lines.push(...treeLines);
541
541
 
542
+ if (result.schema.schemaDiffIssues.length > 0) {
543
+ lines.push('');
544
+ lines.push(formatRed('Schema drift:'));
545
+ for (const issue of result.schema.schemaDiffIssues) {
546
+ lines.push(` ${formatRed('✖')} ${issue.message}`);
547
+ }
548
+ }
549
+
542
550
  // Add counts and timings in verbose mode
543
551
  if (isVerbose(flags, 1)) {
544
552
  lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
@@ -1,9 +1,9 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { relative, resolve } from 'node:path';
3
+ import { loadConfig } from '@prisma-next/config-loader';
3
4
  import { hasMigrations } from '@prisma-next/framework-components/control';
4
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
6
  import type { Command } from 'commander';
6
- import { loadConfig } from '../config-loader';
7
7
  import { createControlClient } from '../control-api/client';
8
8
  import type { ControlClient } from '../control-api/types';
9
9
  import {