@prisma-next/cli 0.5.0-dev.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -25
- package/dist/cli-errors-B9OBbled.d.mts +3 -0
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-qVH-rEgd.mjs +1595 -0
- package/dist/client-qVH-rEgd.mjs.map +1 -0
- package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
- package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +16 -13
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +6 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +9 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +15 -13
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts +28 -13
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -151
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +34 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +33 -6
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +2 -348
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +8 -12
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +35 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +126 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/contract-emit-9DBda5Ou.mjs +150 -0
- package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
- package/dist/contract-emit-B77TsJqf.mjs +327 -0
- package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
- package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BK9YFGEG.mjs} +13 -22
- package/dist/contract-infer-BK9YFGEG.mjs.map +1 -0
- package/dist/db-verify-C0y1PCO2.mjs +404 -0
- package/dist/db-verify-C0y1PCO2.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +101 -586
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
- package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
- package/dist/global-flags-Icqpxk23.d.mts +12 -0
- package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
- package/dist/helpers-eqdN8tH6.mjs +25 -0
- package/dist/helpers-eqdN8tH6.mjs.map +1 -0
- package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
- package/dist/init-DETSgw3h.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-CWYxGKlb.mjs} +10 -11
- package/dist/inspect-live-schema-CWYxGKlb.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -12
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +309 -86
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B5dORFEv.mjs} +8 -9
- package/dist/migration-command-scaffold-B5dORFEv.mjs.map +1 -0
- package/dist/migration-plan-C6lVaHsO.mjs +554 -0
- package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CZ-D5k7k.mjs} +272 -65
- package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
- package/dist/migrations-D_UJnpuW.mjs +216 -0
- package/dist/migrations-D_UJnpuW.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
- package/dist/output-B16Kefzx.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
- package/dist/result-handler-rmPVKIP2.mjs +25 -0
- package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
- package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
- package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
- package/dist/types-D7x-IFLO.d.mts +858 -0
- package/dist/types-D7x-IFLO.d.mts.map +1 -0
- package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
- package/dist/verify-CiwNWM9N.mjs.map +1 -0
- package/package.json +28 -26
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +15 -3
- package/src/commands/db-update.ts +9 -4
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/init/templates/code-templates.ts +26 -18
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +114 -212
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +212 -72
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +60 -41
- package/src/commands/migration-status.ts +483 -64
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +85 -5
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/apply-aggregate.ts +290 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +397 -0
- package/src/control-api/operations/db-init.ts +51 -253
- package/src/control-api/operations/db-update.ts +66 -183
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/operations/migration-apply.ts +424 -131
- package/src/control-api/types.ts +280 -29
- package/src/exports/control-api.ts +15 -3
- package/src/load-ts-contract.ts +28 -26
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +69 -25
- package/src/utils/contract-space-aggregate-loader.ts +204 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/extension-pack-inputs.ts +170 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +197 -61
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs +0 -997
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit--feXyNd7.mjs +0 -4
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/init-C5220SY9.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs +0 -153
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/dist/verify-Bkycc-Tf.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
package/src/utils/cli-errors.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Re-export all domain error factories from @prisma-next/errors for convenience.
|
|
3
|
-
* CLI-specific errors (e.g., Commander
|
|
3
|
+
* CLI-specific errors (e.g., Commander argument validation in the main CLI, or
|
|
4
|
+
* clipanion parse errors in the migration-file CLI) can be added here if needed.
|
|
4
5
|
*/
|
|
5
6
|
export type { CliErrorConflict, CliErrorEnvelope } from '@prisma-next/errors/control';
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
import {
|
|
7
9
|
CliStructuredError,
|
|
8
10
|
errorConfigFileNotFound,
|
|
9
11
|
errorConfigValidation,
|
|
@@ -15,11 +17,33 @@ export {
|
|
|
15
17
|
errorFamilyReadMarkerSqlRequired,
|
|
16
18
|
errorFileNotFound,
|
|
17
19
|
errorMigrationCliInvalidConfigArg,
|
|
20
|
+
errorMigrationCliUnknownFlag,
|
|
18
21
|
errorMigrationPlanningFailed,
|
|
19
22
|
errorQueryRunnerFactoryRequired,
|
|
20
23
|
errorTargetMigrationNotSupported,
|
|
21
24
|
errorUnexpected,
|
|
22
25
|
} from '@prisma-next/errors/control';
|
|
26
|
+
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
27
|
+
import type { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
CliStructuredError,
|
|
31
|
+
errorConfigFileNotFound,
|
|
32
|
+
errorConfigValidation,
|
|
33
|
+
errorContractConfigMissing,
|
|
34
|
+
errorContractMissingExtensionPacks,
|
|
35
|
+
errorContractValidationFailed,
|
|
36
|
+
errorDatabaseConnectionRequired,
|
|
37
|
+
errorDriverRequired,
|
|
38
|
+
errorFamilyReadMarkerSqlRequired,
|
|
39
|
+
errorFileNotFound,
|
|
40
|
+
errorMigrationCliInvalidConfigArg,
|
|
41
|
+
errorMigrationCliUnknownFlag,
|
|
42
|
+
errorMigrationPlanningFailed,
|
|
43
|
+
errorQueryRunnerFactoryRequired,
|
|
44
|
+
errorTargetMigrationNotSupported,
|
|
45
|
+
errorUnexpected,
|
|
46
|
+
};
|
|
23
47
|
export {
|
|
24
48
|
ERROR_CODE_DESTRUCTIVE_CHANGES,
|
|
25
49
|
errorDestructiveChanges,
|
|
@@ -38,3 +62,26 @@ export {
|
|
|
38
62
|
errorUnfilledPlaceholder,
|
|
39
63
|
placeholder,
|
|
40
64
|
} from '@prisma-next/errors/migration';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Maps a `MigrationToolsError` raised by the migration-tools loader/graph
|
|
68
|
+
* surface (`readMigrationPackage`, `readMigrationsDir`, `readRefs`,
|
|
69
|
+
* `resolveRef`, `reconstructGraph`, ...) into a CLI `errorRuntime` envelope.
|
|
70
|
+
*
|
|
71
|
+
* The full `error.details` payload is forwarded into `meta` so machine
|
|
72
|
+
* consumers (`--json`) see structural fields like `dir`, `storedHash`,
|
|
73
|
+
* `computedHash` (for `MIGRATION.HASH_MISMATCH`) alongside the stable
|
|
74
|
+
* `code`. The user-visible `summary`/`why`/`fix` text is unchanged.
|
|
75
|
+
*
|
|
76
|
+
* Callers are expected to gate on `MigrationToolsError.is(error)` first
|
|
77
|
+
* (mirroring the original inline pattern); non-`MigrationToolsError`
|
|
78
|
+
* values are caller-classified (rethrow, wrap with command-specific
|
|
79
|
+
* `errorUnexpected`, etc.).
|
|
80
|
+
*/
|
|
81
|
+
export function mapMigrationToolsError(error: MigrationToolsError): CliStructuredError {
|
|
82
|
+
return errorRuntime(error.message, {
|
|
83
|
+
why: error.why,
|
|
84
|
+
fix: error.fix,
|
|
85
|
+
meta: { code: error.code, ...(error.details ?? {}) },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { VerifyDatabaseSchemaResult } from '@prisma-next/framework-components/control';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collapse the aggregate verifier's per-space schema results into a
|
|
5
|
+
* single {@link VerifyDatabaseSchemaResult} for the existing CLI
|
|
6
|
+
* display surface. Concatenates issues across members; sums counts;
|
|
7
|
+
* uses the app member's result as the structural envelope (storage
|
|
8
|
+
* hash, target).
|
|
9
|
+
*
|
|
10
|
+
* **Summary policy.** Preserve the per-family phrasing whenever the
|
|
11
|
+
* combined `ok` flag agrees with the app member's `ok` flag — this is
|
|
12
|
+
* the common case (single-family deployments, single-app deployments)
|
|
13
|
+
* and the family's "satisfies / does not satisfy contract" phrasing
|
|
14
|
+
* stays user-visible. When the app passes but an extension fails (or
|
|
15
|
+
* vice versa) the app's summary contradicts the envelope, so fall back
|
|
16
|
+
* to the first failing member's summary. This keeps family phrasing
|
|
17
|
+
* intact and the envelope internally consistent (`ok: false` ↔ failure
|
|
18
|
+
* summary).
|
|
19
|
+
*/
|
|
20
|
+
export function combineSchemaResults(
|
|
21
|
+
perSpace: ReadonlyMap<string, VerifyDatabaseSchemaResult>,
|
|
22
|
+
appSpaceId: string,
|
|
23
|
+
strict: boolean,
|
|
24
|
+
): VerifyDatabaseSchemaResult {
|
|
25
|
+
const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;
|
|
26
|
+
if (appResult === undefined) {
|
|
27
|
+
throw new Error('Aggregate verifier returned no schema results — this is a wiring bug.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let okAll = true;
|
|
31
|
+
let firstFailure: VerifyDatabaseSchemaResult | undefined;
|
|
32
|
+
let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];
|
|
33
|
+
const counts = { pass: 0, warn: 0, fail: 0, totalNodes: 0 };
|
|
34
|
+
const childRoots: Array<VerifyDatabaseSchemaResult['schema']['root']> = [];
|
|
35
|
+
for (const [, result] of perSpace) {
|
|
36
|
+
if (!result.ok) {
|
|
37
|
+
okAll = false;
|
|
38
|
+
if (firstFailure === undefined) firstFailure = result;
|
|
39
|
+
}
|
|
40
|
+
issues = [...issues, ...result.schema.issues];
|
|
41
|
+
counts.pass += result.schema.counts.pass;
|
|
42
|
+
counts.warn += result.schema.counts.warn;
|
|
43
|
+
counts.fail += result.schema.counts.fail;
|
|
44
|
+
counts.totalNodes += result.schema.counts.totalNodes;
|
|
45
|
+
childRoots.push(result.schema.root);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// When `okAll !== appResult.ok`, exactly one shape is reachable: app passes
|
|
49
|
+
// (`appResult.ok === true`) and at least one other member failed
|
|
50
|
+
// (`okAll === false`). In that shape the failure was assigned to
|
|
51
|
+
// `firstFailure` during iteration, so non-null assertion is safe. The mirror
|
|
52
|
+
// shape (app fails while every member passes) is impossible because
|
|
53
|
+
// `appResult` either *is* a member of `perSpace` or is the first iterator
|
|
54
|
+
// value; either way its `ok` flag participates in `okAll`.
|
|
55
|
+
const summary =
|
|
56
|
+
okAll === appResult.ok
|
|
57
|
+
? appResult.summary
|
|
58
|
+
: (firstFailure as VerifyDatabaseSchemaResult).summary;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
ok: okAll,
|
|
62
|
+
...(okAll ? {} : { code: appResult.code ?? 'PN-RUN-3010' }),
|
|
63
|
+
summary,
|
|
64
|
+
contract: appResult.contract,
|
|
65
|
+
target: appResult.target,
|
|
66
|
+
schema: {
|
|
67
|
+
issues,
|
|
68
|
+
root: {
|
|
69
|
+
status: okAll ? 'pass' : 'fail',
|
|
70
|
+
kind: 'aggregate',
|
|
71
|
+
name: 'aggregate',
|
|
72
|
+
contractPath: '',
|
|
73
|
+
code: 'AGGREGATE',
|
|
74
|
+
message: okAll ? 'Aggregate schema matches' : 'Aggregate schema mismatch',
|
|
75
|
+
expected: undefined,
|
|
76
|
+
actual: undefined,
|
|
77
|
+
children: childRoots,
|
|
78
|
+
},
|
|
79
|
+
counts,
|
|
80
|
+
},
|
|
81
|
+
meta: { strict },
|
|
82
|
+
timings: { total: 0 },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';
|
|
3
3
|
import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
4
|
+
import type { NoInvariantPathStructuralEdge } from '@prisma-next/migration-tools/errors';
|
|
5
|
+
import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
6
6
|
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
7
|
-
import type {
|
|
7
|
+
import type { PathDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
8
|
+
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
9
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
10
|
+
import { APP_SPACE_ID, spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
|
|
8
11
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
12
|
import type { Command } from 'commander';
|
|
10
13
|
import { relative, resolve } from 'pathe';
|
|
@@ -77,6 +80,16 @@ export function resolveContractPath(config: { contract?: { output?: string } }):
|
|
|
77
80
|
/**
|
|
78
81
|
* Resolves the migrations directory and config path from CLI options.
|
|
79
82
|
* Shared by migration-apply, migration-plan, and migration-status.
|
|
83
|
+
*
|
|
84
|
+
* - `migrationsDir` is the project's top-level `migrations/` directory
|
|
85
|
+
* (the root that the aggregate loader walks for every contract space).
|
|
86
|
+
* - `appMigrationsDir` is the app subspace directory under it
|
|
87
|
+
* (`<migrationsDir>/<APP_SPACE_ID>/`). Every per-app reader / writer
|
|
88
|
+
* (`migration new`, `migration plan`, `migration apply`,
|
|
89
|
+
* `migration status`, `migration show`, `migration ref`) operates on
|
|
90
|
+
* this directory. Extensions own their own `migrations/<spaceId>/`.
|
|
91
|
+
* - `refsDir` is the app's refs directory (`<appMigrationsDir>/refs/`).
|
|
92
|
+
* The framework does not maintain refs at the migrations root.
|
|
80
93
|
*/
|
|
81
94
|
export function resolveMigrationPaths(
|
|
82
95
|
configOption: string | undefined,
|
|
@@ -85,6 +98,8 @@ export function resolveMigrationPaths(
|
|
|
85
98
|
configPath: string;
|
|
86
99
|
migrationsDir: string;
|
|
87
100
|
migrationsRelative: string;
|
|
101
|
+
appMigrationsDir: string;
|
|
102
|
+
appMigrationsRelative: string;
|
|
88
103
|
refsDir: string;
|
|
89
104
|
} {
|
|
90
105
|
const configPath = configOption
|
|
@@ -95,8 +110,17 @@ export function resolveMigrationPaths(
|
|
|
95
110
|
config.migrations?.dir ?? 'migrations',
|
|
96
111
|
);
|
|
97
112
|
const migrationsRelative = relative(process.cwd(), migrationsDir);
|
|
98
|
-
const
|
|
99
|
-
|
|
113
|
+
const appMigrationsDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID);
|
|
114
|
+
const appMigrationsRelative = relative(process.cwd(), appMigrationsDir);
|
|
115
|
+
const refsDir = resolve(appMigrationsDir, 'refs');
|
|
116
|
+
return {
|
|
117
|
+
configPath,
|
|
118
|
+
migrationsDir,
|
|
119
|
+
migrationsRelative,
|
|
120
|
+
appMigrationsDir,
|
|
121
|
+
appMigrationsRelative,
|
|
122
|
+
refsDir,
|
|
123
|
+
};
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/**
|
|
@@ -109,14 +133,45 @@ export interface PathDecisionResult {
|
|
|
109
133
|
readonly alternativeCount: number;
|
|
110
134
|
readonly tieBreakReasons: readonly string[];
|
|
111
135
|
readonly refName?: string;
|
|
136
|
+
readonly requiredInvariants: readonly string[];
|
|
137
|
+
readonly satisfiedInvariants: readonly string[];
|
|
112
138
|
readonly selectedPath: readonly {
|
|
113
139
|
readonly dirName: string;
|
|
114
|
-
readonly
|
|
140
|
+
readonly migrationHash: string;
|
|
115
141
|
readonly from: string;
|
|
116
142
|
readonly to: string;
|
|
143
|
+
readonly invariants: readonly string[];
|
|
117
144
|
}[];
|
|
118
145
|
}
|
|
119
146
|
|
|
147
|
+
export function collectDeclaredInvariants(graph: MigrationGraph): ReadonlySet<string> {
|
|
148
|
+
const declared = new Set<string>();
|
|
149
|
+
for (const edges of graph.forwardChain.values()) {
|
|
150
|
+
for (const edge of edges) {
|
|
151
|
+
for (const inv of edge.invariants) {
|
|
152
|
+
declared.add(inv);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return declared;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Maps a `MigrationEdge` to the structural-edge shape used in the
|
|
161
|
+
* `MIGRATION.NO_INVARIANT_PATH` error envelope. Shared between
|
|
162
|
+
* `migration apply` and `migration status` so both commands surface
|
|
163
|
+
* the same JSON wire shape when an invariant-aware route is unsatisfiable.
|
|
164
|
+
*/
|
|
165
|
+
export function toStructuralEdge(edge: MigrationEdge): NoInvariantPathStructuralEdge {
|
|
166
|
+
return {
|
|
167
|
+
dirName: edge.dirName,
|
|
168
|
+
migrationHash: edge.migrationHash,
|
|
169
|
+
from: edge.from,
|
|
170
|
+
to: edge.to,
|
|
171
|
+
invariants: edge.invariants,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
120
175
|
/**
|
|
121
176
|
* Maps a PathDecision to the slim CLI output representation.
|
|
122
177
|
*/
|
|
@@ -126,12 +181,15 @@ export function toPathDecisionResult(decision: PathDecision): PathDecisionResult
|
|
|
126
181
|
toHash: decision.toHash,
|
|
127
182
|
alternativeCount: decision.alternativeCount,
|
|
128
183
|
tieBreakReasons: decision.tieBreakReasons,
|
|
184
|
+
requiredInvariants: decision.requiredInvariants ?? [],
|
|
185
|
+
satisfiedInvariants: decision.satisfiedInvariants ?? [],
|
|
129
186
|
...ifDefined('refName', decision.refName),
|
|
130
187
|
selectedPath: decision.selectedPath.map((entry) => ({
|
|
131
188
|
dirName: entry.dirName,
|
|
132
|
-
|
|
189
|
+
migrationHash: entry.migrationHash,
|
|
133
190
|
from: entry.from,
|
|
134
191
|
to: entry.to,
|
|
192
|
+
invariants: entry.invariants,
|
|
135
193
|
})),
|
|
136
194
|
};
|
|
137
195
|
}
|
|
@@ -146,13 +204,13 @@ export function getTargetMigrations(target: ControlTargetDescriptor<string, stri
|
|
|
146
204
|
|
|
147
205
|
/**
|
|
148
206
|
* Reads the migrations directory and builds the migration graph from all
|
|
149
|
-
*
|
|
207
|
+
* packages. Throws on I/O or graph errors — callers handle error mapping.
|
|
150
208
|
*
|
|
151
|
-
* Every on-disk
|
|
209
|
+
* Every on-disk package is content-addressed (`migrationHash` is always a
|
|
152
210
|
* string); there is no draft state to filter out.
|
|
153
211
|
*/
|
|
154
|
-
export async function
|
|
155
|
-
bundles: readonly
|
|
212
|
+
export async function loadMigrationPackages(migrationsDir: string): Promise<{
|
|
213
|
+
bundles: readonly OnDiskMigrationPackage[];
|
|
156
214
|
graph: MigrationGraph;
|
|
157
215
|
}> {
|
|
158
216
|
const bundles = await readMigrationsDir(migrationsDir);
|
|
@@ -160,20 +218,6 @@ export async function loadMigrationBundles(migrationsDir: string): Promise<{
|
|
|
160
218
|
return { bundles, graph };
|
|
161
219
|
}
|
|
162
220
|
|
|
163
|
-
export interface MigrationBundleSet {
|
|
164
|
-
readonly bundles: readonly MigrationBundle[];
|
|
165
|
-
readonly graph: MigrationGraph;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Alias of `loadMigrationBundles` retained for naming-clarity in commands
|
|
170
|
-
* that previously needed both attested and draft splits. With the
|
|
171
|
-
* collapse of the draft state, both helpers do the same thing.
|
|
172
|
-
*/
|
|
173
|
-
export async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {
|
|
174
|
-
return loadMigrationBundles(migrationsDir);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
221
|
/**
|
|
178
222
|
* The subset of the emitted contract.json that the framework layer can
|
|
179
223
|
* safely type. The emitter adds these fields on top of the family-specific
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { ControlExtensionDescriptor } from '@prisma-next/framework-components/control';
|
|
3
|
+
import type {
|
|
4
|
+
ContractSpaceAggregate,
|
|
5
|
+
LoadAggregateError,
|
|
6
|
+
LoadAggregateInput,
|
|
7
|
+
LoadAggregateOutput,
|
|
8
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
9
|
+
import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
|
|
10
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
11
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
12
|
+
import { CliStructuredError } from './cli-errors';
|
|
13
|
+
import { toDeclaredExtensions, toExtensionInputs } from './extension-pack-inputs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render a {@link LoadAggregateError} into a CLI structured-error
|
|
17
|
+
* envelope. Preserves error codes `5001` (layout) and `5002` (marker /
|
|
18
|
+
* drift / disjointness / etc.) so existing integration tests and
|
|
19
|
+
* downstream tooling continue to assert on the same `meta.violations[]`
|
|
20
|
+
* shape they did under the old precheck/marker-check helpers.
|
|
21
|
+
*/
|
|
22
|
+
export function mapLoadAggregateError(error: LoadAggregateError): CliStructuredError {
|
|
23
|
+
if (error.kind === 'layoutViolation') {
|
|
24
|
+
const lines = error.violations.map((v) => `- [${v.kind}] ${v.spaceId}`);
|
|
25
|
+
const summary =
|
|
26
|
+
error.violations.length === 1
|
|
27
|
+
? 'Contract-space layout violation detected'
|
|
28
|
+
: `Contract-space layout violations detected (${error.violations.length})`;
|
|
29
|
+
return new CliStructuredError('5001', summary, {
|
|
30
|
+
domain: 'MIG',
|
|
31
|
+
why: `The on-disk \`migrations/\` directory and your \`extensionPacks\` declaration are not in agreement.\n${lines.join('\n')}`,
|
|
32
|
+
fix: 'Run `prisma-next migrate` to materialise on-disk artefacts for declared extensions, or remove the orphan directory.',
|
|
33
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
34
|
+
meta: {
|
|
35
|
+
violations: error.violations.map((v) => ({
|
|
36
|
+
kind: v.kind,
|
|
37
|
+
spaceId: v.spaceId,
|
|
38
|
+
})),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (error.kind === 'driftViolation') {
|
|
43
|
+
return new CliStructuredError('5002', `Contract-space drift detected for "${error.spaceId}"`, {
|
|
44
|
+
domain: 'MIG',
|
|
45
|
+
why: `The on-disk contract for space "${error.spaceId}" (hash ${error.priorHeadHash}) does not match the live extension descriptor (hash ${error.liveHash}).`,
|
|
46
|
+
fix: 'Run `prisma-next migrate` to refresh the on-disk artefacts to match the live descriptor.',
|
|
47
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
48
|
+
meta: {
|
|
49
|
+
violations: [
|
|
50
|
+
{
|
|
51
|
+
kind: 'drift',
|
|
52
|
+
spaceId: error.spaceId,
|
|
53
|
+
priorHeadHash: error.priorHeadHash,
|
|
54
|
+
liveHash: error.liveHash,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (error.kind === 'disjointnessViolation') {
|
|
61
|
+
return new CliStructuredError(
|
|
62
|
+
'5002',
|
|
63
|
+
`Contract-space disjointness violation: storage element "${error.element}" claimed by multiple spaces`,
|
|
64
|
+
{
|
|
65
|
+
domain: 'MIG',
|
|
66
|
+
why: `Spaces ${error.claimedBy.map((s) => `"${s}"`).join(', ')} all claim the storage element "${error.element}". Each storage element must be owned by exactly one contract space.`,
|
|
67
|
+
fix: 'Update the conflicting contracts so each storage element is claimed by exactly one space.',
|
|
68
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
69
|
+
meta: {
|
|
70
|
+
violations: [
|
|
71
|
+
{
|
|
72
|
+
kind: 'disjointness',
|
|
73
|
+
spaceId: error.claimedBy.join(','),
|
|
74
|
+
element: error.element,
|
|
75
|
+
claimedBy: error.claimedBy,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (error.kind === 'integrityFailure') {
|
|
83
|
+
return new CliStructuredError(
|
|
84
|
+
'5002',
|
|
85
|
+
`Contract-space integrity failure for "${error.spaceId}"`,
|
|
86
|
+
{
|
|
87
|
+
domain: 'MIG',
|
|
88
|
+
why: error.detail,
|
|
89
|
+
fix: 'Run `prisma-next migrate` to refresh on-disk artefacts, or restore the on-disk `migrations/` directory from version control.',
|
|
90
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
91
|
+
meta: {
|
|
92
|
+
violations: [{ kind: 'integrity', spaceId: error.spaceId, detail: error.detail }],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (error.kind === 'validationFailure') {
|
|
98
|
+
return new CliStructuredError(
|
|
99
|
+
'5002',
|
|
100
|
+
`Contract-space contract validation failed for "${error.spaceId}"`,
|
|
101
|
+
{
|
|
102
|
+
domain: 'MIG',
|
|
103
|
+
why: error.detail,
|
|
104
|
+
fix: 'Run `prisma-next migrate` to refresh on-disk artefacts, or fix the extension descriptor producing the invalid contract.',
|
|
105
|
+
meta: {
|
|
106
|
+
violations: [{ kind: 'validation', spaceId: error.spaceId, detail: error.detail }],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// targetMismatch
|
|
112
|
+
return new CliStructuredError('5002', `Contract-space target mismatch for "${error.spaceId}"`, {
|
|
113
|
+
domain: 'MIG',
|
|
114
|
+
why: `Space "${error.spaceId}" targets "${error.actual}" but the project's adapter targets "${error.expected}".`,
|
|
115
|
+
fix: 'Update the extension descriptor to target the configured database, or change the project adapter.',
|
|
116
|
+
meta: {
|
|
117
|
+
violations: [
|
|
118
|
+
{
|
|
119
|
+
kind: 'targetMismatch',
|
|
120
|
+
spaceId: error.spaceId,
|
|
121
|
+
expected: error.expected,
|
|
122
|
+
actual: error.actual,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inputs needed to compose the aggregate loader at the CLI surface.
|
|
131
|
+
*
|
|
132
|
+
* Keeps the loader framework-neutral (no `Config` import) by accepting
|
|
133
|
+
* already-resolved structural inputs: validated app contract, target
|
|
134
|
+
* id, migrations root directory, and the set of extension descriptors.
|
|
135
|
+
*/
|
|
136
|
+
export interface BuildAggregateInputs<TFamilyId extends string, TTargetId extends string> {
|
|
137
|
+
readonly targetId: TTargetId;
|
|
138
|
+
readonly migrationsDir: string;
|
|
139
|
+
readonly appContract: Contract;
|
|
140
|
+
readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
141
|
+
readonly validateContract: (contractJson: unknown) => Contract;
|
|
142
|
+
/**
|
|
143
|
+
* App-space migration packages to hydrate the app member's
|
|
144
|
+
* migration graph with. Defaults to `[]` (matches the `db init` /
|
|
145
|
+
* `db update` daily-driver behaviour, where the app's authored
|
|
146
|
+
* `migrations/` graph is not walked — the planner uses the synth
|
|
147
|
+
* strategy for the app member instead).
|
|
148
|
+
*
|
|
149
|
+
* `migration apply` callers thread the user's authored app-space
|
|
150
|
+
* packages (loaded via `loadMigrationPackages(appMigrationsDir)`)
|
|
151
|
+
* through here so the graph-walk strategy can plot a path through
|
|
152
|
+
* them — the prod-time replay path explicitly forbids synth.
|
|
153
|
+
*/
|
|
154
|
+
readonly appMigrationPackages?: ReadonlyArray<OnDiskMigrationPackage>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Run the aggregate loader at the CLI surface, mapping any
|
|
159
|
+
* {@link LoadAggregateError} into a {@link CliStructuredError} envelope.
|
|
160
|
+
*
|
|
161
|
+
* App-side migration packages flow through `inputs.appMigrationPackages`
|
|
162
|
+
* (defaulting to `[]`). `db init` / `db update` leave it empty: the
|
|
163
|
+
* planner's `synth` strategy is used for the app member (driven by
|
|
164
|
+
* `callerPolicy.ignoreGraphFor`), so the app's authored `migrations/`
|
|
165
|
+
* graph does not need to be walked. `migration apply` threads the
|
|
166
|
+
* already-loaded app-space packages through so the graph-walk strategy
|
|
167
|
+
* can plot a path through them — replay forbids synth.
|
|
168
|
+
*
|
|
169
|
+
* @see specs/contract-space-aggregate-spec.md § Loader.
|
|
170
|
+
*/
|
|
171
|
+
export async function buildContractSpaceAggregate<
|
|
172
|
+
TFamilyId extends string,
|
|
173
|
+
TTargetId extends string,
|
|
174
|
+
>(
|
|
175
|
+
inputs: BuildAggregateInputs<TFamilyId, TTargetId>,
|
|
176
|
+
): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {
|
|
177
|
+
const { entries, hashByContractJson } = toDeclaredExtensions(
|
|
178
|
+
toExtensionInputs(inputs.extensionPacks),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const loadInput: LoadAggregateInput = {
|
|
182
|
+
targetId: inputs.targetId,
|
|
183
|
+
migrationsDir: inputs.migrationsDir,
|
|
184
|
+
appContract: inputs.appContract,
|
|
185
|
+
declaredExtensions: entries,
|
|
186
|
+
validateContract: inputs.validateContract,
|
|
187
|
+
hashContract: (contractJson: unknown) => {
|
|
188
|
+
const precomputed = hashByContractJson.get(contractJson);
|
|
189
|
+
if (precomputed === undefined) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
'CLI aggregate loader: encountered an extension contract without a pre-computed descriptor hash. This is a wiring bug.',
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return precomputed;
|
|
195
|
+
},
|
|
196
|
+
appMigrationPackages: inputs.appMigrationPackages ?? [],
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const result: LoadAggregateOutput = await loadContractSpaceAggregate(loadInput);
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
return notOk(mapLoadAggregateError(result.failure));
|
|
202
|
+
}
|
|
203
|
+
return ok(result.value.aggregate);
|
|
204
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { materialiseExtensionMigrationPackageIfMissing } from '@prisma-next/migration-tools/io';
|
|
2
|
+
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
3
|
+
import type { MigrationOps } from '@prisma-next/migration-tools/package';
|
|
4
|
+
import {
|
|
5
|
+
planAllSpaces,
|
|
6
|
+
type SpacePlanOutput,
|
|
7
|
+
spaceMigrationDirectory,
|
|
8
|
+
} from '@prisma-next/migration-tools/spaces';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* In-memory authored migration package shipped by an extension descriptor.
|
|
12
|
+
* Mirrors `MigrationPackage` from `@prisma-next/migration-tools/io`
|
|
13
|
+
* (the on-disk shape minus `dirPath`); redeclared structurally here so
|
|
14
|
+
* the CLI helper does not couple to the SQL family's `ExtensionMigrationPackage`
|
|
15
|
+
* type — any family that ships pre-built migration packages can pass them
|
|
16
|
+
* through unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export interface DescriptorMigrationPackage {
|
|
19
|
+
readonly dirName: string;
|
|
20
|
+
readonly metadata: MigrationMetadata;
|
|
21
|
+
readonly ops: MigrationOps;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Minimal descriptor view consumed by the migration-materialisation pass.
|
|
26
|
+
* Mirrors {@link import('./contract-space-migrate-pass').MigrateExtensionInput}
|
|
27
|
+
* but adds the `migrations` field — the canonical set of pre-built
|
|
28
|
+
* migration packages the extension ships.
|
|
29
|
+
*/
|
|
30
|
+
export interface ExtensionMigrationsExtensionInput {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly contractSpace?: {
|
|
33
|
+
readonly contractJson: unknown;
|
|
34
|
+
readonly migrations: readonly DescriptorMigrationPackage[];
|
|
35
|
+
readonly headRef: { readonly hash: string; readonly invariants: readonly string[] };
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ContractSpaceExtensionMigrationsPassInputs {
|
|
40
|
+
readonly migrationsDir: string;
|
|
41
|
+
readonly extensionPacks: ReadonlyArray<ExtensionMigrationsExtensionInput>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ContractSpaceExtensionMigrationsPassResult {
|
|
45
|
+
readonly emitted: readonly { readonly spaceId: string; readonly dirName: string }[];
|
|
46
|
+
readonly skipped: readonly { readonly spaceId: string; readonly dirName: string }[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Materialise an extension's pre-built migration packages onto disk
|
|
51
|
+
* under `migrations/<spaceId>/<dirName>/` for every package that does
|
|
52
|
+
* not yet exist there.
|
|
53
|
+
*
|
|
54
|
+
* Helper-location pattern — the per-space "planner" for extension
|
|
55
|
+
* spaces is a no-op that just returns the descriptor's `migrations`
|
|
56
|
+
* verbatim; the value `planAllSpaces` brings to this consumer site is
|
|
57
|
+
* **deterministic ordering** (alphabetical by spaceId) and
|
|
58
|
+
* **duplicate-spaceId detection**. The actual write is performed via
|
|
59
|
+
* `materialiseMigrationPackage` per package.
|
|
60
|
+
*
|
|
61
|
+
* Idempotent: an existing `migrations/<spaceId>/<dirName>/` is left
|
|
62
|
+
* untouched and reported in `result.skipped` — the helper never
|
|
63
|
+
* overwrites authored migration content, ensuring re-running
|
|
64
|
+
* `migrate` does not corrupt or churn extension migration packages.
|
|
65
|
+
*
|
|
66
|
+
* Per-space artefacts (`contract.json`, `contract.d.ts`,
|
|
67
|
+
* `refs/head.json`) are emitted by
|
|
68
|
+
* {@link import('./contract-space-migrate-pass').runContractSpaceMigratePass}
|
|
69
|
+
* separately — they cover the head-pointer side of the ledger. This
|
|
70
|
+
* helper covers the migration-graph side.
|
|
71
|
+
*/
|
|
72
|
+
export async function runContractSpaceExtensionMigrationsPass(
|
|
73
|
+
inputs: ContractSpaceExtensionMigrationsPassInputs,
|
|
74
|
+
): Promise<ContractSpaceExtensionMigrationsPassResult> {
|
|
75
|
+
const planInputs = inputs.extensionPacks
|
|
76
|
+
.filter(
|
|
77
|
+
(
|
|
78
|
+
pack,
|
|
79
|
+
): pack is ExtensionMigrationsExtensionInput & {
|
|
80
|
+
contractSpace: NonNullable<ExtensionMigrationsExtensionInput['contractSpace']>;
|
|
81
|
+
} => pack.contractSpace !== undefined,
|
|
82
|
+
)
|
|
83
|
+
.map((pack) => ({
|
|
84
|
+
spaceId: pack.id,
|
|
85
|
+
priorContract: null,
|
|
86
|
+
newContract: pack.contractSpace.contractJson,
|
|
87
|
+
__migrations: pack.contractSpace.migrations,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Threading the descriptor's pre-built migrations into the
|
|
91
|
+
// `planAllSpaces` callback by piggybacking on the input shape.
|
|
92
|
+
// The framework helper is generic over the per-space planner output;
|
|
93
|
+
// here the "planner" is a no-op that returns the descriptor's
|
|
94
|
+
// `migrations` array. The benefit of routing through `planAllSpaces`
|
|
95
|
+
// is duplicate-spaceId detection + alphabetical ordering — failures
|
|
96
|
+
// there throw `MIGRATION.DUPLICATE_SPACE_ID` before any write.
|
|
97
|
+
const planned: readonly SpacePlanOutput<DescriptorMigrationPackage>[] = planAllSpaces(
|
|
98
|
+
planInputs,
|
|
99
|
+
(input) =>
|
|
100
|
+
(input as typeof input & { readonly __migrations: readonly DescriptorMigrationPackage[] })
|
|
101
|
+
.__migrations,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const emitted: { spaceId: string; dirName: string }[] = [];
|
|
105
|
+
const skipped: { spaceId: string; dirName: string }[] = [];
|
|
106
|
+
|
|
107
|
+
for (const space of planned) {
|
|
108
|
+
const spaceDir = spaceMigrationDirectory(inputs.migrationsDir, space.spaceId);
|
|
109
|
+
for (const pkg of space.migrationPackages) {
|
|
110
|
+
const { written } = await materialiseExtensionMigrationPackageIfMissing(spaceDir, pkg);
|
|
111
|
+
if (written) {
|
|
112
|
+
emitted.push({ spaceId: space.spaceId, dirName: pkg.dirName });
|
|
113
|
+
} else {
|
|
114
|
+
skipped.push({ spaceId: space.spaceId, dirName: pkg.dirName });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { emitted, skipped };
|
|
120
|
+
}
|