🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@prisma-next/cli

Package Overview
Dependencies
Maintainers
4
Versions
1011
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@prisma-next/cli - npm Package Compare versions

Comparing version
0.16.0-dev.11
to
0.16.0-dev.12
+1627
dist/client-DeJvLD2h.mjs
import { F as CliStructuredError } from "./command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-VMQJbAwl.mjs";
import { t as enrichContract } from "./contract-enrichment-gn9sWbPw.mjs";
import { t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { emit } from "@prisma-next/emitter";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { APP_SPACE_ID, createControlStack, hasMigrations, hasOperationPreview, hasPslContractInfer, hasSchemaSubjectClassifier, hasSchemaView } from "@prisma-next/framework-components/control";
import { blindCast, castAs } from "@prisma-next/utils/casts";
import { allStorageElementsExternal, buildFabricatedMigrationEdge, collectAggregateNamespaces, planMigration, requireHeadRef, resolveRecordedPath, verifyMigration } from "@prisma-next/migration-tools/aggregate";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { errorNoInvariantPath } from "@prisma-next/migration-tools/errors";
import { findPathWithDecision } from "@prisma-next/migration-tools/migration-graph";
//#region src/control-api/errors.ts
var ContractValidationError = class extends Error {
cause;
constructor(message, cause) {
super(message);
this.name = "ContractValidationError";
this.cause = cause;
}
};
//#endregion
//#region src/control-api/operations/migration-helpers.ts
/**
* Strips operation objects to their public shape (id, label, operationClass).
* Used at the API boundary to avoid leaking internal fields (precheck, execute, postcheck, etc.).
*/
function stripOperations(operations) {
return operations.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
}));
}
//#endregion
//#region src/control-api/operations/run-migration.ts
/**
* Span id emitted via `onProgress` for the run phase. Stable
* identifier consumed by the structured-output renderer and by tests.
*/
const RUN_SPAN_ID = "apply";
/**
* Runner-driving tail shared by every run caller — `db init`,
* `db update`, and `migrate`. Consumes already-resolved per-space
* plans (the planner-vs-replay distinction is owned by the caller) and
* dispatches them to the runner in canonical order.
*
* Marker advancement is part of the runner's per-space transaction
* (the SQL family runner writes the marker as the last step of each
* space's transaction), so this primitive does not advance markers
* separately — by the time `execute` returns ok, every
* space's marker has been advanced to its plan's destination.
*
* Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
* so callers don't have to duplicate it; the `action` field on each
* progress event is taken from the caller's `action` argument.
*/
async function runMigration(inputs) {
const { aggregate, perSpacePlans, applyOrder, driver, familyInstance, migrations, frameworkComponents, policy, action, onProgress } = inputs;
const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
const runner = migrations.createRunner(familyInstance);
onProgress?.({
action,
kind: "spanStart",
spanId: RUN_SPAN_ID,
label: progressLabelForAction(action)
});
const perSpaceOptions = orderedResolutions.map((r) => ({
space: r.spaceId,
plan: r.entry.plan,
driver,
destinationContract: r.entry.destinationContract,
policy,
frameworkComponents,
migrationEdges: r.entry.migrationEdges,
strictVerification: false
}));
const runnerResult = await runner.execute({
driver,
perSpaceOptions
});
if (!runnerResult.ok) {
onProgress?.({
action,
kind: "spanEnd",
spanId: RUN_SPAN_ID,
outcome: "error"
});
return notOk({
summary: runnerResult.failure.summary,
...ifDefined("why", runnerResult.failure.why),
meta: {
...runnerResult.failure.meta ?? {},
failingSpace: runnerResult.failure.failingSpace,
runnerErrorCode: runnerResult.failure.code
}
});
}
onProgress?.({
action,
kind: "spanEnd",
spanId: RUN_SPAN_ID,
outcome: "ok"
});
return ok({
orderedResolutions,
totalOpsPlanned: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsPlanned, 0),
totalOpsExecuted: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsExecuted, 0),
perSpace: buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: true })
});
}
/**
* Project the planner's per-space resolutions into the
* `PerSpaceExecutionEntry[]` shape the CLI surfaces.
*
* `includeMarkers` is `true` for apply-mode (each space's marker is
* the `destination.storageHash` of its plan, which the runner
* advances as the last step of each space's transaction) and `false`
* for plan-mode (no marker has been written yet).
*
* Exported alongside {@link runMigration} so plan-mode callers can
* assemble the same per-space block without going through the runner.
*/
function buildPerSpaceBreakdown(orderedResolutions, appSpaceId, options) {
return orderedResolutions.map((r) => {
const operations = r.entry.displayOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
}));
const base = {
spaceId: r.spaceId,
kind: r.spaceId === appSpaceId ? "app" : "extension",
operations
};
if (!options.includeMarkers) return base;
return {
...base,
marker: { storageHash: r.entry.plan.destination.storageHash }
};
});
}
/**
* Materialise the `applyOrder` ordering into resolved per-space
* entries. Throws if the planner output is missing a contract space listed
* in `applyOrder` — a wiring bug that should never reach runtime.
*
* Exported so callers building their own success envelopes after a
* plan-mode dispatch can replay the same ordering.
*/
function collectOrdered(applyOrder, perSpace) {
return applyOrder.map((spaceId) => {
const entry = perSpace.get(spaceId);
if (!entry) throw new Error(`planner output missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
}
/**
* Action-appropriate label for the `spanStart` event the run
* primitive emits. `runMigration` is shared by `db init`, `db update`,
* and `migrate`; the span label tracks the user-visible action
* so structured-progress output reads naturally for each surface.
*/
function progressLabelForAction(action) {
switch (action) {
case "dbInit": return "Initialising database across spaces";
case "dbUpdate": return "Updating database across spaces";
case "migrate": return "Running migration plan across spaces";
}
}
//#endregion
//#region src/control-api/operations/db-run.ts
/**
* Span IDs emitted via `onProgress` during the run flow.
* Stable identifiers consumed by the structured-output renderer and by
* tests asserting on span ids. The `apply` span itself is owned by
* the {@link runMigration} primitive — only the introspect / plan
* spans are emitted directly here.
*/
const SPAN_IDS$1 = {
introspect: "introspect",
plan: "plan"
};
/**
* Loader → planner → runner pipeline shared by `db init` and `db update`.
*
* The pipeline:
*
* 1. **Load**: build a {@link ContractSpaceAggregate} from the descriptor
* set + on-disk on-disk artefacts. Any layout / drift / disjointness /
* integrity violation short-circuits with a structured error.
* 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
* + introspected schema (`familyInstance.introspect`).
* 3. **Plan**: {@link planMigration} chooses `resolveRecordedPath` vs
* `planFromDiff` per space according to `callerPolicy.ignoreGraphFor`.
* The app space is forced through `planFromDiff` (today's daily-driver
* behaviour); every extension space walks its on-disk graph via
* `resolveRecordedPath`.
* 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
* feeds into the runner's `execute` — one outer
* transaction across every space; failure on any space rolls back
* every space's writes.
*/
async function executeRun(options) {
const { driver, adapter, familyInstance, contract, mode, migrations, frameworkComponents, migrationsDir, extensionPacks, targetId, policy, action, onProgress } = options;
const loaded = await buildContractSpaceAggregate({
targetId,
migrationsDir,
appContract: contract,
extensionPacks,
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!loaded.ok) throw loaded.failure;
const aggregate = loaded.value;
const markerRows = await familyInstance.readAllMarkers({ driver });
if (mode === "apply") {
const orphanMarkerError = detectOrphanMarkers(aggregate, markerRows);
if (orphanMarkerError !== null) throw orphanMarkerError;
}
onProgress?.({
action,
kind: "spanStart",
spanId: SPAN_IDS$1.introspect,
label: "Introspecting database schema"
});
const schemaIR = await familyInstance.introspect({
driver,
contract: collectAggregateNamespaces(aggregate)
});
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.introspect,
outcome: "ok"
});
onProgress?.({
action,
kind: "spanStart",
spanId: SPAN_IDS$1.plan,
label: "Planning migration"
});
const planResult = await planMigration({
aggregate,
currentDBState: {
markersBySpaceId: markerRows,
schemaIntrospection: schemaIR
},
adapter,
migrations,
frameworkComponents,
callerPolicy: { ignoreGraphFor: /* @__PURE__ */ new Set([aggregate.app.spaceId]) },
operationPolicy: policy
});
if (!planResult.ok) {
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.plan,
outcome: "error"
});
return mapPlannerError(planResult.failure);
}
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.plan,
outcome: "ok"
});
const orderedResolutions = collectOrdered(planResult.value.applyOrder, planResult.value.perSpace);
const plannerWarnings = aggregatePlannerWarnings(orderedResolutions);
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
if (!appResolution) throw new Error("Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.");
const appPlan = appResolution.entry.plan;
if (mode === "plan") {
const aggregateOps = orderedResolutions.flatMap((r) => r.entry.displayOps);
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(aggregateOps) : void 0;
const perSpace = buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: false });
const summary = `Planned ${aggregateOps.length} operation(s) across ${orderedResolutions.length} space(s)`;
return wrapPlanResult({
operations: aggregateOps,
destination: appPlan.destination,
preview,
perSpace,
summary,
...ifDefined("warnings", plannerWarnings)
});
}
const applied = await runMigration({
aggregate,
perSpacePlans: planResult.value.perSpace,
applyOrder: planResult.value.applyOrder,
driver,
familyInstance,
migrations,
frameworkComponents,
policy,
action,
...ifDefined("onProgress", onProgress)
});
if (!applied.ok) return buildRunnerFailure({
summary: applied.failure.summary,
...ifDefined("why", applied.failure.why),
meta: applied.failure.meta,
...ifDefined("warnings", plannerWarnings)
});
const aggregateOps = applied.value.orderedResolutions.flatMap((r) => r.entry.displayOps);
const summary = action === "dbInit" ? `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), database signed` : applied.value.totalOpsExecuted === 0 ? `Database already matches contract across ${applied.value.orderedResolutions.length} space(s), signature updated` : `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), signature updated`;
return wrapApplyResult({
operations: aggregateOps,
destination: appPlan.destination,
operationsPlanned: applied.value.totalOpsPlanned,
operationsExecuted: applied.value.totalOpsExecuted,
perSpace: applied.value.perSpace,
summary,
...ifDefined("warnings", plannerWarnings)
});
}
function aggregatePlannerWarnings(orderedResolutions) {
const warnings = orderedResolutions.flatMap((r) => r.entry.warnings ?? []);
return warnings.length > 0 ? warnings : void 0;
}
/**
* Compare the live `_prisma_marker` rows against the aggregate's
* declared contract spaces. Any marker row whose `space` is not a space of
* the aggregate is an "orphan" — typically a marker left behind by
* an extension that was removed from `extensionPacks` without first
* cleaning up its on-disk migrations / database tables.
*
* Returns a {@link CliStructuredError} envelope (code
* `MIGRATION.CONTRACT_SPACE_VIOLATION`, `kind: 'orphanMarker'`) for the
* first orphan it finds, or `null`
* when every marker row maps to a declared contract space. Mirrors the M2
* `runContractSpaceVerifierMarkerCheck` envelope so downstream
* tooling (integration tests, JSON consumers) keeps asserting on the
* same shape.
*/
function detectOrphanMarkers(aggregate, markerRows) {
const aggregateSpaceIds = /* @__PURE__ */ new Set([aggregate.app.spaceId, ...aggregate.extensions.map((m) => m.spaceId)]);
const orphans = [];
for (const [spaceId, row] of markerRows) if (row !== null && row !== void 0 && !aggregateSpaceIds.has(spaceId)) orphans.push(spaceId);
if (orphans.length === 0) return null;
orphans.sort((a, b) => a.localeCompare(b));
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", orphans.length === 1 ? `Orphan contract-space marker detected for "${orphans[0]}"` : `Orphan contract-space markers detected for ${orphans.length} spaces`, {
why: `The database has \`_prisma_marker\` rows for spaces (${orphans.map((s) => `"${s}"`).join(", ")}) that are not declared in the project's \`extensionPacks\`. The aggregate pipeline refuses to advance markers it cannot account for.`,
fix: "Either re-declare the missing extension(s) in `extensionPacks` (so the aggregate owns them again), or remove the orphan marker row(s) from `_prisma_marker` once you have confirmed the corresponding tables can be safely retired.",
docsUrl: "https://pris.ly/contract-spaces",
meta: { violations: orphans.map((spaceId) => ({
kind: "orphanMarker",
spaceId
})) }
});
}
function mapPlannerError(error) {
if (error.kind === "planFromDiffFailed") return blindCast(notOk({
code: "PLANNING_FAILED",
summary: "Migration planning failed due to conflicts",
conflicts: error.conflicts,
why: void 0,
meta: void 0
}));
if (error.kind === "extensionPathUnreachable") return buildRunnerFailure({
summary: `Cannot resolve apply path for extension space "${error.spaceId}"`,
why: `No path in the on-disk migration graph for extension space "${error.spaceId}" reaches the on-disk head ref hash "${error.target}".`,
meta: {
spaceId: error.spaceId,
target: error.target
}
});
if (error.kind === "extensionPathUnsatisfiable") return buildRunnerFailure({
summary: `Cannot resolve apply path for extension space "${error.spaceId}"`,
why: `On-disk migration graph for extension space "${error.spaceId}" reaches the on-disk head ref but does not cover required invariants: ${error.missingInvariants.join(", ")}.`,
meta: {
spaceId: error.spaceId,
missingInvariants: error.missingInvariants
}
});
return buildRunnerFailure({
summary: `Aggregate planner policy conflict for space "${error.spaceId}"`,
why: error.detail,
meta: { spaceId: error.spaceId }
});
}
function wrapPlanResult(args) {
return ok({
mode: "plan",
plan: {
operations: stripOperations(args.operations),
...ifDefined("preview", args.preview)
},
destination: {
storageHash: args.destination.storageHash,
...ifDefined("profileHash", args.destination.profileHash)
},
perSpace: args.perSpace,
summary: args.summary,
...ifDefined("warnings", args.warnings)
});
}
function wrapApplyResult(args) {
return ok({
mode: "apply",
plan: { operations: stripOperations(args.operations) },
destination: {
storageHash: args.destination.storageHash,
...ifDefined("profileHash", args.destination.profileHash)
},
execution: {
operationsPlanned: args.operationsPlanned,
operationsExecuted: args.operationsExecuted
},
marker: args.destination.profileHash ? {
storageHash: args.destination.storageHash,
profileHash: args.destination.profileHash
} : { storageHash: args.destination.storageHash },
perSpace: args.perSpace,
summary: args.summary,
...ifDefined("warnings", args.warnings)
});
}
function buildRunnerFailure(args) {
return blindCast(notOk({
code: "RUNNER_FAILED",
summary: args.summary,
why: args.why,
meta: args.meta,
conflicts: void 0,
...ifDefined("warnings", args.warnings)
}));
}
//#endregion
//#region src/control-api/operations/db-init.ts
/**
* Execute `db init` against the configured contract.
*
* Routes through the loader → planner → runner pipeline (sub-spec
* "Commit-by-commit § Commit 4"). Always additive-only; destructive
* changes belong to `db update`.
*/
async function executeDbInit(options) {
return await executeRun({
driver: options.driver,
adapter: options.adapter,
familyInstance: options.familyInstance,
contract: options.contract,
mode: options.mode,
migrations: options.migrations,
frameworkComponents: options.frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: options.targetId,
extensionPacks: options.extensionPacks ?? [],
policy: { allowedOperationClasses: ["additive"] },
action: "dbInit",
...ifDefined("onProgress", options.onProgress)
});
}
//#endregion
//#region src/control-api/operations/db-update.ts
const DB_UPDATE_POLICY = { allowedOperationClasses: [
"additive",
"widening",
"destructive"
] };
/**
* Execute `db update` against the configured contract.
*
* Routes through the loader → planner → runner pipeline. Destructive
* operations require either `acceptDataLoss: true` or a prior
* `mode: 'plan'` invocation that surfaces the destructive ops; the
* confirmation gate is implemented here so the lower-level applier
* remains policy-agnostic.
*/
async function executeDbUpdate(options) {
const sharedInputs = {
driver: options.driver,
adapter: options.adapter,
familyInstance: options.familyInstance,
contract: options.contract,
migrations: options.migrations,
frameworkComponents: options.frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: options.targetId,
extensionPacks: options.extensionPacks ?? [],
policy: DB_UPDATE_POLICY,
action: "dbUpdate",
...ifDefined("onProgress", options.onProgress)
};
if (options.mode === "apply" && !options.acceptDataLoss) {
const gate = await guardDestructiveChanges(sharedInputs);
if (gate !== null) return gate;
}
return await executeRun({
...sharedInputs,
mode: options.mode
});
}
/**
* Pre-plan once when running `db update apply` without `acceptDataLoss`.
* Surfaces destructive operations across every space; if any are
* planned, returns a `DESTRUCTIVE_CHANGES` failure that the CLI shows
* as a confirmation prompt. Returns `null` when the apply is safe to
* run.
*/
async function guardDestructiveChanges(sharedInputs) {
const planResult = await executeRun({
...sharedInputs,
mode: "plan"
});
if (!planResult.ok) return planResult;
const destructiveOps = planResult.value.plan.operations.filter((op) => op.operationClass === "destructive").map((op) => ({
id: op.id,
label: op.label
}));
if (destructiveOps.length === 0) return null;
return notOk({
code: "DESTRUCTIVE_CHANGES",
summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
why: "Destructive operations require confirmation — re-run with -y to accept",
conflicts: void 0,
meta: { destructiveOperations: destructiveOps }
});
}
//#endregion
//#region src/control-api/operations/db-verify.ts
/**
* Span IDs emitted via `onProgress` during the aggregate verify flow.
* Mirrors the span identifiers used by the legacy precheck / marker-check
* helpers so structured-output renderers and progress tests keep working.
*/
const SPAN_IDS = {
introspect: "introspect",
verify: "verify"
};
/**
* Loader → verifier pipeline shared by `db verify` modes (`full`,
* `marker-only`, `schema-only`).
*
* 1. **Load**: build a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate}
* from descriptors + on-disk on-disk artefacts. Layout / drift /
* integrity / disjointness violations short-circuit with a
* structured CLI error.
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
* schema introspection.
* 3. **Verify**: {@link verifyMigration} returns per-space `markerCheck` +
* per-space `schemaCheck` (each contract space verified against the full schema,
* then scoped to its own contract space). Marker mismatches map to
* `CliStructuredError` (code `5002`) so callers (CLI command) can render
* and exit. Verify results are returned to the caller verbatim.
*/
async function executeDbVerify(options) {
const { driver, familyInstance, onProgress, skipSchema, skipMarker } = options;
const loaded = await buildContractSpaceAggregate(buildLoadInputs(options));
if (!loaded.ok) return notOk(loaded.failure);
const aggregate = loaded.value;
const markersBySpaceId = await familyInstance.readAllMarkers({ driver });
const schemaIntrospection = skipSchema ? null : await runIntrospection({
driver,
familyInstance,
onProgress,
contract: collectAggregateNamespaces(aggregate)
});
const classifySubjectGranularity = hasSchemaSubjectClassifier(familyInstance) ? (issue) => familyInstance.classifySubjectGranularity(issue) : void 0;
const classifyEntityKind = hasSchemaSubjectClassifier(familyInstance) ? (issue) => familyInstance.classifyEntityKind(issue) : void 0;
emitVerifySpan(onProgress, "spanStart");
return finaliseVerifyResult({
verifyResult: verifyMigration({
aggregate,
markersBySpaceId,
schemaIntrospection,
mode: options.mode,
verifySchemaForSpace: createPerSpaceVerifier(options),
...ifDefined("classifySubjectGranularity", classifySubjectGranularity),
...ifDefined("classifyEntityKind", classifyEntityKind)
}),
aggregate,
skipMarker,
onProgress
});
}
function buildLoadInputs(options) {
return {
targetId: options.targetId,
migrationsDir: options.migrationsDir,
appContract: options.contract,
extensionPacks: options.extensionPacks,
deserializeContract: (json) => options.familyInstance.deserializeContract(json)
};
}
async function runIntrospection(args) {
const { driver, familyInstance, onProgress, contract } = args;
onProgress?.({
action: "dbVerify",
kind: "spanStart",
spanId: SPAN_IDS.introspect,
label: "Introspecting database schema"
});
try {
const result = await familyInstance.introspect({
driver,
contract
});
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.introspect,
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.introspect,
outcome: "error"
});
throw error;
}
}
/**
* Build the per-space schema callback handed to the aggregate verifier.
* When `skipSchema` is true the callback short-circuits with a synthetic
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
* without invoking the family's verification path.
*/
function createPerSpaceVerifier(options) {
const { skipSchema, familyInstance, frameworkComponents } = options;
return (schema, space, verifyMode) => {
if (skipSchema) return buildSkippedSchemaResult(space);
return familyInstance.verifySchema({
contract: space.contract(),
schema,
strict: verifyMode === "strict",
frameworkComponents
});
};
}
function emitVerifySpan(onProgress, kind) {
if (kind === "spanStart") {
onProgress?.({
action: "dbVerify",
kind: "spanStart",
spanId: SPAN_IDS.verify,
label: "Verifying contract spaces"
});
return;
}
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.verify,
outcome: kind === "spanEndOk" ? "ok" : "error"
});
}
/**
* Map an {@link VerifierOutput} to the operation's
* {@link ExecuteDbVerifyResult}, applying the `skipMarker` policy used
* by the CLI's `--schema-only` mode.
*/
function finaliseVerifyResult(args) {
const { verifyResult, aggregate, skipMarker, onProgress } = args;
if (!verifyResult.ok) {
emitVerifySpan(onProgress, "spanEndError");
return notOk(new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", "Aggregate verifier introspection failed", {
why: verifyResult.failure.detail,
fix: "Check database connectivity and the introspection tooling.",
docsUrl: "https://pris.ly/contract-spaces"
}));
}
const markerError = skipMarker ? null : mapMarkerCheckFailures(aggregate.app.spaceId, verifyResult.value.markerCheck);
if (markerError !== null) {
emitVerifySpan(onProgress, "spanEndError");
return notOk(markerError);
}
emitVerifySpan(onProgress, "spanEndOk");
return ok({
schemaResults: verifyResult.value.schemaCheck.perSpace,
unclaimed: verifyResult.value.schemaCheck.unclaimed,
spaceOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
appSpaceId: aggregate.app.spaceId
});
}
function buildSkippedSchemaResult(space) {
const contract = space.contract();
const headRef = requireHeadRef(space);
const profileHash = castAs(contract).profileHash;
return {
ok: true,
summary: "Schema verification skipped",
contract: {
storageHash: headRef.hash,
...profileHash ? { profileHash } : {}
},
target: { expected: contract.target },
schema: { issues: [] },
timings: { total: 0 }
};
}
/**
* Translate per-space marker check failures and orphan markers into a
* single CLI structured error envelope. Preserves the legacy code
* `MIGRATION.CONTRACT_SPACE_VIOLATION` (was emitted by
* `runContractSpaceVerifierMarkerCheck`).
*/
function mapMarkerCheckFailures(appSpaceId, section) {
const violations = [];
for (const [spaceId, result] of section.perSpace) {
if (result.kind === "ok" || result.kind === "absent") continue;
if (result.kind === "hashMismatch") {
violations.push({
kind: "hashMismatch",
spaceId,
remediation: spaceId === appSpaceId ? "Run `prisma-next db update` to advance the marker, or roll the database back to the recorded hash." : `Apply on-disk migrations under \`migrations/${spaceId}/\` to advance the marker, or remove the conflicting marker row.`
});
continue;
}
if (result.kind === "missingInvariants") violations.push({
kind: "invariantsMismatch",
spaceId,
remediation: `Re-apply the migrations under \`migrations/${spaceId}/\` so the marker carries invariants: ${result.missing.join(", ")}.`
});
}
for (const orphan of section.orphanMarkers) violations.push({
kind: "orphanMarker",
spaceId: orphan.spaceId,
remediation: `Add the corresponding extension to \`extensionPacks\` in \`prisma-next.config.ts\`, or delete the orphan marker row for "${orphan.spaceId}".`
});
if (violations.length === 0) return null;
const lines = violations.map((v) => `- [${v.kind}] ${v.spaceId}: ${v.remediation}`);
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", violations.length === 1 ? "Contract-space verifier found a violation" : `Contract-space verifier found violations (${violations.length})`, {
why: `The on-disk \`migrations/\` directory, the \`extensionPacks\` declaration, and the live database marker rows are not in agreement.\n${lines.join("\n")}`,
fix: violations[0]?.remediation ?? "Review and reconcile the violations listed above.",
docsUrl: "https://pris.ly/contract-spaces",
meta: { violations }
});
}
//#endregion
//#region src/control-api/operations/migrate.ts
/**
* Apply pending migrations across every contract space (app +
* extensions). Replay-only: graph-walk against the on-disk graph for
* every contract space; no synth, no introspection.
*
* Pipeline:
*
* 1. Load aggregate from disk (loader hydrates extension graphs;
* caller provides app-space packages).
* 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
* 3. Per space: `resolveRecordedPath` plots the path from the live
* marker to `space.headRef.hash` (or `refHash` for the app
* space when provided). An empty-graph space whose elements are ALL
* externally managed resolves declaratively (marker to head, zero
* ops), mirroring the db-init aggregate planner: such a space ships
* no DDL and has nothing to author. Every other empty-graph space
* fails loudly — "never planned" is a user-error condition for
* replay.
* 4. Hand off to {@link runMigration} (the runner-driving tail
* shared with `db init` / `db update`). Marker advancement is
* inside the per-space transaction.
*
* Encodes the replay-only contract: the app contract space must have an
* authored migration graph on disk before this operation can advance it.
*/
async function executeMigrate(options) {
const { driver, familyInstance, contract, migrations, frameworkComponents, migrationsDir, extensionPacks, targetId, refHash, refInvariants, refName, onProgress } = options;
const loaded = await buildContractSpaceAggregate({
targetId,
migrationsDir,
appContract: contract,
extensionPacks,
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!loaded.ok) throw loaded.failure;
const aggregate = loaded.value;
const markerRows = await familyInstance.readAllMarkers({ driver });
const allSpaces = [aggregate.app, ...aggregate.extensions];
const perSpacePlans = /* @__PURE__ */ new Map();
const atHeadResolutions = /* @__PURE__ */ new Map();
for (const space of allSpaces) {
const isAppSpace = space.spaceId === aggregate.app.spaceId;
const headRef = requireHeadRef(space);
const spaceTargetHash = isAppSpace && refHash !== void 0 ? refHash : headRef.hash;
const outcome = planSpacePath({
space,
aggregate,
targetHash: spaceTargetHash,
refInvariants: isAppSpace && refHash !== void 0 ? refInvariants : void 0,
liveMarker: markerRows.get(space.spaceId) ?? null,
...isAppSpace ? { refName } : {}
});
if (outcome.kind === "at-head") {
atHeadResolutions.set(space.spaceId, outcome.plan);
continue;
}
if (outcome.kind === "never-planned") return notOk(buildNeverPlannedFailure(outcome.spaceId, outcome.targetHash));
if (outcome.kind === "unreachable") return notOk(buildPathNotFoundFailure(outcome.spaceId, outcome.liveMarker, outcome.targetHash));
if (outcome.kind === "unsatisfiable") {
const structural = findPathWithDecision(outcome.targetSpace.graph(), outcome.liveHash, spaceTargetHash, { required: /* @__PURE__ */ new Set() });
const structuralPath = structural.kind === "ok" ? structural.decision.selectedPath.map((edge) => ({
dirName: edge.dirName,
migrationHash: edge.migrationHash,
from: edge.from,
to: edge.to,
invariants: edge.invariants
})) : [];
throw errorNoInvariantPath({
...outcome.refName !== void 0 ? { refName: outcome.refName } : {},
required: outcome.targetInvariants,
missing: outcome.missing,
structuralPath
});
}
perSpacePlans.set(space.spaceId, outcome.plan);
}
const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
if (!applyOrder.some((spaceId) => {
const entry = perSpacePlans.get(spaceId);
return entry !== void 0 && planRequiresExecution(entry);
})) {
const ordered = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
const entry = perSpacePlans.get(spaceId) ?? atHeadResolutions.get(spaceId);
if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
const perSpace = buildPerSpaceBreakdown(ordered, aggregate.app.spaceId, { includeMarkers: true });
const totalSpaces = ordered.length;
return ok(buildSuccess({
aggregate,
orderedResolutions: ordered,
perSpace,
totalOpsExecuted: 0,
summary: totalSpaces === 0 ? "Already up to date — no contract spaces are loaded" : totalSpaces === 1 ? "Already up to date" : `Already up to date across ${totalSpaces} space(s)`
}));
}
const applied = await runMigration({
aggregate,
perSpacePlans,
applyOrder,
driver,
familyInstance,
migrations,
frameworkComponents,
policy: { allowedOperationClasses: [
"additive",
"widening",
"destructive",
"data"
] },
action: "migrate",
...ifDefined("onProgress", onProgress)
});
if (!applied.ok) return notOk({
code: "RUNNER_FAILED",
summary: applied.failure.summary,
why: applied.failure.why,
meta: applied.failure.meta
});
const orderedAll = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
if (perSpacePlans.has(spaceId)) {
const fromRunner = applied.value.orderedResolutions.find((r) => r.spaceId === spaceId);
if (fromRunner !== void 0) return fromRunner;
}
const entry = atHeadResolutions.get(spaceId);
if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
const perSpaceAll = buildPerSpaceBreakdown(orderedAll, aggregate.app.spaceId, { includeMarkers: true });
const summary = `Applied ${applied.value.orderedResolutions.reduce((sum, r) => sum + r.entry.migrationEdges.length, 0)} migration(s) (${applied.value.totalOpsExecuted} operation(s)) across ${orderedAll.length} contract space(s)`;
return ok(buildSuccess({
aggregate,
orderedResolutions: orderedAll,
perSpace: perSpaceAll,
totalOpsExecuted: applied.value.totalOpsExecuted,
summary
}));
}
/**
* Compute the graph-walk path for one contract space.
*
* Encapsulates the invariant-correct input assembly that both
* `executeMigrate` and `executeMigrateShowCommand` must use:
* - `currentMarker` carries the full live marker including `invariants`
* (not a stripped `{ storageHash, invariants: [] }` shell).
* - `targetInvariants` uses the caller-supplied `refInvariants` when a
* `--to` ref was resolved (not always the file head ref's invariants).
*
* Both callers map the returned `SpacePathOutcome` to their own error
* representation; the path-compute logic is shared and identical.
*
* @internal Exported for `executeMigrateShowCommand`.
*/
function planSpacePath({ space, aggregate, targetHash, refInvariants, liveMarker, refName }) {
const isAppSpace = space.spaceId === aggregate.app.spaceId;
const headRef = requireHeadRef(space);
if (space.graph().nodes.size === 0) {
const liveHash = liveMarker?.storageHash;
if (targetHash === liveHash || liveHash === void 0 && targetHash === EMPTY_CONTRACT_HASH) return {
kind: "at-head",
plan: buildAtHeadResolution({
aggregateTargetId: aggregate.targetId,
space,
targetHash,
liveMarker
})
};
if (!isAppSpace && allStorageElementsExternal(space.contract())) {
if (headRef.invariants.length > 0) return {
kind: "unsatisfiable",
spaceId: space.spaceId,
isAppSpace,
missing: [...headRef.invariants].sort(),
targetInvariants: headRef.invariants,
targetSpace: space,
liveHash: liveHash ?? EMPTY_CONTRACT_HASH,
refName: void 0
};
return {
kind: "ok",
plan: buildAtHeadResolution({
aggregateTargetId: aggregate.targetId,
space,
targetHash,
liveMarker
})
};
}
return {
kind: "never-planned",
spaceId: space.spaceId,
targetHash
};
}
const targetInvariants = isAppSpace && refInvariants !== void 0 ? refInvariants : headRef.invariants;
const targetSpace = targetHash === headRef.hash && targetInvariants === headRef.invariants ? space : {
...space,
headRef: {
hash: targetHash,
invariants: targetInvariants
}
};
const walked = resolveRecordedPath({
aggregateTargetId: aggregate.targetId,
space: targetSpace,
currentMarker: liveMarker,
...isAppSpace && refName !== void 0 ? { refName } : {}
});
if (walked.kind === "unreachable") return {
kind: "unreachable",
spaceId: space.spaceId,
liveMarker,
targetHash
};
if (walked.kind === "unsatisfiable") {
const liveHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
return {
kind: "unsatisfiable",
spaceId: space.spaceId,
isAppSpace,
missing: walked.missing,
targetInvariants,
targetSpace,
liveHash,
refName
};
}
return {
kind: "ok",
plan: walked.result
};
}
/**
* Build a zero-op {@link PerSpacePlan} for an empty-graph space —
* either one whose live marker already matches the target (at-head), or
* an all-external extension space whose marker must advance to the head
* ref with no DDL (declared-state). Lets the apply pipeline thread the
* space through `perSpacePlans` -> `applyOrder` -> the success
* envelope's `perSpace[]` block so the result reflects every loaded
* space, even when there is nothing to execute.
*/
function buildAtHeadResolution(args) {
const { aggregateTargetId, space, targetHash, liveMarker } = args;
return {
plan: {
targetId: aggregateTargetId,
spaceId: space.spaceId,
origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
destination: { storageHash: targetHash },
operations: [],
providedInvariants: []
},
displayOps: [],
destinationContract: space.contract(),
strategy: "declared-state",
migrationEdges: [buildFabricatedMigrationEdge({
currentMarkerStorageHash: liveMarker?.storageHash,
destinationStorageHash: targetHash,
operationCount: 0
})]
};
}
/**
* A plan needs the runner when it executes operations or advances the
* space's marker (a declared-state resolution has zero operations but a
* destination hash the live marker doesn't carry yet).
*/
function planRequiresExecution(entry) {
if (entry.plan.operations.length > 0) return true;
return entry.plan.origin?.storageHash !== entry.plan.destination.storageHash;
}
function buildSuccess(args) {
const appResolution = args.orderedResolutions.find((r) => r.spaceId === args.aggregate.app.spaceId);
const appMarkerHash = appResolution?.entry.plan.destination.storageHash ?? requireHeadRef(args.aggregate.app).hash;
const applied = args.orderedResolutions.flatMap((r) => {
return r.entry.migrationEdges.map((edge) => ({
spaceId: r.spaceId,
dirName: edge.dirName,
migrationHash: edge.migrationHash,
from: edge.from,
to: edge.to,
operationsExecuted: edge.operationCount
}));
});
const appPlan = appResolution?.entry;
const pathDecision = appPlan?.pathDecision ? {
fromHash: appPlan.pathDecision.fromHash,
toHash: appPlan.pathDecision.toHash,
alternativeCount: appPlan.pathDecision.alternativeCount,
tieBreakReasons: appPlan.pathDecision.tieBreakReasons,
...appPlan.pathDecision.refName !== void 0 ? { refName: appPlan.pathDecision.refName } : {},
requiredInvariants: appPlan.pathDecision.requiredInvariants ?? [],
satisfiedInvariants: appPlan.pathDecision.satisfiedInvariants ?? [],
selectedPath: appPlan.pathDecision.selectedPath.map((entry) => ({
dirName: entry.dirName,
migrationHash: entry.migrationHash,
from: entry.from,
to: entry.to,
invariants: entry.invariants
}))
} : void 0;
return {
migrationsApplied: applied.length,
markerHash: appMarkerHash,
applied,
summary: args.summary,
perSpace: args.perSpace,
...pathDecision !== void 0 ? { pathDecision } : {}
};
}
/**
* Build the `neverPlanned` failure raised when a contract space that
* declares managed storage elements has no on-disk migration graph but
* migrate was asked to reach a target hash. All-external spaces never reach
* this: they resolve declaratively (marker advances to the head ref with
* zero operations). The `why` states only the condition; the recovery
* sequence is composed by `errorPathUnreachable`'s `fix`.
*
* @internal Exported for testing only.
*/
function buildNeverPlannedFailure(spaceId, targetHash) {
return {
code: "MIGRATION_PATH_NOT_FOUND",
summary: `No on-disk migrations for contract space "${spaceId}"`,
why: `migrate is replay-only: a contract space that declares managed storage elements must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
meta: {
spaceId,
target: targetHash,
kind: "neverPlanned"
}
};
}
/**
* Build the `pathUnreachable` failure raised when an emitted contract has no
* on-disk migration edge from the current marker to the target. The `why`
* states only the condition (no edge between the two named states, and migrate
* replays edges rather than inventing them); the recovery sequence — plan the
* edge, then re-apply — is composed by `errorPathUnreachable`'s `fix`, so the
* two read as one non-redundant plan-then-apply story.
*
* @internal Exported for testing only.
*/
function buildPathNotFoundFailure(spaceId, marker, targetHash) {
const fromHash = marker?.storageHash ?? "<empty>";
return {
code: "MIGRATION_PATH_NOT_FOUND",
summary: spaceId === "app" ? "Current contract has no planned migration path" : `Current contract has no planned migration path for contract space "${spaceId}"`,
why: `No migration edge connects the current state "${fromHash}" to the target "${targetHash}" in contract space "${spaceId}". The on-disk migration graph does not join the two, and migrate replays existing edges — it never invents one.`,
meta: {
spaceId,
fromHash,
targetHash,
kind: "pathUnreachable"
}
};
}
//#endregion
//#region src/control-api/client.ts
/**
* Creates a programmatic control client for Prisma Next operations.
*
* The client accepts framework component descriptors at creation time,
* manages driver lifecycle via connect()/close(), and exposes domain
* operations that delegate to the existing family instance methods.
*
* @see {@link ControlClient} for the client interface
* @see README.md "Programmatic Control API" section for usage examples
*/
function createControlClient(options) {
return new ControlClientImpl(options);
}
/**
* Implementation of ControlClient.
* Manages initialization and connection state, delegates operations to family instance.
*/
var ControlClientImpl = class {
options;
stack = null;
driver = null;
familyInstance = null;
frameworkComponents = null;
initialized = false;
defaultConnection;
constructor(options) {
this.options = options;
this.defaultConnection = options.connection;
}
init() {
if (this.initialized) return;
this.stack = createControlStack({
family: this.options.family,
target: this.options.target,
adapter: this.options.adapter,
driver: this.options.driver,
extensionPacks: this.options.extensionPacks
});
this.familyInstance = this.options.family.create(this.stack);
const rawComponents = [
this.options.target,
this.options.adapter,
...this.options.extensionPacks ?? []
];
this.frameworkComponents = assertFrameworkComponentsCompatible(this.options.family.familyId, this.options.target.targetId, rawComponents);
this.initialized = true;
}
async connect(connection) {
this.init();
if (this.driver) throw new Error("Already connected. Call close() before reconnecting.");
const resolvedConnection = connection ?? this.defaultConnection;
if (resolvedConnection === void 0) throw new Error("No connection provided. Pass a connection to connect() or provide a default connection when creating the client.");
if (!this.stack?.driver) throw new Error("Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.");
this.driver = await this.stack.driver.create(resolvedConnection);
}
async close() {
if (this.driver) {
await this.driver.close();
this.driver = null;
}
}
/**
* Construct the control adapter once for a migration operation and return
* it, mirroring how the runtime plane builds the execution adapter once in
* `createExecutionStack`. Only `dbInit` / `dbUpdate` need it (it lowers the
* planner's DDL); read-only operations never build it. The descriptor is
* optional on the stack — targets without migrations omit it.
*/
buildControlAdapter() {
this.init();
if (!this.stack?.adapter) throw new Error(`Target "${this.options.target.targetId}" requires an adapter for migrations`);
return this.stack.adapter.create(this.stack);
}
async ensureConnected() {
this.init();
if (!this.driver && this.defaultConnection !== void 0) await this.connect(this.defaultConnection);
if (!this.driver || !this.familyInstance || !this.frameworkComponents) throw new Error("Not connected. Call connect(connection) first.");
return {
driver: this.driver,
familyInstance: this.familyInstance,
frameworkComponents: this.frameworkComponents
};
}
async connectWithProgress(connection, action, onProgress) {
if (connection === void 0) return;
onProgress?.({
action,
kind: "spanStart",
spanId: "connect",
label: "Connecting to database..."
});
try {
await this.connect(connection);
onProgress?.({
action,
kind: "spanEnd",
spanId: "connect",
outcome: "ok"
});
} catch (error) {
onProgress?.({
action,
kind: "spanEnd",
spanId: "connect",
outcome: "error"
});
throw error;
}
}
async verify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "verify", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "verify",
kind: "spanStart",
spanId: "verify",
label: "Verifying database marker..."
});
try {
const result = await familyInstance.verify({
driver,
contract,
expectedTargetId: this.options.target.targetId,
contractPath: ""
});
onProgress?.({
action: "verify",
kind: "spanEnd",
spanId: "verify",
outcome: result.ok ? "ok" : "error"
});
return result;
} catch (error) {
onProgress?.({
action: "verify",
kind: "spanEnd",
spanId: "verify",
outcome: "error"
});
throw error;
}
}
async schemaVerify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "schemaVerify", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "schemaVerify",
kind: "spanStart",
spanId: "schemaVerify",
label: "Verifying database schema..."
});
try {
const schema = await familyInstance.introspect({
driver,
contract
});
const result = familyInstance.verifySchema({
contract,
schema,
strict: options.strict ?? false,
frameworkComponents
});
onProgress?.({
action: "schemaVerify",
kind: "spanEnd",
spanId: "schemaVerify",
outcome: result.ok ? "ok" : "error"
});
return result;
} catch (error) {
onProgress?.({
action: "schemaVerify",
kind: "spanEnd",
spanId: "schemaVerify",
outcome: "error"
});
throw error;
}
}
async sign(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "sign", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "sign",
kind: "spanStart",
spanId: "sign",
label: "Signing database..."
});
try {
const result = await familyInstance.sign({
driver,
contract,
contractPath: options.contractPath ?? "",
...ifDefined("configPath", options.configPath)
});
onProgress?.({
action: "sign",
kind: "spanEnd",
spanId: "sign",
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "sign",
kind: "spanEnd",
spanId: "sign",
outcome: "error"
});
throw error;
}
}
async dbInit(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbInit", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
const adapter = this.buildControlAdapter();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeDbInit({
driver,
adapter,
familyInstance,
contract,
mode: options.mode,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
...ifDefined("onProgress", onProgress)
});
}
async dbUpdate(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbUpdate", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
const adapter = this.buildControlAdapter();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeDbUpdate({
driver,
adapter,
familyInstance,
contract,
mode: options.mode,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
...ifDefined("acceptDataLoss", options.acceptDataLoss),
...ifDefined("onProgress", onProgress)
});
}
async dbVerify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbVerify", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
return executeDbVerify({
driver,
familyInstance,
contract: options.contract,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
frameworkComponents,
mode: options.strict ? "strict" : "lenient",
skipSchema: options.skipSchema,
skipMarker: options.skipMarker,
...ifDefined("onProgress", onProgress)
});
}
async readMarker() {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readMarker({
driver,
space: APP_SPACE_ID
});
}
async readAllMarkers() {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readAllMarkers({ driver });
}
/** Reads the per-migration journal; omit `space` to return every space. */
async readLedger(space) {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readLedger({
driver,
...ifDefined("space", space)
});
}
async migrate(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "migrate", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeMigrate({
driver,
familyInstance,
contract,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
extensionPacks: this.options.extensionPacks ?? [],
targetId: this.options.target.targetId,
...ifDefined("refHash", options.refHash),
...ifDefined("refInvariants", options.refInvariants),
...ifDefined("refName", options.refName),
...ifDefined("onProgress", onProgress)
});
}
async introspect(options) {
const onProgress = options?.onProgress;
await this.connectWithProgress(options?.connection, "introspect", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
options?.schema;
onProgress?.({
action: "introspect",
kind: "spanStart",
spanId: "introspect",
label: "Introspecting database schema..."
});
try {
const result = await familyInstance.introspect({ driver });
onProgress?.({
action: "introspect",
kind: "spanEnd",
spanId: "introspect",
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "introspect",
kind: "spanEnd",
spanId: "introspect",
outcome: "error"
});
throw error;
}
}
toSchemaView(schemaIR) {
this.init();
if (this.familyInstance && hasSchemaView(this.familyInstance)) return this.familyInstance.toSchemaView(schemaIR);
}
inferPslContract(schemaIR) {
this.init();
if (this.familyInstance && hasPslContractInfer(this.familyInstance)) return this.familyInstance.inferPslContract(schemaIR);
}
getPslBlockDescriptors() {
this.init();
return this.stack.authoringContributions.pslBlockDescriptors;
}
toOperationPreview(operations) {
this.init();
if (this.familyInstance && hasOperationPreview(this.familyInstance)) return this.familyInstance.toOperationPreview(operations);
}
async emit(options) {
const { onProgress, contractConfig } = options;
this.init();
if (!this.familyInstance) throw new Error("Family instance was not initialized. This is a bug.");
let contractRaw;
onProgress?.({
action: "emit",
kind: "spanStart",
spanId: "resolveSource",
label: "Resolving contract source..."
});
try {
const stack = this.stack;
const sourceContext = {
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
composedExtensionContracts: stack.extensionContracts,
authoringContributions: stack.authoringContributions,
codecLookup: stack.codecLookup,
controlMutationDefaults: stack.controlMutationDefaults,
resolvedInputs: contractConfig.source.inputs ?? [],
capabilities: stack.capabilities
};
const providerResult = await contractConfig.source.load(sourceContext);
if (!providerResult.ok) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "error"
});
return notOk({
code: "CONTRACT_SOURCE_INVALID",
summary: providerResult.failure.summary,
why: providerResult.failure.summary,
meta: providerResult.failure.meta,
diagnostics: providerResult.failure
});
}
contractRaw = providerResult.value;
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "ok"
});
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "error"
});
const message = error instanceof Error ? error.message : String(error);
return notOk({
code: "CONTRACT_SOURCE_INVALID",
summary: "Failed to resolve contract source",
why: message,
diagnostics: {
summary: "Contract source provider threw an exception",
diagnostics: [{
code: "PROVIDER_THROW",
message
}]
},
meta: void 0
});
}
onProgress?.({
action: "emit",
kind: "spanStart",
spanId: "emit",
label: "Emitting contract..."
});
try {
const enrichedIR = enrichContract(contractRaw, this.frameworkComponents ?? []);
const rawContractJson = this.options.target.contractSerializer.serializeContract(enrichedIR);
let deserializedContract;
try {
deserializedContract = this.familyInstance.deserializeContract(rawContractJson);
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "error"
});
return notOk({
code: "CONTRACT_VALIDATION_FAILED",
summary: "Contract validation failed",
why: error instanceof Error ? error.message : String(error),
meta: void 0
});
}
const result = await emit(deserializedContract, this.stack, this.options.family.emission, { serializeContract: (contract) => this.options.target.contractSerializer.serializeContract(contract) });
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "ok"
});
return ok({
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
profileHash: result.profileHash,
contractJson: result.contractJson,
contractDts: result.contractDts
});
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "error"
});
return notOk({
code: "EMIT_FAILED",
summary: "Failed to emit contract",
why: error instanceof Error ? error.message : String(error),
meta: void 0
});
}
}
};
//#endregion
export { executeDbInit as a, executeDbUpdate as i, planSpacePath as n, ContractValidationError as o, executeDbVerify as r, createControlClient as t };
//# sourceMappingURL=client-DeJvLD2h.mjs.map

Sorry, the diff of this file is too big to display

import { n as executeContractEmit } from "./contract-emit-DJ8SRI7L.mjs";
import { A as formatStyledHeader, F as CliStructuredError$1, P as isVerbose, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, j as formatSuccessMessage, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
import { errorContractConfigMissing } from "@prisma-next/errors/control";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { dirname, join, relative, resolve } from "pathe";
//#region src/utils/formatters/emit.ts
/**
* Formats human-readable output for contract emit.
*/
function formatEmitOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const jsonPath = relative(process.cwd(), result.files.json);
const dtsPath = relative(process.cwd(), result.files.dts);
lines.push(`✔ Emitted contract.json → ${jsonPath}`);
lines.push(`✔ Emitted contract.d.ts → ${dtsPath}`);
lines.push(` storageHash: ${result.storageHash}`);
if (result.executionHash) lines.push(` executionHash: ${result.executionHash}`);
if (result.profileHash) lines.push(` profileHash: ${result.profileHash}`);
if (isVerbose(flags, 1)) lines.push(` Total time: ${result.timings.total}ms`);
return lines.join("\n");
}
/**
* Formats JSON output for contract emit.
*/
function formatEmitJson(result) {
const output = {
ok: true,
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
...result.profileHash ? { profileHash: result.profileHash } : {},
outDir: result.outDir,
files: result.files,
timings: result.timings
};
return JSON.stringify(output, null, 2);
}
//#endregion
//#region src/commands/contract-emit.ts
/**
* Pre-load the config just to compute display paths for the styled header. The
* actual emit work goes through `executeContractEmit`, which loads the config
* itself; the redundant load here is bounded and lets the header render before
* the emit spans start.
*/
async function resolveHeaderPaths(configOption, outputPath) {
const displayConfigPath = configOption ? relative(process.cwd(), resolve(configOption)) : "prisma-next.config.ts";
let config;
try {
config = await loadConfig(configOption);
} catch (error) {
if (error instanceof CliStructuredError$1) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: "Failed to load config" }));
}
const effectiveJsonPath = outputPath !== void 0 ? join(outputPath, "contract.json") : config.contract?.output;
if (!effectiveJsonPath) return notOk(errorContractConfigMissing({ why: "Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }" }));
try {
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = getEmittedArtifactPaths(effectiveJsonPath);
return ok({
displayConfigPath,
outputJsonPath,
outputDtsPath
});
} catch (error) {
return notOk(errorContractConfigMissing({ why: error instanceof Error ? error.message : String(error) }));
}
}
async function executeContractEmitCommand(options, flags, ui, startTime) {
const outputPath = options.outputPath !== void 0 ? resolve(options.outputPath) : void 0;
const headerPathsResult = await resolveHeaderPaths(options.config, outputPath);
if (!headerPathsResult.ok) return headerPathsResult;
const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;
if (!flags.json && !flags.quiet) ui.stderr(formatStyledHeader({
command: "contract emit",
description: "Emit your contract artifacts",
url: "https://pris.ly/contract-emit",
details: [
{
label: "config",
value: displayConfigPath
},
{
label: "contract",
value: relative(process.cwd(), outputJsonPath)
},
{
label: "types",
value: relative(process.cwd(), outputDtsPath)
}
],
flags
}));
const onProgress = createProgressAdapter({
ui,
flags
});
const configPath = options.config ? resolve(options.config) : "prisma-next.config.ts";
let result;
try {
result = await executeContractEmit({
configPath,
onProgress,
...ifDefined("outputPath", outputPath)
});
} catch (error) {
if (CliStructuredError$1.is(error)) return notOk(error);
return notOk(errorUnexpected("Unexpected error during contract emit", { why: error instanceof Error ? error.message : String(error) }));
}
if (result.validationWarning) ui.warn(result.validationWarning);
return ok({
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
profileHash: result.profileHash,
outDir: dirname(result.files.json),
files: result.files,
timings: { total: Date.now() - startTime }
});
}
function createContractEmitCommand() {
const command = new Command("emit");
setCommandDescriptions(command, "Emit your contract artifacts", "Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\ncontract.d.ts. The contract.json contains the canonical contract structure, and\ncontract.d.ts provides TypeScript types for type-safe query building.");
setCommandExamples(command, [
"prisma-next contract emit",
"prisma-next contract emit --config ./custom-config.ts",
"prisma-next contract emit --output-path ./generated"
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--output-path <dir>", "Directory to write contract.json and contract.d.ts into").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeContractEmitCommand(options, flags, ui, Date.now()), flags, ui, (emitResult) => {
if (flags.json) ui.output(formatEmitJson(emitResult));
else {
const output = formatEmitOutput(emitResult, flags);
if (output) ui.log(output);
if (!flags.quiet) ui.success(formatSuccessMessage(flags));
}
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createContractEmitCommand as t };
//# sourceMappingURL=contract-emit-BMJiefhy.mjs.map
{"version":3,"file":"contract-emit-BMJiefhy.mjs","names":["CliStructuredError"],"sources":["../src/utils/formatters/emit.ts","../src/commands/contract-emit.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport { relative } from 'pathe';\n\nimport type { GlobalFlags } from '../global-flags';\nimport { isVerbose } from './helpers';\n\n// EmitContractResult type for CLI output formatting (includes file paths)\nexport interface EmitContractResult {\n readonly storageHash: string;\n readonly executionHash?: string;\n readonly profileHash: string;\n readonly outDir: string;\n readonly files: {\n readonly json: string;\n readonly dts: string;\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for contract emit.\n */\nexport function formatEmitOutput(result: EmitContractResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n // Convert absolute paths to relative paths from cwd\n const jsonPath = relative(process.cwd(), result.files.json);\n const dtsPath = relative(process.cwd(), result.files.dts);\n\n lines.push(`✔ Emitted contract.json → ${jsonPath}`);\n lines.push(`✔ Emitted contract.d.ts → ${dtsPath}`);\n lines.push(` storageHash: ${result.storageHash}`);\n if (result.executionHash) {\n lines.push(` executionHash: ${result.executionHash}`);\n }\n if (result.profileHash) {\n lines.push(` profileHash: ${result.profileHash}`);\n }\n if (isVerbose(flags, 1)) {\n lines.push(` Total time: ${result.timings.total}ms`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for contract emit.\n */\nexport function formatEmitJson(result: EmitContractResult): string {\n const output = {\n ok: true,\n storageHash: result.storageHash,\n ...ifDefined('executionHash', result.executionHash),\n ...(result.profileHash ? { profileHash: result.profileHash } : {}),\n outDir: result.outDir,\n files: result.files,\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport { getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport { errorContractConfigMissing } from '@prisma-next/errors/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { dirname, join, relative, resolve } from 'pathe';\nimport { executeContractEmit } from '../control-api/operations/contract-emit';\nimport type { ContractEmitResult } from '../control-api/types';\nimport { CliStructuredError, errorUnexpected } from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport {\n type EmitContractResult,\n formatEmitJson,\n formatEmitOutput,\n} from '../utils/formatters/emit';\nimport { formatStyledHeader, formatSuccessMessage } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface ContractEmitOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly outputPath?: string;\n}\n\ninterface HeaderPaths {\n readonly displayConfigPath: string;\n readonly outputJsonPath: string;\n readonly outputDtsPath: string;\n}\n\n/**\n * Pre-load the config just to compute display paths for the styled header. The\n * actual emit work goes through `executeContractEmit`, which loads the config\n * itself; the redundant load here is bounded and lets the header render before\n * the emit spans start.\n */\nasync function resolveHeaderPaths(\n configOption: string | undefined,\n outputPath: string | undefined,\n): Promise<Result<HeaderPaths, CliStructuredError>> {\n const displayConfigPath = configOption\n ? relative(process.cwd(), resolve(configOption))\n : 'prisma-next.config.ts';\n\n let config: Awaited<ReturnType<typeof loadConfig>>;\n try {\n config = await loadConfig(configOption);\n } catch (error) {\n if (error instanceof CliStructuredError) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: 'Failed to load config',\n }),\n );\n }\n\n const effectiveJsonPath =\n outputPath !== undefined ? join(outputPath, 'contract.json') : config.contract?.output;\n\n if (!effectiveJsonPath) {\n return notOk(\n errorContractConfigMissing({\n why: 'Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n }),\n );\n }\n\n try {\n const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } =\n getEmittedArtifactPaths(effectiveJsonPath);\n return ok({ displayConfigPath, outputJsonPath, outputDtsPath });\n } catch (error) {\n return notOk(\n errorContractConfigMissing({\n why: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n}\n\nasync function executeContractEmitCommand(\n options: ContractEmitOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<EmitContractResult, CliStructuredError>> {\n const outputPath = options.outputPath !== undefined ? resolve(options.outputPath) : undefined;\n\n const headerPathsResult = await resolveHeaderPaths(options.config, outputPath);\n if (!headerPathsResult.ok) {\n return headerPathsResult;\n }\n const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;\n\n if (!flags.json && !flags.quiet) {\n ui.stderr(\n formatStyledHeader({\n command: 'contract emit',\n description: 'Emit your contract artifacts',\n url: 'https://pris.ly/contract-emit',\n details: [\n { label: 'config', value: displayConfigPath },\n { label: 'contract', value: relative(process.cwd(), outputJsonPath) },\n { label: 'types', value: relative(process.cwd(), outputDtsPath) },\n ],\n flags,\n }),\n );\n }\n\n const onProgress = createProgressAdapter({ ui, flags });\n const configPath = options.config ? resolve(options.config) : 'prisma-next.config.ts';\n\n let result: ContractEmitResult;\n try {\n result = await executeContractEmit({\n configPath,\n onProgress,\n ...ifDefined('outputPath', outputPath),\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected('Unexpected error during contract emit', {\n why: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n\n if (result.validationWarning) {\n ui.warn(result.validationWarning);\n }\n\n return ok({\n storageHash: result.storageHash,\n ...ifDefined('executionHash', result.executionHash),\n profileHash: result.profileHash,\n outDir: dirname(result.files.json),\n files: result.files,\n timings: { total: Date.now() - startTime },\n });\n}\n\nexport function createContractEmitCommand(): Command {\n const command = new Command('emit');\n setCommandDescriptions(\n command,\n 'Emit your contract artifacts',\n 'Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\\n' +\n 'contract.d.ts. The contract.json contains the canonical contract structure, and\\n' +\n 'contract.d.ts provides TypeScript types for type-safe query building.',\n );\n setCommandExamples(command, [\n 'prisma-next contract emit',\n 'prisma-next contract emit --config ./custom-config.ts',\n 'prisma-next contract emit --output-path ./generated',\n ]);\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--output-path <dir>', 'Directory to write contract.json and contract.d.ts into')\n .action(async (options: ContractEmitOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const startTime = Date.now();\n\n const result = await executeContractEmitCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (emitResult) => {\n if (flags.json) {\n ui.output(formatEmitJson(emitResult));\n } else {\n const output = formatEmitOutput(emitResult, flags);\n if (output) {\n ui.log(output);\n }\n if (!flags.quiet) {\n ui.success(formatSuccessMessage(flags));\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { rt as errorRuntime, z as errorContractConfigMissing } from "./command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-VMQJbAwl.mjs";
import { t as enrichContract } from "./contract-enrichment-gn9sWbPw.mjs";
import { createRequire } from "node:module";
import { loadConfig } from "@prisma-next/config-loader";
import { emit, getEmittedArtifactPaths } from "@prisma-next/emitter";
import { ifDefined } from "@prisma-next/utils/defined";
import { basename, dirname, join } from "pathe";
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { abortable } from "@prisma-next/utils/abortable";
//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
};
//#endregion
//#region src/utils/emit-queue.ts
/**
* Per-output FIFO queue for `executeContractEmit`.
*
* Ensures that at most one emit (load → resolve source → emit bytes → publish)
* runs per output JSON path at a time. Concurrent calls for the same path
* line up behind the in-flight one and run in submission order; the user-visible
* outcome is "last submission wins on disk" without any supersession bookkeeping.
*
* Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`
* when they stop publishing to a path, otherwise the module-global `Map`
* accumulates one entry per unique output path for the lifetime of the process.
*/
const emitQueues = /* @__PURE__ */ new Map();
function queueEmitByOutput(outputJsonPath, action) {
const next = (emitQueues.get(outputJsonPath) ?? Promise.resolve()).then(action, action);
emitQueues.set(outputJsonPath, next);
return next;
}
function disposeEmitQueue(outputJsonPath) {
emitQueues.delete(outputJsonPath);
}
//#endregion
//#region src/utils/publish-contract-artifact-pair.ts
function isRecord$1(value) {
return typeof value === "object" && value !== null;
}
function createTempArtifactPath(path, publicationToken, phase) {
return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);
}
async function readExistingArtifact(path) {
try {
return { content: await readFile(path, "utf-8") };
} catch (error) {
if (isRecord$1(error) && error["code"] === "ENOENT") return "remove";
throw error;
}
}
async function restoreArtifact(path, previous, publicationToken) {
if (previous === "remove") {
await rm(path, { force: true });
return;
}
const restorePath = createTempArtifactPath(path, publicationToken, "rollback");
await writeFile(restorePath, previous.content, "utf-8");
try {
await rename(restorePath, path);
} finally {
await rm(restorePath, { force: true });
}
}
function withRollbackFailureCause(error, rollbackFailures) {
const rollbackCause = new AggregateError(rollbackFailures, "Failed to restore published artifacts");
if (error instanceof Error) {
Object.defineProperty(error, "cause", {
value: rollbackCause,
configurable: true,
writable: true
});
return error;
}
return new Error(String(error), { cause: rollbackCause });
}
async function publishPairWithRollback(entries, publicationToken) {
const replaced = [];
try {
for (const entry of entries) {
await rename(entry.tempPath, entry.outputPath);
replaced.push(entry);
}
} catch (error) {
const rollbackFailures = (await Promise.allSettled(replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
if (rollbackFailures.length > 0) throw withRollbackFailureCause(error, rollbackFailures);
throw error;
}
}
async function publishContractArtifactPair({ outputJsonPath, outputDtsPath, contractJson, contractDts, publicationToken, beforePublish }) {
const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, "next");
const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, "next");
try {
await writeFile(tempJsonPath, contractJson, "utf-8");
await writeFile(tempDtsPath, contractDts, "utf-8");
if (await beforePublish?.() === false) return false;
const previousJson = await readExistingArtifact(outputJsonPath);
await publishPairWithRollback([{
tempPath: tempDtsPath,
outputPath: outputDtsPath,
previous: await readExistingArtifact(outputDtsPath)
}, {
tempPath: tempJsonPath,
outputPath: outputJsonPath,
previous: previousJson
}], publicationToken);
return true;
} finally {
await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);
}
}
//#endregion
//#region src/utils/validate-contract-deps.ts
const IMPORT_PATTERN = /import\s+type\s+.*?\s+from\s+['"](@[^/]+\/[^/'"]+)/g;
function extractPackageSpecifiers(dtsContent) {
const packages = /* @__PURE__ */ new Set();
for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {
const pkg = match[1];
if (pkg) packages.add(pkg);
}
return [...packages];
}
function validateContractDeps(dtsContent, projectRoot) {
const packages = extractPackageSpecifiers(dtsContent);
const resolve = createRequire(`${projectRoot}/package.json`);
const missing = [];
for (const pkg of packages) try {
resolve.resolve(`${pkg}/package.json`);
} catch {
missing.push(pkg);
}
if (missing.length === 0) return { missing };
return {
missing,
warning: [
"contract.d.ts imports types from packages that are not installed:",
missing.map((p) => ` - ${p}`).join("\n"),
"",
"Install them with your package manager:",
...missing.map((p) => ` ${p}`)
].join("\n")
};
}
//#endregion
//#region src/control-api/operations/contract-emit.ts
var contract_emit_exports = /* @__PURE__ */ __exportAll({ executeContractEmit: () => executeContractEmit });
const EMIT_ACTION = "emit";
function isRecord(value) {
return typeof value === "object" && value !== null;
}
function startSpan(onProgress, spanId, label) {
onProgress?.({
action: EMIT_ACTION,
kind: "spanStart",
spanId,
label
});
}
function endSpan(onProgress, spanId, outcome) {
onProgress?.({
action: EMIT_ACTION,
kind: "spanEnd",
spanId,
outcome
});
}
function failedToResolveContractSource(why, fix, meta) {
return errorRuntime("Failed to resolve contract source", {
why,
fix,
...ifDefined("meta", meta)
});
}
function diagnosticLocationSuffix(diagnostic) {
const sourceId = typeof diagnostic["sourceId"] === "string" ? diagnostic["sourceId"] : void 0;
const span = isRecord(diagnostic["span"]) ? diagnostic["span"] : void 0;
const start = span && isRecord(span["start"]) ? span["start"] : void 0;
const line = start && typeof start["line"] === "number" ? start["line"] : void 0;
const column = start && typeof start["column"] === "number" ? start["column"] : void 0;
if (sourceId && line !== void 0 && column !== void 0) return ` (${sourceId}:${line}:${column})`;
if (sourceId) return ` (${sourceId})`;
return "";
}
function mapDiagnosticsToIssues(diagnostics) {
const issues = [];
for (const raw of diagnostics) {
if (!isRecord(raw)) continue;
const code = typeof raw["code"] === "string" ? raw["code"] : "diagnostic";
const message = typeof raw["message"] === "string" ? raw["message"] : "";
issues.push({
kind: code,
message: `${message}${diagnosticLocationSuffix(raw)}`
});
}
return issues;
}
function validateProviderResult(providerResult) {
if (!isRecord(providerResult) || typeof providerResult["ok"] !== "boolean") return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed result shape.", "Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).")
};
if (providerResult["ok"]) {
if (!("value" in providerResult)) return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed success result: missing value.", "Ensure contract.source.load success payload is ok(Contract).")
};
return {
ok: true,
value: providerResult["value"]
};
}
const failure = providerResult["failure"];
if (!isRecord(failure) || typeof failure["summary"] !== "string" || !Array.isArray(failure["diagnostics"])) return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed failure result: expected summary and diagnostics.", "Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).")
};
return {
ok: false,
error: failedToResolveContractSource(String(failure["summary"]), "Fix contract source diagnostics and return ok(Contract).", {
diagnostics: failure["diagnostics"],
issues: mapDiagnosticsToIssues(failure["diagnostics"]),
...ifDefined("providerMeta", failure["meta"])
})
};
}
/**
* Canonical contract emit operation.
*
* This is the SINGLE publication path used by both the CLI command
* (`prisma-next contract emit`) and the Vite plugin
* (`@prisma-next/vite-plugin-contract-emit`). New callers must go through this
* function rather than re-implementing load → emit → publish.
*
* The whole flow (load config → resolve source → emit bytes → atomic publish)
* is serialized per output JSON path via `queueEmitByOutput`. Concurrent calls
* for the same output line up FIFO; the user-visible outcome is "last
* submission wins on disk" without any supersession bookkeeping. Within a
* single emit, `publishContractArtifactPair` stages temp files, renames
* `contract.d.ts` before `contract.json`, and attempts to restore the previous
* pair if either rename fails — so type-only consumers never observe a
* mismatched pair.
*
* @throws {CliStructuredError} on config/source/validation problems
* @throws {DOMException} `AbortError` if cancelled via `signal`
*/
async function executeContractEmit(options) {
const { configPath, outputPath, signal = new AbortController().signal, onProgress } = options;
const unlessAborted = abortable(signal);
const config = await unlessAborted(loadConfig(configPath));
if (!config.contract) throw errorContractConfigMissing({ why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }" });
const contractConfig = config.contract;
const effectiveOutput = outputPath !== void 0 ? join(outputPath, "contract.json") : contractConfig.output;
if (!effectiveOutput) throw errorContractConfigMissing({ why: "Contract config must have output path. This should not happen if defineConfig() was used." });
if (typeof contractConfig.source?.load !== "function") throw errorContractConfigMissing({ why: "Contract config must include a valid source provider object" });
let outputPaths;
try {
outputPaths = getEmittedArtifactPaths(effectiveOutput);
} catch (error) {
throw errorContractConfigMissing({ why: error instanceof Error ? error.message : String(error) });
}
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
return queueEmitByOutput(outputJsonPath, async () => {
const stack = createControlStack(config);
const sourceContext = {
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
composedExtensionContracts: stack.extensionContracts,
authoringContributions: stack.authoringContributions,
codecLookup: stack.codecLookup,
controlMutationDefaults: stack.controlMutationDefaults,
resolvedInputs: contractConfig.source.inputs ?? [],
capabilities: stack.capabilities
};
startSpan(onProgress, "resolveSource", "Resolving contract source...");
let providerResult;
try {
providerResult = await unlessAborted(contractConfig.source.load(sourceContext));
} catch (error) {
endSpan(onProgress, "resolveSource", "error");
if (signal.aborted || isRecord(error) && error["name"] === "AbortError") throw error;
throw failedToResolveContractSource(error instanceof Error ? error.message : String(error), "Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.");
}
const validatedContract = validateProviderResult(providerResult);
if (!validatedContract.ok) {
endSpan(onProgress, "resolveSource", "error");
throw validatedContract.error;
}
endSpan(onProgress, "resolveSource", "ok");
startSpan(onProgress, "emit", "Emitting contract...");
let emitResult;
try {
const familyInstance = config.family.create(stack);
const rawComponents = [
config.target,
config.adapter,
...config.extensionPacks ?? []
];
const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, rawComponents);
const enrichedIR = enrichContract(validatedContract.value, frameworkComponents);
const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);
const deserializedContract = familyInstance.deserializeContract(rawContractJson);
const { contractSerializer } = config.target;
const serializeContract = (c) => contractSerializer.serializeContract(c);
emitResult = await unlessAborted(emit(deserializedContract, stack, config.family.emission, {
outputJsonPath,
serializeContract,
...ifDefined("shouldPreserveEmpty", contractSerializer.shouldPreserveEmpty),
...ifDefined("sortStorage", contractSerializer.sortStorage)
}));
} catch (error) {
endSpan(onProgress, "emit", "error");
throw error;
}
endSpan(onProgress, "emit", "ok");
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
await publishContractArtifactPair({
outputJsonPath,
outputDtsPath,
contractJson: emitResult.contractJson,
contractDts: emitResult.contractDts,
publicationToken: String(process.hrtime.bigint())
});
const validationWarning = validateContractDeps(emitResult.contractDts, dirname(outputDtsPath)).warning;
return {
storageHash: emitResult.storageHash,
...ifDefined("executionHash", emitResult.executionHash),
profileHash: emitResult.profileHash,
files: {
json: outputJsonPath,
dts: outputDtsPath
},
...ifDefined("validationWarning", validationWarning)
};
});
}
//#endregion
export { executeContractEmit as n, disposeEmitQueue as r, contract_emit_exports as t };
//# sourceMappingURL=contract-emit-DJ8SRI7L.mjs.map
{"version":3,"file":"contract-emit-DJ8SRI7L.mjs","names":["isRecord"],"sources":["../src/utils/emit-queue.ts","../src/utils/publish-contract-artifact-pair.ts","../src/utils/validate-contract-deps.ts","../src/control-api/operations/contract-emit.ts"],"sourcesContent":["/**\n * Per-output FIFO queue for `executeContractEmit`.\n *\n * Ensures that at most one emit (load → resolve source → emit bytes → publish)\n * runs per output JSON path at a time. Concurrent calls for the same path\n * line up behind the in-flight one and run in submission order; the user-visible\n * outcome is \"last submission wins on disk\" without any supersession bookkeeping.\n *\n * Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`\n * when they stop publishing to a path, otherwise the module-global `Map`\n * accumulates one entry per unique output path for the lifetime of the process.\n */\nconst emitQueues = new Map<string, Promise<unknown>>();\n\nexport function queueEmitByOutput<T>(outputJsonPath: string, action: () => Promise<T>): Promise<T> {\n const previous = emitQueues.get(outputJsonPath) ?? Promise.resolve();\n // Continue regardless of the previous task's outcome — a failed emit must not\n // block subsequent ones. The current task's outcome propagates via `next`.\n const next = previous.then(action, action);\n emitQueues.set(outputJsonPath, next);\n return next;\n}\n\nexport function disposeEmitQueue(outputJsonPath: string): void {\n emitQueues.delete(outputJsonPath);\n}\n","import { readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'pathe';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction createTempArtifactPath(path: string, publicationToken: string, phase: string): string {\n return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);\n}\n\ntype PreviousArtifact = { readonly content: string } | 'remove';\n\nasync function readExistingArtifact(path: string): Promise<PreviousArtifact> {\n try {\n return { content: await readFile(path, 'utf-8') };\n } catch (error) {\n if (isRecord(error) && error['code'] === 'ENOENT') {\n return 'remove';\n }\n throw error;\n }\n}\n\nasync function restoreArtifact(\n path: string,\n previous: PreviousArtifact,\n publicationToken: string,\n): Promise<void> {\n if (previous === 'remove') {\n await rm(path, { force: true });\n return;\n }\n\n const restorePath = createTempArtifactPath(path, publicationToken, 'rollback');\n await writeFile(restorePath, previous.content, 'utf-8');\n try {\n await rename(restorePath, path);\n } finally {\n await rm(restorePath, { force: true });\n }\n}\n\ninterface PublishEntry {\n readonly tempPath: string;\n readonly outputPath: string;\n readonly previous: PreviousArtifact;\n}\n\nfunction withRollbackFailureCause(error: unknown, rollbackFailures: readonly unknown[]): Error {\n const rollbackCause = new AggregateError(\n rollbackFailures,\n 'Failed to restore published artifacts',\n );\n\n if (error instanceof Error) {\n Object.defineProperty(error, 'cause', {\n value: rollbackCause,\n configurable: true,\n writable: true,\n });\n return error;\n }\n\n return new Error(String(error), { cause: rollbackCause });\n}\n\nasync function publishPairWithRollback(\n entries: readonly PublishEntry[],\n publicationToken: string,\n): Promise<void> {\n const replaced: PublishEntry[] = [];\n try {\n for (const entry of entries) {\n await rename(entry.tempPath, entry.outputPath);\n replaced.push(entry);\n }\n } catch (error) {\n const rollbackResults = await Promise.allSettled(\n replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)),\n );\n const rollbackFailures = rollbackResults.flatMap((result) =>\n result.status === 'rejected' ? [result.reason] : [],\n );\n\n if (rollbackFailures.length > 0) {\n throw withRollbackFailureCause(error, rollbackFailures);\n }\n\n throw error;\n }\n}\n\nexport async function publishContractArtifactPair({\n outputJsonPath,\n outputDtsPath,\n contractJson,\n contractDts,\n publicationToken,\n beforePublish,\n}: {\n readonly outputJsonPath: string;\n readonly outputDtsPath: string;\n readonly contractJson: string;\n readonly contractDts: string;\n readonly publicationToken: string;\n readonly beforePublish?: () => Promise<boolean> | boolean;\n}): Promise<boolean> {\n const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, 'next');\n const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, 'next');\n\n try {\n await writeFile(tempJsonPath, contractJson, 'utf-8');\n await writeFile(tempDtsPath, contractDts, 'utf-8');\n\n if ((await beforePublish?.()) === false) {\n return false;\n }\n\n const previousJson = await readExistingArtifact(outputJsonPath);\n const previousDts = await readExistingArtifact(outputDtsPath);\n\n await publishPairWithRollback(\n [\n { tempPath: tempDtsPath, outputPath: outputDtsPath, previous: previousDts },\n { tempPath: tempJsonPath, outputPath: outputJsonPath, previous: previousJson },\n ],\n publicationToken,\n );\n return true;\n } finally {\n await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);\n }\n}\n","import { createRequire } from 'node:module';\n\nconst IMPORT_PATTERN = /import\\s+type\\s+.*?\\s+from\\s+['\"](@[^/]+\\/[^/'\"]+)/g;\n\nexport function extractPackageSpecifiers(dtsContent: string): string[] {\n const packages = new Set<string>();\n for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {\n const pkg = match[1];\n if (pkg) packages.add(pkg);\n }\n return [...packages];\n}\n\nexport interface ContractDepsValidation {\n readonly missing: readonly string[];\n readonly warning?: string;\n}\n\nexport function validateContractDeps(\n dtsContent: string,\n projectRoot: string,\n): ContractDepsValidation {\n const packages = extractPackageSpecifiers(dtsContent);\n const resolve = createRequire(`${projectRoot}/package.json`);\n\n const missing: string[] = [];\n for (const pkg of packages) {\n try {\n resolve.resolve(`${pkg}/package.json`);\n } catch {\n missing.push(pkg);\n }\n }\n\n if (missing.length === 0) {\n return { missing };\n }\n\n const list = missing.map((p) => ` - ${p}`).join('\\n');\n const warning = [\n 'contract.d.ts imports types from packages that are not installed:',\n list,\n '',\n 'Install them with your package manager:',\n ...missing.map((p) => ` ${p}`),\n ].join('\\n');\n\n return { missing, warning };\n}\n","import { mkdir } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { abortable } from '@prisma-next/utils/abortable';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport { dirname, join } from 'pathe';\nimport { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';\nimport { queueEmitByOutput } from '../../utils/emit-queue';\nimport { assertFrameworkComponentsCompatible } from '../../utils/framework-components';\nimport { publishContractArtifactPair } from '../../utils/publish-contract-artifact-pair';\nimport { validateContractDeps } from '../../utils/validate-contract-deps';\nimport { enrichContract } from '../contract-enrichment';\nimport type {\n ContractEmitOptions,\n ContractEmitResult,\n ControlActionName,\n OnControlProgress,\n} from '../types';\n\nconst EMIT_ACTION: ControlActionName = 'emit';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction startSpan(onProgress: OnControlProgress | undefined, spanId: string, label: string): void {\n onProgress?.({ action: EMIT_ACTION, kind: 'spanStart', spanId, label });\n}\n\nfunction endSpan(\n onProgress: OnControlProgress | undefined,\n spanId: string,\n outcome: 'ok' | 'error',\n): void {\n onProgress?.({ action: EMIT_ACTION, kind: 'spanEnd', spanId, outcome });\n}\n\nfunction failedToResolveContractSource(why: string, fix: string, meta?: Record<string, unknown>) {\n return errorRuntime('Failed to resolve contract source', {\n why,\n fix,\n ...ifDefined('meta', meta),\n });\n}\n\ntype ValidatedProviderResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: ReturnType<typeof errorRuntime> };\n\nfunction diagnosticLocationSuffix(diagnostic: Record<string, unknown>): string {\n const sourceId = typeof diagnostic['sourceId'] === 'string' ? diagnostic['sourceId'] : undefined;\n const span = isRecord(diagnostic['span']) ? diagnostic['span'] : undefined;\n const start = span && isRecord(span['start']) ? span['start'] : undefined;\n const line = start && typeof start['line'] === 'number' ? start['line'] : undefined;\n const column = start && typeof start['column'] === 'number' ? start['column'] : undefined;\n if (sourceId && line !== undefined && column !== undefined) {\n return ` (${sourceId}:${line}:${column})`;\n }\n if (sourceId) {\n return ` (${sourceId})`;\n }\n return '';\n}\n\nfunction mapDiagnosticsToIssues(\n diagnostics: readonly unknown[],\n): ReadonlyArray<{ readonly kind: string; readonly message: string }> {\n const issues: { readonly kind: string; readonly message: string }[] = [];\n for (const raw of diagnostics) {\n if (!isRecord(raw)) continue;\n const code = typeof raw['code'] === 'string' ? raw['code'] : 'diagnostic';\n const message = typeof raw['message'] === 'string' ? raw['message'] : '';\n issues.push({ kind: code, message: `${message}${diagnosticLocationSuffix(raw)}` });\n }\n return issues;\n}\n\nfunction validateProviderResult(providerResult: unknown): ValidatedProviderResult {\n if (!isRecord(providerResult) || typeof providerResult['ok'] !== 'boolean') {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed result shape.',\n 'Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).',\n ),\n };\n }\n\n if (providerResult['ok']) {\n if (!('value' in providerResult)) {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed success result: missing value.',\n 'Ensure contract.source.load success payload is ok(Contract).',\n ),\n };\n }\n return { ok: true, value: providerResult['value'] };\n }\n\n const failure = providerResult['failure'];\n if (\n !isRecord(failure) ||\n typeof failure['summary'] !== 'string' ||\n !Array.isArray(failure['diagnostics'])\n ) {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed failure result: expected summary and diagnostics.',\n 'Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).',\n ),\n };\n }\n return {\n ok: false,\n error: failedToResolveContractSource(\n String(failure['summary']),\n 'Fix contract source diagnostics and return ok(Contract).',\n {\n diagnostics: failure['diagnostics'],\n issues: mapDiagnosticsToIssues(failure['diagnostics']),\n ...ifDefined('providerMeta', failure['meta']),\n },\n ),\n };\n}\n\n/**\n * Canonical contract emit operation.\n *\n * This is the SINGLE publication path used by both the CLI command\n * (`prisma-next contract emit`) and the Vite plugin\n * (`@prisma-next/vite-plugin-contract-emit`). New callers must go through this\n * function rather than re-implementing load → emit → publish.\n *\n * The whole flow (load config → resolve source → emit bytes → atomic publish)\n * is serialized per output JSON path via `queueEmitByOutput`. Concurrent calls\n * for the same output line up FIFO; the user-visible outcome is \"last\n * submission wins on disk\" without any supersession bookkeeping. Within a\n * single emit, `publishContractArtifactPair` stages temp files, renames\n * `contract.d.ts` before `contract.json`, and attempts to restore the previous\n * pair if either rename fails — so type-only consumers never observe a\n * mismatched pair.\n *\n * @throws {CliStructuredError} on config/source/validation problems\n * @throws {DOMException} `AbortError` if cancelled via `signal`\n */\nexport async function executeContractEmit(\n options: ContractEmitOptions,\n): Promise<ContractEmitResult> {\n const { configPath, outputPath, signal = new AbortController().signal, onProgress } = options;\n const unlessAborted = abortable(signal);\n\n const config = await unlessAborted(loadConfig(configPath));\n\n if (!config.contract) {\n throw errorContractConfigMissing({\n why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n });\n }\n\n const contractConfig = config.contract;\n\n const effectiveOutput =\n outputPath !== undefined ? join(outputPath, 'contract.json') : contractConfig.output;\n\n if (!effectiveOutput) {\n throw errorContractConfigMissing({\n why: 'Contract config must have output path. This should not happen if defineConfig() was used.',\n });\n }\n\n if (typeof contractConfig.source?.load !== 'function') {\n throw errorContractConfigMissing({\n why: 'Contract config must include a valid source provider object',\n });\n }\n\n let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;\n try {\n outputPaths = getEmittedArtifactPaths(effectiveOutput);\n } catch (error) {\n throw errorContractConfigMissing({\n why: error instanceof Error ? error.message : String(error),\n });\n }\n const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;\n\n return queueEmitByOutput(outputJsonPath, async () => {\n const stack = createControlStack(config);\n\n const sourceContext = {\n composedExtensionPacks: stack.extensionPacks.map((p) => p.id),\n composedExtensionContracts: stack.extensionContracts,\n authoringContributions: stack.authoringContributions,\n codecLookup: stack.codecLookup,\n controlMutationDefaults: stack.controlMutationDefaults,\n resolvedInputs: contractConfig.source.inputs ?? [],\n capabilities: stack.capabilities,\n };\n\n startSpan(onProgress, 'resolveSource', 'Resolving contract source...');\n let providerResult: Awaited<ReturnType<typeof contractConfig.source.load>>;\n try {\n providerResult = await unlessAborted(contractConfig.source.load(sourceContext));\n } catch (error) {\n endSpan(onProgress, 'resolveSource', 'error');\n if (signal.aborted || (isRecord(error) && error['name'] === 'AbortError')) {\n throw error;\n }\n throw failedToResolveContractSource(\n error instanceof Error ? error.message : String(error),\n 'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',\n );\n }\n\n const validatedContract = validateProviderResult(providerResult);\n if (!validatedContract.ok) {\n endSpan(onProgress, 'resolveSource', 'error');\n throw validatedContract.error;\n }\n endSpan(onProgress, 'resolveSource', 'ok');\n\n startSpan(onProgress, 'emit', 'Emitting contract...');\n let emitResult: Awaited<ReturnType<typeof emit>>;\n try {\n const familyInstance = config.family.create(stack);\n const rawComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n rawComponents,\n );\n // Blind cast: `validateProviderResult` upstream has already\n // pinned `validatedContract.value` to the provider's loose\n // `Contract` envelope, but the local `Contract` type at this\n // call site is the precise structural interface. The cast just\n // defers the structural check by one statement so `enrichContract`\n // can decorate first; the subsequent serialize→deserialize round-trip\n // re-narrows the envelope into the precise type.\n const enrichedIR = enrichContract(\n validatedContract.value as unknown as Contract,\n frameworkComponents,\n );\n const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);\n const deserializedContract = familyInstance.deserializeContract(rawContractJson);\n // Each target's descriptor ships a `contractSerializer` SPI; the\n // framework canonicalizer threads its `serializeContract` so the\n // on-disk JSON envelope is constructed by target-owned code\n // rather than by walking the in-memory contract with\n // `Object.entries` (which would leak runtime-only class API\n // fields into the persisted shape). The optional `shouldPreserveEmpty`\n // and `sortStorage` hooks let the family contribute storage-specific\n // canonicalization rules without the framework importing family code.\n const { contractSerializer } = config.target;\n const serializeContract = (c: Contract): JsonObject =>\n contractSerializer.serializeContract(c);\n emitResult = await unlessAborted(\n emit(deserializedContract, stack, config.family.emission, {\n outputJsonPath,\n serializeContract,\n ...ifDefined('shouldPreserveEmpty', contractSerializer.shouldPreserveEmpty),\n ...ifDefined('sortStorage', contractSerializer.sortStorage),\n }),\n );\n } catch (error) {\n endSpan(onProgress, 'emit', 'error');\n throw error;\n }\n endSpan(onProgress, 'emit', 'ok');\n\n await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));\n await publishContractArtifactPair({\n outputJsonPath,\n outputDtsPath,\n contractJson: emitResult.contractJson,\n contractDts: emitResult.contractDts,\n publicationToken: String(process.hrtime.bigint()),\n });\n\n const validationWarning = validateContractDeps(\n emitResult.contractDts,\n dirname(outputDtsPath),\n ).warning;\n\n return {\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n files: {\n json: outputJsonPath,\n dts: outputDtsPath,\n },\n ...ifDefined('validationWarning', validationWarning),\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,MAAM,6BAAa,IAAI,IAA8B;AAErD,SAAgB,kBAAqB,gBAAwB,QAAsC;CAIjG,MAAM,QAHW,WAAW,IAAI,cAAc,KAAK,QAAQ,QAAQ,EAAA,CAG7C,KAAK,QAAQ,MAAM;CACzC,WAAW,IAAI,gBAAgB,IAAI;CACnC,OAAO;AACT;AAEA,SAAgB,iBAAiB,gBAA8B;CAC7D,WAAW,OAAO,cAAc;AAClC;;;ACtBA,SAASA,WAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,uBAAuB,MAAc,kBAA0B,OAAuB;CAC7F,OAAO,KAAK,QAAQ,IAAI,GAAG,IAAI,SAAS,IAAI,EAAE,GAAG,QAAQ,IAAI,GAAG,iBAAiB,GAAG,MAAM,KAAK;AACjG;AAIA,eAAe,qBAAqB,MAAyC;CAC3E,IAAI;EACF,OAAO,EAAE,SAAS,MAAM,SAAS,MAAM,OAAO,EAAE;CAClD,SAAS,OAAO;EACd,IAAIA,WAAS,KAAK,KAAK,MAAM,YAAY,UACvC,OAAO;EAET,MAAM;CACR;AACF;AAEA,eAAe,gBACb,MACA,UACA,kBACe;CACf,IAAI,aAAa,UAAU;EACzB,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;EAC9B;CACF;CAEA,MAAM,cAAc,uBAAuB,MAAM,kBAAkB,UAAU;CAC7E,MAAM,UAAU,aAAa,SAAS,SAAS,OAAO;CACtD,IAAI;EACF,MAAM,OAAO,aAAa,IAAI;CAChC,UAAU;EACR,MAAM,GAAG,aAAa,EAAE,OAAO,KAAK,CAAC;CACvC;AACF;AAQA,SAAS,yBAAyB,OAAgB,kBAA6C;CAC7F,MAAM,gBAAgB,IAAI,eACxB,kBACA,uCACF;CAEA,IAAI,iBAAiB,OAAO;EAC1B,OAAO,eAAe,OAAO,SAAS;GACpC,OAAO;GACP,cAAc;GACd,UAAU;EACZ,CAAC;EACD,OAAO;CACT;CAEA,OAAO,IAAI,MAAM,OAAO,KAAK,GAAG,EAAE,OAAO,cAAc,CAAC;AAC1D;AAEA,eAAe,wBACb,SACA,kBACe;CACf,MAAM,WAA2B,CAAC;CAClC,IAAI;EACF,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,MAAM,UAAU,MAAM,UAAU;GAC7C,SAAS,KAAK,KAAK;EACrB;CACF,SAAS,OAAO;EAId,MAAM,oBAAmB,MAHK,QAAQ,WACpC,SAAS,KAAK,UAAU,gBAAgB,MAAM,YAAY,MAAM,UAAU,gBAAgB,CAAC,CAC7F,EAAA,CACyC,SAAS,WAChD,OAAO,WAAW,aAAa,CAAC,OAAO,MAAM,IAAI,CAAC,CACpD;EAEA,IAAI,iBAAiB,SAAS,GAC5B,MAAM,yBAAyB,OAAO,gBAAgB;EAGxD,MAAM;CACR;AACF;AAEA,eAAsB,4BAA4B,EAChD,gBACA,eACA,cACA,aACA,kBACA,iBAQmB;CACnB,MAAM,eAAe,uBAAuB,gBAAgB,kBAAkB,MAAM;CACpF,MAAM,cAAc,uBAAuB,eAAe,kBAAkB,MAAM;CAElF,IAAI;EACF,MAAM,UAAU,cAAc,cAAc,OAAO;EACnD,MAAM,UAAU,aAAa,aAAa,OAAO;EAEjD,IAAK,MAAM,gBAAgB,MAAO,OAChC,OAAO;EAGT,MAAM,eAAe,MAAM,qBAAqB,cAAc;EAG9D,MAAM,wBACJ,CACE;GAAE,UAAU;GAAa,YAAY;GAAe,UAAU,MAJxC,qBAAqB,aAAa;EAIkB,GAC1E;GAAE,UAAU;GAAc,YAAY;GAAgB,UAAU;EAAa,CAC/E,GACA,gBACF;EACA,OAAO;CACT,UAAU;EACR,MAAM,QAAQ,WAAW,CAAC,GAAG,cAAc,EAAE,OAAO,KAAK,CAAC,GAAG,GAAG,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC;CAChG;AACF;;;ACnIA,MAAM,iBAAiB;AAEvB,SAAgB,yBAAyB,YAA8B;CACrE,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,SAAS,WAAW,SAAS,cAAc,GAAG;EACvD,MAAM,MAAM,MAAM;EAClB,IAAI,KAAK,SAAS,IAAI,GAAG;CAC3B;CACA,OAAO,CAAC,GAAG,QAAQ;AACrB;AAOA,SAAgB,qBACd,YACA,aACwB;CACxB,MAAM,WAAW,yBAAyB,UAAU;CACpD,MAAM,UAAU,cAAc,GAAG,YAAY,cAAc;CAE3D,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,OAAO,UAChB,IAAI;EACF,QAAQ,QAAQ,GAAG,IAAI,cAAc;CACvC,QAAQ;EACN,QAAQ,KAAK,GAAG;CAClB;CAGF,IAAI,QAAQ,WAAW,GACrB,OAAO,EAAE,QAAQ;CAYnB,OAAO;EAAE;EAAS,SARF;GACd;GAFW,QAAQ,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,IAG5C;GACH;GACA;GACA,GAAG,QAAQ,KAAK,MAAM,KAAK,GAAG;EAChC,CAAC,CAAC,KAAK,IAEiB;CAAE;AAC5B;;;;AC1BA,MAAM,cAAiC;AAEvC,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,UAAU,YAA2C,QAAgB,OAAqB;CACjG,aAAa;EAAE,QAAQ;EAAa,MAAM;EAAa;EAAQ;CAAM,CAAC;AACxE;AAEA,SAAS,QACP,YACA,QACA,SACM;CACN,aAAa;EAAE,QAAQ;EAAa,MAAM;EAAW;EAAQ;CAAQ,CAAC;AACxE;AAEA,SAAS,8BAA8B,KAAa,KAAa,MAAgC;CAC/F,OAAO,aAAa,qCAAqC;EACvD;EACA;EACA,GAAG,UAAU,QAAQ,IAAI;CAC3B,CAAC;AACH;AAMA,SAAS,yBAAyB,YAA6C;CAC7E,MAAM,WAAW,OAAO,WAAW,gBAAgB,WAAW,WAAW,cAAc,KAAA;CACvF,MAAM,OAAO,SAAS,WAAW,OAAO,IAAI,WAAW,UAAU,KAAA;CACjE,MAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ,IAAI,KAAK,WAAW,KAAA;CAChE,MAAM,OAAO,SAAS,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,KAAA;CAC1E,MAAM,SAAS,SAAS,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY,KAAA;CAChF,IAAI,YAAY,SAAS,KAAA,KAAa,WAAW,KAAA,GAC/C,OAAO,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO;CAEzC,IAAI,UACF,OAAO,KAAK,SAAS;CAEvB,OAAO;AACT;AAEA,SAAS,uBACP,aACoE;CACpE,MAAM,SAAgE,CAAC;CACvE,KAAK,MAAM,OAAO,aAAa;EAC7B,IAAI,CAAC,SAAS,GAAG,GAAG;EACpB,MAAM,OAAO,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;EAC7D,MAAM,UAAU,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;EACtE,OAAO,KAAK;GAAE,MAAM;GAAM,SAAS,GAAG,UAAU,yBAAyB,GAAG;EAAI,CAAC;CACnF;CACA,OAAO;AACT;AAEA,SAAS,uBAAuB,gBAAkD;CAChF,IAAI,CAAC,SAAS,cAAc,KAAK,OAAO,eAAe,UAAU,WAC/D,OAAO;EACL,IAAI;EACJ,OAAO,8BACL,6DACA,0FACF;CACF;CAGF,IAAI,eAAe,OAAO;EACxB,IAAI,EAAE,WAAW,iBACf,OAAO;GACL,IAAI;GACJ,OAAO,8BACL,8EACA,8DACF;EACF;EAEF,OAAO;GAAE,IAAI;GAAM,OAAO,eAAe;EAAS;CACpD;CAEA,MAAM,UAAU,eAAe;CAC/B,IACE,CAAC,SAAS,OAAO,KACjB,OAAO,QAAQ,eAAe,YAC9B,CAAC,MAAM,QAAQ,QAAQ,cAAc,GAErC,OAAO;EACL,IAAI;EACJ,OAAO,8BACL,iGACA,wFACF;CACF;CAEF,OAAO;EACL,IAAI;EACJ,OAAO,8BACL,OAAO,QAAQ,UAAU,GACzB,4DACA;GACE,aAAa,QAAQ;GACrB,QAAQ,uBAAuB,QAAQ,cAAc;GACrD,GAAG,UAAU,gBAAgB,QAAQ,OAAO;EAC9C,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,oBACpB,SAC6B;CAC7B,MAAM,EAAE,YAAY,YAAY,SAAS,IAAI,gBAAgB,CAAC,CAAC,QAAQ,eAAe;CACtF,MAAM,gBAAgB,UAAU,MAAM;CAEtC,MAAM,SAAS,MAAM,cAAc,WAAW,UAAU,CAAC;CAEzD,IAAI,CAAC,OAAO,UACV,MAAM,2BAA2B,EAC/B,KAAK,yGACP,CAAC;CAGH,MAAM,iBAAiB,OAAO;CAE9B,MAAM,kBACJ,eAAe,KAAA,IAAY,KAAK,YAAY,eAAe,IAAI,eAAe;CAEhF,IAAI,CAAC,iBACH,MAAM,2BAA2B,EAC/B,KAAK,4FACP,CAAC;CAGH,IAAI,OAAO,eAAe,QAAQ,SAAS,YACzC,MAAM,2BAA2B,EAC/B,KAAK,8DACP,CAAC;CAGH,IAAI;CACJ,IAAI;EACF,cAAc,wBAAwB,eAAe;CACvD,SAAS,OAAO;EACd,MAAM,2BAA2B,EAC/B,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC5D,CAAC;CACH;CACA,MAAM,EAAE,UAAU,gBAAgB,SAAS,kBAAkB;CAE7D,OAAO,kBAAkB,gBAAgB,YAAY;EACnD,MAAM,QAAQ,mBAAmB,MAAM;EAEvC,MAAM,gBAAgB;GACpB,wBAAwB,MAAM,eAAe,KAAK,MAAM,EAAE,EAAE;GAC5D,4BAA4B,MAAM;GAClC,wBAAwB,MAAM;GAC9B,aAAa,MAAM;GACnB,yBAAyB,MAAM;GAC/B,gBAAgB,eAAe,OAAO,UAAU,CAAC;GACjD,cAAc,MAAM;EACtB;EAEA,UAAU,YAAY,iBAAiB,8BAA8B;EACrE,IAAI;EACJ,IAAI;GACF,iBAAiB,MAAM,cAAc,eAAe,OAAO,KAAK,aAAa,CAAC;EAChF,SAAS,OAAO;GACd,QAAQ,YAAY,iBAAiB,OAAO;GAC5C,IAAI,OAAO,WAAY,SAAS,KAAK,KAAK,MAAM,YAAY,cAC1D,MAAM;GAER,MAAM,8BACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACrD,yFACF;EACF;EAEA,MAAM,oBAAoB,uBAAuB,cAAc;EAC/D,IAAI,CAAC,kBAAkB,IAAI;GACzB,QAAQ,YAAY,iBAAiB,OAAO;GAC5C,MAAM,kBAAkB;EAC1B;EACA,QAAQ,YAAY,iBAAiB,IAAI;EAEzC,UAAU,YAAY,QAAQ,sBAAsB;EACpD,IAAI;EACJ,IAAI;GACF,MAAM,iBAAiB,OAAO,OAAO,OAAO,KAAK;GACjD,MAAM,gBAAgB;IAAC,OAAO;IAAQ,OAAO;IAAS,GAAI,OAAO,kBAAkB,CAAC;GAAE;GACtF,MAAM,sBAAsB,oCAC1B,OAAO,OAAO,UACd,OAAO,OAAO,UACd,aACF;GAQA,MAAM,aAAa,eACjB,kBAAkB,OAClB,mBACF;GACA,MAAM,kBAAkB,OAAO,OAAO,mBAAmB,kBAAkB,UAAU;GACrF,MAAM,uBAAuB,eAAe,oBAAoB,eAAe;GAS/E,MAAM,EAAE,uBAAuB,OAAO;GACtC,MAAM,qBAAqB,MACzB,mBAAmB,kBAAkB,CAAC;GACxC,aAAa,MAAM,cACjB,KAAK,sBAAsB,OAAO,OAAO,OAAO,UAAU;IACxD;IACA;IACA,GAAG,UAAU,uBAAuB,mBAAmB,mBAAmB;IAC1E,GAAG,UAAU,eAAe,mBAAmB,WAAW;GAC5D,CAAC,CACH;EACF,SAAS,OAAO;GACd,QAAQ,YAAY,QAAQ,OAAO;GACnC,MAAM;EACR;EACA,QAAQ,YAAY,QAAQ,IAAI;EAEhC,MAAM,cAAc,MAAM,QAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC,CAAC;EACvE,MAAM,4BAA4B;GAChC;GACA;GACA,cAAc,WAAW;GACzB,aAAa,WAAW;GACxB,kBAAkB,OAAO,QAAQ,OAAO,OAAO,CAAC;EAClD,CAAC;EAED,MAAM,oBAAoB,qBACxB,WAAW,aACX,QAAQ,aAAa,CACvB,CAAC,CAAC;EAEF,OAAO;GACL,aAAa,WAAW;GACxB,GAAG,UAAU,iBAAiB,WAAW,aAAa;GACtD,aAAa,WAAW;GACxB,OAAO;IACL,MAAM;IACN,KAAK;GACP;GACA,GAAG,UAAU,qBAAqB,iBAAiB;EACrD;CACF,CAAC;AACH"}
import { _ as createTerminalUI, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as inspectLiveSchema } from "./inspect-live-schema-xS6VqjHE.mjs";
import { Command } from "commander";
import { notOk, ok } from "@prisma-next/utils/result";
import { dirname, relative, resolve } from "pathe";
import { errorRuntime } from "@prisma-next/errors/execution";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { printPsl } from "@prisma-next/psl-printer";
//#region src/commands/contract-infer-paths.ts
/**
* Resolves the output path for the inferred PSL contract.
*
* Priority:
* 1. --output <path> flag (resolved relative to cwd)
* 2. contract.prisma next to config.contract.output
* 3. Canonical default: contract.prisma in the config directory
*/
function resolveContractInferOutputPath(options, contractOutput) {
const configDir = options.config ? dirname(resolve(process.cwd(), options.config)) : process.cwd();
if (options.output) return resolve(process.cwd(), options.output);
if (contractOutput) return resolve(dirname(resolve(configDir, contractOutput)), "contract.prisma");
return resolve(configDir, "contract.prisma");
}
//#endregion
//#region src/commands/contract-infer.ts
async function executeContractInferCommand(options, flags, ui, startTime) {
const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {
commandName: "contract infer",
description: "Infer a PSL contract from the live database schema",
url: "https://pris.ly/contract-infer"
});
if (!inspectResult.ok) return inspectResult;
const { config, target, meta, pslContractAst, pslBlockDescriptors } = inspectResult.value;
if (!pslContractAst) return notOk(errorRuntime("contract infer is not supported for this family", {
why: "The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.",
fix: "Use a family that supports contract inference (e.g. SQL/Postgres)."
}));
const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
const pslContent = printPsl(pslContractAst, { pslBlockDescriptors });
if (existsSync(outputPath) && !flags.json && !flags.quiet) ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, pslContent, "utf-8");
const pslPath = relative(process.cwd(), outputPath);
if (!flags.json && !flags.quiet) ui.stderr(`\u2714 Contract written to ${pslPath}`);
return ok({
ok: true,
summary: "Contract inferred successfully",
target,
psl: { path: pslPath },
meta,
timings: { total: Date.now() - startTime }
});
}
function createContractInferCommand() {
const command = new Command("infer");
setCommandDescriptions(command, "Infer a PSL contract from the live database schema", "Reads the live database schema and writes an inferred PSL contract to disk.\nThis command stops at `contract.prisma`; follow it with `contract emit` and\n`db sign` as separate steps.");
setCommandExamples(command, [
"prisma-next contract infer --db $DATABASE_URL",
"prisma-next contract infer --db $DATABASE_URL --output ./src/prisma/contract.prisma",
"prisma-next contract infer --db $DATABASE_URL --json"
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--output <path>", "Write the inferred PSL contract to the specified path").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeContractInferCommand(options, flags, ui, Date.now()), flags, ui, (value) => {
if (flags.json) ui.output(JSON.stringify(value, null, 2));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createContractInferCommand as t };
//# sourceMappingURL=contract-infer-_tstfy3k.mjs.map
{"version":3,"file":"contract-infer-_tstfy3k.mjs","names":[],"sources":["../src/commands/contract-infer-paths.ts","../src/commands/contract-infer.ts"],"sourcesContent":["import { dirname, resolve } from 'pathe';\n\ninterface ContractInferPathOptions {\n readonly output?: string;\n readonly config?: string;\n}\n\n/**\n * Resolves the output path for the inferred PSL contract.\n *\n * Priority:\n * 1. --output <path> flag (resolved relative to cwd)\n * 2. contract.prisma next to config.contract.output\n * 3. Canonical default: contract.prisma in the config directory\n */\nexport function resolveContractInferOutputPath(\n options: ContractInferPathOptions,\n contractOutput: string | undefined,\n): string {\n const configDir = options.config\n ? dirname(resolve(process.cwd(), options.config))\n : process.cwd();\n\n if (options.output) {\n return resolve(process.cwd(), options.output);\n }\n if (contractOutput) {\n const contractPath = resolve(configDir, contractOutput);\n return resolve(dirname(contractPath), 'contract.prisma');\n }\n return resolve(configDir, 'contract.prisma');\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { errorRuntime } from '@prisma-next/errors/execution';\nimport { printPsl } from '@prisma-next/psl-printer';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { dirname, relative } from 'pathe';\nimport type { CliStructuredError } from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport { resolveContractInferOutputPath } from './contract-infer-paths';\nimport {\n type InspectLiveSchemaOptions,\n type InspectLiveSchemaResult,\n inspectLiveSchema,\n} from './inspect-live-schema';\n\ninterface ContractInferOptions extends InspectLiveSchemaOptions {\n readonly output?: string;\n}\n\ninterface ContractInferSuccessResult {\n readonly ok: true;\n readonly summary: string;\n readonly target: InspectLiveSchemaResult['target'];\n readonly psl: {\n readonly path: string;\n };\n readonly meta: InspectLiveSchemaResult['meta'];\n readonly timings: {\n readonly total: number;\n };\n}\n\nasync function executeContractInferCommand(\n options: ContractInferOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<ContractInferSuccessResult, CliStructuredError>> {\n const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {\n commandName: 'contract infer',\n description: 'Infer a PSL contract from the live database schema',\n url: 'https://pris.ly/contract-infer',\n });\n\n if (!inspectResult.ok) {\n return inspectResult;\n }\n\n const { config, target, meta, pslContractAst, pslBlockDescriptors } = inspectResult.value;\n\n if (!pslContractAst) {\n return notOk(\n errorRuntime('contract infer is not supported for this family', {\n why: 'The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.',\n fix: 'Use a family that supports contract inference (e.g. SQL/Postgres).',\n }),\n );\n }\n\n const outputPath = resolveContractInferOutputPath(options, config.contract?.output);\n const pslContent = printPsl(pslContractAst, { pslBlockDescriptors: pslBlockDescriptors });\n\n if (existsSync(outputPath) && !flags.json && !flags.quiet) {\n ui.stderr(`\\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);\n }\n\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, pslContent, 'utf-8');\n\n const pslPath = relative(process.cwd(), outputPath);\n if (!flags.json && !flags.quiet) {\n ui.stderr(`\\u2714 Contract written to ${pslPath}`);\n }\n\n return ok({\n ok: true,\n summary: 'Contract inferred successfully',\n target,\n psl: {\n path: pslPath,\n },\n meta,\n timings: {\n total: Date.now() - startTime,\n },\n });\n}\n\nexport function createContractInferCommand(): Command {\n const command = new Command('infer');\n setCommandDescriptions(\n command,\n 'Infer a PSL contract from the live database schema',\n 'Reads the live database schema and writes an inferred PSL contract to disk.\\n' +\n 'This command stops at `contract.prisma`; follow it with `contract emit` and\\n' +\n '`db sign` as separate steps.',\n );\n setCommandExamples(command, [\n 'prisma-next contract infer --db $DATABASE_URL',\n 'prisma-next contract infer --db $DATABASE_URL --output ./src/prisma/contract.prisma',\n 'prisma-next contract infer --db $DATABASE_URL --json',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--output <path>', 'Write the inferred PSL contract to the specified path')\n .action(async (options: ContractInferOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const startTime = Date.now();\n\n const result = await executeContractInferCommand(options, flags, ui, startTime);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value, null, 2));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,SAAgB,+BACd,SACA,gBACQ;CACR,MAAM,YAAY,QAAQ,SACtB,QAAQ,QAAQ,QAAQ,IAAI,GAAG,QAAQ,MAAM,CAAC,IAC9C,QAAQ,IAAI;CAEhB,IAAI,QAAQ,QACV,OAAO,QAAQ,QAAQ,IAAI,GAAG,QAAQ,MAAM;CAE9C,IAAI,gBAEF,OAAO,QAAQ,QADM,QAAQ,WAAW,cACN,CAAC,GAAG,iBAAiB;CAEzD,OAAO,QAAQ,WAAW,iBAAiB;AAC7C;;;ACQA,eAAe,4BACb,SACA,OACA,IACA,WACiE;CACjE,MAAM,gBAAgB,MAAM,kBAAkB,SAAS,OAAO,IAAI,WAAW;EAC3E,aAAa;EACb,aAAa;EACb,KAAK;CACP,CAAC;CAED,IAAI,CAAC,cAAc,IACjB,OAAO;CAGT,MAAM,EAAE,QAAQ,QAAQ,MAAM,gBAAgB,wBAAwB,cAAc;CAEpF,IAAI,CAAC,gBACH,OAAO,MACL,aAAa,mDAAmD;EAC9D,KAAK;EACL,KAAK;CACP,CAAC,CACH;CAGF,MAAM,aAAa,+BAA+B,SAAS,OAAO,UAAU,MAAM;CAClF,MAAM,aAAa,SAAS,gBAAgB,EAAuB,oBAAoB,CAAC;CAExF,IAAI,WAAW,UAAU,KAAK,CAAC,MAAM,QAAQ,CAAC,MAAM,OAClD,GAAG,OAAO,qCAAqC,SAAS,QAAQ,IAAI,GAAG,UAAU,GAAG;CAGtF,UAAU,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;CAClD,cAAc,YAAY,YAAY,OAAO;CAE7C,MAAM,UAAU,SAAS,QAAQ,IAAI,GAAG,UAAU;CAClD,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OACxB,GAAG,OAAO,8BAA8B,SAAS;CAGnD,OAAO,GAAG;EACR,IAAI;EACJ,SAAS;EACT;EACA,KAAK,EACH,MAAM,QACR;EACA;EACA,SAAS,EACP,OAAO,KAAK,IAAI,IAAI,UACtB;CACF,CAAC;AACH;AAEA,SAAgB,6BAAsC;CACpD,MAAM,UAAU,IAAI,QAAQ,OAAO;CACnC,uBACE,SACA,sDACA,wLAGF;CACA,mBAAmB,SAAS;EAC1B;EACA;EACA;CACF,CAAC;CACD,iBAAiB,OAAO,CAAC,CACtB,OAAO,cAAc,4BAA4B,CAAC,CAClD,OAAO,mBAAmB,+BAA+B,CAAC,CAC1D,OAAO,mBAAmB,uDAAuD,CAAC,CAClF,OAAO,OAAO,YAAkC;EAC/C,MAAM,QAAQ,uBAAuB,OAAO;EAC5C,MAAM,KAAK,iBAAiB,KAAK;EAIjC,MAAM,WAAW,aAAa,MADT,4BAA4B,SAAS,OAAO,IAF/C,KAAK,IAEsD,CAAC,GACxC,OAAO,KAAK,UAAU;GAC1D,IAAI,MAAM,MACR,GAAG,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;EAE5C,CAAC;EAED,QAAQ,KAAK,QAAQ;CACvB,CAAC;CAEH,OAAO;AACT"}
import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, G as errorHashMismatch, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, Y as errorMarkerMissing, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, o as resolveContractPath, rt as errorRuntime, s as resolveMigrationPaths, st as errorTargetMismatch, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { o as ContractValidationError, t as createControlClient } from "./client-DeJvLD2h.mjs";
import { c as formatVerifyOutput, i as formatSchemaVerifyOutput, r as formatSchemaVerifyJson, s as formatVerifyJson } from "./verify-C4RazDE1.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { relative, resolve } from "pathe";
import { readFile } from "node:fs/promises";
import { VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, createControlStack } from "@prisma-next/framework-components/control";
//#region src/utils/combine-verify-results.ts
/**
* Collapse the aggregate verifier's per-space contract-satisfaction results
* into a single {@link VerifyDatabaseSchemaResult} for the CLI display
* surface, and carry the deduplicated unclaimed-elements list alongside it.
* Concatenates the issue lists across spaces; uses the app space's result as
* the structural envelope (storage hash, target). Extras are already
* stripped from each per-space result, so nothing here duplicates an
* unclaimed element per space.
*
* **Unclaimed disposition.** In strict mode a non-empty `unclaimed` list fails
* the combined verdict (`ok: false`); in lenient mode it is carried for
* informational rendering only. The list itself is returned unchanged for the
* renderer.
*
* **Summary policy.** Preserve the per-family phrasing whenever the
* combined `ok` flag agrees with the app space's `ok` flag — this is
* the common case (single-family deployments, single-app deployments)
* and the family's "satisfies / does not satisfy contract" phrasing
* stays user-visible. When the app passes but an extension fails (or
* vice versa) the app's summary contradicts the envelope, so fall back
* to the first failing space's summary. This keeps family phrasing
* intact and the envelope internally consistent (`ok: false` ↔ failure
* summary).
*/
function combineVerifyResults(perSpace, appSpaceId, strict, unclaimed) {
const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;
if (appResult === void 0) throw new Error("Aggregate verifier returned no per-space verify results — this is a wiring bug.");
let okAll = true;
let firstFailure;
let issues = [];
let warningIssues = [];
for (const [, result] of perSpace) {
if (!result.ok) {
okAll = false;
if (firstFailure === void 0) firstFailure = result;
}
issues = [...issues, ...result.schema.issues];
warningIssues = [...warningIssues, ...result.schema.warnings?.issues ?? []];
}
const unclaimedFails = strict && unclaimed.length > 0;
const ok = okAll && !unclaimedFails;
const summary = okAll ? unclaimedFails ? `Database schema has ${unclaimed.length} unclaimed element${unclaimed.length === 1 ? "" : "s"} (not in any contract)` : appResult.summary : appResult.ok ? firstFailure?.summary ?? appResult.summary : appResult.summary;
return {
result: {
ok,
...ok ? {} : { code: appResult.code ?? "CONTRACT.MARKER_REQUIRED" },
summary,
contract: appResult.contract,
target: appResult.target,
schema: {
issues,
warnings: { issues: warningIssues }
},
meta: { strict },
timings: { total: 0 }
},
unclaimed
};
}
//#endregion
//#region src/commands/db-verify.ts
/**
* Maps a VerifyDatabaseResult failure to a CliStructuredError.
*/
function mapVerifyFailure(verifyResult) {
if (!verifyResult.ok && verifyResult.code) {
if (verifyResult.code === VERIFY_CODE_MARKER_MISSING) return errorMarkerMissing();
if (verifyResult.code === VERIFY_CODE_HASH_MISMATCH) {
const storageMatch = verifyResult.marker?.storageHash === verifyResult.contract.storageHash;
const profileMatch = !verifyResult.contract.profileHash || verifyResult.marker?.profileHash === verifyResult.contract.profileHash;
if (!storageMatch) return errorHashMismatch({
why: "Contract storageHash does not match database marker",
expected: verifyResult.contract.storageHash,
...ifDefined("actual", verifyResult.marker?.storageHash)
});
return errorHashMismatch({
why: profileMatch ? "Contract hash does not match database marker" : "Contract profileHash does not match database marker",
...ifDefined("expected", verifyResult.contract.profileHash),
...ifDefined("actual", verifyResult.marker?.profileHash)
});
}
if (verifyResult.code === VERIFY_CODE_TARGET_MISMATCH) return errorTargetMismatch(verifyResult.target.expected, verifyResult.target.actual ?? "unknown");
}
return errorRuntime(verifyResult.summary);
}
function errorInvalidVerifyMode(options) {
return new CliStructuredError("CLI.INVALID_VERIFY_MODE", "Invalid verify mode", {
why: options.why,
fix: options.fix,
docsUrl: "https://pris.ly/db-verify"
});
}
function resolveDbVerifyMode(options) {
if (options.markerOnly && options.schemaOnly) return notOk(errorInvalidVerifyMode({
why: "`--marker-only` and `--schema-only` cannot be used together",
fix: "Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema."
}));
if (options.markerOnly && options.strict) return notOk(errorInvalidVerifyMode({
why: "`--strict` requires schema verification, but `--marker-only` skips it",
fix: "Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode."
}));
if (options.schemaOnly) return ok("schema-only");
if (options.markerOnly) return ok("marker-only");
return ok("full");
}
function formatDbVerifyModeLabel(mode, strict) {
if (mode === "marker-only") return "marker only";
if (mode === "schema-only") return `schema only (${strict ? "strict" : "tolerant"})`;
return `full (marker + schema, ${strict ? "strict" : "tolerant"})`;
}
function formatDbVerifyInvocation(mode, strict) {
const args = ["db verify"];
if (mode === "marker-only") args.push("--marker-only");
if (mode === "schema-only") args.push("--schema-only");
if (strict) args.push("--strict");
return args.join(" ");
}
function createDbVerifyConnectionRequiredError(options) {
const invocation = formatDbVerifyInvocation(options.mode, options.strict);
return errorDatabaseConnectionRequired({
why: `Database connection is required for ${invocation} (set db.connection in ${options.configPath}, or pass --db <url>)`,
retryCommand: `prisma-next ${invocation} --db <url>`
});
}
function renderVerifyHeader(paths, options, mode, flags, ui) {
if (flags.json || flags.quiet) return;
const description = mode === "schema-only" ? "Check whether the live database schema matches your contract" : mode === "marker-only" ? "Check whether the database marker matches your contract" : "Check whether the database marker and live schema match your contract";
const details = [
{
label: "config",
value: paths.configPath
},
{
label: "contract",
value: paths.contractPath
},
{
label: "mode",
value: formatDbVerifyModeLabel(mode, options.strict ?? false)
}
];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
ui.stderr(formatStyledHeader({
command: "db verify",
description,
url: "https://pris.ly/db-verify",
details,
flags
}));
}
async function resolveVerifyPaths(options) {
const config = await loadConfig(options.config);
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
const contractPathAbsolute = resolveContractPath(config);
return {
config,
configPath,
contractPathAbsolute,
contractPath: relative(process.cwd(), contractPathAbsolute)
};
}
async function resolveVerifySetup(paths, options, mode) {
const { config, configPath, contractPathAbsolute, contractPath } = paths;
let contractJsonContent;
try {
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
} catch (error) {
if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
why: `Contract file not found at ${contractPathAbsolute}`,
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
}
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
let contractJson;
try {
contractJson = familyInstance.deserializeContract(JSON.parse(contractJsonContent));
} catch (error) {
if (error instanceof ContractValidationError) return notOk(errorContractValidationFailed(`Contract validation failed: ${error.message}`, { where: { path: contractPathAbsolute } }));
return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const dbConnection = options.db ?? config.db?.connection;
if (typeof dbConnection !== "string" || dbConnection.length === 0) return notOk(createDbVerifyConnectionRequiredError({
configPath,
mode,
strict: options.strict ?? false
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${formatDbVerifyInvocation(mode, options.strict ?? false)}` }));
return ok({
...paths,
contractJson,
dbConnection
});
}
function createVerifyClient(setup) {
return createControlClient({
family: setup.config.family,
target: setup.config.target,
adapter: setup.config.adapter,
driver: setup.config.driver,
extensionPacks: setup.config.extensionPacks ?? []
});
}
function wrapVerifyError(error, contractPathAbsolute, modeLabel) {
if (CliStructuredError.is(error)) return notOk(error);
if (error instanceof ContractValidationError) return notOk(errorContractValidationFailed(`Contract validation failed: ${error.message}`, { where: { path: contractPathAbsolute } }));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during ${modeLabel}: ${error instanceof Error ? error.message : String(error)}` }));
}
/**
* Executes the db verify command and returns a structured Result.
*/
async function executeDbVerifyCommand(options, flags, ui, mode) {
const startTime = Date.now();
const paths = await resolveVerifyPaths(options);
renderVerifyHeader(paths, options, mode, flags, ui);
const setupResult = await resolveVerifySetup(paths, options, mode);
if (!setupResult.ok) return setupResult;
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
const client = createVerifyClient(setupResult.value);
const onProgress = createProgressAdapter({
ui,
flags
});
try {
const verifyResult = await client.verify({
contract: contractJson,
connection: dbConnection,
onProgress
});
if (!verifyResult.ok) return notOk(mapVerifyFailure(verifyResult));
const aggregateResult = await client.dbVerify({
contract: contractJson,
migrationsDir,
strict: options.strict ?? false,
skipSchema: mode === "marker-only",
skipMarker: false,
onProgress
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
if (mode === "marker-only") return ok({
ok: true,
mode: "marker-only",
summary: "Database marker matches contract",
contract: verifyResult.contract,
marker: verifyResult.marker,
target: verifyResult.target,
...ifDefined("missingCodecs", verifyResult.missingCodecs),
...ifDefined("codecCoverageSkipped", verifyResult.codecCoverageSkipped),
warning: "Schema verification skipped because --marker-only was provided",
meta: {
...verifyResult.meta ?? {},
schemaVerification: "skipped"
},
timings: { total: Date.now() - startTime }
});
const combined = combineVerifyResults(aggregateResult.value.schemaResults, aggregateResult.value.appSpaceId, options.strict ?? false, aggregateResult.value.unclaimed);
if (!combined.result.ok) return notOk(combined);
return ok({
ok: true,
mode: "full",
summary: "Database marker and schema match contract",
contract: verifyResult.contract,
marker: verifyResult.marker,
target: verifyResult.target,
...ifDefined("missingCodecs", verifyResult.missingCodecs),
...ifDefined("codecCoverageSkipped", verifyResult.codecCoverageSkipped),
schema: {
summary: combined.result.summary,
strict: combined.result.meta?.strict ?? false,
warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) => issue.path.join("/"))
},
unclaimed: combined.unclaimed,
meta: {
...verifyResult.meta ?? {},
schemaVerification: "performed"
},
timings: { total: Date.now() - startTime }
});
} catch (error) {
return wrapVerifyError(error, contractPathAbsolute, "db verify");
} finally {
await client.close();
}
}
async function executeDbSchemaOnlyVerifyCommand(options, flags, ui) {
const paths = await resolveVerifyPaths(options);
renderVerifyHeader(paths, options, "schema-only", flags, ui);
const setupResult = await resolveVerifySetup(paths, options, "schema-only");
if (!setupResult.ok) return setupResult;
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
const client = createVerifyClient(setupResult.value);
const onProgress = createProgressAdapter({
ui,
flags
});
try {
await client.connect(dbConnection);
const aggregateResult = await client.dbVerify({
contract: contractJson,
migrationsDir,
strict: options.strict ?? false,
skipSchema: false,
skipMarker: true,
onProgress
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
return ok(combineVerifyResults(aggregateResult.value.schemaResults, aggregateResult.value.appSpaceId, options.strict ?? false, aggregateResult.value.unclaimed));
} catch (error) {
return wrapVerifyError(error, contractPathAbsolute, "db verify --schema-only");
} finally {
await client.close();
}
}
function createDbVerifyCommand() {
const command = new Command("verify");
setCommandDescriptions(command, "Check whether the database marker and live schema match your contract", "Verifies the database marker first, then checks the database schema matches your contract.\nUse `--marker-only` for marker-only verification, `--schema-only` to skip marker checks and\ninspect only the live schema, and `--strict` to fail if the database includes elements\nnot present in the contract.");
setCommandExamples(command, [
"prisma-next db verify --db $DATABASE_URL",
"prisma-next db verify --db $DATABASE_URL --strict",
"prisma-next db verify --db $DATABASE_URL --schema-only",
"prisma-next db verify --db $DATABASE_URL --schema-only --strict",
"prisma-next db verify --db $DATABASE_URL --marker-only",
"prisma-next db verify --db $DATABASE_URL --json"
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--marker-only", "Skip schema verification and only check the database marker").option("--schema-only", "Skip marker verification and only check whether the live schema satisfies the contract").option("--strict", "Strict mode: schema elements not present in the contract are considered an error", false).action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const modeResult = resolveDbVerifyMode(options);
if (!modeResult.ok) {
const exitCode = handleResult(modeResult, flags, ui);
process.exit(exitCode);
}
const mode = modeResult.value;
if (mode === "schema-only") {
const result = await executeDbSchemaOnlyVerifyCommand(options, flags, ui);
const exitCode = handleResult(result, flags, ui, (combined) => {
if (flags.json) ui.output(formatSchemaVerifyJson(combined.result, combined.unclaimed));
else {
const renderFlags = combined.result.ok ? flags : {
...flags,
quiet: false
};
const output = formatSchemaVerifyOutput(combined.result, renderFlags, combined.unclaimed);
if (output) ui.log(output);
}
});
if (result.ok && !result.value.result.ok) process.exit(1);
process.exit(exitCode);
}
const result = await executeDbVerifyCommand(options, flags, ui, mode);
if (result.ok) {
if (flags.json) ui.output(formatVerifyJson(result.value));
else {
const output = formatVerifyOutput(result.value, flags);
if (output) ui.log(output);
}
process.exit(0);
}
if (CliStructuredError.is(result.failure)) {
const exitCode = handleResult(result, flags, ui);
process.exit(exitCode);
}
if (flags.json) ui.output(formatSchemaVerifyJson(result.failure.result, result.failure.unclaimed));
else {
const output = formatSchemaVerifyOutput(result.failure.result, {
...flags,
quiet: false
}, result.failure.unclaimed);
if (output) ui.log(output);
}
process.exit(1);
});
return command;
}
//#endregion
export { createDbVerifyCommand as t };
//# sourceMappingURL=db-verify-3WM8y323.mjs.map
{"version":3,"file":"db-verify-3WM8y323.mjs","names":[],"sources":["../src/utils/combine-verify-results.ts","../src/commands/db-verify.ts"],"sourcesContent":["import type { VerifyDatabaseSchemaResult } from '@prisma-next/framework-components/control';\n\n/**\n * The combined per-space contract-satisfaction result plus the standalone\n * unclaimed-elements list, reported once. The CLI renders\n * both; `unclaimed` is never folded into the combined tree or its issues.\n */\nexport interface CombinedVerifyResult {\n readonly result: VerifyDatabaseSchemaResult;\n readonly unclaimed: readonly string[];\n}\n\n/**\n * Collapse the aggregate verifier's per-space contract-satisfaction results\n * into a single {@link VerifyDatabaseSchemaResult} for the CLI display\n * surface, and carry the deduplicated unclaimed-elements list alongside it.\n * Concatenates the issue lists across spaces; uses the app space's result as\n * the structural envelope (storage hash, target). Extras are already\n * stripped from each per-space result, so nothing here duplicates an\n * unclaimed element per space.\n *\n * **Unclaimed disposition.** In strict mode a non-empty `unclaimed` list fails\n * the combined verdict (`ok: false`); in lenient mode it is carried for\n * informational rendering only. The list itself is returned unchanged for the\n * renderer.\n *\n * **Summary policy.** Preserve the per-family phrasing whenever the\n * combined `ok` flag agrees with the app space's `ok` flag — this is\n * the common case (single-family deployments, single-app deployments)\n * and the family's \"satisfies / does not satisfy contract\" phrasing\n * stays user-visible. When the app passes but an extension fails (or\n * vice versa) the app's summary contradicts the envelope, so fall back\n * to the first failing space's summary. This keeps family phrasing\n * intact and the envelope internally consistent (`ok: false` ↔ failure\n * summary).\n */\nexport function combineVerifyResults(\n perSpace: ReadonlyMap<string, VerifyDatabaseSchemaResult>,\n appSpaceId: string,\n strict: boolean,\n unclaimed: readonly string[],\n): CombinedVerifyResult {\n const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;\n if (appResult === undefined) {\n throw new Error(\n 'Aggregate verifier returned no per-space verify results — this is a wiring bug.',\n );\n }\n\n let okAll = true;\n let firstFailure: VerifyDatabaseSchemaResult | undefined;\n let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];\n let warningIssues: VerifyDatabaseSchemaResult['schema']['issues'] = [];\n for (const [, result] of perSpace) {\n if (!result.ok) {\n okAll = false;\n if (firstFailure === undefined) firstFailure = result;\n }\n issues = [...issues, ...result.schema.issues];\n warningIssues = [...warningIssues, ...(result.schema.warnings?.issues ?? [])];\n }\n\n const unclaimedFails = strict && unclaimed.length > 0;\n const ok = okAll && !unclaimedFails;\n\n // Prefer a failing space's family phrasing; else, when only the unclaimed list\n // fails the verdict, say so; else keep the app space's phrasing. When `okAll`\n // is false the loop assigned `firstFailure`, so the `?? appResult.summary`\n // fallback is unreachable — it exists only to keep the read cast-free.\n const summary = okAll\n ? unclaimedFails\n ? `Database schema has ${unclaimed.length} unclaimed element${unclaimed.length === 1 ? '' : 's'} (not in any contract)`\n : appResult.summary\n : appResult.ok\n ? (firstFailure?.summary ?? appResult.summary)\n : appResult.summary;\n\n return {\n result: {\n ok,\n ...(ok ? {} : { code: appResult.code ?? 'CONTRACT.MARKER_REQUIRED' }),\n summary,\n contract: appResult.contract,\n target: appResult.target,\n schema: {\n issues,\n warnings: { issues: warningIssues },\n },\n meta: { strict },\n timings: { total: 0 },\n },\n unclaimed,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { VerifyDatabaseResult } from '@prisma-next/framework-components/control';\nimport {\n createControlStack,\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { createControlClient } from '../control-api/client';\nimport { ContractValidationError } from '../control-api/errors';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorHashMismatch,\n errorMarkerMissing,\n errorRuntime,\n errorTargetMismatch,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { type CombinedVerifyResult, combineVerifyResults } from '../utils/combine-verify-results';\nimport {\n addGlobalOptions,\n maskConnectionUrl,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport {\n type DbVerifyCommandSuccessResult,\n formatSchemaVerifyJson,\n formatSchemaVerifyOutput,\n formatVerifyJson,\n formatVerifyOutput,\n} from '../utils/formatters/verify';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbVerifyOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly markerOnly?: boolean;\n readonly schemaOnly?: boolean;\n readonly strict?: boolean;\n}\n\ntype DbVerifyMode = 'full' | 'marker-only' | 'schema-only';\n\n/**\n * Maps a VerifyDatabaseResult failure to a CliStructuredError.\n */\nfunction mapVerifyFailure(verifyResult: VerifyDatabaseResult): CliStructuredError {\n if (!verifyResult.ok && verifyResult.code) {\n if (verifyResult.code === VERIFY_CODE_MARKER_MISSING) {\n return errorMarkerMissing();\n }\n if (verifyResult.code === VERIFY_CODE_HASH_MISMATCH) {\n const storageMatch = verifyResult.marker?.storageHash === verifyResult.contract.storageHash;\n const profileMatch =\n !verifyResult.contract.profileHash ||\n verifyResult.marker?.profileHash === verifyResult.contract.profileHash;\n\n if (!storageMatch) {\n return errorHashMismatch({\n why: 'Contract storageHash does not match database marker',\n expected: verifyResult.contract.storageHash,\n ...ifDefined('actual', verifyResult.marker?.storageHash),\n });\n }\n\n return errorHashMismatch({\n why: profileMatch\n ? 'Contract hash does not match database marker'\n : 'Contract profileHash does not match database marker',\n ...ifDefined('expected', verifyResult.contract.profileHash),\n ...ifDefined('actual', verifyResult.marker?.profileHash),\n });\n }\n if (verifyResult.code === VERIFY_CODE_TARGET_MISMATCH) {\n return errorTargetMismatch(\n verifyResult.target.expected,\n verifyResult.target.actual ?? 'unknown',\n );\n }\n // Unknown code - fall through to runtime error\n }\n return errorRuntime(verifyResult.summary);\n}\n\ntype DbVerifyFailure = CliStructuredError | CombinedVerifyResult;\n\nfunction errorInvalidVerifyMode(options: {\n readonly why: string;\n readonly fix: string;\n}): CliStructuredError {\n return new CliStructuredError('CLI.INVALID_VERIFY_MODE', 'Invalid verify mode', {\n why: options.why,\n fix: options.fix,\n docsUrl: 'https://pris.ly/db-verify',\n });\n}\n\nfunction resolveDbVerifyMode(options: DbVerifyOptions): Result<DbVerifyMode, CliStructuredError> {\n if (options.markerOnly && options.schemaOnly) {\n return notOk(\n errorInvalidVerifyMode({\n why: '`--marker-only` and `--schema-only` cannot be used together',\n fix: 'Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema.',\n }),\n );\n }\n\n if (options.markerOnly && options.strict) {\n return notOk(\n errorInvalidVerifyMode({\n why: '`--strict` requires schema verification, but `--marker-only` skips it',\n fix: 'Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode.',\n }),\n );\n }\n\n if (options.schemaOnly) {\n return ok('schema-only');\n }\n\n if (options.markerOnly) {\n return ok('marker-only');\n }\n\n return ok('full');\n}\n\nfunction formatDbVerifyModeLabel(mode: DbVerifyMode, strict: boolean): string {\n if (mode === 'marker-only') {\n return 'marker only';\n }\n\n if (mode === 'schema-only') {\n return `schema only (${strict ? 'strict' : 'tolerant'})`;\n }\n\n return `full (marker + schema, ${strict ? 'strict' : 'tolerant'})`;\n}\n\nfunction formatDbVerifyInvocation(mode: DbVerifyMode, strict: boolean): string {\n const args = ['db verify'];\n\n if (mode === 'marker-only') {\n args.push('--marker-only');\n }\n\n if (mode === 'schema-only') {\n args.push('--schema-only');\n }\n\n if (strict) {\n args.push('--strict');\n }\n\n return args.join(' ');\n}\n\nfunction createDbVerifyConnectionRequiredError(options: {\n readonly configPath: string;\n readonly mode: DbVerifyMode;\n readonly strict: boolean;\n}): CliStructuredError {\n const invocation = formatDbVerifyInvocation(options.mode, options.strict);\n return errorDatabaseConnectionRequired({\n why: `Database connection is required for ${invocation} (set db.connection in ${options.configPath}, or pass --db <url>)`,\n retryCommand: `prisma-next ${invocation} --db <url>`,\n });\n}\n\nfunction renderVerifyHeader(\n paths: { configPath: string; contractPath: string },\n options: DbVerifyOptions,\n mode: DbVerifyMode,\n flags: GlobalFlags,\n ui: TerminalUI,\n): void {\n if (flags.json || flags.quiet) return;\n\n const description =\n mode === 'schema-only'\n ? 'Check whether the live database schema matches your contract'\n : mode === 'marker-only'\n ? 'Check whether the database marker matches your contract'\n : 'Check whether the database marker and live schema match your contract';\n\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: paths.configPath },\n { label: 'contract', value: paths.contractPath },\n { label: 'mode', value: formatDbVerifyModeLabel(mode, options.strict ?? false) },\n ];\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n }\n\n ui.stderr(\n formatStyledHeader({\n command: 'db verify',\n description,\n url: 'https://pris.ly/db-verify',\n details,\n flags,\n }),\n );\n}\n\nasync function resolveVerifyPaths(options: DbVerifyOptions) {\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n return { config, configPath, contractPathAbsolute, contractPath };\n}\n\ntype VerifyPaths = Awaited<ReturnType<typeof resolveVerifyPaths>>;\n\ninterface VerifySetup extends VerifyPaths {\n readonly contractJson: Contract;\n readonly dbConnection: string;\n}\n\nasync function resolveVerifySetup(\n paths: VerifyPaths,\n options: DbVerifyOptions,\n mode: DbVerifyMode,\n): Promise<Result<VerifySetup, CliStructuredError>> {\n const { config, configPath, contractPathAbsolute, contractPath } = paths;\n\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n // Cross the family `deserializeContract` seam at the read site, just\n // like every other CLI on-disk read (TML-2536). The downstream\n // `dbVerify` op accepts the hydrated `Contract` directly and no\n // longer re-deserializes.\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n\n let contractJson: Contract;\n try {\n contractJson = familyInstance.deserializeContract(JSON.parse(contractJsonContent) as unknown);\n } catch (error) {\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n const dbConnection = options.db ?? config.db?.connection;\n if (typeof dbConnection !== 'string' || dbConnection.length === 0) {\n return notOk(\n createDbVerifyConnectionRequiredError({\n configPath,\n mode,\n strict: options.strict ?? false,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: `Config.driver is required for ${formatDbVerifyInvocation(mode, options.strict ?? false)}`,\n }),\n );\n }\n\n return ok({ ...paths, contractJson, dbConnection });\n}\n\nfunction createVerifyClient(setup: VerifySetup) {\n return createControlClient({\n family: setup.config.family,\n target: setup.config.target,\n adapter: setup.config.adapter,\n driver: setup.config.driver!,\n extensionPacks: setup.config.extensionPacks ?? [],\n });\n}\n\nfunction wrapVerifyError(\n error: unknown,\n contractPathAbsolute: string,\n modeLabel: string,\n): Result<never, CliStructuredError> {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during ${modeLabel}: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n}\n\n/**\n * Executes the db verify command and returns a structured Result.\n */\nasync function executeDbVerifyCommand(\n options: DbVerifyOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n mode: Extract<DbVerifyMode, 'full' | 'marker-only'>,\n): Promise<Result<DbVerifyCommandSuccessResult, DbVerifyFailure>> {\n const startTime = Date.now();\n const paths = await resolveVerifyPaths(options);\n renderVerifyHeader(paths, options, mode, flags, ui);\n\n const setupResult = await resolveVerifySetup(paths, options, mode);\n if (!setupResult.ok) return setupResult;\n const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;\n const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);\n\n const client = createVerifyClient(setupResult.value);\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n // Single-contract marker verification preserved for the existing\n // marker / target / hash failure surface (`CONTRACT.MARKER_MISSING/CONTRACT.MARKER_MISMATCH/CONTRACT.TARGET_MISMATCH`).\n // The aggregate verifier (run below for the per-space marker /\n // schema checks) does not duplicate this: it concerns itself with\n // marker-vs-on-disk and orphan-marker drift, not the\n // hash-mismatch-against-the-app-contract lane that today's\n // `client.verify` covers.\n const verifyResult = await client.verify({\n contract: contractJson,\n connection: dbConnection,\n onProgress,\n });\n\n if (!verifyResult.ok) {\n return notOk(mapVerifyFailure(verifyResult));\n }\n\n // Aggregate verifier (loader → verifier pipeline). Runs the layout\n // precheck, marker-aware per-space verifier, and (full mode only)\n // per-space pre-projected schema verification (closes F23).\n const aggregateResult = await client.dbVerify({\n contract: contractJson,\n migrationsDir,\n strict: options.strict ?? false,\n skipSchema: mode === 'marker-only',\n skipMarker: false,\n onProgress,\n });\n if (!aggregateResult.ok) return notOk(aggregateResult.failure);\n\n if (mode === 'marker-only') {\n return ok({\n ok: true,\n mode: 'marker-only',\n summary: 'Database marker matches contract',\n contract: verifyResult.contract,\n marker: verifyResult.marker,\n target: verifyResult.target,\n ...ifDefined('missingCodecs', verifyResult.missingCodecs),\n ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),\n warning: 'Schema verification skipped because --marker-only was provided',\n meta: {\n ...(verifyResult.meta ?? {}),\n schemaVerification: 'skipped',\n },\n timings: { total: Date.now() - startTime },\n });\n }\n\n const combined = combineVerifyResults(\n aggregateResult.value.schemaResults,\n aggregateResult.value.appSpaceId,\n options.strict ?? false,\n aggregateResult.value.unclaimed,\n );\n if (!combined.result.ok) {\n return notOk(combined);\n }\n\n return ok({\n ok: true,\n mode: 'full',\n summary: 'Database marker and schema match contract',\n contract: verifyResult.contract,\n marker: verifyResult.marker,\n target: verifyResult.target,\n ...ifDefined('missingCodecs', verifyResult.missingCodecs),\n ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),\n schema: {\n summary: combined.result.summary,\n strict: combined.result.meta?.strict ?? false,\n warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) =>\n issue.path.join('/'),\n ),\n },\n unclaimed: combined.unclaimed,\n meta: {\n ...(verifyResult.meta ?? {}),\n schemaVerification: 'performed',\n },\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n return wrapVerifyError(error, contractPathAbsolute, 'db verify');\n } finally {\n await client.close();\n }\n}\n\nasync function executeDbSchemaOnlyVerifyCommand(\n options: DbVerifyOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<CombinedVerifyResult, CliStructuredError>> {\n const paths = await resolveVerifyPaths(options);\n renderVerifyHeader(paths, options, 'schema-only', flags, ui);\n\n const setupResult = await resolveVerifySetup(paths, options, 'schema-only');\n if (!setupResult.ok) return setupResult;\n const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;\n const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);\n\n const client = createVerifyClient(setupResult.value);\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n await client.connect(dbConnection);\n const aggregateResult = await client.dbVerify({\n contract: contractJson,\n migrationsDir,\n strict: options.strict ?? false,\n skipSchema: false,\n skipMarker: true,\n onProgress,\n });\n if (!aggregateResult.ok) return notOk(aggregateResult.failure);\n\n return ok(\n combineVerifyResults(\n aggregateResult.value.schemaResults,\n aggregateResult.value.appSpaceId,\n options.strict ?? false,\n aggregateResult.value.unclaimed,\n ),\n );\n } catch (error) {\n return wrapVerifyError(error, contractPathAbsolute, 'db verify --schema-only');\n } finally {\n await client.close();\n }\n}\n\nexport function createDbVerifyCommand(): Command {\n const command = new Command('verify');\n setCommandDescriptions(\n command,\n 'Check whether the database marker and live schema match your contract',\n 'Verifies the database marker first, then checks the database schema matches your contract.\\n' +\n 'Use `--marker-only` for marker-only verification, `--schema-only` to skip marker checks and\\n' +\n 'inspect only the live schema, and `--strict` to fail if the database includes elements\\n' +\n 'not present in the contract.',\n );\n setCommandExamples(command, [\n 'prisma-next db verify --db $DATABASE_URL',\n 'prisma-next db verify --db $DATABASE_URL --strict',\n 'prisma-next db verify --db $DATABASE_URL --schema-only',\n 'prisma-next db verify --db $DATABASE_URL --schema-only --strict',\n 'prisma-next db verify --db $DATABASE_URL --marker-only',\n 'prisma-next db verify --db $DATABASE_URL --json',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--marker-only', 'Skip schema verification and only check the database marker')\n .option(\n '--schema-only',\n 'Skip marker verification and only check whether the live schema satisfies the contract',\n )\n .option(\n '--strict',\n 'Strict mode: schema elements not present in the contract are considered an error',\n false,\n )\n .action(async (options: DbVerifyOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n const modeResult = resolveDbVerifyMode(options);\n if (!modeResult.ok) {\n const exitCode = handleResult(modeResult as Result<never, CliStructuredError>, flags, ui);\n process.exit(exitCode);\n }\n\n const mode = modeResult.value;\n\n if (mode === 'schema-only') {\n const result = await executeDbSchemaOnlyVerifyCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, (combined) => {\n if (flags.json) {\n ui.output(formatSchemaVerifyJson(combined.result, combined.unclaimed));\n } else {\n // Always show schema-drift failures, even in quiet mode — exiting 1\n // without diagnostics is unhelpful (same policy as the full-mode\n // failure branch below).\n const renderFlags = combined.result.ok ? flags : { ...flags, quiet: false };\n const output = formatSchemaVerifyOutput(\n combined.result,\n renderFlags,\n combined.unclaimed,\n );\n if (output) {\n ui.log(output);\n }\n }\n });\n\n if (result.ok && !result.value.result.ok) {\n process.exit(1);\n }\n\n process.exit(exitCode);\n }\n\n const result = await executeDbVerifyCommand(options, flags, ui, mode);\n\n if (result.ok) {\n if (flags.json) {\n ui.output(formatVerifyJson(result.value));\n } else {\n const output = formatVerifyOutput(result.value, flags);\n if (output) {\n ui.log(output);\n }\n }\n process.exit(0);\n }\n\n if (CliStructuredError.is(result.failure)) {\n const exitCode = handleResult(result as Result<never, CliStructuredError>, flags, ui);\n process.exit(exitCode);\n }\n\n if (flags.json) {\n ui.output(formatSchemaVerifyJson(result.failure.result, result.failure.unclaimed));\n } else {\n // Always show schema-drift failures, even in quiet mode — exiting 1 without\n // diagnostics is unhelpful.\n const output = formatSchemaVerifyOutput(\n result.failure.result,\n { ...flags, quiet: false },\n result.failure.unclaimed,\n );\n if (output) {\n ui.log(output);\n }\n }\n process.exit(1);\n });\n\n return command;\n}\n"],"mappings":"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"}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

import { A as formatStyledHeader, F as CliStructuredError, U as errorDriverRequired, V as errorDatabaseConnectionRequired, c as sanitizeErrorMessage, ct as errorUnexpected, i as maskConnectionUrl } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-DeJvLD2h.mjs";
import { loadConfig } from "@prisma-next/config-loader";
import { notOk, ok } from "@prisma-next/utils/result";
import { relative, resolve } from "pathe";
//#region src/commands/inspect-live-schema.ts
async function inspectLiveSchema(options, flags, ui, startTime, context) {
let config;
try {
config = await loadConfig(options.config);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: "Failed to load config" }));
}
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
else if (config.db?.connection && typeof config.db.connection === "string") details.push({
label: "database",
value: maskConnectionUrl(config.db.connection)
});
ui.stderr(formatStyledHeader({
command: context.commandName,
description: context.description,
url: context.url,
details,
flags
}));
}
const dbConnection = options.db ?? config.db?.connection;
if (!dbConnection) return notOk(errorDatabaseConnectionRequired({
why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: context.commandName
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${context.commandName}` }));
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
const onProgress = createProgressAdapter({
ui,
flags
});
try {
const schema = await client.introspect({
connection: dbConnection,
onProgress
});
const schemaView = client.toSchemaView(schema);
const pslContractAst = client.inferPslContract(schema);
const pslBlockDescriptors = client.getPslBlockDescriptors();
const dbUrl = typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0;
return ok({
config,
schema,
schemaView,
pslContractAst,
pslBlockDescriptors,
target: {
familyId: config.family.familyId,
id: config.target.targetId
},
meta: {
configPath,
...dbUrl ? { dbUrl } : {}
},
timings: { total: Date.now() - startTime }
});
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
const safeMessage = sanitizeErrorMessage(error instanceof Error ? error.message : String(error), typeof dbConnection === "string" ? dbConnection : void 0);
return notOk(errorUnexpected(safeMessage, { why: `Unexpected error during ${context.commandName}: ${safeMessage}` }));
} finally {
await client.close();
}
}
//#endregion
export { inspectLiveSchema as t };
//# sourceMappingURL=inspect-live-schema-xS6VqjHE.mjs.map
{"version":3,"file":"inspect-live-schema-xS6VqjHE.mjs","names":[],"sources":["../src/commands/inspect-live-schema.ts"],"sourcesContent":["import { loadConfig } from '@prisma-next/config-loader';\nimport type { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';\nimport type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { relative, resolve } from 'pathe';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { maskConnectionUrl, sanitizeErrorMessage } from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions, GlobalFlags } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport type { TerminalUI } from '../utils/terminal-ui';\n\nexport interface InspectLiveSchemaOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n}\n\ninterface InspectLiveSchemaContext {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\ntype LoadedCliConfig = Awaited<ReturnType<typeof loadConfig>>;\n\nexport interface InspectLiveSchemaResult {\n readonly config: LoadedCliConfig;\n readonly schema: unknown;\n readonly schemaView: CoreSchemaView | undefined;\n /**\n * PSL AST inferred from the introspected schema, when the configured family\n * implements `PslContractInferCapable`. `undefined` for families that do not\n * support inference (e.g. Mongo today).\n */\n readonly pslContractAst: PslDocumentAst | undefined;\n /**\n * The assembled PSL block descriptors from the control stack — the full set of\n * extension-contributed top-level block descriptors. Downstream commands pass\n * this through to `printPsl` so contributed-block AST nodes round-trip back to\n * source.\n */\n readonly pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace;\n readonly target: {\n readonly familyId: string;\n readonly id: string;\n };\n readonly meta: {\n readonly configPath?: string;\n readonly dbUrl?: string;\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nexport async function inspectLiveSchema(\n options: InspectLiveSchemaOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n context: InspectLiveSchemaContext,\n): Promise<Result<InspectLiveSchemaResult, CliStructuredError>> {\n let config: LoadedCliConfig;\n try {\n config = await loadConfig(options.config);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: 'Failed to load config',\n }),\n );\n }\n\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n ];\n\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n } else if (config.db?.connection && typeof config.db.connection === 'string') {\n details.push({ label: 'database', value: maskConnectionUrl(config.db.connection) });\n }\n\n ui.stderr(\n formatStyledHeader({\n command: context.commandName,\n description: context.description,\n url: context.url,\n details,\n flags,\n }),\n );\n }\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: context.commandName,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: `Config.driver is required for ${context.commandName}`,\n }),\n );\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n const schema = await client.introspect({\n connection: dbConnection,\n onProgress,\n });\n const schemaView = client.toSchemaView(schema);\n const pslContractAst = client.inferPslContract(schema);\n const pslBlockDescriptors = client.getPslBlockDescriptors();\n\n const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;\n\n return ok({\n config,\n schema,\n schemaView,\n pslContractAst,\n pslBlockDescriptors,\n target: {\n familyId: config.family.familyId,\n id: config.target.targetId,\n },\n meta: {\n configPath,\n ...(dbUrl ? { dbUrl } : {}),\n },\n timings: {\n total: Date.now() - startTime,\n },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n const rawMessage = error instanceof Error ? error.message : String(error);\n const safeMessage = sanitizeErrorMessage(\n rawMessage,\n typeof dbConnection === 'string' ? dbConnection : undefined,\n );\n return notOk(\n errorUnexpected(safeMessage, {\n why: `Unexpected error during ${context.commandName}: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n"],"mappings":";;;;;;;AA8DA,eAAsB,kBACpB,SACA,OACA,IACA,WACA,SAC8D;CAC9D,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC1C,SAAS,OAAO;EACd,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;EAGpB,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,wBACP,CAAC,CACH;CACF;CAEA,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,IAAI,GAAG,QAAQ,QAAQ,MAAM,CAAC,IAC/C;CAEJ,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;EAAW,CACvC;EAEA,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,EAAE;EAAE,CAAC;OACnE,IAAI,OAAO,IAAI,cAAc,OAAO,OAAO,GAAG,eAAe,UAClE,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,OAAO,GAAG,UAAU;EAAE,CAAC;EAGpF,GAAG,OACD,mBAAmB;GACjB,SAAS,QAAQ;GACjB,aAAa,QAAQ;GACrB,KAAK,QAAQ;GACb;GACA;EACF,CAAC,CACH;CACF;CAEA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,QAAQ,YAAY,yBAAyB,WAAW;EACpG,aAAa,QAAQ;CACvB,CAAC,CACH;CAGF,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAClB,KAAK,iCAAiC,QAAQ,cAChD,CAAC,CACH;CAGF,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,CAAC;CAC5C,CAAC;CACD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAAW;GACrC,YAAY;GACZ;EACF,CAAC;EACD,MAAM,aAAa,OAAO,aAAa,MAAM;EAC7C,MAAM,iBAAiB,OAAO,iBAAiB,MAAM;EACrD,MAAM,sBAAsB,OAAO,uBAAuB;EAE1D,MAAM,QAAQ,OAAO,iBAAiB,WAAW,kBAAkB,YAAY,IAAI,KAAA;EAEnF,OAAO,GAAG;GACR;GACA;GACA;GACA;GACA;GACA,QAAQ;IACN,UAAU,OAAO,OAAO;IACxB,IAAI,OAAO,OAAO;GACpB;GACA,MAAM;IACJ;IACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC3B;GACA,SAAS,EACP,OAAO,KAAK,IAAI,IAAI,UACtB;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;EAIpB,MAAM,cAAc,qBADD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAGtE,OAAO,iBAAiB,WAAW,eAAe,KAAA,CACpD;EACA,OAAO,MACL,gBAAgB,aAAa,EAC3B,KAAK,2BAA2B,QAAQ,YAAY,IAAI,cAC1D,CAAC,CACH;CACF,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF"}
import { A as formatStyledHeader, K as errorInvalidSpaceId, L as errorAmbiguousMigrationRef, _ as createTerminalUI, at as errorSpaceNotFound, b as formatErrorJson, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, o as resolveContractPath, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, x as formatErrorOutput } from "./command-helpers-CVn3U9Uz.mjs";
import { n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { i as resolveTargetPathAcrossSpaces, n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-C0TQuV5n.mjs";
import "./schemas-KhXMzNA_.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { join, relative } from "pathe";
import { readFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { isValidSpaceId, listContractSpaceDirectories, spaceMigrationDirectory, spaceRefsDirectory } from "@prisma-next/migration-tools/spaces";
import { existsSync, readdirSync, statSync } from "node:fs";
import { loadContractSpaceAggregate } from "@prisma-next/migration-tools/aggregate";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
import { contractSnapshotDir, readContractSnapshotJson } from "@prisma-next/migration-tools/contract-snapshot-store";
import { parseMigrationRef } from "@prisma-next/migration-tools/ref-resolution";
import { verifyMigrationHash } from "@prisma-next/migration-tools/hash";
//#region src/utils/integrity-violation-to-check-failure.ts
function migrationPathRelative$1(dirPath) {
return relative(process.cwd(), dirPath);
}
function migrationFileRelative$1(dirPath, fileName) {
return join(migrationPathRelative$1(dirPath), fileName);
}
/**
* Map one {@link IntegrityViolation} onto a `migration check` failure row.
* Sole catalogue mapping from integrity violations to `MIGRATION.CHECK_*` codes.
*/
function integrityViolationToCheckFailure(violation, migrationsDir) {
const spaceRelative = (spaceId) => migrationPathRelative$1(join(migrationsDir, spaceId));
const packageRelative = (spaceId, dirName) => migrationPathRelative$1(join(migrationsDir, spaceId, dirName));
const refRelative = (spaceId, refName) => migrationPathRelative$1(join(migrationsDir, spaceId, "refs", `${refName}.json`));
switch (violation.kind) {
case "hashMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HASH_MISMATCH",
where: migrationFileRelative$1(join(migrationsDir, violation.spaceId, violation.dirName), "migration.json"),
why: `Stored hash ${violation.stored} does not match recomputed hash ${violation.computed}`,
fix: "Re-emit the migration package or restore from version control."
};
case "providedInvariantsMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_PROVIDED_INVARIANTS_MISMATCH",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`,
fix: "Re-emit the migration package so migration.json and ops.json agree."
};
case "packageUnloadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_PACKAGE_UNLOADABLE",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`,
fix: "Re-emit the migration package or restore from version control."
};
case "sameSourceAndTarget": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_NOOP_SELF_EDGE",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" in space "${violation.spaceId}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,
fix: "Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op."
};
case "orphanSpaceDir": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_ORPHAN_SPACE_DIR",
where: spaceRelative(violation.spaceId),
why: `Contract-space directory "${violation.spaceId}" exists on disk but no extension declares it.`,
fix: "Remove the orphan directory, or declare the extension in `extensionPacks`."
};
case "declaredButUnmigrated": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_DECLARED_BUT_UNMIGRATED",
where: spaceRelative(violation.spaceId),
why: `Extension "${violation.spaceId}" is declared in \`extensionPacks\` but has no on-disk migrations directory.`,
fix: "Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused."
};
case "headRefMissing": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HEAD_REF_MISSING",
where: refRelative(violation.spaceId, "head"),
why: `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`,
fix: "Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control."
};
case "headRefNotInGraph": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HEAD_REF_NOT_IN_GRAPH",
where: refRelative(violation.spaceId, "head"),
why: `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in its migration graph.`,
fix: "Re-emit the contract space migrations, or restore the missing migration package."
};
case "refUnreadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_REF_UNREADABLE",
where: refRelative(violation.spaceId, violation.refName),
why: `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`,
fix: "Repair or remove the corrupt ref file."
};
case "targetMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_TARGET_MISMATCH",
where: spaceRelative(violation.spaceId),
why: `Contract space "${violation.spaceId}" targets "${violation.actual}" but the project targets "${violation.expected}".`,
fix: "Update the extension to target the configured database, or change the project target."
};
case "disjointness": return {
space: "app",
code: "MIGRATION.CHECK_SPACE_DISJOINTNESS_VIOLATION",
where: migrationPathRelative$1(migrationsDir),
why: `Storage element "${violation.element}" is claimed by multiple contract spaces: ${violation.claimedBy.join(", ")}.`,
fix: "Update the contracts so each storage element is owned by exactly one contract space."
};
case "contractUnreadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_CONTRACT_UNREADABLE",
where: migrationFileRelative$1(join(migrationsDir, violation.spaceId), "contract.json"),
why: `Contract for space "${violation.spaceId}" is unreadable: ${violation.detail}`,
fix: "Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract."
};
case "duplicateMigrationHash": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_DUPLICATE_MIGRATION_HASH",
where: spaceRelative(violation.spaceId),
why: `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(", ")}).`,
fix: "Re-emit one of the conflicting packages so each migrationHash is unique."
};
}
}
//#endregion
//#region src/commands/migration-check.ts
function migrationPathRelative(dirPath) {
return relative(process.cwd(), dirPath);
}
function migrationFileRelative(dirPath, fileName) {
return join(migrationPathRelative(dirPath), fileName);
}
function checkFileExists(spaceId, dirPath, dirName, fileName) {
if (!existsSync(join(dirPath, fileName))) return {
space: spaceId,
code: "MIGRATION.CHECK_FILE_MISSING",
where: migrationFileRelative(dirPath, fileName),
why: `${fileName} is missing from ${dirName}`,
fix: "Re-emit the migration package or restore from version control."
};
return null;
}
/**
* Snapshot-store consistency check for one migration package (D6.5):
* absent store entry is not an issue (runner independence, ADR 199 — a
* package dir with only `migration.json` + `ops.json` is legitimate); a
* present entry whose inner `storage.storageHash` disagrees with
* `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store
* entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.
*/
async function checkSnapshotConsistency(spaceId, pkg, migrationsDir) {
let snapshotDir;
try {
snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);
} catch {
return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" declares to="${pkg.metadata.to}", which is not a well-formed contract snapshot hash.`,
fix: "Re-emit the migration package so migration.json declares a valid `sha256:<64hex>` to-hash."
};
}
let raw;
try {
raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);
} catch (error) {
if (MigrationToolsError.is(error) && error.code === "MIGRATION.CONTRACT_SNAPSHOT_MISSING") return null;
return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,
fix: "Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot."
};
}
const snapshotHash = (raw !== null && typeof raw === "object" ? raw : {})["storage"]?.["storageHash"];
if (typeof snapshotHash === "string" && snapshotHash !== pkg.metadata.to) return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,
fix: "Re-emit the migration package so migration.json and its contract snapshot agree."
};
return null;
}
/**
* Project the loaded {@link ContractSpaceAggregate} into the
* {@link CheckSpace} rows the multi-space check iterates — one per on-disk
* contract-space directory, in the aggregate's `app`-first ordering. Mirrors
* `migration list`'s `migrationSpaceListEntriesFromAggregate`: space
* membership matches the on-disk directories, package / ref / graph data come
* from `aggregate.space(id)`.
*/
async function enumerateCheckSpaces(aggregate, projectMigrationsDir) {
const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));
const spaces = [];
for (const space of aggregate.spaces()) {
const spaceId = space.spaceId;
if (!isValidSpaceId(spaceId)) continue;
if (!onDiskSpaceIds.has(spaceId)) continue;
const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
spaces.push({
spaceId,
packages: space.packages,
refs: space.refs,
graph: space.graph(),
migrationsDir,
refsDir: spaceRefsDirectory(migrationsDir),
projectMigrationsDir
});
}
return spaces;
}
function checkManifestFilesPresent(space) {
if (!existsSync(space.migrationsDir)) return [];
const loadedDirNames = new Set(space.packages.map((p) => p.dirName));
const failures = [];
let entries;
try {
entries = readdirSync(space.migrationsDir);
} catch {
return failures;
}
for (const entry of entries) {
if (entry.startsWith(".") || entry.startsWith("_") || entry === "refs") continue;
const entryPath = join(space.migrationsDir, entry);
try {
if (!statSync(entryPath).isDirectory()) continue;
} catch {
continue;
}
if (!loadedDirNames.has(entry)) for (const f of ["migration.json", "ops.json"]) {
const fail = checkFileExists(space.spaceId, entryPath, entry, f);
if (fail) failures.push(fail);
}
}
return failures;
}
function checkReachability(space) {
const allToHashes = new Set(space.packages.map((p) => p.metadata.to));
const failures = [];
for (const pkg of space.packages) if (!(pkg.metadata.from === null || allToHashes.has(pkg.metadata.from) || pkg.metadata.from === "sha256:empty")) failures.push({
space: space.spaceId,
code: "MIGRATION.CHECK_UNREACHABLE_MIGRATION",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
fix: "This migration is unreachable in the graph. Delete it or re-emit a connecting migration."
});
return failures;
}
function checkDanglingRefs(space) {
const failures = [];
for (const [name, entry] of Object.entries(space.refs)) if (!space.graph.nodes.has(entry.hash)) failures.push({
space: space.spaceId,
code: "MIGRATION.CHECK_DANGLING_REF",
where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`
});
return failures;
}
async function checkSpace(space) {
const snapshotFailures = await Promise.all(space.packages.map((pkg) => checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir)));
return [
...checkManifestFilesPresent(space),
...snapshotFailures.filter((f) => f !== null),
...checkReachability(space),
...checkDanglingRefs(space)
];
}
/**
* Policy core of the holistic `migration check`: validates `--space`,
* narrows the pre-enumerated spaces, and runs the per-space explicit graph
* checks (file-existence, snapshot consistency, reachability, dangling
* refs), aggregating every failure into one {@link MigrationCheckResult}.
*
* `--space` validation mirrors `migration list`: an invalid id →
* {@link errorInvalidSpaceId}; an id with no on-disk space →
* {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.
* Aggregate-integrity violations (which already span every space) are folded
* in by the caller, not here.
*/
async function runMigrationCheck(inputs) {
const { spaces, spaceFilter } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return notOk(errorInvalidSpaceId(spaceFilter));
if (spaceFilter !== void 0 && !spaces.some((s) => s.spaceId === spaceFilter)) return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();
if (failures.length === 0) return ok({
ok: true,
failures: [],
summary: "All checks passed"
});
return ok({
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
});
}
async function loadAggregateIntegrityViolations(config, migrationsDir) {
try {
const contractJsonContent = await readFile(resolveContractPath(config), "utf-8");
const familyInstance = config.family.create(createControlStack(config));
const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
const parsedAppContract = JSON.parse(contractJsonContent);
return (await loadContractSpaceAggregate({
migrationsDir,
deserializeContract: (json) => familyInstance.deserializeContract(json),
appContract: familyInstance.deserializeContract(parsedAppContract)
})).checkIntegrity({
declaredExtensions,
checkContracts: true
});
} catch {
return [];
}
}
async function executeMigrationCheckCommand(target, options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "migrations",
value: appMigrationsRelative
}];
if (target) details.push({
label: "target",
value: target
});
const header = formatStyledHeader({
command: "migration check",
description: "Verify artifact and graph integrity",
details,
flags
});
ui.stderr(header);
}
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
if (!loadedAggregate.ok) return {
error: loadedAggregate.failure,
exitCode: 2
};
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
if (target) return await checkSingleTarget(target, {
spaces,
...options.space !== void 0 ? { spaceFilter: options.space } : {},
appMigrationsDir,
appMigrationsRelative
});
const checkResult = await runMigrationCheck({
spaces,
...options.space !== void 0 ? { spaceFilter: options.space } : {}
});
if (!checkResult.ok) return {
error: checkResult.failure,
exitCode: 2
};
const failures = [...checkResult.value.failures];
const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);
const scopedViolations = options.space === void 0 ? allViolations : allViolations.filter((v) => v.kind !== "disjointness" && v.spaceId === options.space);
for (const violation of scopedViolations) failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
if (failures.length === 0) return {
result: {
ok: true,
failures: [],
summary: "All checks passed"
},
exitCode: 0
};
return {
result: {
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
},
exitCode: 4
};
}
/**
* Ranks ref-resolution failure kinds by how informative they are, so a
* single-target check surfaces the most useful failure across spaces instead of
* whichever space failed first. `not-found` (the input matched nothing here)
* says the least; a malformed input, a wrong grammar, or an in-space ambiguity
* all say more.
*/
function refFailureSpecificity(error) {
switch (error.kind) {
case "wrong-grammar": return 3;
case "ambiguous": return 2;
case "invalid-format": return 1;
case "not-found": return 0;
}
}
/**
* Single-target (`check <ref/path>`) mode — resolves a migration reference
* across all contract spaces (or the one space narrowed by `--space <id>`).
*
* Resolution:
* - filesystem path → find the owning space by checking which space's
* `migrationsDir` contains the resolved path; falls back to app-relative
* validation when the path is outside every space dir.
* - ref → `parseMigrationRef` against each in-scope space; collect every
* (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity
* error (qualify with `--space`).
*
* `--space <id>` is validated the same way the holistic path does it:
* invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.
*/
async function checkSingleTarget(target, inputs) {
const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return {
error: errorInvalidSpaceId(spaceFilter),
exitCode: 2
};
if (spaceFilter !== void 0 && !spaces.some((s) => s.spaceId === spaceFilter)) return {
error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),
exitCode: 2
};
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
let matchedSpace;
let matchedPkg;
if (looksLikePath(target)) {
const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);
if (resolvedPath !== null) for (const space of scopedSpaces) {
const found = findPackageByDirPath(space.packages, resolvedPath);
if (found) {
matchedSpace = space;
matchedPkg = found;
break;
}
}
else {
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
if (!resolved.ok) return {
error: resolved.failure,
exitCode: 2
};
const appSpace = scopedSpaces.find((s) => s.spaceId === "app");
if (appSpace) {
matchedSpace = appSpace;
matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);
}
}
} else {
const hits = [];
let bestParseFailure;
for (const space of scopedSpaces) {
const migResult = parseMigrationRef(target, {
graph: space.graph,
refs: space.refs
});
if (!migResult.ok) {
if (bestParseFailure === void 0 || refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)) bestParseFailure = migResult.failure;
continue;
}
const pkg = space.packages.find((p) => p.metadata.migrationHash === migResult.value.migrationHash);
if (pkg) hits.push({
space,
pkg
});
}
if (hits.length > 1) return {
error: errorAmbiguousMigrationRef(target, hits.map((h) => h.space.spaceId)),
exitCode: 2
};
if (hits.length === 1) {
matchedSpace = hits[0].space;
matchedPkg = hits[0].pkg;
} else if (bestParseFailure !== void 0) return {
error: mapRefResolutionError(bestParseFailure),
exitCode: 2
};
}
if (!matchedPkg || !matchedSpace) return {
result: {
ok: false,
failures: [],
summary: `Migration package for "${target}" not found on disk`
},
exitCode: 2
};
const failures = [...checkManifestFilesPresent(matchedSpace)];
for (const f of ["migration.json", "ops.json"]) {
const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);
if (fail) failures.push(fail);
}
const verification = verifyMigrationHash(matchedPkg);
if (!verification.ok) failures.push({
space: matchedSpace.spaceId,
code: "MIGRATION.CHECK_HASH_MISMATCH",
where: migrationFileRelative(matchedPkg.dirPath, "migration.json"),
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
fix: "Re-emit the migration package or restore from version control."
});
const snapshotFailure = await checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg, matchedSpace.projectMigrationsDir);
if (snapshotFailure) failures.push(snapshotFailure);
const resolvedSpaceId = matchedSpace.spaceId !== "app" ? matchedSpace.spaceId : void 0;
if (failures.length === 0) return {
result: {
ok: true,
failures: [],
summary: "All checks passed"
},
exitCode: 0,
...ifDefined("resolvedSpaceId", resolvedSpaceId)
};
return {
result: {
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
},
exitCode: 4,
...ifDefined("resolvedSpaceId", resolvedSpaceId)
};
}
function createMigrationCheckCommand() {
const command = new Command("check");
setCommandDescriptions(command, "Verify artifact and graph integrity", "Validates that on-disk migration packages are internally consistent\n(hashes match, manifests are complete) and that the graph is well-formed\n(edges connect, refs point at valid nodes). The whole-graph check spans\nevery contract space by default; pass --space <id> to narrow to one. A\nmigration reference checks a single package, resolved across all contract\nspaces (narrow with --space; an ambiguous reference is a precondition failure).\nOffline — does not consult the database.\nExit codes: 0 = all checks passed, 2 = precondition failed\n(unresolved target or unknown --space), 4 = integrity failure(s) found.");
setCommandExamples(command, [
"prisma-next migration check",
"prisma-next migration check --space app",
"prisma-next migration check 20260101-add-users",
"prisma-next migration check 20260101-add-users --space app",
"prisma-next migration check --json"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
command.exitOverride();
addGlobalOptions(command).argument("[target]", "Migration reference: directory name, hash/prefix, ref, or path").option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").action(async (target, options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
let outcome;
try {
outcome = await executeMigrationCheckCommand(target, options, flags, ui);
} catch (error) {
outcome = {
result: {
ok: false,
failures: [],
summary: error instanceof Error ? error.message : String(error)
},
exitCode: 2
};
}
if (outcome.error) {
const envelope = outcome.error.toEnvelope();
if (flags.json) ui.output(formatErrorJson(envelope));
else if (!flags.quiet) ui.error(formatErrorOutput(envelope, flags));
process.exit(outcome.exitCode);
}
const result = outcome.result ?? {
ok: false,
failures: [],
summary: "No check result produced"
};
if (flags.json) ui.output(JSON.stringify(result, null, 2));
else if (!flags.quiet) if (result.ok) {
const spaceSuffix = outcome.resolvedSpaceId !== void 0 ? ` (space: ${outcome.resolvedSpaceId})` : "";
ui.log(`✔ ${result.summary}${spaceSuffix}`);
} else {
for (const f of result.failures) {
ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);
ui.log(` fix: ${f.fix}`);
}
ui.log(`\n${result.summary}`);
}
process.exit(outcome.exitCode);
});
return command;
}
//#endregion
export { enumerateCheckSpaces as n, runMigrationCheck as r, createMigrationCheckCommand as t };
//# sourceMappingURL=migration-check-Biu1YZXu.mjs.map
{"version":3,"file":"migration-check-Biu1YZXu.mjs","names":["migrationPathRelative","migrationFileRelative"],"sources":["../src/utils/integrity-violation-to-check-failure.ts","../src/commands/migration-check.ts"],"sourcesContent":["import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';\nimport { join, relative } from 'pathe';\nimport type { CheckFailure } from '../commands/json/schemas';\n\nexport type { CheckFailure } from '../commands/json/schemas';\n\nfunction migrationPathRelative(dirPath: string): string {\n return relative(process.cwd(), dirPath);\n}\n\nfunction migrationFileRelative(dirPath: string, fileName: string): string {\n return join(migrationPathRelative(dirPath), fileName);\n}\n\n/**\n * Map one {@link IntegrityViolation} onto a `migration check` failure row.\n * Sole catalogue mapping from integrity violations to `MIGRATION.CHECK_*` codes.\n */\nexport function integrityViolationToCheckFailure(\n violation: IntegrityViolation,\n migrationsDir: string,\n): CheckFailure {\n const spaceRelative = (spaceId: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId));\n const packageRelative = (spaceId: string, dirName: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId, dirName));\n const refRelative = (spaceId: string, refName: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId, 'refs', `${refName}.json`));\n\n switch (violation.kind) {\n case 'hashMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HASH_MISMATCH',\n where: migrationFileRelative(\n join(migrationsDir, violation.spaceId, violation.dirName),\n 'migration.json',\n ),\n why: `Stored hash ${violation.stored} does not match recomputed hash ${violation.computed}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n case 'providedInvariantsMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_PROVIDED_INVARIANTS_MISMATCH',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" providedInvariants in migration.json disagrees with ops.json.`,\n fix: 'Re-emit the migration package so migration.json and ops.json agree.',\n };\n case 'packageUnloadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_PACKAGE_UNLOADABLE',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" could not be loaded: ${violation.detail}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n case 'sameSourceAndTarget':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_NOOP_SELF_EDGE',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" in space \"${violation.spaceId}\" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,\n fix: 'Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op.',\n };\n case 'orphanSpaceDir':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_ORPHAN_SPACE_DIR',\n where: spaceRelative(violation.spaceId),\n why: `Contract-space directory \"${violation.spaceId}\" exists on disk but no extension declares it.`,\n fix: 'Remove the orphan directory, or declare the extension in `extensionPacks`.',\n };\n case 'declaredButUnmigrated':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_DECLARED_BUT_UNMIGRATED',\n where: spaceRelative(violation.spaceId),\n why: `Extension \"${violation.spaceId}\" is declared in \\`extensionPacks\\` but has no on-disk migrations directory.`,\n fix: 'Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused.',\n };\n case 'headRefMissing':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HEAD_REF_MISSING',\n where: refRelative(violation.spaceId, 'head'),\n why: `Head ref \\`refs/head.json\\` is missing for contract space \"${violation.spaceId}\".`,\n fix: 'Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control.',\n };\n case 'headRefNotInGraph':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HEAD_REF_NOT_IN_GRAPH',\n where: refRelative(violation.spaceId, 'head'),\n why: `Head ref ${violation.hash} for contract space \"${violation.spaceId}\" is not present in its migration graph.`,\n fix: 'Re-emit the contract space migrations, or restore the missing migration package.',\n };\n case 'refUnreadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_REF_UNREADABLE',\n where: refRelative(violation.spaceId, violation.refName),\n why: `Ref \"${violation.refName}\" for contract space \"${violation.spaceId}\" is unreadable: ${violation.detail}`,\n fix: 'Repair or remove the corrupt ref file.',\n };\n case 'targetMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_TARGET_MISMATCH',\n where: spaceRelative(violation.spaceId),\n why: `Contract space \"${violation.spaceId}\" targets \"${violation.actual}\" but the project targets \"${violation.expected}\".`,\n fix: 'Update the extension to target the configured database, or change the project target.',\n };\n case 'disjointness':\n return {\n space: 'app',\n code: 'MIGRATION.CHECK_SPACE_DISJOINTNESS_VIOLATION',\n where: migrationPathRelative(migrationsDir),\n why: `Storage element \"${violation.element}\" is claimed by multiple contract spaces: ${violation.claimedBy.join(', ')}.`,\n fix: 'Update the contracts so each storage element is owned by exactly one contract space.',\n };\n case 'contractUnreadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_CONTRACT_UNREADABLE',\n where: migrationFileRelative(join(migrationsDir, violation.spaceId), 'contract.json'),\n why: `Contract for space \"${violation.spaceId}\" is unreadable: ${violation.detail}`,\n fix: 'Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract.',\n };\n case 'duplicateMigrationHash':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_DUPLICATE_MIGRATION_HASH',\n where: spaceRelative(violation.spaceId),\n why: `Multiple migrations in space \"${violation.spaceId}\" share migrationHash \"${violation.migrationHash}\" (${violation.dirNames.join(', ')}).`,\n fix: 'Re-emit one of the conflicting packages so each migrationHash is unique.',\n };\n }\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport type {\n ContractSpaceAggregate,\n IntegrityViolation,\n} from '@prisma-next/migration-tools/aggregate';\nimport { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';\nimport {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { verifyMigrationHash } from '@prisma-next/migration-tools/hash';\nimport type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';\nimport {\n parseMigrationRef,\n type RefResolutionError,\n} from '@prisma-next/migration-tools/ref-resolution';\nimport type { Refs } from '@prisma-next/migration-tools/refs';\nimport {\n isValidSpaceId,\n listContractSpaceDirectories,\n spaceMigrationDirectory,\n spaceRefsDirectory,\n} from '@prisma-next/migration-tools/spaces';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative } from 'pathe';\nimport {\n type CliStructuredError,\n errorAmbiguousMigrationRef,\n errorInvalidSpaceId,\n errorSpaceNotFound,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';\nimport { formatErrorJson, formatErrorOutput } from '../utils/formatters/errors';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { integrityViolationToCheckFailure } from '../utils/integrity-violation-to-check-failure';\nimport {\n findPackageByDirPath,\n looksLikePath,\n resolveAppTargetPath,\n resolveTargetPathAcrossSpaces,\n} from '../utils/migration-path-target';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type { CheckFailure, MigrationCheckResult } from './json/schemas';\nimport { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';\n\ninterface MigrationCheckOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly space?: string;\n}\n\nexport type { CheckFailure, MigrationCheckResult } from './json/schemas';\nexport { migrationCheckResultSchema } from './json/schemas';\n\nfunction migrationPathRelative(dirPath: string): string {\n return relative(process.cwd(), dirPath);\n}\n\nfunction migrationFileRelative(dirPath: string, fileName: string): string {\n return join(migrationPathRelative(dirPath), fileName);\n}\n\nfunction checkFileExists(\n spaceId: string,\n dirPath: string,\n dirName: string,\n fileName: string,\n): CheckFailure | null {\n if (!existsSync(join(dirPath, fileName))) {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_FILE_MISSING',\n where: migrationFileRelative(dirPath, fileName),\n why: `${fileName} is missing from ${dirName}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n }\n return null;\n}\n\n/**\n * Snapshot-store consistency check for one migration package (D6.5):\n * absent store entry is not an issue (runner independence, ADR 199 — a\n * package dir with only `migration.json` + `ops.json` is legitimate); a\n * present entry whose inner `storage.storageHash` disagrees with\n * `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store\n * entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.\n */\nasync function checkSnapshotConsistency(\n spaceId: string,\n pkg: OnDiskMigrationPackage,\n migrationsDir: string,\n): Promise<CheckFailure | null> {\n let snapshotDir: string;\n try {\n snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);\n } catch {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" declares to=\"${pkg.metadata.to}\", which is not a well-formed contract snapshot hash.`,\n fix: 'Re-emit the migration package so migration.json declares a valid `sha256:<64hex>` to-hash.',\n };\n }\n\n let raw: unknown;\n try {\n raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);\n } catch (error) {\n if (MigrationToolsError.is(error) && error.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING') {\n return null;\n }\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n };\n }\n const record = raw !== null && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};\n const storage = record['storage'] as Record<string, unknown> | undefined;\n const snapshotHash = storage?.['storageHash'];\n if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,\n fix: 'Re-emit the migration package so migration.json and its contract snapshot agree.',\n };\n }\n return null;\n}\n\n/**\n * One contract space's on-disk state, resolved for the explicit graph\n * checks `runMigrationCheck` runs per space: the space's migration\n * packages, its user-authored refs, its induced graph, and the absolute\n * `migrations/<space>/` + `migrations/<space>/refs/` directories the\n * file-existence and dangling-ref `where` paths are derived from.\n */\nexport interface CheckSpace {\n readonly spaceId: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly refs: Refs;\n readonly graph: MigrationGraph;\n readonly migrationsDir: string;\n readonly refsDir: string;\n /** Migrations root the contract-snapshot store lives under — shared by every space. */\n readonly projectMigrationsDir: string;\n}\n\n/**\n * Project the loaded {@link ContractSpaceAggregate} into the\n * {@link CheckSpace} rows the multi-space check iterates — one per on-disk\n * contract-space directory, in the aggregate's `app`-first ordering. Mirrors\n * `migration list`'s `migrationSpaceListEntriesFromAggregate`: space\n * membership matches the on-disk directories, package / ref / graph data come\n * from `aggregate.space(id)`.\n */\nexport async function enumerateCheckSpaces(\n aggregate: ContractSpaceAggregate,\n projectMigrationsDir: string,\n): Promise<readonly CheckSpace[]> {\n const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);\n const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));\n const spaces: CheckSpace[] = [];\n for (const space of aggregate.spaces()) {\n const spaceId = space.spaceId;\n if (!isValidSpaceId(spaceId)) continue;\n if (!onDiskSpaceIds.has(spaceId)) continue;\n const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);\n spaces.push({\n spaceId,\n packages: space.packages,\n refs: space.refs,\n graph: space.graph(),\n migrationsDir,\n refsDir: spaceRefsDirectory(migrationsDir),\n projectMigrationsDir,\n });\n }\n return spaces;\n}\n\nfunction checkManifestFilesPresent(space: CheckSpace): readonly CheckFailure[] {\n if (!existsSync(space.migrationsDir)) return [];\n const loadedDirNames = new Set(space.packages.map((p) => p.dirName));\n const failures: CheckFailure[] = [];\n let entries: string[];\n try {\n entries = readdirSync(space.migrationsDir);\n } catch {\n return failures;\n }\n for (const entry of entries) {\n if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;\n const entryPath = join(space.migrationsDir, entry);\n try {\n if (!statSync(entryPath).isDirectory()) continue;\n } catch {\n continue;\n }\n if (!loadedDirNames.has(entry)) {\n for (const f of ['migration.json', 'ops.json']) {\n const fail = checkFileExists(space.spaceId, entryPath, entry, f);\n if (fail) failures.push(fail);\n }\n }\n }\n return failures;\n}\n\nfunction checkReachability(space: CheckSpace): readonly CheckFailure[] {\n const allToHashes = new Set(space.packages.map((p) => p.metadata.to));\n const failures: CheckFailure[] = [];\n for (const pkg of space.packages) {\n const isReachable =\n pkg.metadata.from === null ||\n allToHashes.has(pkg.metadata.from) ||\n pkg.metadata.from === 'sha256:empty';\n if (!isReachable) {\n failures.push({\n space: space.spaceId,\n code: 'MIGRATION.CHECK_UNREACHABLE_MIGRATION',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" starts from ${pkg.metadata.from} which no other migration produces`,\n fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',\n });\n }\n }\n return failures;\n}\n\nfunction checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {\n const failures: CheckFailure[] = [];\n for (const [name, entry] of Object.entries(space.refs)) {\n if (!space.graph.nodes.has(entry.hash)) {\n failures.push({\n space: space.spaceId,\n code: 'MIGRATION.CHECK_DANGLING_REF',\n where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),\n why: `Ref \"${name}\" points at ${entry.hash} which does not exist in the migration graph`,\n fix: `Update the ref with \\`prisma-next ref set ${name} <valid-hash>\\` or delete it.`,\n });\n }\n }\n return failures;\n}\n\nasync function checkSpace(space: CheckSpace): Promise<readonly CheckFailure[]> {\n const snapshotFailures = await Promise.all(\n space.packages.map((pkg) =>\n checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir),\n ),\n );\n return [\n ...checkManifestFilesPresent(space),\n ...snapshotFailures.filter((f): f is CheckFailure => f !== null),\n ...checkReachability(space),\n ...checkDanglingRefs(space),\n ];\n}\n\n/**\n * Inputs for {@link runMigrationCheck} — the multi-space policy core of\n * the holistic (no-arg) `migration check`. Enumeration is supplied by the\n * caller (the CLI shell builds it from {@link enumerateCheckSpaces}); the\n * core does not touch config, flags, or streams.\n */\nexport interface RunMigrationCheckInputs {\n readonly spaces: readonly CheckSpace[];\n readonly spaceFilter?: string;\n}\n\n/**\n * Policy core of the holistic `migration check`: validates `--space`,\n * narrows the pre-enumerated spaces, and runs the per-space explicit graph\n * checks (file-existence, snapshot consistency, reachability, dangling\n * refs), aggregating every failure into one {@link MigrationCheckResult}.\n *\n * `--space` validation mirrors `migration list`: an invalid id →\n * {@link errorInvalidSpaceId}; an id with no on-disk space →\n * {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.\n * Aggregate-integrity violations (which already span every space) are folded\n * in by the caller, not here.\n */\nexport async function runMigrationCheck(\n inputs: RunMigrationCheckInputs,\n): Promise<Result<MigrationCheckResult, CliStructuredError>> {\n const { spaces, spaceFilter } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return notOk(errorInvalidSpaceId(spaceFilter));\n }\n if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {\n return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;\n\n const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();\n if (failures.length === 0) {\n return ok({ ok: true, failures: [], summary: 'All checks passed' });\n }\n return ok({ ok: false, failures, summary: `${failures.length} integrity failure(s)` });\n}\n\nasync function loadAggregateIntegrityViolations(\n config: Awaited<ReturnType<typeof loadConfig>>,\n migrationsDir: string,\n): Promise<readonly IntegrityViolation[]> {\n try {\n const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');\n const familyInstance = config.family.create(createControlStack(config));\n const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);\n\n const parsedAppContract: unknown = JSON.parse(contractJsonContent);\n const aggregate = await loadContractSpaceAggregate({\n migrationsDir,\n deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),\n appContract: familyInstance.deserializeContract(parsedAppContract),\n });\n return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });\n } catch {\n return [];\n }\n}\n\ninterface MigrationCheckOutcome {\n readonly result?: MigrationCheckResult;\n readonly error?: CliStructuredError;\n readonly exitCode: number;\n readonly resolvedSpaceId?: string;\n}\n\nasync function executeMigrationCheckCommand(\n target: string | undefined,\n options: MigrationCheckOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<MigrationCheckOutcome> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =\n resolveMigrationPaths(options.config, config);\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: appMigrationsRelative },\n ];\n if (target) {\n details.push({ label: 'target', value: target });\n }\n const header = formatStyledHeader({\n command: 'migration check',\n description: 'Verify artifact and graph integrity',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n const loadedAggregate = await buildReadAggregate(config, { migrationsDir });\n if (!loadedAggregate.ok) {\n return { error: loadedAggregate.failure, exitCode: PRECONDITION };\n }\n\n const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);\n\n if (target) {\n return await checkSingleTarget(target, {\n spaces,\n ...(options.space !== undefined ? { spaceFilter: options.space } : {}),\n appMigrationsDir,\n appMigrationsRelative,\n });\n }\n\n const checkResult = await runMigrationCheck({\n spaces,\n ...(options.space !== undefined ? { spaceFilter: options.space } : {}),\n });\n if (!checkResult.ok) {\n return { error: checkResult.failure, exitCode: PRECONDITION };\n }\n\n const failures: CheckFailure[] = [...checkResult.value.failures];\n const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);\n const scopedViolations =\n options.space === undefined\n ? allViolations\n : allViolations.filter((v) => v.kind !== 'disjointness' && v.spaceId === options.space);\n for (const violation of scopedViolations) {\n failures.push(integrityViolationToCheckFailure(violation, migrationsDir));\n }\n\n if (failures.length === 0) {\n return {\n result: { ok: true, failures: [], summary: 'All checks passed' },\n exitCode: OK,\n };\n }\n\n return {\n result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },\n exitCode: INTEGRITY_FAILED,\n };\n}\n\ninterface SingleTargetInputs {\n readonly spaces: readonly CheckSpace[];\n readonly spaceFilter?: string;\n readonly appMigrationsDir: string;\n readonly appMigrationsRelative: string;\n}\n\n/**\n * Ranks ref-resolution failure kinds by how informative they are, so a\n * single-target check surfaces the most useful failure across spaces instead of\n * whichever space failed first. `not-found` (the input matched nothing here)\n * says the least; a malformed input, a wrong grammar, or an in-space ambiguity\n * all say more.\n */\nfunction refFailureSpecificity(error: RefResolutionError): number {\n switch (error.kind) {\n case 'wrong-grammar':\n return 3;\n case 'ambiguous':\n return 2;\n case 'invalid-format':\n return 1;\n case 'not-found':\n return 0;\n }\n}\n\n/**\n * Single-target (`check <ref/path>`) mode — resolves a migration reference\n * across all contract spaces (or the one space narrowed by `--space <id>`).\n *\n * Resolution:\n * - filesystem path → find the owning space by checking which space's\n * `migrationsDir` contains the resolved path; falls back to app-relative\n * validation when the path is outside every space dir.\n * - ref → `parseMigrationRef` against each in-scope space; collect every\n * (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity\n * error (qualify with `--space`).\n *\n * `--space <id>` is validated the same way the holistic path does it:\n * invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.\n */\nasync function checkSingleTarget(\n target: string,\n inputs: SingleTargetInputs,\n): Promise<MigrationCheckOutcome> {\n const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return { error: errorInvalidSpaceId(spaceFilter), exitCode: PRECONDITION };\n }\n if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {\n return {\n error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),\n exitCode: PRECONDITION,\n };\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;\n\n let matchedSpace: CheckSpace | undefined;\n let matchedPkg: OnDiskMigrationPackage | undefined;\n\n if (looksLikePath(target)) {\n const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);\n if (resolvedPath !== null) {\n for (const space of scopedSpaces) {\n const found = findPackageByDirPath(space.packages, resolvedPath);\n if (found) {\n matchedSpace = space;\n matchedPkg = found;\n break;\n }\n }\n } else {\n // Path outside every space dir — fall back to app-relative validation\n const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);\n if (!resolved.ok) {\n return { error: resolved.failure, exitCode: PRECONDITION };\n }\n const appSpace = scopedSpaces.find((s) => s.spaceId === 'app');\n if (appSpace) {\n matchedSpace = appSpace;\n matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);\n }\n }\n } else {\n // Ref resolution: try each in-scope space, collect all hits.\n const hits: Array<{ space: CheckSpace; pkg: OnDiskMigrationPackage }> = [];\n let bestParseFailure: RefResolutionError | undefined;\n for (const space of scopedSpaces) {\n const migResult = parseMigrationRef(target, { graph: space.graph, refs: space.refs });\n if (!migResult.ok) {\n // Keep scanning — a later space may hold a hit that must not be discarded.\n // When no space yields a hit, keep the most informative failure rather than\n // whichever space failed first (the kind is space-dependent).\n if (\n bestParseFailure === undefined ||\n refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)\n ) {\n bestParseFailure = migResult.failure;\n }\n continue;\n }\n const pkg = space.packages.find(\n (p) => p.metadata.migrationHash === migResult.value.migrationHash,\n );\n if (pkg) {\n hits.push({ space, pkg });\n }\n }\n\n if (hits.length > 1) {\n const spaceIds = hits.map((h) => h.space.spaceId);\n return {\n error: errorAmbiguousMigrationRef(target, spaceIds),\n exitCode: PRECONDITION,\n };\n }\n\n if (hits.length === 1) {\n matchedSpace = hits[0]!.space;\n matchedPkg = hits[0]!.pkg;\n } else if (bestParseFailure !== undefined) {\n // The ref didn't resolve in any in-scope space — surface the most informative\n // parse failure through the shared ref-resolution envelope (CONTRACT.VERIFY_FAILED) the\n // earlier work established, rather than a bespoke string. (Ref-resolved-but-\n // no-package falls through to the \"not found on disk\" result below.)\n return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };\n }\n }\n\n if (!matchedPkg || !matchedSpace) {\n return {\n result: {\n ok: false,\n failures: [],\n summary: `Migration package for \"${target}\" not found on disk`,\n },\n exitCode: PRECONDITION,\n };\n }\n\n const failures: CheckFailure[] = [...checkManifestFilesPresent(matchedSpace)];\n\n for (const f of ['migration.json', 'ops.json']) {\n const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);\n if (fail) failures.push(fail);\n }\n\n const verification = verifyMigrationHash(matchedPkg);\n if (!verification.ok) {\n failures.push({\n space: matchedSpace.spaceId,\n code: 'MIGRATION.CHECK_HASH_MISMATCH',\n where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),\n why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,\n fix: 'Re-emit the migration package or restore from version control.',\n });\n }\n\n const snapshotFailure = await checkSnapshotConsistency(\n matchedSpace.spaceId,\n matchedPkg,\n matchedSpace.projectMigrationsDir,\n );\n if (snapshotFailure) failures.push(snapshotFailure);\n\n const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;\n\n if (failures.length === 0) {\n return {\n result: { ok: true, failures: [], summary: 'All checks passed' },\n exitCode: OK,\n ...ifDefined('resolvedSpaceId', resolvedSpaceId),\n };\n }\n return {\n result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },\n exitCode: INTEGRITY_FAILED,\n ...ifDefined('resolvedSpaceId', resolvedSpaceId),\n };\n}\n\nexport function createMigrationCheckCommand(): Command {\n const command = new Command('check');\n setCommandDescriptions(\n command,\n 'Verify artifact and graph integrity',\n 'Validates that on-disk migration packages are internally consistent\\n' +\n '(hashes match, manifests are complete) and that the graph is well-formed\\n' +\n '(edges connect, refs point at valid nodes). The whole-graph check spans\\n' +\n 'every contract space by default; pass --space <id> to narrow to one. A\\n' +\n 'migration reference checks a single package, resolved across all contract\\n' +\n 'spaces (narrow with --space; an ambiguous reference is a precondition failure).\\n' +\n 'Offline — does not consult the database.\\n' +\n 'Exit codes: 0 = all checks passed, 2 = precondition failed\\n' +\n '(unresolved target or unknown --space), 4 = integrity failure(s) found.',\n );\n setCommandExamples(command, [\n 'prisma-next migration check',\n 'prisma-next migration check --space app',\n 'prisma-next migration check 20260101-add-users',\n 'prisma-next migration check 20260101-add-users --space app',\n 'prisma-next migration check --json',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n command.exitOverride();\n addGlobalOptions(command)\n .argument('[target]', 'Migration reference: directory name, hash/prefix, ref, or path')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .action(async (target: string | undefined, options: MigrationCheckOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n let outcome: MigrationCheckOutcome;\n try {\n outcome = await executeMigrationCheckCommand(target, options, flags, ui);\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n outcome = {\n result: { ok: false, failures: [], summary: msg },\n exitCode: PRECONDITION,\n };\n }\n\n if (outcome.error) {\n const envelope = outcome.error.toEnvelope();\n if (flags.json) {\n ui.output(formatErrorJson(envelope));\n } else if (!flags.quiet) {\n ui.error(formatErrorOutput(envelope, flags));\n }\n process.exit(outcome.exitCode);\n }\n\n const result = outcome.result ?? {\n ok: false,\n failures: [],\n summary: 'No check result produced',\n };\n\n if (flags.json) {\n ui.output(JSON.stringify(result, null, 2));\n } else if (!flags.quiet) {\n if (result.ok) {\n const spaceSuffix =\n outcome.resolvedSpaceId !== undefined ? ` (space: ${outcome.resolvedSpaceId})` : '';\n ui.log(`✔ ${result.summary}${spaceSuffix}`);\n } else {\n for (const f of result.failures) {\n ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);\n ui.log(` fix: ${f.fix}`);\n }\n ui.log(`\\n${result.summary}`);\n }\n }\n\n process.exit(outcome.exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { A as formatStyledHeader, B as errorContractValidationFailed, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, ct as errorUnexpected, i as maskConnectionUrl, o as resolveContractPath, ot as errorTargetMigrationNotSupported, t as addGlobalOptions } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-DeJvLD2h.mjs";
import { loadConfig } from "@prisma-next/config-loader";
import { notOk, ok } from "@prisma-next/utils/result";
import { readFile } from "node:fs/promises";
import { hasMigrations } from "@prisma-next/framework-components/control";
import { relative, resolve } from "node:path";
//#region src/utils/migration-command-scaffold.ts
/**
* Prepares the shared context for migration commands (db init, db update).
*
* Handles: config loading, contract file reading, JSON parsing, connection resolution,
* driver/migration-support validation, client creation, and header output.
*
* Returns a Result with either the resolved context or a structured error.
*/
async function prepareMigrationContext(options, flags, ui, descriptor) {
const config = await loadConfig(options.config);
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
const contractPathAbsolute = resolveContractPath(config);
const contractPath = relative(process.cwd(), contractPathAbsolute);
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "contract",
value: contractPath
}];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
if (options.dryRun) details.push({
label: "mode",
value: "dry run"
});
const header = formatStyledHeader({
command: descriptor.commandName,
description: descriptor.description,
url: descriptor.url,
details,
flags
});
ui.stderr(header);
}
let contractJsonContent;
try {
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
} catch (error) {
if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
why: `Contract file not found at ${contractPathAbsolute}`,
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
}
let contractJson;
try {
contractJson = JSON.parse(contractJsonContent);
} catch (error) {
return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const dbConnection = options.db ?? config.db?.connection;
if (!dbConnection) return notOk(errorDatabaseConnectionRequired({
why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: descriptor.commandName
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }));
if (!hasMigrations(config.target)) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
const onProgress = createProgressAdapter({
ui,
flags
});
return ok({
client,
contractJson,
dbConnection,
onProgress,
configPath,
contractPath,
contractPathAbsolute,
config
});
}
/**
* Registers the shared CLI options for migration commands (db init, db update).
*/
function addMigrationCommandOptions(command) {
addGlobalOptions(command);
return command.option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--dry-run", "Preview planned operations without applying", false);
}
//#endregion
export { prepareMigrationContext as n, addMigrationCommandOptions as t };
//# sourceMappingURL=migration-command-scaffold-D9JkLq_M.mjs.map
{"version":3,"file":"migration-command-scaffold-D9JkLq_M.mjs","names":[],"sources":["../src/utils/migration-command-scaffold.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { hasMigrations } from '@prisma-next/framework-components/control';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport type { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport type { ControlClient } from '../control-api/types';\nimport {\n type CliStructuredError,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from './cli-errors';\nimport { addGlobalOptions, maskConnectionUrl, resolveContractPath } from './command-helpers';\nimport { formatStyledHeader } from './formatters/styled';\nimport type { GlobalFlags } from './global-flags';\nimport { createProgressAdapter } from './progress-adapter';\nimport type { TerminalUI } from './terminal-ui';\n\n/**\n * Resolved context for a migration command.\n * Contains everything needed to invoke a control-api operation.\n */\nexport interface MigrationContext {\n readonly client: ControlClient;\n readonly contractJson: Record<string, unknown>;\n readonly dbConnection: unknown;\n readonly onProgress: ReturnType<typeof createProgressAdapter>;\n readonly configPath: string;\n readonly contractPath: string;\n readonly contractPathAbsolute: string;\n readonly config: Awaited<ReturnType<typeof loadConfig>>;\n}\n\n/**\n * Command-specific configuration for the shared scaffold.\n */\nexport interface MigrationCommandDescriptor {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\n/**\n * Prepares the shared context for migration commands (db init, db update).\n *\n * Handles: config loading, contract file reading, JSON parsing, connection resolution,\n * driver/migration-support validation, client creation, and header output.\n *\n * Returns a Result with either the resolved context or a structured error.\n */\nexport async function prepareMigrationContext(\n options: { readonly db?: string; readonly config?: string; readonly dryRun?: boolean },\n flags: GlobalFlags,\n ui: TerminalUI,\n descriptor: MigrationCommandDescriptor,\n): Promise<Result<MigrationContext, CliStructuredError>> {\n // Load config\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header to stderr (decoration)\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n }\n if (options.dryRun) {\n details.push({ label: 'mode', value: 'dry run' });\n }\n const header = formatStyledHeader({\n command: descriptor.commandName,\n description: descriptor.description,\n url: descriptor.url,\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Load contract file\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n // Parse contract JSON\n let contractJson: Record<string, unknown>;\n try {\n contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n // Resolve database connection (--db flag or config.db.connection)\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: descriptor.commandName,\n }),\n );\n }\n\n // Check for driver\n if (!config.driver) {\n return notOk(\n errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),\n );\n }\n\n if (!hasMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n // Create control client\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n // Create progress adapter\n const onProgress = createProgressAdapter({ ui, flags });\n\n return ok({\n client,\n contractJson,\n dbConnection,\n onProgress,\n configPath,\n contractPath,\n contractPathAbsolute,\n config,\n });\n}\n\n/**\n * Registers the shared CLI options for migration commands (db init, db update).\n */\nexport function addMigrationCommandOptions(command: Command): Command {\n addGlobalOptions(command);\n return command\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--dry-run', 'Preview planned operations without applying', false);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,eAAsB,wBACpB,SACA,OACA,IACA,YACuD;CAEvD,MAAM,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC9C,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,IAAI,GAAG,QAAQ,QAAQ,MAAM,CAAC,IAC/C;CACJ,MAAM,uBAAuB,oBAAoB,MAAM;CACvD,MAAM,eAAe,SAAS,QAAQ,IAAI,GAAG,oBAAoB;CAGjE,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;EAAW,GACrC;GAAE,OAAO;GAAY,OAAO;EAAa,CAC3C;EACA,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,EAAE;EAAE,CAAC;EAE1E,IAAI,QAAQ,QACV,QAAQ,KAAK;GAAE,OAAO;GAAQ,OAAO;EAAU,CAAC;EAElD,MAAM,SAAS,mBAAmB;GAChC,SAAS,WAAW;GACpB,aAAa,WAAW;GACxB,KAAK,WAAW;GAChB;GACA;EACF,CAAC;EACD,GAAG,OAAO,MAAM;CAClB;CAGA,IAAI;CACJ,IAAI;EACF,sBAAsB,MAAM,SAAS,sBAAsB,OAAO;CACpE,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAU,MAA4B,SAAS,UAClE,OAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;EACjH,CAAC,CACH;EAEF,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IAC7F,CAAC,CACH;CACF;CAGA,IAAI;CACJ,IAAI;EACF,eAAe,KAAK,MAAM,mBAAmB;CAC/C,SAAS,OAAO;EACd,OAAO,MACL,8BACE,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAClF,EAAE,OAAO,EAAE,MAAM,qBAAqB,EAAE,CAC1C,CACF;CACF;CAGA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,WAAW,YAAY,yBAAyB,WAAW;EACvG,aAAa,WAAW;CAC1B,CAAC,CACH;CAIF,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAAE,KAAK,iCAAiC,WAAW,cAAc,CAAC,CACxF;CAGF,IAAI,CAAC,cAAc,OAAO,MAAM,GAC9B,OAAO,MACL,iCAAiC,EAC/B,KAAK,WAAW,OAAO,OAAO,GAAG,+BACnC,CAAC,CACH;CAIF,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,CAAC;CAC5C,CAAC;CAGD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH;;;;AAKA,SAAgB,2BAA2B,SAA2B;CACpE,iBAAiB,OAAO;CACxB,OAAO,QACJ,OAAO,cAAc,4BAA4B,CAAC,CAClD,OAAO,mBAAmB,+BAA+B,CAAC,CAC1D,OAAO,aAAa,+CAA+C,KAAK;AAC7E"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, d as setCommandSeeAlso, dt as requireLiveDatabase, f as targetSupportsMigrations, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createControlClient } from "./client-DeJvLD2h.mjs";
import { _ as migrationListEmptySource, g as abbreviateContractHash, o as createAnsiMigrationListStyler, s as IDENTITY_MIGRATION_LIST_STYLER, v as migrationListForwardArrow } from "./migration-graph-command-render-B83xrnNy.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import stringWidth from "string-width";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/formatters/migration-log-table.ts
const HEADING_APPLIED_AT = "Applied at";
const HEADING_SPACE = "Space";
const HEADING_MIGRATION = "Migration";
const HEADING_CHANGE = "Change";
const HEADING_OPS = "Ops";
const COLUMN_SEPARATOR = " ";
const DIVIDER_CHAR = "─";
const ASCII_DIVIDER_CHAR = "-";
function sortLedgerEntries(entries) {
return [...entries].sort((left, right) => {
const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();
if (timeDiff !== 0) return timeDiff;
const spaceDiff = left.space.localeCompare(right.space);
if (spaceDiff !== 0) return spaceDiff;
return left.migrationName.localeCompare(right.migrationName);
});
}
function pad2(value) {
return String(value).padStart(2, "0");
}
function formatLedgerAppliedAt(date, mode) {
if (mode === "iso") return date.toISOString();
if (mode === "utc") return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;
const offsetMinutes = -date.getTimezoneOffset();
const sign = offsetMinutes >= 0 ? "+" : "-";
const absoluteOffset = Math.abs(offsetMinutes);
const offsetHours = pad2(Math.floor(absoluteOffset / 60));
const offsetMins = pad2(absoluteOffset % 60);
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;
}
function formatHashEndpoint(hash, glyphMode = "unicode") {
if (hash === null) return migrationListEmptySource(glyphMode);
return abbreviateContractHash(hash);
}
function formatHashTransition(from, to, glyphMode = "unicode") {
return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;
}
function styleHashTransition(from, to, styler, glyphMode = "unicode") {
return `${from === null ? styler.glyph(migrationListEmptySource(glyphMode)) : styler.sourceHash(abbreviateContractHash(from))} ${styler.glyph(migrationListForwardArrow(glyphMode))} ${styler.destHash(abbreviateContractHash(to))}`;
}
function padVisible(text, targetWidth) {
const padding = Math.max(0, targetWidth - stringWidth(text));
return text + " ".repeat(padding);
}
function columnWidth(values) {
return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);
}
function padDividerCell(valueWidth, dividerChar) {
return dividerChar.repeat(valueWidth + 2);
}
function padTextCell(value, valueWidth) {
return ` ${padVisible(value, valueWidth)} `;
}
function padOpsCell(value, valueWidth) {
const padding = Math.max(0, valueWidth - stringWidth(value));
return ` ${" ".repeat(padding)}${value} `;
}
function renderMigrationLogTable(entries, options = {}) {
const sorted = sortLedgerEntries(entries);
if (sorted.length === 0) return "";
const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;
const glyphMode = options.glyphMode ?? "unicode";
const dividerChar = glyphMode === "ascii" ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;
const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;
const timestampMode = options.utc ? "utc" : "local";
const rows = sorted.map((entry) => ({
appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),
space: entry.space,
migrationName: entry.migrationName,
transition: formatHashTransition(entry.from, entry.to, glyphMode),
ops: `${entry.operationCount} ops`,
from: entry.from,
to: entry.to
}));
const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);
const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;
const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);
const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);
const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);
const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];
if (showSpace) headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));
headingParts.push(padTextCell(HEADING_MIGRATION, nameWidth), padTextCell(HEADING_CHANGE, transitionWidth), padOpsCell(HEADING_OPS, opsWidth));
const heading = headingParts.join(COLUMN_SEPARATOR);
const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];
if (showSpace) dividerParts.push(padDividerCell(spaceWidth, dividerChar));
dividerParts.push(padDividerCell(nameWidth, dividerChar), padDividerCell(transitionWidth, dividerChar), padDividerCell(opsWidth, dividerChar));
return [
heading,
dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR),
...rows.map((row) => {
const parts = [padTextCell(row.appliedAt, appliedAtWidth)];
if (showSpace) parts.push(padTextCell(row.space, spaceWidth));
parts.push(padTextCell(styler.dirName(row.migrationName), nameWidth), padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth), padOpsCell(row.ops, opsWidth));
return parts.join(COLUMN_SEPARATOR);
})
].join("\n");
}
function serializeLedgerEntriesForJson(entries) {
return sortLedgerEntries(entries).map((entry) => ({
space: entry.space,
name: entry.migrationName,
hash: entry.migrationHash,
fromContract: entry.from,
toContract: entry.to,
appliedAt: formatLedgerAppliedAt(entry.appliedAt, "iso"),
operationCount: entry.operationCount
}));
}
const MIGRATION_LOG_EMPTY_MESSAGE = "No migrations have been applied to this database.";
//#endregion
//#region src/commands/migration-log.ts
async function executeMigrationLogCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath } = resolveMigrationPaths(options.config, config);
const dbConnection = options.db ?? config.db?.connection;
const missingDb = requireLiveDatabase({
dbConnection,
hasDriver: !!config.driver,
why: `migration log needs a database connection and driver to read the ledger (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: "migration log"
});
if (missingDb) return notOk(missingDb);
if (!targetSupportsMigrations(config.target)) return notOk(errorUnexpected("Target does not support migrations"));
if (!flags.json && !flags.quiet) {
const header = formatStyledHeader({
command: "migration log",
description: "Show executed migration history from the database ledger",
details: [{
label: "config",
value: configPath
}, ...typeof dbConnection === "string" ? [{
label: "database",
value: maskConnectionUrl(dbConnection)
}] : []],
flags
});
ui.stderr(header);
}
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
...ifDefined("driver", config.driver),
extensionPacks: config.extensionPacks ?? []
});
try {
await client.connect(dbConnection);
return ok(await client.readLedger());
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` }));
} finally {
await client.close();
}
}
function createMigrationLogCommand() {
const command = new Command("log");
setCommandDescriptions(command, "Show executed migration history", "Reads the database ledger and displays every applied migration edge\nin chronological order, including rollbacks and re-applies, merged\nacross all contract spaces. Requires a database connection.");
setCommandExamples(command, [
"prisma-next migration log --db $DATABASE_URL",
"prisma-next migration log --utc --db $DATABASE_URL",
"prisma-next migration log --json --db $DATABASE_URL"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--utc", "Render human timestamps in UTC instead of local time").option("--ascii", "Use ASCII glyphs (pipe-friendly)").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeMigrationLogCommand(options, flags, ui), flags, ui, (entries) => {
if (flags.json) {
const records = serializeLedgerEntriesForJson(entries);
const result = {
ok: true,
records,
summary: `${records.length} migration(s) applied`
};
ui.output(JSON.stringify(result, null, 2));
} else if (!flags.quiet) if (entries.length === 0) ui.output(MIGRATION_LOG_EMPTY_MESSAGE);
else {
const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });
ui.output(renderMigrationLogTable(entries, {
utc: options.utc === true,
styler,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
}
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { executeMigrationLogCommand as n, createMigrationLogCommand as t };
//# sourceMappingURL=migration-log-CGfn5C_I.mjs.map
{"version":3,"file":"migration-log-CGfn5C_I.mjs","names":[],"sources":["../src/utils/formatters/migration-log-table.ts","../src/commands/migration-log.ts"],"sourcesContent":["import type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport stringWidth from 'string-width';\nimport type { GlyphMode } from '../glyph-mode';\nimport {\n abbreviateContractHash,\n migrationListEmptySource,\n migrationListForwardArrow,\n} from './migration-list-data-column';\nimport { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';\n\nexport type LedgerTimestampMode = 'local' | 'utc' | 'iso';\n\nexport interface RenderMigrationLogTableOptions {\n readonly utc?: boolean;\n readonly styler?: MigrationListStyler;\n readonly glyphMode?: GlyphMode;\n}\n\nexport interface SerializedLedgerEntryRecord {\n readonly space: string;\n readonly name: string;\n readonly hash: string;\n readonly fromContract: string | null;\n readonly toContract: string;\n readonly appliedAt: string;\n readonly operationCount: number;\n}\n\nconst HEADING_APPLIED_AT = 'Applied at';\nconst HEADING_SPACE = 'Space';\nconst HEADING_MIGRATION = 'Migration';\nconst HEADING_CHANGE = 'Change';\nconst HEADING_OPS = 'Ops';\nconst COLUMN_SEPARATOR = ' ';\nconst DIVIDER_CHAR = '─';\nconst ASCII_DIVIDER_CHAR = '-';\n\nexport function sortLedgerEntries(entries: readonly LedgerEntryRecord[]): LedgerEntryRecord[] {\n return [...entries].sort((left, right) => {\n const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();\n if (timeDiff !== 0) {\n return timeDiff;\n }\n const spaceDiff = left.space.localeCompare(right.space);\n if (spaceDiff !== 0) {\n return spaceDiff;\n }\n return left.migrationName.localeCompare(right.migrationName);\n });\n}\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, '0');\n}\n\nexport function formatLedgerAppliedAt(date: Date, mode: LedgerTimestampMode): string {\n if (mode === 'iso') {\n return date.toISOString();\n }\n if (mode === 'utc') {\n return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;\n }\n const offsetMinutes = -date.getTimezoneOffset();\n const sign = offsetMinutes >= 0 ? '+' : '-';\n const absoluteOffset = Math.abs(offsetMinutes);\n const offsetHours = pad2(Math.floor(absoluteOffset / 60));\n const offsetMins = pad2(absoluteOffset % 60);\n return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;\n}\n\nexport function formatHashEndpoint(hash: string | null, glyphMode: GlyphMode = 'unicode'): string {\n if (hash === null) {\n return migrationListEmptySource(glyphMode);\n }\n return abbreviateContractHash(hash);\n}\n\nexport function formatHashTransition(\n from: string | null,\n to: string,\n glyphMode: GlyphMode = 'unicode',\n): string {\n return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;\n}\n\nexport function styleHashTransition(\n from: string | null,\n to: string,\n styler: MigrationListStyler,\n glyphMode: GlyphMode = 'unicode',\n): string {\n const fromPart =\n from === null\n ? styler.glyph(migrationListEmptySource(glyphMode))\n : styler.sourceHash(abbreviateContractHash(from));\n const arrow = styler.glyph(migrationListForwardArrow(glyphMode));\n const dest = styler.destHash(abbreviateContractHash(to));\n return `${fromPart} ${arrow} ${dest}`;\n}\n\nfunction padVisible(text: string, targetWidth: number): string {\n const padding = Math.max(0, targetWidth - stringWidth(text));\n return text + ' '.repeat(padding);\n}\n\nfunction columnWidth(values: readonly string[]): number {\n return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);\n}\n\nfunction padDividerCell(valueWidth: number, dividerChar: string): string {\n return dividerChar.repeat(valueWidth + 2);\n}\n\nfunction padTextCell(value: string, valueWidth: number): string {\n return ` ${padVisible(value, valueWidth)} `;\n}\n\nfunction padOpsCell(value: string, valueWidth: number): string {\n const padding = Math.max(0, valueWidth - stringWidth(value));\n return ` ${' '.repeat(padding)}${value} `;\n}\n\nexport function renderMigrationLogTable(\n entries: readonly LedgerEntryRecord[],\n options: RenderMigrationLogTableOptions = {},\n): string {\n const sorted = sortLedgerEntries(entries);\n if (sorted.length === 0) {\n return '';\n }\n\n const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;\n const glyphMode = options.glyphMode ?? 'unicode';\n const dividerChar = glyphMode === 'ascii' ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;\n const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;\n const timestampMode: LedgerTimestampMode = options.utc ? 'utc' : 'local';\n const rows = sorted.map((entry) => ({\n appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),\n space: entry.space,\n migrationName: entry.migrationName,\n transition: formatHashTransition(entry.from, entry.to, glyphMode),\n ops: `${entry.operationCount} ops`,\n from: entry.from,\n to: entry.to,\n }));\n\n const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);\n const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;\n const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);\n const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);\n const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);\n\n const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];\n if (showSpace) {\n headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));\n }\n headingParts.push(\n padTextCell(HEADING_MIGRATION, nameWidth),\n padTextCell(HEADING_CHANGE, transitionWidth),\n padOpsCell(HEADING_OPS, opsWidth),\n );\n const heading = headingParts.join(COLUMN_SEPARATOR);\n\n const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];\n if (showSpace) {\n dividerParts.push(padDividerCell(spaceWidth, dividerChar));\n }\n dividerParts.push(\n padDividerCell(nameWidth, dividerChar),\n padDividerCell(transitionWidth, dividerChar),\n padDividerCell(opsWidth, dividerChar),\n );\n const divider = dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR);\n\n const dataRows = rows.map((row) => {\n const parts = [padTextCell(row.appliedAt, appliedAtWidth)];\n if (showSpace) {\n parts.push(padTextCell(row.space, spaceWidth));\n }\n parts.push(\n padTextCell(styler.dirName(row.migrationName), nameWidth),\n padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth),\n padOpsCell(row.ops, opsWidth),\n );\n return parts.join(COLUMN_SEPARATOR);\n });\n\n return [heading, divider, ...dataRows].join('\\n');\n}\n\nexport function serializeLedgerEntriesForJson(\n entries: readonly LedgerEntryRecord[],\n): SerializedLedgerEntryRecord[] {\n return sortLedgerEntries(entries).map((entry) => ({\n space: entry.space,\n name: entry.migrationName,\n hash: entry.migrationHash,\n fromContract: entry.from,\n toContract: entry.to,\n appliedAt: formatLedgerAppliedAt(entry.appliedAt, 'iso'),\n operationCount: entry.operationCount,\n }));\n}\n\nexport const MIGRATION_LOG_EMPTY_MESSAGE = 'No migrations have been applied to this database.';\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorUnexpected,\n mapMigrationToolsError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n maskConnectionUrl,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n targetSupportsMigrations,\n} from '../utils/command-helpers';\nimport { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';\nimport {\n MIGRATION_LOG_EMPTY_MESSAGE,\n renderMigrationLogTable,\n serializeLedgerEntriesForJson,\n} from '../utils/formatters/migration-log-table';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type { MigrationLogResult } from './json/schemas';\n\nexport type { MigrationLogResult };\n\ninterface MigrationLogOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly utc?: boolean;\n readonly ascii?: boolean;\n}\n\nexport async function executeMigrationLogCommand(\n options: MigrationLogOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<readonly LedgerEntryRecord[], CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath } = resolveMigrationPaths(options.config, config);\n\n const dbConnection = options.db ?? config.db?.connection;\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver: !!config.driver,\n why: `migration log needs a database connection and driver to read the ledger (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration log',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n if (!targetSupportsMigrations(config.target)) {\n return notOk(errorUnexpected('Target does not support migrations'));\n }\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration log',\n description: 'Show executed migration history from the database ledger',\n details: [\n { label: 'config', value: configPath },\n ...(typeof dbConnection === 'string'\n ? [{ label: 'database', value: maskConnectionUrl(dbConnection) }]\n : []),\n ],\n flags,\n });\n ui.stderr(header);\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n ...ifDefined('driver', config.driver),\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const ledger = await client.readLedger();\n return ok(ledger);\n } catch (error) {\n if (CliStructuredError.is(error)) return notOk(error);\n if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationLogCommand(): Command {\n const command = new Command('log');\n setCommandDescriptions(\n command,\n 'Show executed migration history',\n 'Reads the database ledger and displays every applied migration edge\\n' +\n 'in chronological order, including rollbacks and re-applies, merged\\n' +\n 'across all contract spaces. Requires a database connection.',\n );\n setCommandExamples(command, [\n 'prisma-next migration log --db $DATABASE_URL',\n 'prisma-next migration log --utc --db $DATABASE_URL',\n 'prisma-next migration log --json --db $DATABASE_URL',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--utc', 'Render human timestamps in UTC instead of local time')\n .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')\n .action(async (options: MigrationLogOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeMigrationLogCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, (entries) => {\n if (flags.json) {\n const records = serializeLedgerEntriesForJson(entries);\n const result: MigrationLogResult = {\n ok: true,\n records,\n summary: `${records.length} migration(s) applied`,\n };\n ui.output(JSON.stringify(result, null, 2));\n } else if (!flags.quiet) {\n if (entries.length === 0) {\n ui.output(MIGRATION_LOG_EMPTY_MESSAGE);\n } else {\n const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });\n ui.output(\n renderMigrationLogTable(entries, {\n utc: options.utc === true,\n styler,\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n }),\n );\n }\n }\n });\n process.exit(exitCode);\n });\n return command;\n}\n"],"mappings":"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"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, a as readContractEnvelope, ct as errorUnexpected, d as setCommandSeeAlso, dt as requireLiveDatabase, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, n as collectDeclaredInvariants, p as toStructuralEdge, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createControlClient } from "./client-DeJvLD2h.mjs";
import { n as buildReadAggregate, o as refusePackageCorruptionOnAggregate, r as loadContractRawSafely } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { a as renderMigrationGraphLegend, f as indentMigrationGraphTreeBlock, l as computeGlobalMaxDirNameWidth, p as renderMigrationGraphSpaceTree, u as computeGlobalMaxEdgeTreePrefixWidth } from "./migration-graph-command-render-B83xrnNy.mjs";
import { c as validateLegendOptions, i as migrationSpaceListEntriesFromAggregate, o as runMigrationList, r as listRefsByContractHash, s as shouldShowLegend } from "./migration-list-cnSKilYH.mjs";
import "./schemas-KhXMzNA_.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { dim, yellow } from "colorette";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { MigrationToolsError, errorNoInvariantPath, errorUnknownInvariant } from "@prisma-next/migration-tools/errors";
import { findPath, findPathWithDecision } from "@prisma-next/migration-tools/migration-graph";
import { readRefs } from "@prisma-next/migration-tools/refs";
import { parseContractRef } from "@prisma-next/migration-tools/ref-resolution";
//#region src/commands/migration-status-overlay.ts
function deriveStatusEdgeAnnotations(input) {
const annotations = /* @__PURE__ */ new Map();
if (input.showAppliedOverlay) {
for (const edge of input.graph.migrationByHash.values()) if (input.appliedMigrationHashes.has(edge.migrationHash)) annotations.set(edge.migrationHash, { status: "applied" });
}
if (!input.graph.nodes.has(input.originHash)) return annotations;
const pendingPath = findPath(input.graph, input.originHash, input.targetHash);
if (!pendingPath) return annotations;
for (const edge of pendingPath) {
if (input.appliedMigrationHashes.has(edge.migrationHash)) continue;
if (annotations.get(edge.migrationHash)?.status === "applied") continue;
annotations.set(edge.migrationHash, { status: "pending" });
}
return annotations;
}
function appliedHashesFromLedger(ledgerEntries) {
return new Set(ledgerEntries.map((entry) => entry.migrationHash));
}
function statusForMigrationHash(migrationHash, annotations) {
return annotations.get(migrationHash)?.status ?? null;
}
//#endregion
//#region src/commands/migration-status.ts
function shortDisplayHash(hash) {
return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, 12);
}
function resolveTarget(contractHash, activeRefHash) {
return activeRefHash ?? contractHash;
}
function buildStatusMigrations(listMigrations, annotations) {
return listMigrations.map((migration) => ({
...migration,
status: statusForMigrationHash(migration.hash, annotations)
}));
}
function renderSpaceTree(args) {
const graph = args.space.graph();
if (graph.nodes.size === 0) return "";
return renderMigrationGraphSpaceTree({
graph,
migrations: args.migrations,
liveContractHash: args.liveContractHash,
refsByHash: listRefsByContractHash(args.space),
statusOverlayByHash: args.statusOverlay,
colorize: args.colorize,
glyphMode: args.glyphMode,
isAppSpace: args.isAppSpace,
...args.showDbMarker && args.markerHash !== void 0 ? { dbHash: args.markerHash } : {},
...args.globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth } : {},
...args.globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth: args.globalMaxDirNameWidth } : {}
});
}
function countPending(migrations) {
return migrations.filter((m) => m.status === "pending").length;
}
function buildNoPathSummary(args) {
const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
const targetShort = shortDisplayHash(args.targetHash);
if (!args.explicitTarget) return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
return `No migration path from ${markerPart} to ${args.refName !== void 0 ? `the target (${targetShort} via \`${args.refName}\`)` : `the target (${targetShort})`}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
}
function buildStatusHeadline(args) {
if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
if (args.pendingCount === 0) return "Up to date";
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
}
function formatStatusSummary(result, colorize) {
const c = (fn, s) => colorize ? fn(s) : s;
const lines = [];
const pendingTotal = result.spaces.reduce((sum, space) => sum + countPending(space.migrations), 0);
if (result.diagnostics.some((d) => d.code === "MIGRATION.MARKER_NOT_IN_HISTORY") || pendingTotal > 0) lines.push(c(yellow, result.summary));
else lines.push(result.summary);
const missingInvariantsDiagnostic = result.diagnostics.find((d) => d.code === "MIGRATION.MISSING_INVARIANTS");
if (missingInvariantsDiagnostic !== void 0) lines.push(c(dim, missingInvariantsDiagnostic.message));
return lines.join("\n");
}
function formatStatusHumanOutput(result, colorize) {
const sections = [];
for (const section of result.treeSections) {
if (section.showHeading) sections.push(`${section.space}:`);
if (section.tree.length > 0) sections.push(section.tree);
else sections.push("(no migrations)");
sections.push("");
}
sections.push(formatStatusSummary(result, colorize));
return sections.join("\n").trimEnd();
}
async function readMarkersAndLedgers(args) {
const markersBySpace = /* @__PURE__ */ new Map();
const all = await args.client.readAllMarkers();
for (const [spaceId, marker] of all) markersBySpace.set(spaceId, marker);
const ledgersBySpace = /* @__PURE__ */ new Map();
for (const spaceId of args.spaceIds) ledgersBySpace.set(spaceId, await args.client.readLedger(spaceId));
return {
markersBySpace,
ledgersBySpace
};
}
async function executeMigrationStatusCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(options.config, config);
const dbConnection = options.db ?? config.db?.connection;
const hasDriver = !!config.driver;
const usingFromOverride = options.from !== void 0;
if (!usingFromOverride) {
const missingDb = requireLiveDatabase({
dbConnection,
hasDriver,
why: "migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)",
retryCommand: "prisma-next migration status --from <contract>"
});
if (missingDb) return notOk(missingDb);
}
let allRefs = {};
try {
allRefs = await readRefs(refsDir);
} catch (error) {
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
throw error;
}
const diagnostics = [];
let contractHash = EMPTY_CONTRACT_HASH;
try {
contractHash = (await readContractEnvelope(config)).storageHash;
} catch (error) {
diagnostics.push({
code: "CONTRACT.UNREADABLE",
severity: "warn",
message: `Could not read contract: ${error instanceof Error ? error.message : "unknown error"}`,
hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
});
}
const loaded = await buildReadAggregate(config, { migrationsDir });
if (!loaded.ok) return notOk(loaded.failure);
const { aggregate } = loaded.value;
if (await loadContractRawSafely(config) !== null) {
const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
if (corruptionFailure) return notOk(corruptionFailure);
}
const appGraph = aggregate.app.graph();
let activeRefHash;
let activeRefName;
let activeRefEntry;
let fromOverrideHash;
if (options.to) {
const refResult = parseContractRef(options.to, {
graph: appGraph,
refs: allRefs
});
if (!refResult.ok) return notOk(mapRefResolutionError(refResult.failure));
activeRefHash = refResult.value.hash;
if (refResult.value.provenance.kind === "ref") {
activeRefName = refResult.value.provenance.refName;
activeRefEntry = allRefs[activeRefName];
}
}
if (options.from) {
const fromResult = parseContractRef(options.from, {
graph: appGraph,
refs: allRefs
});
if (!fromResult.ok) return notOk(mapRefResolutionError(fromResult.failure));
fromOverrideHash = fromResult.value.hash;
}
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "migrations",
value: migrationsRelative
}];
if (dbConnection && hasDriver) details.push({
label: "database",
value: maskConnectionUrl(String(dbConnection))
});
if (activeRefName) details.push({
label: "ref",
value: activeRefName
});
if (options.from) details.push({
label: "from",
value: options.from
});
if (options.space) details.push({
label: "space",
value: options.space
});
const header = formatStyledHeader({
command: "migration status",
description: "Show migration history and applied status",
details,
flags
});
ui.stderr(header);
if (shouldShowLegend(options, flags)) {
ui.stderr(renderMigrationGraphLegend({
colorize: flags.color !== false,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
ui.stderr("");
}
}
const listResult = runMigrationList({
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
...ifDefined("spaceFilter", options.space)
});
if (!listResult.ok) return listResult;
const scopedSpaces = listResult.value.spaces;
const showSpaceHeadings = scopedSpaces.length > 1;
let markersBySpace = /* @__PURE__ */ new Map();
let ledgersBySpace = /* @__PURE__ */ new Map();
let connected = false;
if (dbConnection && hasDriver && !usingFromOverride) {
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
try {
await client.connect(dbConnection);
connected = true;
const read = await readMarkersAndLedgers({
client,
spaceIds: scopedSpaces.map((s) => s.space)
});
markersBySpace = new Map(read.markersBySpace);
ledgersBySpace = new Map(read.ledgersBySpace);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}` }));
} finally {
await client.close();
}
}
if (activeRefEntry && activeRefEntry.invariants.length > 0 && connected) {
const declared = collectDeclaredInvariants(appGraph);
const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];
const known = new Set(declared);
for (const id of markerInvariants) known.add(id);
const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
if (unknown.length > 0) return notOk(mapMigrationToolsError(errorUnknownInvariant({
...ifDefined("refName", activeRefName),
unknown,
declared: [...declared].sort()
})));
}
const showAppliedOverlay = connected && !usingFromOverride;
const showDbMarker = connected && !usingFromOverride;
const glyphMode = ui.resolveGlyphMode(options.ascii === true);
const colorize = flags.color !== false;
const statusSpaces = [];
const treeSections = [];
let markerDiverged = false;
let markerCannotReachTarget = false;
let headlineTargetHash = activeRefHash ?? contractHash;
let totalPending = 0;
const globalLayoutInputs = showSpaceHeadings ? scopedSpaces.filter((spaceEntry) => spaceEntry.migrations.length > 0).map((spaceEntry) => ({
graph: aggregate.space(spaceEntry.space).graph(),
liveContractHash: contractHash
})) : [];
const globalMaxEdgeTreePrefixWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs) : void 0;
const globalMaxDirNameWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : void 0;
for (const spaceEntry of scopedSpaces) {
const space = aggregate.space(spaceEntry.space);
if (space === void 0) continue;
const graph = space.graph();
const spaceContractHash = space.contract().storage.storageHash;
const targetHash = resolveTarget(spaceContractHash, activeRefHash);
if (spaceEntry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
const markerRecord = markersBySpace.get(spaceEntry.space);
const markerHash = usingFromOverride ? fromOverrideHash : markerRecord?.storageHash ?? void 0;
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
if (connected && !usingFromOverride && markerInGraph && originHash !== targetHash && findPath(graph, originHash, targetHash) === null) markerCannotReachTarget = true;
if (connected && !usingFromOverride && markerHash !== void 0 && !markerInGraph) {
markerDiverged = true;
diagnostics.push({
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
severity: "warn",
message: "Database was updated outside the migration system (marker does not match any migration)",
hints: ["Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database"]
});
}
const ledger = ledgersBySpace.get(spaceEntry.space) ?? [];
const annotations = deriveStatusEdgeAnnotations({
graph,
targetHash,
originHash,
appliedMigrationHashes: showAppliedOverlay ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
showAppliedOverlay
});
const isAppSpace = spaceEntry.space === aggregate.app.spaceId;
const tree = renderSpaceTree({
space,
liveContractHash: contractHash,
migrations: spaceEntry.migrations,
markerHash,
showDbMarker,
statusOverlay: annotations,
colorize,
glyphMode,
isAppSpace,
...globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth } : {},
...globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth } : {}
});
const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);
const pending = countPending(migrations);
totalPending += pending;
statusSpaces.push({
space: spaceEntry.space,
currentContract: markerHash ?? null,
targetContract: targetHash,
migrations: [...migrations]
});
const displayTree = showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree;
treeSections.push({
space: spaceEntry.space,
tree: displayTree,
showHeading: showSpaceHeadings
});
}
if (connected && requiredInvariants.length > 0) {
const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];
const markerSet = new Set(markerInvariants);
const missing = requiredInvariants.filter((id) => !markerSet.has(id));
if (missing.length > 0) {
diagnostics.push({
code: "MIGRATION.MISSING_INVARIANTS",
...ifDefined("ref", activeRefName),
invariants: missing,
message: `missing invariant(s): ${missing.join(", ")}`
});
if (activeRefHash !== void 0) {
const outcome = findPathWithDecision(appGraph, markersBySpace.get(aggregate.app.spaceId)?.storageHash ?? EMPTY_CONTRACT_HASH, activeRefHash, {
...ifDefined("refName", activeRefName),
required: new Set(missing)
});
if (outcome.kind === "unsatisfiable") return notOk(mapMigrationToolsError(errorNoInvariantPath({
...ifDefined("refName", activeRefName),
required: [...missing].sort(),
missing: outcome.missing,
structuralPath: outcome.structuralPath.map(toStructuralEdge)
})));
}
}
}
const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;
const summary = markerCannotReachTarget ? buildNoPathSummary({
markerHash: appMarkerHash,
targetHash: headlineTargetHash,
explicitTarget: options.to !== void 0,
refName: activeRefName
}) : buildStatusHeadline({
pendingCount: totalPending,
targetHash: headlineTargetHash,
markerDiverged,
markerHash: appMarkerHash
});
if (scopedSpaces.every((s) => s.migrations.length === 0)) return ok({
ok: true,
spaces: statusSpaces,
summary: "No migrations found",
diagnostics,
treeSections
});
return ok({
ok: true,
spaces: statusSpaces,
summary,
diagnostics,
treeSections
});
}
function createMigrationStatusCommand() {
const command = new Command("status");
setCommandDescriptions(command, "Show migration path and pending status", "Shows which migrations are pending between the database marker and\nthe target contract. Requires a database connection.\nPass --from for an offline path preview without a database.\nUse `migration graph` for topology, `migration log` for history,\nand `migration list` for on-disk enumeration.");
setCommandExamples(command, [
"prisma-next migration status --db $DATABASE_URL",
"prisma-next migration status --to production --db $DATABASE_URL",
"prisma-next migration status --from sha256:abc --to production",
"prisma-next migration status --from sha256:abc --to production --json",
"prisma-next migration status --ascii --from sha256:abc --to production",
"prisma-next migration status --legend --from sha256:abc --to production"
]);
setCommandSeeAlso(command, [
{
verb: "migration log",
oneLiner: "Show executed migration history"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").option("--to <contract>", "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)").option("--from <contract>", "Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.").option("--legend", "Print a key for the tree glyphs and lane colors").option("--ascii", "Use ASCII glyphs (pipe-friendly)").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const legendValidation = validateLegendOptions(options, flags);
if (!legendValidation.ok) process.exit(handleResult(legendValidation, flags, ui));
const exitCode = handleResult(await executeMigrationStatusCommand(options, flags, ui), flags, ui, (statusResult) => {
if (flags.json) {
const jsonResult = {
ok: true,
spaces: [...statusResult.spaces],
summary: statusResult.summary,
diagnostics: [...statusResult.diagnostics]
};
ui.output(JSON.stringify(jsonResult, null, 2));
} else if (!flags.quiet) ui.output(formatStatusHumanOutput(statusResult, flags.color !== false));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { formatStatusHumanOutput as a, executeMigrationStatusCommand as i, buildStatusHeadline as n, formatStatusSummary as o, createMigrationStatusCommand as r, buildNoPathSummary as t };
//# sourceMappingURL=migration-status-Cp0Sv3Jn.mjs.map
{"version":3,"file":"migration-status-Cp0Sv3Jn.mjs","names":[],"sources":["../src/commands/migration-status-overlay.ts","../src/commands/migration-status.ts"],"sourcesContent":["import type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { findPath } from '@prisma-next/migration-tools/migration-graph';\nimport type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-labels';\n\nexport interface DeriveStatusEdgeAnnotationsInput {\n readonly graph: MigrationGraph;\n readonly targetHash: string;\n readonly originHash: string;\n readonly appliedMigrationHashes: ReadonlySet<string>;\n readonly showAppliedOverlay: boolean;\n}\n\nexport function deriveStatusEdgeAnnotations(\n input: DeriveStatusEdgeAnnotationsInput,\n): ReadonlyMap<string, MigrationEdgeAnnotation> {\n const annotations = new Map<string, MigrationEdgeAnnotation>();\n\n if (input.showAppliedOverlay) {\n for (const edge of input.graph.migrationByHash.values()) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n annotations.set(edge.migrationHash, { status: 'applied' });\n }\n }\n }\n\n if (!input.graph.nodes.has(input.originHash)) {\n return annotations;\n }\n\n const pendingPath = findPath(input.graph, input.originHash, input.targetHash);\n if (!pendingPath) {\n return annotations;\n }\n\n for (const edge of pendingPath) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n continue;\n }\n const existing = annotations.get(edge.migrationHash);\n if (existing?.status === 'applied') {\n continue;\n }\n annotations.set(edge.migrationHash, { status: 'pending' });\n }\n\n return annotations;\n}\n\nexport function appliedHashesFromLedger(\n ledgerEntries: ReadonlyArray<{ readonly migrationHash: string }>,\n): ReadonlySet<string> {\n return new Set(ledgerEntries.map((entry) => entry.migrationHash));\n}\n\nexport function statusForMigrationHash(\n migrationHash: string,\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): 'applied' | 'pending' | null {\n const status = annotations.get(migrationHash)?.status;\n return status ?? null;\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport type {\n AggregateContractSpace,\n ContractMarkerRecordLike,\n} from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n errorNoInvariantPath,\n errorUnknownInvariant,\n MigrationToolsError,\n} from '@prisma-next/migration-tools/errors';\nimport { findPath, findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';\nimport { readRefs } from '@prisma-next/migration-tools/refs';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { dim, yellow } from 'colorette';\nimport { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n collectDeclaredInvariants,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n toStructuralEdge,\n} from '../utils/command-helpers';\nimport {\n buildReadAggregate,\n loadContractRawSafely,\n refusePackageCorruptionOnAggregate,\n} from '../utils/contract-space-aggregate-loader';\nimport {\n type MigrationEdgeAnnotation,\n renderMigrationGraphLegend,\n} from '../utils/formatters/migration-graph-labels';\nimport {\n computeGlobalMaxDirNameWidth,\n computeGlobalMaxEdgeTreePrefixWidth,\n indentMigrationGraphTreeBlock,\n renderMigrationGraphSpaceTree,\n} from '../utils/formatters/migration-graph-space-render';\nimport type { MigrationListEntry } from '../utils/formatters/migration-list-types';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { shouldShowLegend, validateLegendOptions } from '../utils/legend';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n} from './json/schemas';\nimport { migrationStatusJsonResultSchema } from './json/schemas';\nimport {\n listRefsByContractHash,\n migrationSpaceListEntriesFromAggregate,\n runMigrationList,\n} from './migration-list';\nimport {\n appliedHashesFromLedger,\n deriveStatusEdgeAnnotations,\n statusForMigrationHash,\n} from './migration-status-overlay';\n\nexport type { StatusRef } from '../utils/migration-types';\nexport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n};\nexport { migrationStatusJsonResultSchema };\n\nexport interface MigrationStatusOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly to?: string;\n readonly from?: string;\n readonly space?: string;\n readonly legend?: boolean;\n readonly ascii?: boolean;\n}\n\nexport interface MigrationStatusTreeSection {\n readonly space: string;\n readonly tree: string;\n readonly showHeading: boolean;\n}\n\ninterface MigrationStatusCommandResult {\n readonly ok: true;\n readonly spaces: readonly MigrationStatusSpace[];\n readonly summary: string;\n readonly diagnostics: readonly StatusDiagnosticJson[];\n readonly treeSections: readonly MigrationStatusTreeSection[];\n}\n\nfunction shortDisplayHash(hash: string): string {\n const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;\n return stripped.slice(0, 12);\n}\n\nfunction resolveTarget(contractHash: string, activeRefHash: string | undefined): string {\n return activeRefHash ?? contractHash;\n}\n\nfunction buildStatusMigrations(\n listMigrations: readonly MigrationListEntry[],\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): readonly MigrationStatusEntry[] {\n return listMigrations.map((migration) => ({\n ...migration,\n status: statusForMigrationHash(migration.hash, annotations),\n }));\n}\n\nfunction renderSpaceTree(args: {\n readonly space: AggregateContractSpace;\n readonly liveContractHash: string;\n readonly migrations: readonly MigrationListEntry[];\n readonly markerHash: string | undefined;\n readonly showDbMarker: boolean;\n readonly statusOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>;\n readonly colorize: boolean;\n readonly glyphMode: 'unicode' | 'ascii';\n readonly isAppSpace: boolean;\n readonly globalMaxEdgeTreePrefixWidth?: number;\n readonly globalMaxDirNameWidth?: number;\n}): string {\n const graph = args.space.graph();\n if (graph.nodes.size === 0) {\n return '';\n }\n return renderMigrationGraphSpaceTree({\n graph,\n migrations: args.migrations,\n liveContractHash: args.liveContractHash,\n refsByHash: listRefsByContractHash(args.space),\n statusOverlayByHash: args.statusOverlay,\n colorize: args.colorize,\n glyphMode: args.glyphMode,\n isAppSpace: args.isAppSpace,\n ...(args.showDbMarker && args.markerHash !== undefined ? { dbHash: args.markerHash } : {}),\n ...(args.globalMaxEdgeTreePrefixWidth !== undefined\n ? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth }\n : {}),\n ...(args.globalMaxDirNameWidth !== undefined\n ? { globalMaxDirNameWidth: args.globalMaxDirNameWidth }\n : {}),\n });\n}\n\nfunction countPending(migrations: readonly MigrationStatusEntry[]): number {\n return migrations.filter((m) => m.status === 'pending').length;\n}\n\nexport function buildNoPathSummary(args: {\n readonly markerHash: string | undefined;\n readonly targetHash: string;\n readonly explicitTarget: boolean;\n readonly refName: string | undefined;\n}): string {\n const markerPart =\n args.markerHash !== undefined\n ? `the database state (${shortDisplayHash(args.markerHash)})`\n : 'the database state';\n const targetShort = shortDisplayHash(args.targetHash);\n if (!args.explicitTarget) {\n return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \\`prisma-next migration plan --name <name>\\` to author one.`;\n }\n const targetLabel =\n args.refName !== undefined\n ? `the target (${targetShort} via \\`${args.refName}\\`)`\n : `the target (${targetShort})`;\n return `No migration path from ${markerPart} to ${targetLabel}. Run \\`prisma-next migration plan --name <name>\\` to author one, or pass \\`--to <contract>\\` to pick a reachable target.`;\n}\n\nexport function buildStatusHeadline(args: {\n readonly pendingCount: number;\n readonly targetHash: string;\n readonly markerDiverged: boolean;\n readonly markerHash: string | undefined;\n}): string {\n if (args.markerDiverged && args.markerHash !== undefined) {\n return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;\n }\n if (args.pendingCount === 0) {\n return 'Up to date';\n }\n return `${args.pendingCount} pending — run \\`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\\``;\n}\n\nexport function formatStatusSummary(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);\n const lines: string[] = [];\n const pendingTotal = result.spaces.reduce(\n (sum, space) => sum + countPending(space.migrations),\n 0,\n );\n const hasDivergence = result.diagnostics.some(\n (d) => d.code === 'MIGRATION.MARKER_NOT_IN_HISTORY',\n );\n if (hasDivergence || pendingTotal > 0) {\n lines.push(c(yellow, result.summary));\n } else {\n lines.push(result.summary);\n }\n const missingInvariantsDiagnostic = result.diagnostics.find(\n (d) => d.code === 'MIGRATION.MISSING_INVARIANTS',\n );\n if (missingInvariantsDiagnostic !== undefined) {\n lines.push(c(dim, missingInvariantsDiagnostic.message));\n }\n return lines.join('\\n');\n}\n\nexport function formatStatusHumanOutput(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const sections: string[] = [];\n for (const section of result.treeSections) {\n if (section.showHeading) {\n sections.push(`${section.space}:`);\n }\n if (section.tree.length > 0) {\n sections.push(section.tree);\n } else {\n sections.push('(no migrations)');\n }\n sections.push('');\n }\n sections.push(formatStatusSummary(result, colorize));\n return sections.join('\\n').trimEnd();\n}\n\nasync function readMarkersAndLedgers(args: {\n readonly client: ReturnType<typeof createControlClient>;\n readonly spaceIds: readonly string[];\n}): Promise<{\n readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike>;\n readonly ledgersBySpace: ReadonlyMap<string, readonly LedgerEntryRecord[]>;\n}> {\n const markersBySpace = new Map<string, ContractMarkerRecordLike>();\n const all = await args.client.readAllMarkers();\n for (const [spaceId, marker] of all) {\n markersBySpace.set(spaceId, marker);\n }\n const ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n for (const spaceId of args.spaceIds) {\n ledgersBySpace.set(spaceId, await args.client.readLedger(spaceId));\n }\n return { markersBySpace, ledgersBySpace };\n}\n\nexport async function executeMigrationStatusCommand(\n options: MigrationStatusOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationStatusCommandResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n const hasDriver = !!config.driver;\n const usingFromOverride = options.from !== undefined;\n\n if (!usingFromOverride) {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: 'migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)',\n retryCommand: 'prisma-next migration status --from <contract>',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n }\n\n let allRefs: Refs = {};\n try {\n allRefs = await readRefs(refsDir);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n const diagnostics: StatusDiagnosticJson[] = [];\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch (error) {\n diagnostics.push({\n code: 'CONTRACT.UNREADABLE',\n severity: 'warn',\n message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,\n hints: [\"Run 'prisma-next contract emit' to generate a valid contract\"],\n });\n }\n\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n\n const { aggregate } = loaded.value;\n const contractRawForAggregate = await loadContractRawSafely(config);\n if (contractRawForAggregate !== null) {\n const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);\n if (corruptionFailure) {\n return notOk(corruptionFailure);\n }\n }\n const appGraph = aggregate.app.graph();\n\n let activeRefHash: string | undefined;\n let activeRefName: string | undefined;\n let activeRefEntry: RefEntry | undefined;\n let fromOverrideHash: string | undefined;\n\n if (options.to) {\n const refResult = parseContractRef(options.to, { graph: appGraph, refs: allRefs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n activeRefHash = refResult.value.hash;\n if (refResult.value.provenance.kind === 'ref') {\n activeRefName = refResult.value.provenance.refName;\n activeRefEntry = allRefs[activeRefName];\n }\n }\n\n if (options.from) {\n const fromResult = parseContractRef(options.from, { graph: appGraph, refs: allRefs });\n if (!fromResult.ok) {\n return notOk(mapRefResolutionError(fromResult.failure));\n }\n fromOverrideHash = fromResult.value.hash;\n }\n\n const requiredInvariants: readonly string[] = [...(activeRefEntry?.invariants ?? [])].sort();\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (dbConnection && hasDriver) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (activeRefName) {\n details.push({ label: 'ref', value: activeRefName });\n }\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.space) {\n details.push({ label: 'space', value: options.space });\n }\n const header = formatStyledHeader({\n command: 'migration status',\n description: 'Show migration history and applied status',\n details,\n flags,\n });\n ui.stderr(header);\n if (shouldShowLegend(options, flags)) {\n ui.stderr(\n renderMigrationGraphLegend({\n colorize: flags.color !== false,\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n }),\n );\n ui.stderr('');\n }\n }\n\n const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);\n const listResult = runMigrationList({\n spaces: listSpaces,\n ...ifDefined('spaceFilter', options.space),\n });\n if (!listResult.ok) {\n return listResult;\n }\n\n const scopedSpaces = listResult.value.spaces;\n const showSpaceHeadings = scopedSpaces.length > 1;\n\n let markersBySpace = new Map<string, ContractMarkerRecordLike>();\n let ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n let connected = false;\n\n if (dbConnection && hasDriver && !usingFromOverride) {\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n try {\n await client.connect(dbConnection);\n connected = true;\n const read = await readMarkersAndLedgers({\n client,\n spaceIds: scopedSpaces.map((s) => s.space),\n });\n markersBySpace = new Map(read.markersBySpace);\n ledgersBySpace = new Map(read.ledgersBySpace);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n }\n\n if (activeRefEntry && activeRefEntry.invariants.length > 0 && connected) {\n const declared = collectDeclaredInvariants(appGraph);\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const known = new Set<string>(declared);\n for (const id of markerInvariants) known.add(id);\n const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));\n if (unknown.length > 0) {\n return notOk(\n mapMigrationToolsError(\n errorUnknownInvariant({\n ...ifDefined('refName', activeRefName),\n unknown,\n declared: [...declared].sort(),\n }),\n ),\n );\n }\n }\n\n const showAppliedOverlay = connected && !usingFromOverride;\n const showDbMarker = connected && !usingFromOverride;\n const glyphMode = ui.resolveGlyphMode(options.ascii === true);\n const colorize = flags.color !== false;\n\n const statusSpaces: MigrationStatusSpace[] = [];\n const treeSections: MigrationStatusTreeSection[] = [];\n let markerDiverged = false;\n let markerCannotReachTarget = false;\n let headlineTargetHash = activeRefHash ?? contractHash;\n let totalPending = 0;\n\n const globalLayoutInputs = showSpaceHeadings\n ? scopedSpaces\n .filter((spaceEntry) => spaceEntry.migrations.length > 0)\n .map((spaceEntry) => ({\n graph: aggregate.space(spaceEntry.space)!.graph(),\n liveContractHash: contractHash,\n }))\n : [];\n const globalMaxEdgeTreePrefixWidth =\n globalLayoutInputs.length > 0\n ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)\n : undefined;\n const globalMaxDirNameWidth =\n globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;\n\n for (const spaceEntry of scopedSpaces) {\n const space = aggregate.space(spaceEntry.space);\n if (space === undefined) {\n continue;\n }\n const graph = space.graph();\n const spaceContractHash = space.contract().storage.storageHash;\n const targetHash = resolveTarget(spaceContractHash, activeRefHash);\n if (spaceEntry.space === aggregate.app.spaceId) {\n headlineTargetHash = targetHash;\n }\n\n const markerRecord = markersBySpace.get(spaceEntry.space);\n const markerHash = usingFromOverride\n ? fromOverrideHash\n : (markerRecord?.storageHash ?? undefined);\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n const markerInGraph =\n markerHash === undefined || graph.nodes.has(markerHash) || markerHash === spaceContractHash;\n if (\n connected &&\n !usingFromOverride &&\n markerInGraph &&\n originHash !== targetHash &&\n findPath(graph, originHash, targetHash) === null\n ) {\n markerCannotReachTarget = true;\n }\n\n if (connected && !usingFromOverride && markerHash !== undefined && !markerInGraph) {\n markerDiverged = true;\n diagnostics.push({\n code: 'MIGRATION.MARKER_NOT_IN_HISTORY',\n severity: 'warn',\n message:\n 'Database was updated outside the migration system (marker does not match any migration)',\n hints: [\n \"Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract\",\n \"Run 'prisma-next db update' to push the current contract to the database\",\n ],\n });\n }\n\n const ledger = ledgersBySpace.get(spaceEntry.space) ?? [];\n const appliedHashes = showAppliedOverlay ? appliedHashesFromLedger(ledger) : new Set<string>();\n\n const annotations = deriveStatusEdgeAnnotations({\n graph,\n targetHash,\n originHash,\n appliedMigrationHashes: appliedHashes,\n showAppliedOverlay,\n });\n const isAppSpace = spaceEntry.space === aggregate.app.spaceId;\n const tree = renderSpaceTree({\n space,\n liveContractHash: contractHash,\n migrations: spaceEntry.migrations,\n markerHash,\n showDbMarker,\n statusOverlay: annotations,\n colorize,\n glyphMode,\n isAppSpace,\n ...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),\n ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),\n });\n const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);\n const pending = countPending(migrations);\n totalPending += pending;\n\n statusSpaces.push({\n space: spaceEntry.space,\n currentContract: markerHash ?? null,\n targetContract: targetHash,\n migrations: [...migrations],\n });\n const displayTree =\n showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;\n treeSections.push({\n space: spaceEntry.space,\n tree: displayTree,\n showHeading: showSpaceHeadings,\n });\n }\n\n if (connected && requiredInvariants.length > 0) {\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const markerSet = new Set(markerInvariants);\n const missing = requiredInvariants.filter((id) => !markerSet.has(id));\n if (missing.length > 0) {\n diagnostics.push({\n code: 'MIGRATION.MISSING_INVARIANTS',\n ...ifDefined('ref', activeRefName),\n invariants: missing,\n message: `missing invariant(s): ${missing.join(', ')}`,\n });\n if (activeRefHash !== undefined) {\n const originHash =\n markersBySpace.get(aggregate.app.spaceId)?.storageHash ?? EMPTY_CONTRACT_HASH;\n const outcome = findPathWithDecision(appGraph, originHash, activeRefHash, {\n ...ifDefined('refName', activeRefName),\n required: new Set(missing),\n });\n if (outcome.kind === 'unsatisfiable') {\n return notOk(\n mapMigrationToolsError(\n errorNoInvariantPath({\n ...ifDefined('refName', activeRefName),\n required: [...missing].sort(),\n missing: outcome.missing,\n structuralPath: outcome.structuralPath.map(toStructuralEdge),\n }),\n ),\n );\n }\n }\n }\n }\n\n const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;\n const summary = markerCannotReachTarget\n ? buildNoPathSummary({\n markerHash: appMarkerHash,\n targetHash: headlineTargetHash,\n explicitTarget: options.to !== undefined,\n refName: activeRefName,\n })\n : buildStatusHeadline({\n pendingCount: totalPending,\n targetHash: headlineTargetHash,\n markerDiverged,\n markerHash: appMarkerHash,\n });\n\n if (scopedSpaces.every((s) => s.migrations.length === 0)) {\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary: 'No migrations found',\n diagnostics,\n treeSections,\n });\n }\n\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary,\n diagnostics,\n treeSections,\n });\n}\n\nexport function createMigrationStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show migration path and pending status',\n 'Shows which migrations are pending between the database marker and\\n' +\n 'the target contract. Requires a database connection.\\n' +\n 'Pass --from for an offline path preview without a database.\\n' +\n 'Use `migration graph` for topology, `migration log` for history,\\n' +\n 'and `migration list` for on-disk enumeration.',\n );\n setCommandExamples(command, [\n 'prisma-next migration status --db $DATABASE_URL',\n 'prisma-next migration status --to production --db $DATABASE_URL',\n 'prisma-next migration status --from sha256:abc --to production',\n 'prisma-next migration status --from sha256:abc --to production --json',\n 'prisma-next migration status --ascii --from sha256:abc --to production',\n 'prisma-next migration status --legend --from sha256:abc --to production',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration log', oneLiner: 'Show executed migration history' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option(\n '--from <contract>',\n 'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',\n )\n .option('--legend', 'Print a key for the tree glyphs and lane colors')\n .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')\n .action(async (options: MigrationStatusOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n const legendValidation = validateLegendOptions(options, flags);\n if (!legendValidation.ok) {\n process.exit(handleResult(legendValidation, flags, ui));\n }\n\n const result = await executeMigrationStatusCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (statusResult) => {\n if (flags.json) {\n const jsonResult: MigrationStatusResult = {\n ok: true,\n spaces: [...statusResult.spaces],\n summary: statusResult.summary,\n diagnostics: [...statusResult.diagnostics],\n };\n ui.output(JSON.stringify(jsonResult, null, 2));\n } else if (!flags.quiet) {\n ui.output(formatStatusHumanOutput(statusResult, flags.color !== false));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
+8
-8
#!/usr/bin/env node
import { D as isCI, O as formatCommandHelp, g as parseGlobalFlagsOrExit, h as parseGlobalFlags, k as formatRootHelp, l as setCommandDescriptions, m as deriveCanPrompt, t as addGlobalOptions, u as setCommandExamples, v as installShutdownHandlers } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createContractEmitCommand } from "./contract-emit-DzOkTs11.mjs";
import { t as createContractInferCommand } from "./contract-infer-BVqworBB.mjs";
import { t as createContractEmitCommand } from "./contract-emit-BMJiefhy.mjs";
import { t as createContractInferCommand } from "./contract-infer-_tstfy3k.mjs";
import { createDbInitCommand } from "./commands/db-init.mjs";

@@ -9,13 +9,13 @@ import { createDbSchemaCommand } from "./commands/db-schema.mjs";

import { createDbUpdateCommand } from "./commands/db-update.mjs";
import { t as createDbVerifyCommand } from "./db-verify-BpwCMyWJ.mjs";
import { t as createDbVerifyCommand } from "./db-verify-3WM8y323.mjs";
import { t as createFormatCommand } from "./format-CFLZv_zU.mjs";
import { t as createMigrationListCommand } from "./migration-list-cnSKilYH.mjs";
import { createMigrateCommand } from "./commands/migrate.mjs";
import { t as createMigrationCheckCommand } from "./migration-check-DMxPzbHl.mjs";
import { t as createMigrationCheckCommand } from "./migration-check-Biu1YZXu.mjs";
import { createMigrationGraphCommand } from "./commands/migration-graph.mjs";
import { t as createMigrationLogCommand } from "./migration-log-CEqFiez9.mjs";
import { t as createMigrationLogCommand } from "./migration-log-CGfn5C_I.mjs";
import { createMigrationNewCommand } from "./commands/migration-new.mjs";
import { t as createMigrationPlanCommand } from "./migration-plan-BJHulFZC.mjs";
import { createMigrationShowCommand } from "./commands/migration-show.mjs";
import { r as createMigrationStatusCommand } from "./migration-status-ugWwGk1V.mjs";
import { r as createMigrationStatusCommand } from "./migration-status-Cp0Sv3Jn.mjs";
import { createRefCommand } from "./commands/ref.mjs";

@@ -29,3 +29,3 @@ import { t as createTelemetryCommand } from "./telemetry-BEyNMfco.mjs";

//#region package.json
var version = "0.16.0-dev.11";
var version = "0.16.0-dev.12";
//#endregion

@@ -299,3 +299,3 @@ //#region src/commands/init/templates/code-templates.ts

return addGlobalOptions(command).option("--target <db>", "Database target: postgres or mongodb").option("--authoring <style>", "Schema authoring style: psl or typescript").option("--schema-path <path>", `Where to write the starter schema (default: ${defaultSchemaPath("psl")})`).option("--force", "Overwrite an existing scaffold without prompting").option("--write-env", "Write a .env file from .env.example (gitignored; default: only .env.example)").option("--probe-db", "Connect to DATABASE_URL once and check the server version against the target minimum (opt-in; off by default)").option("--strict-probe", "Treat a failed --probe-db as fatal (no-op without --probe-db; init is offline-by-default)").option("--no-install", "Skip dependency installation and contract emission").option("--no-skill", "Skip Prisma Next skills install (air-gapped CI, restricted registries, etc.)").action(async (options) => {
const { runInit } = await import("./init-BSgPxVXA.mjs");
const { runInit } = await import("./init-XycYREvA.mjs");
const flags = parseGlobalFlagsOrExit(options);

@@ -302,0 +302,0 @@ const canPrompt = deriveCanPrompt({

@@ -1,2 +0,2 @@

import { t as createContractEmitCommand } from "../contract-emit-DzOkTs11.mjs";
import { t as createContractEmitCommand } from "../contract-emit-BMJiefhy.mjs";
export { createContractEmitCommand };

@@ -1,2 +0,2 @@

import { t as createContractInferCommand } from "../contract-infer-BVqworBB.mjs";
import { t as createContractInferCommand } from "../contract-infer-_tstfy3k.mjs";
export { createContractInferCommand };
import { B as errorContractValidationFailed, C as formatMigrationApplyOutput, F as CliStructuredError, T as formatMigrationPlanOutput, X as errorMigrationPlanningFailed, _ as createTerminalUI, c as sanitizeErrorMessage, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, lt as mapMigrationToolsError, nt as errorRunnerFailed, rt as errorRuntime, s as resolveMigrationPaths, u as setCommandExamples, w as formatMigrationJson, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { o as ContractValidationError } from "../client-2kwLMBSf.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-DB_i9nBC.mjs";
import { o as ContractValidationError } from "../client-DeJvLD2h.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-D9JkLq_M.mjs";
import { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BCz7X7qJ.mjs";

@@ -5,0 +5,0 @@ import { Command } from "commander";

import { _ as createTerminalUI, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { t as inspectLiveSchema } from "../inspect-live-schema-DxsD_a4d.mjs";
import { t as inspectLiveSchema } from "../inspect-live-schema-xS6VqjHE.mjs";
import { n as formatIntrospectOutput, t as formatIntrospectJson } from "../verify-C4RazDE1.mjs";

@@ -4,0 +4,0 @@ import { Command } from "commander";

import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, o as resolveContractPath, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "../progress-adapter-CjAeTxY_.mjs";
import { o as ContractValidationError, t as createControlClient } from "../client-2kwLMBSf.mjs";
import { o as ContractValidationError, t as createControlClient } from "../client-DeJvLD2h.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-hPVymNLw.mjs";

@@ -5,0 +5,0 @@ import { a as formatSignJson, i as formatSchemaVerifyOutput, o as formatSignOutput, r as formatSchemaVerifyJson } from "../verify-C4RazDE1.mjs";

import { B as errorContractValidationFailed, C as formatMigrationApplyOutput, F as CliStructuredError, H as errorDestructiveChanges, I as ERROR_CODE_DESTRUCTIVE_CHANGES, T as formatMigrationPlanOutput, X as errorMigrationPlanningFailed, _ as createTerminalUI, c as sanitizeErrorMessage, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, lt as mapMigrationToolsError, nt as errorRunnerFailed, s as resolveMigrationPaths, u as setCommandExamples, ut as mapRefResolutionError, w as formatMigrationJson, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { o as ContractValidationError } from "../client-2kwLMBSf.mjs";
import { o as ContractValidationError } from "../client-DeJvLD2h.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-hPVymNLw.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-DB_i9nBC.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-D9JkLq_M.mjs";
import { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BCz7X7qJ.mjs";

@@ -6,0 +6,0 @@ import { Command } from "commander";

@@ -1,2 +0,2 @@

import { t as createDbVerifyCommand } from "../db-verify-BpwCMyWJ.mjs";
import { t as createDbVerifyCommand } from "../db-verify-3WM8y323.mjs";
export { createDbVerifyCommand };
import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, J as errorMarkerMismatch, S as formatMigrationApplyCommandOutput, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, Z as errorPathUnreachable, _ as createTerminalUI, ct as errorUnexpected, dt as requireLiveDatabase, f as targetSupportsMigrations, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, n as collectDeclaredInvariants, o as resolveContractPath, ot as errorTargetMigrationNotSupported, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { n as planSpacePath, t as createControlClient } from "../client-2kwLMBSf.mjs";
import { n as planSpacePath, t as createControlClient } from "../client-DeJvLD2h.mjs";
import { a as refuseContractSpaceIntegrity, i as loadContractSpaceAggregateForCli, n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "../contract-space-aggregate-loader-hPVymNLw.mjs";

@@ -4,0 +4,0 @@ import { i as readContractIR, r as executeRefAdvancement } from "../ref-advancement-BCz7X7qJ.mjs";

@@ -1,3 +0,3 @@

import { n as enumerateCheckSpaces, r as runMigrationCheck, t as createMigrationCheckCommand } from "../migration-check-DMxPzbHl.mjs";
import { n as enumerateCheckSpaces, r as runMigrationCheck, t as createMigrationCheckCommand } from "../migration-check-Biu1YZXu.mjs";
import { t as migrationCheckResultSchema } from "../schemas-KhXMzNA_.mjs";
export { createMigrationCheckCommand, enumerateCheckSpaces, migrationCheckResultSchema, runMigrationCheck };

@@ -1,2 +0,2 @@

import { n as executeMigrationLogCommand, t as createMigrationLogCommand } from "../migration-log-CEqFiez9.mjs";
import { n as executeMigrationLogCommand, t as createMigrationLogCommand } from "../migration-log-CGfn5C_I.mjs";
export { createMigrationLogCommand, executeMigrationLogCommand };
import { A as formatStyledHeader, B as errorContractValidationFailed, E as formatMigrationShowOutput, W as errorFileNotFound, _ as createTerminalUI, ct as errorUnexpected, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, o as resolveContractPath, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { t as createControlClient } from "../client-2kwLMBSf.mjs";
import { t as createControlClient } from "../client-DeJvLD2h.mjs";
import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "../migration-path-target-C0TQuV5n.mjs";

@@ -4,0 +4,0 @@ import { Command } from "commander";

import { n as migrationStatusJsonResultSchema } from "../schemas-KhXMzNA_.mjs";
import { a as formatStatusHumanOutput, i as executeMigrationStatusCommand, n as buildStatusHeadline, o as formatStatusSummary, r as createMigrationStatusCommand, t as buildNoPathSummary } from "../migration-status-ugWwGk1V.mjs";
import { a as formatStatusHumanOutput, i as executeMigrationStatusCommand, n as buildStatusHeadline, o as formatStatusSummary, r as createMigrationStatusCommand, t as buildNoPathSummary } from "../migration-status-Cp0Sv3Jn.mjs";
export { buildNoPathSummary, buildStatusHeadline, createMigrationStatusCommand, executeMigrationStatusCommand, formatStatusHumanOutput, formatStatusSummary, migrationStatusJsonResultSchema };

@@ -1,4 +0,4 @@

import { n as executeContractEmit, r as disposeEmitQueue } from "../contract-emit-iSMJVs26.mjs";
import { n as executeContractEmit, r as disposeEmitQueue } from "../contract-emit-DJ8SRI7L.mjs";
import { t as enrichContract } from "../contract-enrichment-gn9sWbPw.mjs";
import { a as executeDbInit, i as executeDbUpdate, r as executeDbVerify, t as createControlClient } from "../client-2kwLMBSf.mjs";
import { a as executeDbInit, i as executeDbUpdate, r as executeDbVerify, t as createControlClient } from "../client-DeJvLD2h.mjs";
export { createControlClient, disposeEmitQueue, enrichContract, executeContractEmit, executeDbInit, executeDbUpdate, executeDbVerify };

@@ -1,2 +0,2 @@

import { t as createContractEmitCommand } from "../contract-emit-DzOkTs11.mjs";
import { t as createContractEmitCommand } from "../contract-emit-BMJiefhy.mjs";
import { t as createFormatCommand } from "../format-CFLZv_zU.mjs";

@@ -3,0 +3,0 @@ import { join, resolve } from "pathe";

{
"name": "@prisma-next/cli",
"version": "0.16.0-dev.11",
"version": "0.16.0-dev.12",
"license": "Apache-2.0",

@@ -12,14 +12,14 @@ "type": "module",

"@clack/prompts": "^1.7.0",
"@prisma-next/config": "0.16.0-dev.11",
"@prisma-next/config-loader": "0.16.0-dev.11",
"@prisma-next/contract": "0.16.0-dev.11",
"@prisma-next/emitter": "0.16.0-dev.11",
"@prisma-next/errors": "0.16.0-dev.11",
"@prisma-next/framework-components": "0.16.0-dev.11",
"@prisma-next/language-server": "0.16.0-dev.11",
"@prisma-next/migration-tools": "0.16.0-dev.11",
"@prisma-next/psl-parser": "0.16.0-dev.11",
"@prisma-next/psl-printer": "0.16.0-dev.11",
"@prisma-next/cli-telemetry": "0.16.0-dev.11",
"@prisma-next/utils": "0.16.0-dev.11",
"@prisma-next/config": "0.16.0-dev.12",
"@prisma-next/config-loader": "0.16.0-dev.12",
"@prisma-next/contract": "0.16.0-dev.12",
"@prisma-next/emitter": "0.16.0-dev.12",
"@prisma-next/errors": "0.16.0-dev.12",
"@prisma-next/framework-components": "0.16.0-dev.12",
"@prisma-next/language-server": "0.16.0-dev.12",
"@prisma-next/migration-tools": "0.16.0-dev.12",
"@prisma-next/psl-parser": "0.16.0-dev.12",
"@prisma-next/psl-printer": "0.16.0-dev.12",
"@prisma-next/cli-telemetry": "0.16.0-dev.12",
"@prisma-next/utils": "0.16.0-dev.12",
"arktype": "^2.2.2",

@@ -40,10 +40,10 @@ "ci-info": "^4.3.1",

"devDependencies": {
"@prisma-next/sql-contract": "0.16.0-dev.11",
"@prisma-next/sql-contract-emitter": "0.16.0-dev.11",
"@prisma-next/sql-contract-ts": "0.16.0-dev.11",
"@prisma-next/sql-operations": "0.16.0-dev.11",
"@prisma-next/sql-runtime": "0.16.0-dev.11",
"@prisma-next/test-utils": "0.16.0-dev.11",
"@prisma-next/tsconfig": "0.16.0-dev.11",
"@prisma-next/tsdown": "0.16.0-dev.11",
"@prisma-next/sql-contract": "0.16.0-dev.12",
"@prisma-next/sql-contract-emitter": "0.16.0-dev.12",
"@prisma-next/sql-contract-ts": "0.16.0-dev.12",
"@prisma-next/sql-operations": "0.16.0-dev.12",
"@prisma-next/sql-runtime": "0.16.0-dev.12",
"@prisma-next/test-utils": "0.16.0-dev.12",
"@prisma-next/tsconfig": "0.16.0-dev.12",
"@prisma-next/tsdown": "0.16.0-dev.12",
"@types/node": "25.9.4",

@@ -50,0 +50,0 @@ "tsdown": "0.22.8",

@@ -612,3 +612,2 @@ import type {

composedExtensionContracts: stack.extensionContracts,
scalarTypeDescriptors: stack.scalarTypeDescriptors,
authoringContributions: stack.authoringContributions,

@@ -615,0 +614,0 @@ codecLookup: stack.codecLookup,

@@ -200,3 +200,2 @@ import { mkdir } from 'node:fs/promises';

composedExtensionContracts: stack.extensionContracts,
scalarTypeDescriptors: stack.scalarTypeDescriptors,
authoringContributions: stack.authoringContributions,

@@ -203,0 +202,0 @@ codecLookup: stack.codecLookup,

import { F as CliStructuredError } from "./command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-VMQJbAwl.mjs";
import { t as enrichContract } from "./contract-enrichment-gn9sWbPw.mjs";
import { t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { emit } from "@prisma-next/emitter";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { APP_SPACE_ID, createControlStack, hasMigrations, hasOperationPreview, hasPslContractInfer, hasSchemaSubjectClassifier, hasSchemaView } from "@prisma-next/framework-components/control";
import { blindCast, castAs } from "@prisma-next/utils/casts";
import { allStorageElementsExternal, buildFabricatedMigrationEdge, collectAggregateNamespaces, planMigration, requireHeadRef, resolveRecordedPath, verifyMigration } from "@prisma-next/migration-tools/aggregate";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { errorNoInvariantPath } from "@prisma-next/migration-tools/errors";
import { findPathWithDecision } from "@prisma-next/migration-tools/migration-graph";
//#region src/control-api/errors.ts
var ContractValidationError = class extends Error {
cause;
constructor(message, cause) {
super(message);
this.name = "ContractValidationError";
this.cause = cause;
}
};
//#endregion
//#region src/control-api/operations/migration-helpers.ts
/**
* Strips operation objects to their public shape (id, label, operationClass).
* Used at the API boundary to avoid leaking internal fields (precheck, execute, postcheck, etc.).
*/
function stripOperations(operations) {
return operations.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
}));
}
//#endregion
//#region src/control-api/operations/run-migration.ts
/**
* Span id emitted via `onProgress` for the run phase. Stable
* identifier consumed by the structured-output renderer and by tests.
*/
const RUN_SPAN_ID = "apply";
/**
* Runner-driving tail shared by every run caller — `db init`,
* `db update`, and `migrate`. Consumes already-resolved per-space
* plans (the planner-vs-replay distinction is owned by the caller) and
* dispatches them to the runner in canonical order.
*
* Marker advancement is part of the runner's per-space transaction
* (the SQL family runner writes the marker as the last step of each
* space's transaction), so this primitive does not advance markers
* separately — by the time `execute` returns ok, every
* space's marker has been advanced to its plan's destination.
*
* Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
* so callers don't have to duplicate it; the `action` field on each
* progress event is taken from the caller's `action` argument.
*/
async function runMigration(inputs) {
const { aggregate, perSpacePlans, applyOrder, driver, familyInstance, migrations, frameworkComponents, policy, action, onProgress } = inputs;
const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
const runner = migrations.createRunner(familyInstance);
onProgress?.({
action,
kind: "spanStart",
spanId: RUN_SPAN_ID,
label: progressLabelForAction(action)
});
const perSpaceOptions = orderedResolutions.map((r) => ({
space: r.spaceId,
plan: r.entry.plan,
driver,
destinationContract: r.entry.destinationContract,
policy,
frameworkComponents,
migrationEdges: r.entry.migrationEdges,
strictVerification: false
}));
const runnerResult = await runner.execute({
driver,
perSpaceOptions
});
if (!runnerResult.ok) {
onProgress?.({
action,
kind: "spanEnd",
spanId: RUN_SPAN_ID,
outcome: "error"
});
return notOk({
summary: runnerResult.failure.summary,
...ifDefined("why", runnerResult.failure.why),
meta: {
...runnerResult.failure.meta ?? {},
failingSpace: runnerResult.failure.failingSpace,
runnerErrorCode: runnerResult.failure.code
}
});
}
onProgress?.({
action,
kind: "spanEnd",
spanId: RUN_SPAN_ID,
outcome: "ok"
});
return ok({
orderedResolutions,
totalOpsPlanned: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsPlanned, 0),
totalOpsExecuted: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsExecuted, 0),
perSpace: buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: true })
});
}
/**
* Project the planner's per-space resolutions into the
* `PerSpaceExecutionEntry[]` shape the CLI surfaces.
*
* `includeMarkers` is `true` for apply-mode (each space's marker is
* the `destination.storageHash` of its plan, which the runner
* advances as the last step of each space's transaction) and `false`
* for plan-mode (no marker has been written yet).
*
* Exported alongside {@link runMigration} so plan-mode callers can
* assemble the same per-space block without going through the runner.
*/
function buildPerSpaceBreakdown(orderedResolutions, appSpaceId, options) {
return orderedResolutions.map((r) => {
const operations = r.entry.displayOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
}));
const base = {
spaceId: r.spaceId,
kind: r.spaceId === appSpaceId ? "app" : "extension",
operations
};
if (!options.includeMarkers) return base;
return {
...base,
marker: { storageHash: r.entry.plan.destination.storageHash }
};
});
}
/**
* Materialise the `applyOrder` ordering into resolved per-space
* entries. Throws if the planner output is missing a contract space listed
* in `applyOrder` — a wiring bug that should never reach runtime.
*
* Exported so callers building their own success envelopes after a
* plan-mode dispatch can replay the same ordering.
*/
function collectOrdered(applyOrder, perSpace) {
return applyOrder.map((spaceId) => {
const entry = perSpace.get(spaceId);
if (!entry) throw new Error(`planner output missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
}
/**
* Action-appropriate label for the `spanStart` event the run
* primitive emits. `runMigration` is shared by `db init`, `db update`,
* and `migrate`; the span label tracks the user-visible action
* so structured-progress output reads naturally for each surface.
*/
function progressLabelForAction(action) {
switch (action) {
case "dbInit": return "Initialising database across spaces";
case "dbUpdate": return "Updating database across spaces";
case "migrate": return "Running migration plan across spaces";
}
}
//#endregion
//#region src/control-api/operations/db-run.ts
/**
* Span IDs emitted via `onProgress` during the run flow.
* Stable identifiers consumed by the structured-output renderer and by
* tests asserting on span ids. The `apply` span itself is owned by
* the {@link runMigration} primitive — only the introspect / plan
* spans are emitted directly here.
*/
const SPAN_IDS$1 = {
introspect: "introspect",
plan: "plan"
};
/**
* Loader → planner → runner pipeline shared by `db init` and `db update`.
*
* The pipeline:
*
* 1. **Load**: build a {@link ContractSpaceAggregate} from the descriptor
* set + on-disk on-disk artefacts. Any layout / drift / disjointness /
* integrity violation short-circuits with a structured error.
* 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
* + introspected schema (`familyInstance.introspect`).
* 3. **Plan**: {@link planMigration} chooses `resolveRecordedPath` vs
* `planFromDiff` per space according to `callerPolicy.ignoreGraphFor`.
* The app space is forced through `planFromDiff` (today's daily-driver
* behaviour); every extension space walks its on-disk graph via
* `resolveRecordedPath`.
* 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
* feeds into the runner's `execute` — one outer
* transaction across every space; failure on any space rolls back
* every space's writes.
*/
async function executeRun(options) {
const { driver, adapter, familyInstance, contract, mode, migrations, frameworkComponents, migrationsDir, extensionPacks, targetId, policy, action, onProgress } = options;
const loaded = await buildContractSpaceAggregate({
targetId,
migrationsDir,
appContract: contract,
extensionPacks,
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!loaded.ok) throw loaded.failure;
const aggregate = loaded.value;
const markerRows = await familyInstance.readAllMarkers({ driver });
if (mode === "apply") {
const orphanMarkerError = detectOrphanMarkers(aggregate, markerRows);
if (orphanMarkerError !== null) throw orphanMarkerError;
}
onProgress?.({
action,
kind: "spanStart",
spanId: SPAN_IDS$1.introspect,
label: "Introspecting database schema"
});
const schemaIR = await familyInstance.introspect({
driver,
contract: collectAggregateNamespaces(aggregate)
});
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.introspect,
outcome: "ok"
});
onProgress?.({
action,
kind: "spanStart",
spanId: SPAN_IDS$1.plan,
label: "Planning migration"
});
const planResult = await planMigration({
aggregate,
currentDBState: {
markersBySpaceId: markerRows,
schemaIntrospection: schemaIR
},
adapter,
migrations,
frameworkComponents,
callerPolicy: { ignoreGraphFor: /* @__PURE__ */ new Set([aggregate.app.spaceId]) },
operationPolicy: policy
});
if (!planResult.ok) {
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.plan,
outcome: "error"
});
return mapPlannerError(planResult.failure);
}
onProgress?.({
action,
kind: "spanEnd",
spanId: SPAN_IDS$1.plan,
outcome: "ok"
});
const orderedResolutions = collectOrdered(planResult.value.applyOrder, planResult.value.perSpace);
const plannerWarnings = aggregatePlannerWarnings(orderedResolutions);
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
if (!appResolution) throw new Error("Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.");
const appPlan = appResolution.entry.plan;
if (mode === "plan") {
const aggregateOps = orderedResolutions.flatMap((r) => r.entry.displayOps);
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(aggregateOps) : void 0;
const perSpace = buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: false });
const summary = `Planned ${aggregateOps.length} operation(s) across ${orderedResolutions.length} space(s)`;
return wrapPlanResult({
operations: aggregateOps,
destination: appPlan.destination,
preview,
perSpace,
summary,
...ifDefined("warnings", plannerWarnings)
});
}
const applied = await runMigration({
aggregate,
perSpacePlans: planResult.value.perSpace,
applyOrder: planResult.value.applyOrder,
driver,
familyInstance,
migrations,
frameworkComponents,
policy,
action,
...ifDefined("onProgress", onProgress)
});
if (!applied.ok) return buildRunnerFailure({
summary: applied.failure.summary,
...ifDefined("why", applied.failure.why),
meta: applied.failure.meta,
...ifDefined("warnings", plannerWarnings)
});
const aggregateOps = applied.value.orderedResolutions.flatMap((r) => r.entry.displayOps);
const summary = action === "dbInit" ? `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), database signed` : applied.value.totalOpsExecuted === 0 ? `Database already matches contract across ${applied.value.orderedResolutions.length} space(s), signature updated` : `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), signature updated`;
return wrapApplyResult({
operations: aggregateOps,
destination: appPlan.destination,
operationsPlanned: applied.value.totalOpsPlanned,
operationsExecuted: applied.value.totalOpsExecuted,
perSpace: applied.value.perSpace,
summary,
...ifDefined("warnings", plannerWarnings)
});
}
function aggregatePlannerWarnings(orderedResolutions) {
const warnings = orderedResolutions.flatMap((r) => r.entry.warnings ?? []);
return warnings.length > 0 ? warnings : void 0;
}
/**
* Compare the live `_prisma_marker` rows against the aggregate's
* declared contract spaces. Any marker row whose `space` is not a space of
* the aggregate is an "orphan" — typically a marker left behind by
* an extension that was removed from `extensionPacks` without first
* cleaning up its on-disk migrations / database tables.
*
* Returns a {@link CliStructuredError} envelope (code
* `MIGRATION.CONTRACT_SPACE_VIOLATION`, `kind: 'orphanMarker'`) for the
* first orphan it finds, or `null`
* when every marker row maps to a declared contract space. Mirrors the M2
* `runContractSpaceVerifierMarkerCheck` envelope so downstream
* tooling (integration tests, JSON consumers) keeps asserting on the
* same shape.
*/
function detectOrphanMarkers(aggregate, markerRows) {
const aggregateSpaceIds = /* @__PURE__ */ new Set([aggregate.app.spaceId, ...aggregate.extensions.map((m) => m.spaceId)]);
const orphans = [];
for (const [spaceId, row] of markerRows) if (row !== null && row !== void 0 && !aggregateSpaceIds.has(spaceId)) orphans.push(spaceId);
if (orphans.length === 0) return null;
orphans.sort((a, b) => a.localeCompare(b));
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", orphans.length === 1 ? `Orphan contract-space marker detected for "${orphans[0]}"` : `Orphan contract-space markers detected for ${orphans.length} spaces`, {
why: `The database has \`_prisma_marker\` rows for spaces (${orphans.map((s) => `"${s}"`).join(", ")}) that are not declared in the project's \`extensionPacks\`. The aggregate pipeline refuses to advance markers it cannot account for.`,
fix: "Either re-declare the missing extension(s) in `extensionPacks` (so the aggregate owns them again), or remove the orphan marker row(s) from `_prisma_marker` once you have confirmed the corresponding tables can be safely retired.",
docsUrl: "https://pris.ly/contract-spaces",
meta: { violations: orphans.map((spaceId) => ({
kind: "orphanMarker",
spaceId
})) }
});
}
function mapPlannerError(error) {
if (error.kind === "planFromDiffFailed") return blindCast(notOk({
code: "PLANNING_FAILED",
summary: "Migration planning failed due to conflicts",
conflicts: error.conflicts,
why: void 0,
meta: void 0
}));
if (error.kind === "extensionPathUnreachable") return buildRunnerFailure({
summary: `Cannot resolve apply path for extension space "${error.spaceId}"`,
why: `No path in the on-disk migration graph for extension space "${error.spaceId}" reaches the on-disk head ref hash "${error.target}".`,
meta: {
spaceId: error.spaceId,
target: error.target
}
});
if (error.kind === "extensionPathUnsatisfiable") return buildRunnerFailure({
summary: `Cannot resolve apply path for extension space "${error.spaceId}"`,
why: `On-disk migration graph for extension space "${error.spaceId}" reaches the on-disk head ref but does not cover required invariants: ${error.missingInvariants.join(", ")}.`,
meta: {
spaceId: error.spaceId,
missingInvariants: error.missingInvariants
}
});
return buildRunnerFailure({
summary: `Aggregate planner policy conflict for space "${error.spaceId}"`,
why: error.detail,
meta: { spaceId: error.spaceId }
});
}
function wrapPlanResult(args) {
return ok({
mode: "plan",
plan: {
operations: stripOperations(args.operations),
...ifDefined("preview", args.preview)
},
destination: {
storageHash: args.destination.storageHash,
...ifDefined("profileHash", args.destination.profileHash)
},
perSpace: args.perSpace,
summary: args.summary,
...ifDefined("warnings", args.warnings)
});
}
function wrapApplyResult(args) {
return ok({
mode: "apply",
plan: { operations: stripOperations(args.operations) },
destination: {
storageHash: args.destination.storageHash,
...ifDefined("profileHash", args.destination.profileHash)
},
execution: {
operationsPlanned: args.operationsPlanned,
operationsExecuted: args.operationsExecuted
},
marker: args.destination.profileHash ? {
storageHash: args.destination.storageHash,
profileHash: args.destination.profileHash
} : { storageHash: args.destination.storageHash },
perSpace: args.perSpace,
summary: args.summary,
...ifDefined("warnings", args.warnings)
});
}
function buildRunnerFailure(args) {
return blindCast(notOk({
code: "RUNNER_FAILED",
summary: args.summary,
why: args.why,
meta: args.meta,
conflicts: void 0,
...ifDefined("warnings", args.warnings)
}));
}
//#endregion
//#region src/control-api/operations/db-init.ts
/**
* Execute `db init` against the configured contract.
*
* Routes through the loader → planner → runner pipeline (sub-spec
* "Commit-by-commit § Commit 4"). Always additive-only; destructive
* changes belong to `db update`.
*/
async function executeDbInit(options) {
return await executeRun({
driver: options.driver,
adapter: options.adapter,
familyInstance: options.familyInstance,
contract: options.contract,
mode: options.mode,
migrations: options.migrations,
frameworkComponents: options.frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: options.targetId,
extensionPacks: options.extensionPacks ?? [],
policy: { allowedOperationClasses: ["additive"] },
action: "dbInit",
...ifDefined("onProgress", options.onProgress)
});
}
//#endregion
//#region src/control-api/operations/db-update.ts
const DB_UPDATE_POLICY = { allowedOperationClasses: [
"additive",
"widening",
"destructive"
] };
/**
* Execute `db update` against the configured contract.
*
* Routes through the loader → planner → runner pipeline. Destructive
* operations require either `acceptDataLoss: true` or a prior
* `mode: 'plan'` invocation that surfaces the destructive ops; the
* confirmation gate is implemented here so the lower-level applier
* remains policy-agnostic.
*/
async function executeDbUpdate(options) {
const sharedInputs = {
driver: options.driver,
adapter: options.adapter,
familyInstance: options.familyInstance,
contract: options.contract,
migrations: options.migrations,
frameworkComponents: options.frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: options.targetId,
extensionPacks: options.extensionPacks ?? [],
policy: DB_UPDATE_POLICY,
action: "dbUpdate",
...ifDefined("onProgress", options.onProgress)
};
if (options.mode === "apply" && !options.acceptDataLoss) {
const gate = await guardDestructiveChanges(sharedInputs);
if (gate !== null) return gate;
}
return await executeRun({
...sharedInputs,
mode: options.mode
});
}
/**
* Pre-plan once when running `db update apply` without `acceptDataLoss`.
* Surfaces destructive operations across every space; if any are
* planned, returns a `DESTRUCTIVE_CHANGES` failure that the CLI shows
* as a confirmation prompt. Returns `null` when the apply is safe to
* run.
*/
async function guardDestructiveChanges(sharedInputs) {
const planResult = await executeRun({
...sharedInputs,
mode: "plan"
});
if (!planResult.ok) return planResult;
const destructiveOps = planResult.value.plan.operations.filter((op) => op.operationClass === "destructive").map((op) => ({
id: op.id,
label: op.label
}));
if (destructiveOps.length === 0) return null;
return notOk({
code: "DESTRUCTIVE_CHANGES",
summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
why: "Destructive operations require confirmation — re-run with -y to accept",
conflicts: void 0,
meta: { destructiveOperations: destructiveOps }
});
}
//#endregion
//#region src/control-api/operations/db-verify.ts
/**
* Span IDs emitted via `onProgress` during the aggregate verify flow.
* Mirrors the span identifiers used by the legacy precheck / marker-check
* helpers so structured-output renderers and progress tests keep working.
*/
const SPAN_IDS = {
introspect: "introspect",
verify: "verify"
};
/**
* Loader → verifier pipeline shared by `db verify` modes (`full`,
* `marker-only`, `schema-only`).
*
* 1. **Load**: build a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate}
* from descriptors + on-disk on-disk artefacts. Layout / drift /
* integrity / disjointness violations short-circuit with a
* structured CLI error.
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
* schema introspection.
* 3. **Verify**: {@link verifyMigration} returns per-space `markerCheck` +
* per-space `schemaCheck` (each contract space verified against the full schema,
* then scoped to its own contract space). Marker mismatches map to
* `CliStructuredError` (code `5002`) so callers (CLI command) can render
* and exit. Verify results are returned to the caller verbatim.
*/
async function executeDbVerify(options) {
const { driver, familyInstance, onProgress, skipSchema, skipMarker } = options;
const loaded = await buildContractSpaceAggregate(buildLoadInputs(options));
if (!loaded.ok) return notOk(loaded.failure);
const aggregate = loaded.value;
const markersBySpaceId = await familyInstance.readAllMarkers({ driver });
const schemaIntrospection = skipSchema ? null : await runIntrospection({
driver,
familyInstance,
onProgress,
contract: collectAggregateNamespaces(aggregate)
});
const classifySubjectGranularity = hasSchemaSubjectClassifier(familyInstance) ? (issue) => familyInstance.classifySubjectGranularity(issue) : void 0;
const classifyEntityKind = hasSchemaSubjectClassifier(familyInstance) ? (issue) => familyInstance.classifyEntityKind(issue) : void 0;
emitVerifySpan(onProgress, "spanStart");
return finaliseVerifyResult({
verifyResult: verifyMigration({
aggregate,
markersBySpaceId,
schemaIntrospection,
mode: options.mode,
verifySchemaForSpace: createPerSpaceVerifier(options),
...ifDefined("classifySubjectGranularity", classifySubjectGranularity),
...ifDefined("classifyEntityKind", classifyEntityKind)
}),
aggregate,
skipMarker,
onProgress
});
}
function buildLoadInputs(options) {
return {
targetId: options.targetId,
migrationsDir: options.migrationsDir,
appContract: options.contract,
extensionPacks: options.extensionPacks,
deserializeContract: (json) => options.familyInstance.deserializeContract(json)
};
}
async function runIntrospection(args) {
const { driver, familyInstance, onProgress, contract } = args;
onProgress?.({
action: "dbVerify",
kind: "spanStart",
spanId: SPAN_IDS.introspect,
label: "Introspecting database schema"
});
try {
const result = await familyInstance.introspect({
driver,
contract
});
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.introspect,
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.introspect,
outcome: "error"
});
throw error;
}
}
/**
* Build the per-space schema callback handed to the aggregate verifier.
* When `skipSchema` is true the callback short-circuits with a synthetic
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
* without invoking the family's verification path.
*/
function createPerSpaceVerifier(options) {
const { skipSchema, familyInstance, frameworkComponents } = options;
return (schema, space, verifyMode) => {
if (skipSchema) return buildSkippedSchemaResult(space);
return familyInstance.verifySchema({
contract: space.contract(),
schema,
strict: verifyMode === "strict",
frameworkComponents
});
};
}
function emitVerifySpan(onProgress, kind) {
if (kind === "spanStart") {
onProgress?.({
action: "dbVerify",
kind: "spanStart",
spanId: SPAN_IDS.verify,
label: "Verifying contract spaces"
});
return;
}
onProgress?.({
action: "dbVerify",
kind: "spanEnd",
spanId: SPAN_IDS.verify,
outcome: kind === "spanEndOk" ? "ok" : "error"
});
}
/**
* Map an {@link VerifierOutput} to the operation's
* {@link ExecuteDbVerifyResult}, applying the `skipMarker` policy used
* by the CLI's `--schema-only` mode.
*/
function finaliseVerifyResult(args) {
const { verifyResult, aggregate, skipMarker, onProgress } = args;
if (!verifyResult.ok) {
emitVerifySpan(onProgress, "spanEndError");
return notOk(new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", "Aggregate verifier introspection failed", {
why: verifyResult.failure.detail,
fix: "Check database connectivity and the introspection tooling.",
docsUrl: "https://pris.ly/contract-spaces"
}));
}
const markerError = skipMarker ? null : mapMarkerCheckFailures(aggregate.app.spaceId, verifyResult.value.markerCheck);
if (markerError !== null) {
emitVerifySpan(onProgress, "spanEndError");
return notOk(markerError);
}
emitVerifySpan(onProgress, "spanEndOk");
return ok({
schemaResults: verifyResult.value.schemaCheck.perSpace,
unclaimed: verifyResult.value.schemaCheck.unclaimed,
spaceOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
appSpaceId: aggregate.app.spaceId
});
}
function buildSkippedSchemaResult(space) {
const contract = space.contract();
const headRef = requireHeadRef(space);
const profileHash = castAs(contract).profileHash;
return {
ok: true,
summary: "Schema verification skipped",
contract: {
storageHash: headRef.hash,
...profileHash ? { profileHash } : {}
},
target: { expected: contract.target },
schema: { issues: [] },
timings: { total: 0 }
};
}
/**
* Translate per-space marker check failures and orphan markers into a
* single CLI structured error envelope. Preserves the legacy code
* `MIGRATION.CONTRACT_SPACE_VIOLATION` (was emitted by
* `runContractSpaceVerifierMarkerCheck`).
*/
function mapMarkerCheckFailures(appSpaceId, section) {
const violations = [];
for (const [spaceId, result] of section.perSpace) {
if (result.kind === "ok" || result.kind === "absent") continue;
if (result.kind === "hashMismatch") {
violations.push({
kind: "hashMismatch",
spaceId,
remediation: spaceId === appSpaceId ? "Run `prisma-next db update` to advance the marker, or roll the database back to the recorded hash." : `Apply on-disk migrations under \`migrations/${spaceId}/\` to advance the marker, or remove the conflicting marker row.`
});
continue;
}
if (result.kind === "missingInvariants") violations.push({
kind: "invariantsMismatch",
spaceId,
remediation: `Re-apply the migrations under \`migrations/${spaceId}/\` so the marker carries invariants: ${result.missing.join(", ")}.`
});
}
for (const orphan of section.orphanMarkers) violations.push({
kind: "orphanMarker",
spaceId: orphan.spaceId,
remediation: `Add the corresponding extension to \`extensionPacks\` in \`prisma-next.config.ts\`, or delete the orphan marker row for "${orphan.spaceId}".`
});
if (violations.length === 0) return null;
const lines = violations.map((v) => `- [${v.kind}] ${v.spaceId}: ${v.remediation}`);
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", violations.length === 1 ? "Contract-space verifier found a violation" : `Contract-space verifier found violations (${violations.length})`, {
why: `The on-disk \`migrations/\` directory, the \`extensionPacks\` declaration, and the live database marker rows are not in agreement.\n${lines.join("\n")}`,
fix: violations[0]?.remediation ?? "Review and reconcile the violations listed above.",
docsUrl: "https://pris.ly/contract-spaces",
meta: { violations }
});
}
//#endregion
//#region src/control-api/operations/migrate.ts
/**
* Apply pending migrations across every contract space (app +
* extensions). Replay-only: graph-walk against the on-disk graph for
* every contract space; no synth, no introspection.
*
* Pipeline:
*
* 1. Load aggregate from disk (loader hydrates extension graphs;
* caller provides app-space packages).
* 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
* 3. Per space: `resolveRecordedPath` plots the path from the live
* marker to `space.headRef.hash` (or `refHash` for the app
* space when provided). An empty-graph space whose elements are ALL
* externally managed resolves declaratively (marker to head, zero
* ops), mirroring the db-init aggregate planner: such a space ships
* no DDL and has nothing to author. Every other empty-graph space
* fails loudly — "never planned" is a user-error condition for
* replay.
* 4. Hand off to {@link runMigration} (the runner-driving tail
* shared with `db init` / `db update`). Marker advancement is
* inside the per-space transaction.
*
* Encodes the replay-only contract: the app contract space must have an
* authored migration graph on disk before this operation can advance it.
*/
async function executeMigrate(options) {
const { driver, familyInstance, contract, migrations, frameworkComponents, migrationsDir, extensionPacks, targetId, refHash, refInvariants, refName, onProgress } = options;
const loaded = await buildContractSpaceAggregate({
targetId,
migrationsDir,
appContract: contract,
extensionPacks,
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!loaded.ok) throw loaded.failure;
const aggregate = loaded.value;
const markerRows = await familyInstance.readAllMarkers({ driver });
const allSpaces = [aggregate.app, ...aggregate.extensions];
const perSpacePlans = /* @__PURE__ */ new Map();
const atHeadResolutions = /* @__PURE__ */ new Map();
for (const space of allSpaces) {
const isAppSpace = space.spaceId === aggregate.app.spaceId;
const headRef = requireHeadRef(space);
const spaceTargetHash = isAppSpace && refHash !== void 0 ? refHash : headRef.hash;
const outcome = planSpacePath({
space,
aggregate,
targetHash: spaceTargetHash,
refInvariants: isAppSpace && refHash !== void 0 ? refInvariants : void 0,
liveMarker: markerRows.get(space.spaceId) ?? null,
...isAppSpace ? { refName } : {}
});
if (outcome.kind === "at-head") {
atHeadResolutions.set(space.spaceId, outcome.plan);
continue;
}
if (outcome.kind === "never-planned") return notOk(buildNeverPlannedFailure(outcome.spaceId, outcome.targetHash));
if (outcome.kind === "unreachable") return notOk(buildPathNotFoundFailure(outcome.spaceId, outcome.liveMarker, outcome.targetHash));
if (outcome.kind === "unsatisfiable") {
const structural = findPathWithDecision(outcome.targetSpace.graph(), outcome.liveHash, spaceTargetHash, { required: /* @__PURE__ */ new Set() });
const structuralPath = structural.kind === "ok" ? structural.decision.selectedPath.map((edge) => ({
dirName: edge.dirName,
migrationHash: edge.migrationHash,
from: edge.from,
to: edge.to,
invariants: edge.invariants
})) : [];
throw errorNoInvariantPath({
...outcome.refName !== void 0 ? { refName: outcome.refName } : {},
required: outcome.targetInvariants,
missing: outcome.missing,
structuralPath
});
}
perSpacePlans.set(space.spaceId, outcome.plan);
}
const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
if (!applyOrder.some((spaceId) => {
const entry = perSpacePlans.get(spaceId);
return entry !== void 0 && planRequiresExecution(entry);
})) {
const ordered = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
const entry = perSpacePlans.get(spaceId) ?? atHeadResolutions.get(spaceId);
if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
const perSpace = buildPerSpaceBreakdown(ordered, aggregate.app.spaceId, { includeMarkers: true });
const totalSpaces = ordered.length;
return ok(buildSuccess({
aggregate,
orderedResolutions: ordered,
perSpace,
totalOpsExecuted: 0,
summary: totalSpaces === 0 ? "Already up to date — no contract spaces are loaded" : totalSpaces === 1 ? "Already up to date" : `Already up to date across ${totalSpaces} space(s)`
}));
}
const applied = await runMigration({
aggregate,
perSpacePlans,
applyOrder,
driver,
familyInstance,
migrations,
frameworkComponents,
policy: { allowedOperationClasses: [
"additive",
"widening",
"destructive",
"data"
] },
action: "migrate",
...ifDefined("onProgress", onProgress)
});
if (!applied.ok) return notOk({
code: "RUNNER_FAILED",
summary: applied.failure.summary,
why: applied.failure.why,
meta: applied.failure.meta
});
const orderedAll = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
if (perSpacePlans.has(spaceId)) {
const fromRunner = applied.value.orderedResolutions.find((r) => r.spaceId === spaceId);
if (fromRunner !== void 0) return fromRunner;
}
const entry = atHeadResolutions.get(spaceId);
if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
return {
spaceId,
entry
};
});
const perSpaceAll = buildPerSpaceBreakdown(orderedAll, aggregate.app.spaceId, { includeMarkers: true });
const summary = `Applied ${applied.value.orderedResolutions.reduce((sum, r) => sum + r.entry.migrationEdges.length, 0)} migration(s) (${applied.value.totalOpsExecuted} operation(s)) across ${orderedAll.length} contract space(s)`;
return ok(buildSuccess({
aggregate,
orderedResolutions: orderedAll,
perSpace: perSpaceAll,
totalOpsExecuted: applied.value.totalOpsExecuted,
summary
}));
}
/**
* Compute the graph-walk path for one contract space.
*
* Encapsulates the invariant-correct input assembly that both
* `executeMigrate` and `executeMigrateShowCommand` must use:
* - `currentMarker` carries the full live marker including `invariants`
* (not a stripped `{ storageHash, invariants: [] }` shell).
* - `targetInvariants` uses the caller-supplied `refInvariants` when a
* `--to` ref was resolved (not always the file head ref's invariants).
*
* Both callers map the returned `SpacePathOutcome` to their own error
* representation; the path-compute logic is shared and identical.
*
* @internal Exported for `executeMigrateShowCommand`.
*/
function planSpacePath({ space, aggregate, targetHash, refInvariants, liveMarker, refName }) {
const isAppSpace = space.spaceId === aggregate.app.spaceId;
const headRef = requireHeadRef(space);
if (space.graph().nodes.size === 0) {
const liveHash = liveMarker?.storageHash;
if (targetHash === liveHash || liveHash === void 0 && targetHash === EMPTY_CONTRACT_HASH) return {
kind: "at-head",
plan: buildAtHeadResolution({
aggregateTargetId: aggregate.targetId,
space,
targetHash,
liveMarker
})
};
if (!isAppSpace && allStorageElementsExternal(space.contract())) {
if (headRef.invariants.length > 0) return {
kind: "unsatisfiable",
spaceId: space.spaceId,
isAppSpace,
missing: [...headRef.invariants].sort(),
targetInvariants: headRef.invariants,
targetSpace: space,
liveHash: liveHash ?? EMPTY_CONTRACT_HASH,
refName: void 0
};
return {
kind: "ok",
plan: buildAtHeadResolution({
aggregateTargetId: aggregate.targetId,
space,
targetHash,
liveMarker
})
};
}
return {
kind: "never-planned",
spaceId: space.spaceId,
targetHash
};
}
const targetInvariants = isAppSpace && refInvariants !== void 0 ? refInvariants : headRef.invariants;
const targetSpace = targetHash === headRef.hash && targetInvariants === headRef.invariants ? space : {
...space,
headRef: {
hash: targetHash,
invariants: targetInvariants
}
};
const walked = resolveRecordedPath({
aggregateTargetId: aggregate.targetId,
space: targetSpace,
currentMarker: liveMarker,
...isAppSpace && refName !== void 0 ? { refName } : {}
});
if (walked.kind === "unreachable") return {
kind: "unreachable",
spaceId: space.spaceId,
liveMarker,
targetHash
};
if (walked.kind === "unsatisfiable") {
const liveHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
return {
kind: "unsatisfiable",
spaceId: space.spaceId,
isAppSpace,
missing: walked.missing,
targetInvariants,
targetSpace,
liveHash,
refName
};
}
return {
kind: "ok",
plan: walked.result
};
}
/**
* Build a zero-op {@link PerSpacePlan} for an empty-graph space —
* either one whose live marker already matches the target (at-head), or
* an all-external extension space whose marker must advance to the head
* ref with no DDL (declared-state). Lets the apply pipeline thread the
* space through `perSpacePlans` -> `applyOrder` -> the success
* envelope's `perSpace[]` block so the result reflects every loaded
* space, even when there is nothing to execute.
*/
function buildAtHeadResolution(args) {
const { aggregateTargetId, space, targetHash, liveMarker } = args;
return {
plan: {
targetId: aggregateTargetId,
spaceId: space.spaceId,
origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
destination: { storageHash: targetHash },
operations: [],
providedInvariants: []
},
displayOps: [],
destinationContract: space.contract(),
strategy: "declared-state",
migrationEdges: [buildFabricatedMigrationEdge({
currentMarkerStorageHash: liveMarker?.storageHash,
destinationStorageHash: targetHash,
operationCount: 0
})]
};
}
/**
* A plan needs the runner when it executes operations or advances the
* space's marker (a declared-state resolution has zero operations but a
* destination hash the live marker doesn't carry yet).
*/
function planRequiresExecution(entry) {
if (entry.plan.operations.length > 0) return true;
return entry.plan.origin?.storageHash !== entry.plan.destination.storageHash;
}
function buildSuccess(args) {
const appResolution = args.orderedResolutions.find((r) => r.spaceId === args.aggregate.app.spaceId);
const appMarkerHash = appResolution?.entry.plan.destination.storageHash ?? requireHeadRef(args.aggregate.app).hash;
const applied = args.orderedResolutions.flatMap((r) => {
return r.entry.migrationEdges.map((edge) => ({
spaceId: r.spaceId,
dirName: edge.dirName,
migrationHash: edge.migrationHash,
from: edge.from,
to: edge.to,
operationsExecuted: edge.operationCount
}));
});
const appPlan = appResolution?.entry;
const pathDecision = appPlan?.pathDecision ? {
fromHash: appPlan.pathDecision.fromHash,
toHash: appPlan.pathDecision.toHash,
alternativeCount: appPlan.pathDecision.alternativeCount,
tieBreakReasons: appPlan.pathDecision.tieBreakReasons,
...appPlan.pathDecision.refName !== void 0 ? { refName: appPlan.pathDecision.refName } : {},
requiredInvariants: appPlan.pathDecision.requiredInvariants ?? [],
satisfiedInvariants: appPlan.pathDecision.satisfiedInvariants ?? [],
selectedPath: appPlan.pathDecision.selectedPath.map((entry) => ({
dirName: entry.dirName,
migrationHash: entry.migrationHash,
from: entry.from,
to: entry.to,
invariants: entry.invariants
}))
} : void 0;
return {
migrationsApplied: applied.length,
markerHash: appMarkerHash,
applied,
summary: args.summary,
perSpace: args.perSpace,
...pathDecision !== void 0 ? { pathDecision } : {}
};
}
/**
* Build the `neverPlanned` failure raised when a contract space that
* declares managed storage elements has no on-disk migration graph but
* migrate was asked to reach a target hash. All-external spaces never reach
* this: they resolve declaratively (marker advances to the head ref with
* zero operations). The `why` states only the condition; the recovery
* sequence is composed by `errorPathUnreachable`'s `fix`.
*
* @internal Exported for testing only.
*/
function buildNeverPlannedFailure(spaceId, targetHash) {
return {
code: "MIGRATION_PATH_NOT_FOUND",
summary: `No on-disk migrations for contract space "${spaceId}"`,
why: `migrate is replay-only: a contract space that declares managed storage elements must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
meta: {
spaceId,
target: targetHash,
kind: "neverPlanned"
}
};
}
/**
* Build the `pathUnreachable` failure raised when an emitted contract has no
* on-disk migration edge from the current marker to the target. The `why`
* states only the condition (no edge between the two named states, and migrate
* replays edges rather than inventing them); the recovery sequence — plan the
* edge, then re-apply — is composed by `errorPathUnreachable`'s `fix`, so the
* two read as one non-redundant plan-then-apply story.
*
* @internal Exported for testing only.
*/
function buildPathNotFoundFailure(spaceId, marker, targetHash) {
const fromHash = marker?.storageHash ?? "<empty>";
return {
code: "MIGRATION_PATH_NOT_FOUND",
summary: spaceId === "app" ? "Current contract has no planned migration path" : `Current contract has no planned migration path for contract space "${spaceId}"`,
why: `No migration edge connects the current state "${fromHash}" to the target "${targetHash}" in contract space "${spaceId}". The on-disk migration graph does not join the two, and migrate replays existing edges — it never invents one.`,
meta: {
spaceId,
fromHash,
targetHash,
kind: "pathUnreachable"
}
};
}
//#endregion
//#region src/control-api/client.ts
/**
* Creates a programmatic control client for Prisma Next operations.
*
* The client accepts framework component descriptors at creation time,
* manages driver lifecycle via connect()/close(), and exposes domain
* operations that delegate to the existing family instance methods.
*
* @see {@link ControlClient} for the client interface
* @see README.md "Programmatic Control API" section for usage examples
*/
function createControlClient(options) {
return new ControlClientImpl(options);
}
/**
* Implementation of ControlClient.
* Manages initialization and connection state, delegates operations to family instance.
*/
var ControlClientImpl = class {
options;
stack = null;
driver = null;
familyInstance = null;
frameworkComponents = null;
initialized = false;
defaultConnection;
constructor(options) {
this.options = options;
this.defaultConnection = options.connection;
}
init() {
if (this.initialized) return;
this.stack = createControlStack({
family: this.options.family,
target: this.options.target,
adapter: this.options.adapter,
driver: this.options.driver,
extensionPacks: this.options.extensionPacks
});
this.familyInstance = this.options.family.create(this.stack);
const rawComponents = [
this.options.target,
this.options.adapter,
...this.options.extensionPacks ?? []
];
this.frameworkComponents = assertFrameworkComponentsCompatible(this.options.family.familyId, this.options.target.targetId, rawComponents);
this.initialized = true;
}
async connect(connection) {
this.init();
if (this.driver) throw new Error("Already connected. Call close() before reconnecting.");
const resolvedConnection = connection ?? this.defaultConnection;
if (resolvedConnection === void 0) throw new Error("No connection provided. Pass a connection to connect() or provide a default connection when creating the client.");
if (!this.stack?.driver) throw new Error("Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.");
this.driver = await this.stack.driver.create(resolvedConnection);
}
async close() {
if (this.driver) {
await this.driver.close();
this.driver = null;
}
}
/**
* Construct the control adapter once for a migration operation and return
* it, mirroring how the runtime plane builds the execution adapter once in
* `createExecutionStack`. Only `dbInit` / `dbUpdate` need it (it lowers the
* planner's DDL); read-only operations never build it. The descriptor is
* optional on the stack — targets without migrations omit it.
*/
buildControlAdapter() {
this.init();
if (!this.stack?.adapter) throw new Error(`Target "${this.options.target.targetId}" requires an adapter for migrations`);
return this.stack.adapter.create(this.stack);
}
async ensureConnected() {
this.init();
if (!this.driver && this.defaultConnection !== void 0) await this.connect(this.defaultConnection);
if (!this.driver || !this.familyInstance || !this.frameworkComponents) throw new Error("Not connected. Call connect(connection) first.");
return {
driver: this.driver,
familyInstance: this.familyInstance,
frameworkComponents: this.frameworkComponents
};
}
async connectWithProgress(connection, action, onProgress) {
if (connection === void 0) return;
onProgress?.({
action,
kind: "spanStart",
spanId: "connect",
label: "Connecting to database..."
});
try {
await this.connect(connection);
onProgress?.({
action,
kind: "spanEnd",
spanId: "connect",
outcome: "ok"
});
} catch (error) {
onProgress?.({
action,
kind: "spanEnd",
spanId: "connect",
outcome: "error"
});
throw error;
}
}
async verify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "verify", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "verify",
kind: "spanStart",
spanId: "verify",
label: "Verifying database marker..."
});
try {
const result = await familyInstance.verify({
driver,
contract,
expectedTargetId: this.options.target.targetId,
contractPath: ""
});
onProgress?.({
action: "verify",
kind: "spanEnd",
spanId: "verify",
outcome: result.ok ? "ok" : "error"
});
return result;
} catch (error) {
onProgress?.({
action: "verify",
kind: "spanEnd",
spanId: "verify",
outcome: "error"
});
throw error;
}
}
async schemaVerify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "schemaVerify", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "schemaVerify",
kind: "spanStart",
spanId: "schemaVerify",
label: "Verifying database schema..."
});
try {
const schema = await familyInstance.introspect({
driver,
contract
});
const result = familyInstance.verifySchema({
contract,
schema,
strict: options.strict ?? false,
frameworkComponents
});
onProgress?.({
action: "schemaVerify",
kind: "spanEnd",
spanId: "schemaVerify",
outcome: result.ok ? "ok" : "error"
});
return result;
} catch (error) {
onProgress?.({
action: "schemaVerify",
kind: "spanEnd",
spanId: "schemaVerify",
outcome: "error"
});
throw error;
}
}
async sign(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "sign", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
onProgress?.({
action: "sign",
kind: "spanStart",
spanId: "sign",
label: "Signing database..."
});
try {
const result = await familyInstance.sign({
driver,
contract,
contractPath: options.contractPath ?? "",
...ifDefined("configPath", options.configPath)
});
onProgress?.({
action: "sign",
kind: "spanEnd",
spanId: "sign",
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "sign",
kind: "spanEnd",
spanId: "sign",
outcome: "error"
});
throw error;
}
}
async dbInit(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbInit", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
const adapter = this.buildControlAdapter();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeDbInit({
driver,
adapter,
familyInstance,
contract,
mode: options.mode,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
...ifDefined("onProgress", onProgress)
});
}
async dbUpdate(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbUpdate", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
const adapter = this.buildControlAdapter();
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeDbUpdate({
driver,
adapter,
familyInstance,
contract,
mode: options.mode,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
...ifDefined("acceptDataLoss", options.acceptDataLoss),
...ifDefined("onProgress", onProgress)
});
}
async dbVerify(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "dbVerify", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
return executeDbVerify({
driver,
familyInstance,
contract: options.contract,
migrationsDir: options.migrationsDir,
targetId: this.options.target.targetId,
extensionPacks: this.options.extensionPacks ?? [],
frameworkComponents,
mode: options.strict ? "strict" : "lenient",
skipSchema: options.skipSchema,
skipMarker: options.skipMarker,
...ifDefined("onProgress", onProgress)
});
}
async readMarker() {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readMarker({
driver,
space: APP_SPACE_ID
});
}
async readAllMarkers() {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readAllMarkers({ driver });
}
/** Reads the per-migration journal; omit `space` to return every space. */
async readLedger(space) {
const { driver, familyInstance } = await this.ensureConnected();
return familyInstance.readLedger({
driver,
...ifDefined("space", space)
});
}
async migrate(options) {
const { onProgress } = options;
await this.connectWithProgress(options.connection, "migrate", onProgress);
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
let contract;
try {
contract = familyInstance.deserializeContract(options.contract);
} catch (error) {
throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
}
return executeMigrate({
driver,
familyInstance,
contract,
migrations: this.options.target.migrations,
frameworkComponents,
migrationsDir: options.migrationsDir,
extensionPacks: this.options.extensionPacks ?? [],
targetId: this.options.target.targetId,
...ifDefined("refHash", options.refHash),
...ifDefined("refInvariants", options.refInvariants),
...ifDefined("refName", options.refName),
...ifDefined("onProgress", onProgress)
});
}
async introspect(options) {
const onProgress = options?.onProgress;
await this.connectWithProgress(options?.connection, "introspect", onProgress);
const { driver, familyInstance } = await this.ensureConnected();
options?.schema;
onProgress?.({
action: "introspect",
kind: "spanStart",
spanId: "introspect",
label: "Introspecting database schema..."
});
try {
const result = await familyInstance.introspect({ driver });
onProgress?.({
action: "introspect",
kind: "spanEnd",
spanId: "introspect",
outcome: "ok"
});
return result;
} catch (error) {
onProgress?.({
action: "introspect",
kind: "spanEnd",
spanId: "introspect",
outcome: "error"
});
throw error;
}
}
toSchemaView(schemaIR) {
this.init();
if (this.familyInstance && hasSchemaView(this.familyInstance)) return this.familyInstance.toSchemaView(schemaIR);
}
inferPslContract(schemaIR) {
this.init();
if (this.familyInstance && hasPslContractInfer(this.familyInstance)) return this.familyInstance.inferPslContract(schemaIR);
}
getPslBlockDescriptors() {
this.init();
return this.stack.authoringContributions.pslBlockDescriptors;
}
toOperationPreview(operations) {
this.init();
if (this.familyInstance && hasOperationPreview(this.familyInstance)) return this.familyInstance.toOperationPreview(operations);
}
async emit(options) {
const { onProgress, contractConfig } = options;
this.init();
if (!this.familyInstance) throw new Error("Family instance was not initialized. This is a bug.");
let contractRaw;
onProgress?.({
action: "emit",
kind: "spanStart",
spanId: "resolveSource",
label: "Resolving contract source..."
});
try {
const stack = this.stack;
const sourceContext = {
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
composedExtensionContracts: stack.extensionContracts,
scalarTypeDescriptors: stack.scalarTypeDescriptors,
authoringContributions: stack.authoringContributions,
codecLookup: stack.codecLookup,
controlMutationDefaults: stack.controlMutationDefaults,
resolvedInputs: contractConfig.source.inputs ?? [],
capabilities: stack.capabilities
};
const providerResult = await contractConfig.source.load(sourceContext);
if (!providerResult.ok) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "error"
});
return notOk({
code: "CONTRACT_SOURCE_INVALID",
summary: providerResult.failure.summary,
why: providerResult.failure.summary,
meta: providerResult.failure.meta,
diagnostics: providerResult.failure
});
}
contractRaw = providerResult.value;
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "ok"
});
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "resolveSource",
outcome: "error"
});
const message = error instanceof Error ? error.message : String(error);
return notOk({
code: "CONTRACT_SOURCE_INVALID",
summary: "Failed to resolve contract source",
why: message,
diagnostics: {
summary: "Contract source provider threw an exception",
diagnostics: [{
code: "PROVIDER_THROW",
message
}]
},
meta: void 0
});
}
onProgress?.({
action: "emit",
kind: "spanStart",
spanId: "emit",
label: "Emitting contract..."
});
try {
const enrichedIR = enrichContract(contractRaw, this.frameworkComponents ?? []);
const rawContractJson = this.options.target.contractSerializer.serializeContract(enrichedIR);
let deserializedContract;
try {
deserializedContract = this.familyInstance.deserializeContract(rawContractJson);
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "error"
});
return notOk({
code: "CONTRACT_VALIDATION_FAILED",
summary: "Contract validation failed",
why: error instanceof Error ? error.message : String(error),
meta: void 0
});
}
const result = await emit(deserializedContract, this.stack, this.options.family.emission, { serializeContract: (contract) => this.options.target.contractSerializer.serializeContract(contract) });
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "ok"
});
return ok({
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
profileHash: result.profileHash,
contractJson: result.contractJson,
contractDts: result.contractDts
});
} catch (error) {
onProgress?.({
action: "emit",
kind: "spanEnd",
spanId: "emit",
outcome: "error"
});
return notOk({
code: "EMIT_FAILED",
summary: "Failed to emit contract",
why: error instanceof Error ? error.message : String(error),
meta: void 0
});
}
}
};
//#endregion
export { executeDbInit as a, executeDbUpdate as i, planSpacePath as n, ContractValidationError as o, executeDbVerify as r, createControlClient as t };
//# sourceMappingURL=client-2kwLMBSf.mjs.map

Sorry, the diff of this file is too big to display

import { n as executeContractEmit } from "./contract-emit-iSMJVs26.mjs";
import { A as formatStyledHeader, F as CliStructuredError$1, P as isVerbose, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, j as formatSuccessMessage, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
import { errorContractConfigMissing } from "@prisma-next/errors/control";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { dirname, join, relative, resolve } from "pathe";
//#region src/utils/formatters/emit.ts
/**
* Formats human-readable output for contract emit.
*/
function formatEmitOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const jsonPath = relative(process.cwd(), result.files.json);
const dtsPath = relative(process.cwd(), result.files.dts);
lines.push(`✔ Emitted contract.json → ${jsonPath}`);
lines.push(`✔ Emitted contract.d.ts → ${dtsPath}`);
lines.push(` storageHash: ${result.storageHash}`);
if (result.executionHash) lines.push(` executionHash: ${result.executionHash}`);
if (result.profileHash) lines.push(` profileHash: ${result.profileHash}`);
if (isVerbose(flags, 1)) lines.push(` Total time: ${result.timings.total}ms`);
return lines.join("\n");
}
/**
* Formats JSON output for contract emit.
*/
function formatEmitJson(result) {
const output = {
ok: true,
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
...result.profileHash ? { profileHash: result.profileHash } : {},
outDir: result.outDir,
files: result.files,
timings: result.timings
};
return JSON.stringify(output, null, 2);
}
//#endregion
//#region src/commands/contract-emit.ts
/**
* Pre-load the config just to compute display paths for the styled header. The
* actual emit work goes through `executeContractEmit`, which loads the config
* itself; the redundant load here is bounded and lets the header render before
* the emit spans start.
*/
async function resolveHeaderPaths(configOption, outputPath) {
const displayConfigPath = configOption ? relative(process.cwd(), resolve(configOption)) : "prisma-next.config.ts";
let config;
try {
config = await loadConfig(configOption);
} catch (error) {
if (error instanceof CliStructuredError$1) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: "Failed to load config" }));
}
const effectiveJsonPath = outputPath !== void 0 ? join(outputPath, "contract.json") : config.contract?.output;
if (!effectiveJsonPath) return notOk(errorContractConfigMissing({ why: "Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }" }));
try {
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = getEmittedArtifactPaths(effectiveJsonPath);
return ok({
displayConfigPath,
outputJsonPath,
outputDtsPath
});
} catch (error) {
return notOk(errorContractConfigMissing({ why: error instanceof Error ? error.message : String(error) }));
}
}
async function executeContractEmitCommand(options, flags, ui, startTime) {
const outputPath = options.outputPath !== void 0 ? resolve(options.outputPath) : void 0;
const headerPathsResult = await resolveHeaderPaths(options.config, outputPath);
if (!headerPathsResult.ok) return headerPathsResult;
const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;
if (!flags.json && !flags.quiet) ui.stderr(formatStyledHeader({
command: "contract emit",
description: "Emit your contract artifacts",
url: "https://pris.ly/contract-emit",
details: [
{
label: "config",
value: displayConfigPath
},
{
label: "contract",
value: relative(process.cwd(), outputJsonPath)
},
{
label: "types",
value: relative(process.cwd(), outputDtsPath)
}
],
flags
}));
const onProgress = createProgressAdapter({
ui,
flags
});
const configPath = options.config ? resolve(options.config) : "prisma-next.config.ts";
let result;
try {
result = await executeContractEmit({
configPath,
onProgress,
...ifDefined("outputPath", outputPath)
});
} catch (error) {
if (CliStructuredError$1.is(error)) return notOk(error);
return notOk(errorUnexpected("Unexpected error during contract emit", { why: error instanceof Error ? error.message : String(error) }));
}
if (result.validationWarning) ui.warn(result.validationWarning);
return ok({
storageHash: result.storageHash,
...ifDefined("executionHash", result.executionHash),
profileHash: result.profileHash,
outDir: dirname(result.files.json),
files: result.files,
timings: { total: Date.now() - startTime }
});
}
function createContractEmitCommand() {
const command = new Command("emit");
setCommandDescriptions(command, "Emit your contract artifacts", "Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\ncontract.d.ts. The contract.json contains the canonical contract structure, and\ncontract.d.ts provides TypeScript types for type-safe query building.");
setCommandExamples(command, [
"prisma-next contract emit",
"prisma-next contract emit --config ./custom-config.ts",
"prisma-next contract emit --output-path ./generated"
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--output-path <dir>", "Directory to write contract.json and contract.d.ts into").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeContractEmitCommand(options, flags, ui, Date.now()), flags, ui, (emitResult) => {
if (flags.json) ui.output(formatEmitJson(emitResult));
else {
const output = formatEmitOutput(emitResult, flags);
if (output) ui.log(output);
if (!flags.quiet) ui.success(formatSuccessMessage(flags));
}
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createContractEmitCommand as t };
//# sourceMappingURL=contract-emit-DzOkTs11.mjs.map
{"version":3,"file":"contract-emit-DzOkTs11.mjs","names":["CliStructuredError"],"sources":["../src/utils/formatters/emit.ts","../src/commands/contract-emit.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport { relative } from 'pathe';\n\nimport type { GlobalFlags } from '../global-flags';\nimport { isVerbose } from './helpers';\n\n// EmitContractResult type for CLI output formatting (includes file paths)\nexport interface EmitContractResult {\n readonly storageHash: string;\n readonly executionHash?: string;\n readonly profileHash: string;\n readonly outDir: string;\n readonly files: {\n readonly json: string;\n readonly dts: string;\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for contract emit.\n */\nexport function formatEmitOutput(result: EmitContractResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n // Convert absolute paths to relative paths from cwd\n const jsonPath = relative(process.cwd(), result.files.json);\n const dtsPath = relative(process.cwd(), result.files.dts);\n\n lines.push(`✔ Emitted contract.json → ${jsonPath}`);\n lines.push(`✔ Emitted contract.d.ts → ${dtsPath}`);\n lines.push(` storageHash: ${result.storageHash}`);\n if (result.executionHash) {\n lines.push(` executionHash: ${result.executionHash}`);\n }\n if (result.profileHash) {\n lines.push(` profileHash: ${result.profileHash}`);\n }\n if (isVerbose(flags, 1)) {\n lines.push(` Total time: ${result.timings.total}ms`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for contract emit.\n */\nexport function formatEmitJson(result: EmitContractResult): string {\n const output = {\n ok: true,\n storageHash: result.storageHash,\n ...ifDefined('executionHash', result.executionHash),\n ...(result.profileHash ? { profileHash: result.profileHash } : {}),\n outDir: result.outDir,\n files: result.files,\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport { getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport { errorContractConfigMissing } from '@prisma-next/errors/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { dirname, join, relative, resolve } from 'pathe';\nimport { executeContractEmit } from '../control-api/operations/contract-emit';\nimport type { ContractEmitResult } from '../control-api/types';\nimport { CliStructuredError, errorUnexpected } from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport {\n type EmitContractResult,\n formatEmitJson,\n formatEmitOutput,\n} from '../utils/formatters/emit';\nimport { formatStyledHeader, formatSuccessMessage } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface ContractEmitOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly outputPath?: string;\n}\n\ninterface HeaderPaths {\n readonly displayConfigPath: string;\n readonly outputJsonPath: string;\n readonly outputDtsPath: string;\n}\n\n/**\n * Pre-load the config just to compute display paths for the styled header. The\n * actual emit work goes through `executeContractEmit`, which loads the config\n * itself; the redundant load here is bounded and lets the header render before\n * the emit spans start.\n */\nasync function resolveHeaderPaths(\n configOption: string | undefined,\n outputPath: string | undefined,\n): Promise<Result<HeaderPaths, CliStructuredError>> {\n const displayConfigPath = configOption\n ? relative(process.cwd(), resolve(configOption))\n : 'prisma-next.config.ts';\n\n let config: Awaited<ReturnType<typeof loadConfig>>;\n try {\n config = await loadConfig(configOption);\n } catch (error) {\n if (error instanceof CliStructuredError) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: 'Failed to load config',\n }),\n );\n }\n\n const effectiveJsonPath =\n outputPath !== undefined ? join(outputPath, 'contract.json') : config.contract?.output;\n\n if (!effectiveJsonPath) {\n return notOk(\n errorContractConfigMissing({\n why: 'Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n }),\n );\n }\n\n try {\n const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } =\n getEmittedArtifactPaths(effectiveJsonPath);\n return ok({ displayConfigPath, outputJsonPath, outputDtsPath });\n } catch (error) {\n return notOk(\n errorContractConfigMissing({\n why: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n}\n\nasync function executeContractEmitCommand(\n options: ContractEmitOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<EmitContractResult, CliStructuredError>> {\n const outputPath = options.outputPath !== undefined ? resolve(options.outputPath) : undefined;\n\n const headerPathsResult = await resolveHeaderPaths(options.config, outputPath);\n if (!headerPathsResult.ok) {\n return headerPathsResult;\n }\n const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;\n\n if (!flags.json && !flags.quiet) {\n ui.stderr(\n formatStyledHeader({\n command: 'contract emit',\n description: 'Emit your contract artifacts',\n url: 'https://pris.ly/contract-emit',\n details: [\n { label: 'config', value: displayConfigPath },\n { label: 'contract', value: relative(process.cwd(), outputJsonPath) },\n { label: 'types', value: relative(process.cwd(), outputDtsPath) },\n ],\n flags,\n }),\n );\n }\n\n const onProgress = createProgressAdapter({ ui, flags });\n const configPath = options.config ? resolve(options.config) : 'prisma-next.config.ts';\n\n let result: ContractEmitResult;\n try {\n result = await executeContractEmit({\n configPath,\n onProgress,\n ...ifDefined('outputPath', outputPath),\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected('Unexpected error during contract emit', {\n why: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n\n if (result.validationWarning) {\n ui.warn(result.validationWarning);\n }\n\n return ok({\n storageHash: result.storageHash,\n ...ifDefined('executionHash', result.executionHash),\n profileHash: result.profileHash,\n outDir: dirname(result.files.json),\n files: result.files,\n timings: { total: Date.now() - startTime },\n });\n}\n\nexport function createContractEmitCommand(): Command {\n const command = new Command('emit');\n setCommandDescriptions(\n command,\n 'Emit your contract artifacts',\n 'Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\\n' +\n 'contract.d.ts. The contract.json contains the canonical contract structure, and\\n' +\n 'contract.d.ts provides TypeScript types for type-safe query building.',\n );\n setCommandExamples(command, [\n 'prisma-next contract emit',\n 'prisma-next contract emit --config ./custom-config.ts',\n 'prisma-next contract emit --output-path ./generated',\n ]);\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--output-path <dir>', 'Directory to write contract.json and contract.d.ts into')\n .action(async (options: ContractEmitOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const startTime = Date.now();\n\n const result = await executeContractEmitCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (emitResult) => {\n if (flags.json) {\n ui.output(formatEmitJson(emitResult));\n } else {\n const output = formatEmitOutput(emitResult, flags);\n if (output) {\n ui.log(output);\n }\n if (!flags.quiet) {\n ui.success(formatSuccessMessage(flags));\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { rt as errorRuntime, z as errorContractConfigMissing } from "./command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-VMQJbAwl.mjs";
import { t as enrichContract } from "./contract-enrichment-gn9sWbPw.mjs";
import { createRequire } from "node:module";
import { loadConfig } from "@prisma-next/config-loader";
import { emit, getEmittedArtifactPaths } from "@prisma-next/emitter";
import { ifDefined } from "@prisma-next/utils/defined";
import { basename, dirname, join } from "pathe";
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { abortable } from "@prisma-next/utils/abortable";
//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
};
//#endregion
//#region src/utils/emit-queue.ts
/**
* Per-output FIFO queue for `executeContractEmit`.
*
* Ensures that at most one emit (load → resolve source → emit bytes → publish)
* runs per output JSON path at a time. Concurrent calls for the same path
* line up behind the in-flight one and run in submission order; the user-visible
* outcome is "last submission wins on disk" without any supersession bookkeeping.
*
* Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`
* when they stop publishing to a path, otherwise the module-global `Map`
* accumulates one entry per unique output path for the lifetime of the process.
*/
const emitQueues = /* @__PURE__ */ new Map();
function queueEmitByOutput(outputJsonPath, action) {
const next = (emitQueues.get(outputJsonPath) ?? Promise.resolve()).then(action, action);
emitQueues.set(outputJsonPath, next);
return next;
}
function disposeEmitQueue(outputJsonPath) {
emitQueues.delete(outputJsonPath);
}
//#endregion
//#region src/utils/publish-contract-artifact-pair.ts
function isRecord$1(value) {
return typeof value === "object" && value !== null;
}
function createTempArtifactPath(path, publicationToken, phase) {
return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);
}
async function readExistingArtifact(path) {
try {
return { content: await readFile(path, "utf-8") };
} catch (error) {
if (isRecord$1(error) && error["code"] === "ENOENT") return "remove";
throw error;
}
}
async function restoreArtifact(path, previous, publicationToken) {
if (previous === "remove") {
await rm(path, { force: true });
return;
}
const restorePath = createTempArtifactPath(path, publicationToken, "rollback");
await writeFile(restorePath, previous.content, "utf-8");
try {
await rename(restorePath, path);
} finally {
await rm(restorePath, { force: true });
}
}
function withRollbackFailureCause(error, rollbackFailures) {
const rollbackCause = new AggregateError(rollbackFailures, "Failed to restore published artifacts");
if (error instanceof Error) {
Object.defineProperty(error, "cause", {
value: rollbackCause,
configurable: true,
writable: true
});
return error;
}
return new Error(String(error), { cause: rollbackCause });
}
async function publishPairWithRollback(entries, publicationToken) {
const replaced = [];
try {
for (const entry of entries) {
await rename(entry.tempPath, entry.outputPath);
replaced.push(entry);
}
} catch (error) {
const rollbackFailures = (await Promise.allSettled(replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
if (rollbackFailures.length > 0) throw withRollbackFailureCause(error, rollbackFailures);
throw error;
}
}
async function publishContractArtifactPair({ outputJsonPath, outputDtsPath, contractJson, contractDts, publicationToken, beforePublish }) {
const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, "next");
const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, "next");
try {
await writeFile(tempJsonPath, contractJson, "utf-8");
await writeFile(tempDtsPath, contractDts, "utf-8");
if (await beforePublish?.() === false) return false;
const previousJson = await readExistingArtifact(outputJsonPath);
await publishPairWithRollback([{
tempPath: tempDtsPath,
outputPath: outputDtsPath,
previous: await readExistingArtifact(outputDtsPath)
}, {
tempPath: tempJsonPath,
outputPath: outputJsonPath,
previous: previousJson
}], publicationToken);
return true;
} finally {
await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);
}
}
//#endregion
//#region src/utils/validate-contract-deps.ts
const IMPORT_PATTERN = /import\s+type\s+.*?\s+from\s+['"](@[^/]+\/[^/'"]+)/g;
function extractPackageSpecifiers(dtsContent) {
const packages = /* @__PURE__ */ new Set();
for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {
const pkg = match[1];
if (pkg) packages.add(pkg);
}
return [...packages];
}
function validateContractDeps(dtsContent, projectRoot) {
const packages = extractPackageSpecifiers(dtsContent);
const resolve = createRequire(`${projectRoot}/package.json`);
const missing = [];
for (const pkg of packages) try {
resolve.resolve(`${pkg}/package.json`);
} catch {
missing.push(pkg);
}
if (missing.length === 0) return { missing };
return {
missing,
warning: [
"contract.d.ts imports types from packages that are not installed:",
missing.map((p) => ` - ${p}`).join("\n"),
"",
"Install them with your package manager:",
...missing.map((p) => ` ${p}`)
].join("\n")
};
}
//#endregion
//#region src/control-api/operations/contract-emit.ts
var contract_emit_exports = /* @__PURE__ */ __exportAll({ executeContractEmit: () => executeContractEmit });
const EMIT_ACTION = "emit";
function isRecord(value) {
return typeof value === "object" && value !== null;
}
function startSpan(onProgress, spanId, label) {
onProgress?.({
action: EMIT_ACTION,
kind: "spanStart",
spanId,
label
});
}
function endSpan(onProgress, spanId, outcome) {
onProgress?.({
action: EMIT_ACTION,
kind: "spanEnd",
spanId,
outcome
});
}
function failedToResolveContractSource(why, fix, meta) {
return errorRuntime("Failed to resolve contract source", {
why,
fix,
...ifDefined("meta", meta)
});
}
function diagnosticLocationSuffix(diagnostic) {
const sourceId = typeof diagnostic["sourceId"] === "string" ? diagnostic["sourceId"] : void 0;
const span = isRecord(diagnostic["span"]) ? diagnostic["span"] : void 0;
const start = span && isRecord(span["start"]) ? span["start"] : void 0;
const line = start && typeof start["line"] === "number" ? start["line"] : void 0;
const column = start && typeof start["column"] === "number" ? start["column"] : void 0;
if (sourceId && line !== void 0 && column !== void 0) return ` (${sourceId}:${line}:${column})`;
if (sourceId) return ` (${sourceId})`;
return "";
}
function mapDiagnosticsToIssues(diagnostics) {
const issues = [];
for (const raw of diagnostics) {
if (!isRecord(raw)) continue;
const code = typeof raw["code"] === "string" ? raw["code"] : "diagnostic";
const message = typeof raw["message"] === "string" ? raw["message"] : "";
issues.push({
kind: code,
message: `${message}${diagnosticLocationSuffix(raw)}`
});
}
return issues;
}
function validateProviderResult(providerResult) {
if (!isRecord(providerResult) || typeof providerResult["ok"] !== "boolean") return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed result shape.", "Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).")
};
if (providerResult["ok"]) {
if (!("value" in providerResult)) return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed success result: missing value.", "Ensure contract.source.load success payload is ok(Contract).")
};
return {
ok: true,
value: providerResult["value"]
};
}
const failure = providerResult["failure"];
if (!isRecord(failure) || typeof failure["summary"] !== "string" || !Array.isArray(failure["diagnostics"])) return {
ok: false,
error: failedToResolveContractSource("Contract source provider returned malformed failure result: expected summary and diagnostics.", "Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).")
};
return {
ok: false,
error: failedToResolveContractSource(String(failure["summary"]), "Fix contract source diagnostics and return ok(Contract).", {
diagnostics: failure["diagnostics"],
issues: mapDiagnosticsToIssues(failure["diagnostics"]),
...ifDefined("providerMeta", failure["meta"])
})
};
}
/**
* Canonical contract emit operation.
*
* This is the SINGLE publication path used by both the CLI command
* (`prisma-next contract emit`) and the Vite plugin
* (`@prisma-next/vite-plugin-contract-emit`). New callers must go through this
* function rather than re-implementing load → emit → publish.
*
* The whole flow (load config → resolve source → emit bytes → atomic publish)
* is serialized per output JSON path via `queueEmitByOutput`. Concurrent calls
* for the same output line up FIFO; the user-visible outcome is "last
* submission wins on disk" without any supersession bookkeeping. Within a
* single emit, `publishContractArtifactPair` stages temp files, renames
* `contract.d.ts` before `contract.json`, and attempts to restore the previous
* pair if either rename fails — so type-only consumers never observe a
* mismatched pair.
*
* @throws {CliStructuredError} on config/source/validation problems
* @throws {DOMException} `AbortError` if cancelled via `signal`
*/
async function executeContractEmit(options) {
const { configPath, outputPath, signal = new AbortController().signal, onProgress } = options;
const unlessAborted = abortable(signal);
const config = await unlessAborted(loadConfig(configPath));
if (!config.contract) throw errorContractConfigMissing({ why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }" });
const contractConfig = config.contract;
const effectiveOutput = outputPath !== void 0 ? join(outputPath, "contract.json") : contractConfig.output;
if (!effectiveOutput) throw errorContractConfigMissing({ why: "Contract config must have output path. This should not happen if defineConfig() was used." });
if (typeof contractConfig.source?.load !== "function") throw errorContractConfigMissing({ why: "Contract config must include a valid source provider object" });
let outputPaths;
try {
outputPaths = getEmittedArtifactPaths(effectiveOutput);
} catch (error) {
throw errorContractConfigMissing({ why: error instanceof Error ? error.message : String(error) });
}
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
return queueEmitByOutput(outputJsonPath, async () => {
const stack = createControlStack(config);
const sourceContext = {
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
composedExtensionContracts: stack.extensionContracts,
scalarTypeDescriptors: stack.scalarTypeDescriptors,
authoringContributions: stack.authoringContributions,
codecLookup: stack.codecLookup,
controlMutationDefaults: stack.controlMutationDefaults,
resolvedInputs: contractConfig.source.inputs ?? [],
capabilities: stack.capabilities
};
startSpan(onProgress, "resolveSource", "Resolving contract source...");
let providerResult;
try {
providerResult = await unlessAborted(contractConfig.source.load(sourceContext));
} catch (error) {
endSpan(onProgress, "resolveSource", "error");
if (signal.aborted || isRecord(error) && error["name"] === "AbortError") throw error;
throw failedToResolveContractSource(error instanceof Error ? error.message : String(error), "Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.");
}
const validatedContract = validateProviderResult(providerResult);
if (!validatedContract.ok) {
endSpan(onProgress, "resolveSource", "error");
throw validatedContract.error;
}
endSpan(onProgress, "resolveSource", "ok");
startSpan(onProgress, "emit", "Emitting contract...");
let emitResult;
try {
const familyInstance = config.family.create(stack);
const rawComponents = [
config.target,
config.adapter,
...config.extensionPacks ?? []
];
const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, rawComponents);
const enrichedIR = enrichContract(validatedContract.value, frameworkComponents);
const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);
const deserializedContract = familyInstance.deserializeContract(rawContractJson);
const { contractSerializer } = config.target;
const serializeContract = (c) => contractSerializer.serializeContract(c);
emitResult = await unlessAborted(emit(deserializedContract, stack, config.family.emission, {
outputJsonPath,
serializeContract,
...ifDefined("shouldPreserveEmpty", contractSerializer.shouldPreserveEmpty),
...ifDefined("sortStorage", contractSerializer.sortStorage)
}));
} catch (error) {
endSpan(onProgress, "emit", "error");
throw error;
}
endSpan(onProgress, "emit", "ok");
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
await publishContractArtifactPair({
outputJsonPath,
outputDtsPath,
contractJson: emitResult.contractJson,
contractDts: emitResult.contractDts,
publicationToken: String(process.hrtime.bigint())
});
const validationWarning = validateContractDeps(emitResult.contractDts, dirname(outputDtsPath)).warning;
return {
storageHash: emitResult.storageHash,
...ifDefined("executionHash", emitResult.executionHash),
profileHash: emitResult.profileHash,
files: {
json: outputJsonPath,
dts: outputDtsPath
},
...ifDefined("validationWarning", validationWarning)
};
});
}
//#endregion
export { executeContractEmit as n, disposeEmitQueue as r, contract_emit_exports as t };
//# sourceMappingURL=contract-emit-iSMJVs26.mjs.map
{"version":3,"file":"contract-emit-iSMJVs26.mjs","names":["isRecord"],"sources":["../src/utils/emit-queue.ts","../src/utils/publish-contract-artifact-pair.ts","../src/utils/validate-contract-deps.ts","../src/control-api/operations/contract-emit.ts"],"sourcesContent":["/**\n * Per-output FIFO queue for `executeContractEmit`.\n *\n * Ensures that at most one emit (load → resolve source → emit bytes → publish)\n * runs per output JSON path at a time. Concurrent calls for the same path\n * line up behind the in-flight one and run in submission order; the user-visible\n * outcome is \"last submission wins on disk\" without any supersession bookkeeping.\n *\n * Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`\n * when they stop publishing to a path, otherwise the module-global `Map`\n * accumulates one entry per unique output path for the lifetime of the process.\n */\nconst emitQueues = new Map<string, Promise<unknown>>();\n\nexport function queueEmitByOutput<T>(outputJsonPath: string, action: () => Promise<T>): Promise<T> {\n const previous = emitQueues.get(outputJsonPath) ?? Promise.resolve();\n // Continue regardless of the previous task's outcome — a failed emit must not\n // block subsequent ones. The current task's outcome propagates via `next`.\n const next = previous.then(action, action);\n emitQueues.set(outputJsonPath, next);\n return next;\n}\n\nexport function disposeEmitQueue(outputJsonPath: string): void {\n emitQueues.delete(outputJsonPath);\n}\n","import { readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'pathe';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction createTempArtifactPath(path: string, publicationToken: string, phase: string): string {\n return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);\n}\n\ntype PreviousArtifact = { readonly content: string } | 'remove';\n\nasync function readExistingArtifact(path: string): Promise<PreviousArtifact> {\n try {\n return { content: await readFile(path, 'utf-8') };\n } catch (error) {\n if (isRecord(error) && error['code'] === 'ENOENT') {\n return 'remove';\n }\n throw error;\n }\n}\n\nasync function restoreArtifact(\n path: string,\n previous: PreviousArtifact,\n publicationToken: string,\n): Promise<void> {\n if (previous === 'remove') {\n await rm(path, { force: true });\n return;\n }\n\n const restorePath = createTempArtifactPath(path, publicationToken, 'rollback');\n await writeFile(restorePath, previous.content, 'utf-8');\n try {\n await rename(restorePath, path);\n } finally {\n await rm(restorePath, { force: true });\n }\n}\n\ninterface PublishEntry {\n readonly tempPath: string;\n readonly outputPath: string;\n readonly previous: PreviousArtifact;\n}\n\nfunction withRollbackFailureCause(error: unknown, rollbackFailures: readonly unknown[]): Error {\n const rollbackCause = new AggregateError(\n rollbackFailures,\n 'Failed to restore published artifacts',\n );\n\n if (error instanceof Error) {\n Object.defineProperty(error, 'cause', {\n value: rollbackCause,\n configurable: true,\n writable: true,\n });\n return error;\n }\n\n return new Error(String(error), { cause: rollbackCause });\n}\n\nasync function publishPairWithRollback(\n entries: readonly PublishEntry[],\n publicationToken: string,\n): Promise<void> {\n const replaced: PublishEntry[] = [];\n try {\n for (const entry of entries) {\n await rename(entry.tempPath, entry.outputPath);\n replaced.push(entry);\n }\n } catch (error) {\n const rollbackResults = await Promise.allSettled(\n replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)),\n );\n const rollbackFailures = rollbackResults.flatMap((result) =>\n result.status === 'rejected' ? [result.reason] : [],\n );\n\n if (rollbackFailures.length > 0) {\n throw withRollbackFailureCause(error, rollbackFailures);\n }\n\n throw error;\n }\n}\n\nexport async function publishContractArtifactPair({\n outputJsonPath,\n outputDtsPath,\n contractJson,\n contractDts,\n publicationToken,\n beforePublish,\n}: {\n readonly outputJsonPath: string;\n readonly outputDtsPath: string;\n readonly contractJson: string;\n readonly contractDts: string;\n readonly publicationToken: string;\n readonly beforePublish?: () => Promise<boolean> | boolean;\n}): Promise<boolean> {\n const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, 'next');\n const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, 'next');\n\n try {\n await writeFile(tempJsonPath, contractJson, 'utf-8');\n await writeFile(tempDtsPath, contractDts, 'utf-8');\n\n if ((await beforePublish?.()) === false) {\n return false;\n }\n\n const previousJson = await readExistingArtifact(outputJsonPath);\n const previousDts = await readExistingArtifact(outputDtsPath);\n\n await publishPairWithRollback(\n [\n { tempPath: tempDtsPath, outputPath: outputDtsPath, previous: previousDts },\n { tempPath: tempJsonPath, outputPath: outputJsonPath, previous: previousJson },\n ],\n publicationToken,\n );\n return true;\n } finally {\n await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);\n }\n}\n","import { createRequire } from 'node:module';\n\nconst IMPORT_PATTERN = /import\\s+type\\s+.*?\\s+from\\s+['\"](@[^/]+\\/[^/'\"]+)/g;\n\nexport function extractPackageSpecifiers(dtsContent: string): string[] {\n const packages = new Set<string>();\n for (const match of dtsContent.matchAll(IMPORT_PATTERN)) {\n const pkg = match[1];\n if (pkg) packages.add(pkg);\n }\n return [...packages];\n}\n\nexport interface ContractDepsValidation {\n readonly missing: readonly string[];\n readonly warning?: string;\n}\n\nexport function validateContractDeps(\n dtsContent: string,\n projectRoot: string,\n): ContractDepsValidation {\n const packages = extractPackageSpecifiers(dtsContent);\n const resolve = createRequire(`${projectRoot}/package.json`);\n\n const missing: string[] = [];\n for (const pkg of packages) {\n try {\n resolve.resolve(`${pkg}/package.json`);\n } catch {\n missing.push(pkg);\n }\n }\n\n if (missing.length === 0) {\n return { missing };\n }\n\n const list = missing.map((p) => ` - ${p}`).join('\\n');\n const warning = [\n 'contract.d.ts imports types from packages that are not installed:',\n list,\n '',\n 'Install them with your package manager:',\n ...missing.map((p) => ` ${p}`),\n ].join('\\n');\n\n return { missing, warning };\n}\n","import { mkdir } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { abortable } from '@prisma-next/utils/abortable';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport { dirname, join } from 'pathe';\nimport { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';\nimport { queueEmitByOutput } from '../../utils/emit-queue';\nimport { assertFrameworkComponentsCompatible } from '../../utils/framework-components';\nimport { publishContractArtifactPair } from '../../utils/publish-contract-artifact-pair';\nimport { validateContractDeps } from '../../utils/validate-contract-deps';\nimport { enrichContract } from '../contract-enrichment';\nimport type {\n ContractEmitOptions,\n ContractEmitResult,\n ControlActionName,\n OnControlProgress,\n} from '../types';\n\nconst EMIT_ACTION: ControlActionName = 'emit';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction startSpan(onProgress: OnControlProgress | undefined, spanId: string, label: string): void {\n onProgress?.({ action: EMIT_ACTION, kind: 'spanStart', spanId, label });\n}\n\nfunction endSpan(\n onProgress: OnControlProgress | undefined,\n spanId: string,\n outcome: 'ok' | 'error',\n): void {\n onProgress?.({ action: EMIT_ACTION, kind: 'spanEnd', spanId, outcome });\n}\n\nfunction failedToResolveContractSource(why: string, fix: string, meta?: Record<string, unknown>) {\n return errorRuntime('Failed to resolve contract source', {\n why,\n fix,\n ...ifDefined('meta', meta),\n });\n}\n\ntype ValidatedProviderResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: ReturnType<typeof errorRuntime> };\n\nfunction diagnosticLocationSuffix(diagnostic: Record<string, unknown>): string {\n const sourceId = typeof diagnostic['sourceId'] === 'string' ? diagnostic['sourceId'] : undefined;\n const span = isRecord(diagnostic['span']) ? diagnostic['span'] : undefined;\n const start = span && isRecord(span['start']) ? span['start'] : undefined;\n const line = start && typeof start['line'] === 'number' ? start['line'] : undefined;\n const column = start && typeof start['column'] === 'number' ? start['column'] : undefined;\n if (sourceId && line !== undefined && column !== undefined) {\n return ` (${sourceId}:${line}:${column})`;\n }\n if (sourceId) {\n return ` (${sourceId})`;\n }\n return '';\n}\n\nfunction mapDiagnosticsToIssues(\n diagnostics: readonly unknown[],\n): ReadonlyArray<{ readonly kind: string; readonly message: string }> {\n const issues: { readonly kind: string; readonly message: string }[] = [];\n for (const raw of diagnostics) {\n if (!isRecord(raw)) continue;\n const code = typeof raw['code'] === 'string' ? raw['code'] : 'diagnostic';\n const message = typeof raw['message'] === 'string' ? raw['message'] : '';\n issues.push({ kind: code, message: `${message}${diagnosticLocationSuffix(raw)}` });\n }\n return issues;\n}\n\nfunction validateProviderResult(providerResult: unknown): ValidatedProviderResult {\n if (!isRecord(providerResult) || typeof providerResult['ok'] !== 'boolean') {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed result shape.',\n 'Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).',\n ),\n };\n }\n\n if (providerResult['ok']) {\n if (!('value' in providerResult)) {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed success result: missing value.',\n 'Ensure contract.source.load success payload is ok(Contract).',\n ),\n };\n }\n return { ok: true, value: providerResult['value'] };\n }\n\n const failure = providerResult['failure'];\n if (\n !isRecord(failure) ||\n typeof failure['summary'] !== 'string' ||\n !Array.isArray(failure['diagnostics'])\n ) {\n return {\n ok: false,\n error: failedToResolveContractSource(\n 'Contract source provider returned malformed failure result: expected summary and diagnostics.',\n 'Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).',\n ),\n };\n }\n return {\n ok: false,\n error: failedToResolveContractSource(\n String(failure['summary']),\n 'Fix contract source diagnostics and return ok(Contract).',\n {\n diagnostics: failure['diagnostics'],\n issues: mapDiagnosticsToIssues(failure['diagnostics']),\n ...ifDefined('providerMeta', failure['meta']),\n },\n ),\n };\n}\n\n/**\n * Canonical contract emit operation.\n *\n * This is the SINGLE publication path used by both the CLI command\n * (`prisma-next contract emit`) and the Vite plugin\n * (`@prisma-next/vite-plugin-contract-emit`). New callers must go through this\n * function rather than re-implementing load → emit → publish.\n *\n * The whole flow (load config → resolve source → emit bytes → atomic publish)\n * is serialized per output JSON path via `queueEmitByOutput`. Concurrent calls\n * for the same output line up FIFO; the user-visible outcome is \"last\n * submission wins on disk\" without any supersession bookkeeping. Within a\n * single emit, `publishContractArtifactPair` stages temp files, renames\n * `contract.d.ts` before `contract.json`, and attempts to restore the previous\n * pair if either rename fails — so type-only consumers never observe a\n * mismatched pair.\n *\n * @throws {CliStructuredError} on config/source/validation problems\n * @throws {DOMException} `AbortError` if cancelled via `signal`\n */\nexport async function executeContractEmit(\n options: ContractEmitOptions,\n): Promise<ContractEmitResult> {\n const { configPath, outputPath, signal = new AbortController().signal, onProgress } = options;\n const unlessAborted = abortable(signal);\n\n const config = await unlessAborted(loadConfig(configPath));\n\n if (!config.contract) {\n throw errorContractConfigMissing({\n why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n });\n }\n\n const contractConfig = config.contract;\n\n const effectiveOutput =\n outputPath !== undefined ? join(outputPath, 'contract.json') : contractConfig.output;\n\n if (!effectiveOutput) {\n throw errorContractConfigMissing({\n why: 'Contract config must have output path. This should not happen if defineConfig() was used.',\n });\n }\n\n if (typeof contractConfig.source?.load !== 'function') {\n throw errorContractConfigMissing({\n why: 'Contract config must include a valid source provider object',\n });\n }\n\n let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;\n try {\n outputPaths = getEmittedArtifactPaths(effectiveOutput);\n } catch (error) {\n throw errorContractConfigMissing({\n why: error instanceof Error ? error.message : String(error),\n });\n }\n const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;\n\n return queueEmitByOutput(outputJsonPath, async () => {\n const stack = createControlStack(config);\n\n const sourceContext = {\n composedExtensionPacks: stack.extensionPacks.map((p) => p.id),\n composedExtensionContracts: stack.extensionContracts,\n scalarTypeDescriptors: stack.scalarTypeDescriptors,\n authoringContributions: stack.authoringContributions,\n codecLookup: stack.codecLookup,\n controlMutationDefaults: stack.controlMutationDefaults,\n resolvedInputs: contractConfig.source.inputs ?? [],\n capabilities: stack.capabilities,\n };\n\n startSpan(onProgress, 'resolveSource', 'Resolving contract source...');\n let providerResult: Awaited<ReturnType<typeof contractConfig.source.load>>;\n try {\n providerResult = await unlessAborted(contractConfig.source.load(sourceContext));\n } catch (error) {\n endSpan(onProgress, 'resolveSource', 'error');\n if (signal.aborted || (isRecord(error) && error['name'] === 'AbortError')) {\n throw error;\n }\n throw failedToResolveContractSource(\n error instanceof Error ? error.message : String(error),\n 'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',\n );\n }\n\n const validatedContract = validateProviderResult(providerResult);\n if (!validatedContract.ok) {\n endSpan(onProgress, 'resolveSource', 'error');\n throw validatedContract.error;\n }\n endSpan(onProgress, 'resolveSource', 'ok');\n\n startSpan(onProgress, 'emit', 'Emitting contract...');\n let emitResult: Awaited<ReturnType<typeof emit>>;\n try {\n const familyInstance = config.family.create(stack);\n const rawComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n rawComponents,\n );\n // Blind cast: `validateProviderResult` upstream has already\n // pinned `validatedContract.value` to the provider's loose\n // `Contract` envelope, but the local `Contract` type at this\n // call site is the precise structural interface. The cast just\n // defers the structural check by one statement so `enrichContract`\n // can decorate first; the subsequent serialize→deserialize round-trip\n // re-narrows the envelope into the precise type.\n const enrichedIR = enrichContract(\n validatedContract.value as unknown as Contract,\n frameworkComponents,\n );\n const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);\n const deserializedContract = familyInstance.deserializeContract(rawContractJson);\n // Each target's descriptor ships a `contractSerializer` SPI; the\n // framework canonicalizer threads its `serializeContract` so the\n // on-disk JSON envelope is constructed by target-owned code\n // rather than by walking the in-memory contract with\n // `Object.entries` (which would leak runtime-only class API\n // fields into the persisted shape). The optional `shouldPreserveEmpty`\n // and `sortStorage` hooks let the family contribute storage-specific\n // canonicalization rules without the framework importing family code.\n const { contractSerializer } = config.target;\n const serializeContract = (c: Contract): JsonObject =>\n contractSerializer.serializeContract(c);\n emitResult = await unlessAborted(\n emit(deserializedContract, stack, config.family.emission, {\n outputJsonPath,\n serializeContract,\n ...ifDefined('shouldPreserveEmpty', contractSerializer.shouldPreserveEmpty),\n ...ifDefined('sortStorage', contractSerializer.sortStorage),\n }),\n );\n } catch (error) {\n endSpan(onProgress, 'emit', 'error');\n throw error;\n }\n endSpan(onProgress, 'emit', 'ok');\n\n await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));\n await publishContractArtifactPair({\n outputJsonPath,\n outputDtsPath,\n contractJson: emitResult.contractJson,\n contractDts: emitResult.contractDts,\n publicationToken: String(process.hrtime.bigint()),\n });\n\n const validationWarning = validateContractDeps(\n emitResult.contractDts,\n dirname(outputDtsPath),\n ).warning;\n\n return {\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n files: {\n json: outputJsonPath,\n dts: outputDtsPath,\n },\n ...ifDefined('validationWarning', validationWarning),\n };\n });\n}\n"],"mappings":"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"}
import { _ as createTerminalUI, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as inspectLiveSchema } from "./inspect-live-schema-DxsD_a4d.mjs";
import { Command } from "commander";
import { notOk, ok } from "@prisma-next/utils/result";
import { dirname, relative, resolve } from "pathe";
import { errorRuntime } from "@prisma-next/errors/execution";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { printPsl } from "@prisma-next/psl-printer";
//#region src/commands/contract-infer-paths.ts
/**
* Resolves the output path for the inferred PSL contract.
*
* Priority:
* 1. --output <path> flag (resolved relative to cwd)
* 2. contract.prisma next to config.contract.output
* 3. Canonical default: contract.prisma in the config directory
*/
function resolveContractInferOutputPath(options, contractOutput) {
const configDir = options.config ? dirname(resolve(process.cwd(), options.config)) : process.cwd();
if (options.output) return resolve(process.cwd(), options.output);
if (contractOutput) return resolve(dirname(resolve(configDir, contractOutput)), "contract.prisma");
return resolve(configDir, "contract.prisma");
}
//#endregion
//#region src/commands/contract-infer.ts
async function executeContractInferCommand(options, flags, ui, startTime) {
const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {
commandName: "contract infer",
description: "Infer a PSL contract from the live database schema",
url: "https://pris.ly/contract-infer"
});
if (!inspectResult.ok) return inspectResult;
const { config, target, meta, pslContractAst, pslBlockDescriptors } = inspectResult.value;
if (!pslContractAst) return notOk(errorRuntime("contract infer is not supported for this family", {
why: "The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.",
fix: "Use a family that supports contract inference (e.g. SQL/Postgres)."
}));
const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
const pslContent = printPsl(pslContractAst, { pslBlockDescriptors });
if (existsSync(outputPath) && !flags.json && !flags.quiet) ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, pslContent, "utf-8");
const pslPath = relative(process.cwd(), outputPath);
if (!flags.json && !flags.quiet) ui.stderr(`\u2714 Contract written to ${pslPath}`);
return ok({
ok: true,
summary: "Contract inferred successfully",
target,
psl: { path: pslPath },
meta,
timings: { total: Date.now() - startTime }
});
}
function createContractInferCommand() {
const command = new Command("infer");
setCommandDescriptions(command, "Infer a PSL contract from the live database schema", "Reads the live database schema and writes an inferred PSL contract to disk.\nThis command stops at `contract.prisma`; follow it with `contract emit` and\n`db sign` as separate steps.");
setCommandExamples(command, [
"prisma-next contract infer --db $DATABASE_URL",
"prisma-next contract infer --db $DATABASE_URL --output ./src/prisma/contract.prisma",
"prisma-next contract infer --db $DATABASE_URL --json"
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--output <path>", "Write the inferred PSL contract to the specified path").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeContractInferCommand(options, flags, ui, Date.now()), flags, ui, (value) => {
if (flags.json) ui.output(JSON.stringify(value, null, 2));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createContractInferCommand as t };
//# sourceMappingURL=contract-infer-BVqworBB.mjs.map
{"version":3,"file":"contract-infer-BVqworBB.mjs","names":[],"sources":["../src/commands/contract-infer-paths.ts","../src/commands/contract-infer.ts"],"sourcesContent":["import { dirname, resolve } from 'pathe';\n\ninterface ContractInferPathOptions {\n readonly output?: string;\n readonly config?: string;\n}\n\n/**\n * Resolves the output path for the inferred PSL contract.\n *\n * Priority:\n * 1. --output <path> flag (resolved relative to cwd)\n * 2. contract.prisma next to config.contract.output\n * 3. Canonical default: contract.prisma in the config directory\n */\nexport function resolveContractInferOutputPath(\n options: ContractInferPathOptions,\n contractOutput: string | undefined,\n): string {\n const configDir = options.config\n ? dirname(resolve(process.cwd(), options.config))\n : process.cwd();\n\n if (options.output) {\n return resolve(process.cwd(), options.output);\n }\n if (contractOutput) {\n const contractPath = resolve(configDir, contractOutput);\n return resolve(dirname(contractPath), 'contract.prisma');\n }\n return resolve(configDir, 'contract.prisma');\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { errorRuntime } from '@prisma-next/errors/execution';\nimport { printPsl } from '@prisma-next/psl-printer';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { dirname, relative } from 'pathe';\nimport type { CliStructuredError } from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport { resolveContractInferOutputPath } from './contract-infer-paths';\nimport {\n type InspectLiveSchemaOptions,\n type InspectLiveSchemaResult,\n inspectLiveSchema,\n} from './inspect-live-schema';\n\ninterface ContractInferOptions extends InspectLiveSchemaOptions {\n readonly output?: string;\n}\n\ninterface ContractInferSuccessResult {\n readonly ok: true;\n readonly summary: string;\n readonly target: InspectLiveSchemaResult['target'];\n readonly psl: {\n readonly path: string;\n };\n readonly meta: InspectLiveSchemaResult['meta'];\n readonly timings: {\n readonly total: number;\n };\n}\n\nasync function executeContractInferCommand(\n options: ContractInferOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<ContractInferSuccessResult, CliStructuredError>> {\n const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {\n commandName: 'contract infer',\n description: 'Infer a PSL contract from the live database schema',\n url: 'https://pris.ly/contract-infer',\n });\n\n if (!inspectResult.ok) {\n return inspectResult;\n }\n\n const { config, target, meta, pslContractAst, pslBlockDescriptors } = inspectResult.value;\n\n if (!pslContractAst) {\n return notOk(\n errorRuntime('contract infer is not supported for this family', {\n why: 'The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.',\n fix: 'Use a family that supports contract inference (e.g. SQL/Postgres).',\n }),\n );\n }\n\n const outputPath = resolveContractInferOutputPath(options, config.contract?.output);\n const pslContent = printPsl(pslContractAst, { pslBlockDescriptors: pslBlockDescriptors });\n\n if (existsSync(outputPath) && !flags.json && !flags.quiet) {\n ui.stderr(`\\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);\n }\n\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, pslContent, 'utf-8');\n\n const pslPath = relative(process.cwd(), outputPath);\n if (!flags.json && !flags.quiet) {\n ui.stderr(`\\u2714 Contract written to ${pslPath}`);\n }\n\n return ok({\n ok: true,\n summary: 'Contract inferred successfully',\n target,\n psl: {\n path: pslPath,\n },\n meta,\n timings: {\n total: Date.now() - startTime,\n },\n });\n}\n\nexport function createContractInferCommand(): Command {\n const command = new Command('infer');\n setCommandDescriptions(\n command,\n 'Infer a PSL contract from the live database schema',\n 'Reads the live database schema and writes an inferred PSL contract to disk.\\n' +\n 'This command stops at `contract.prisma`; follow it with `contract emit` and\\n' +\n '`db sign` as separate steps.',\n );\n setCommandExamples(command, [\n 'prisma-next contract infer --db $DATABASE_URL',\n 'prisma-next contract infer --db $DATABASE_URL --output ./src/prisma/contract.prisma',\n 'prisma-next contract infer --db $DATABASE_URL --json',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--output <path>', 'Write the inferred PSL contract to the specified path')\n .action(async (options: ContractInferOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const startTime = Date.now();\n\n const result = await executeContractInferCommand(options, flags, ui, startTime);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value, null, 2));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,SAAgB,+BACd,SACA,gBACQ;CACR,MAAM,YAAY,QAAQ,SACtB,QAAQ,QAAQ,QAAQ,IAAI,GAAG,QAAQ,MAAM,CAAC,IAC9C,QAAQ,IAAI;CAEhB,IAAI,QAAQ,QACV,OAAO,QAAQ,QAAQ,IAAI,GAAG,QAAQ,MAAM;CAE9C,IAAI,gBAEF,OAAO,QAAQ,QADM,QAAQ,WAAW,cACN,CAAC,GAAG,iBAAiB;CAEzD,OAAO,QAAQ,WAAW,iBAAiB;AAC7C;;;ACQA,eAAe,4BACb,SACA,OACA,IACA,WACiE;CACjE,MAAM,gBAAgB,MAAM,kBAAkB,SAAS,OAAO,IAAI,WAAW;EAC3E,aAAa;EACb,aAAa;EACb,KAAK;CACP,CAAC;CAED,IAAI,CAAC,cAAc,IACjB,OAAO;CAGT,MAAM,EAAE,QAAQ,QAAQ,MAAM,gBAAgB,wBAAwB,cAAc;CAEpF,IAAI,CAAC,gBACH,OAAO,MACL,aAAa,mDAAmD;EAC9D,KAAK;EACL,KAAK;CACP,CAAC,CACH;CAGF,MAAM,aAAa,+BAA+B,SAAS,OAAO,UAAU,MAAM;CAClF,MAAM,aAAa,SAAS,gBAAgB,EAAuB,oBAAoB,CAAC;CAExF,IAAI,WAAW,UAAU,KAAK,CAAC,MAAM,QAAQ,CAAC,MAAM,OAClD,GAAG,OAAO,qCAAqC,SAAS,QAAQ,IAAI,GAAG,UAAU,GAAG;CAGtF,UAAU,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;CAClD,cAAc,YAAY,YAAY,OAAO;CAE7C,MAAM,UAAU,SAAS,QAAQ,IAAI,GAAG,UAAU;CAClD,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OACxB,GAAG,OAAO,8BAA8B,SAAS;CAGnD,OAAO,GAAG;EACR,IAAI;EACJ,SAAS;EACT;EACA,KAAK,EACH,MAAM,QACR;EACA;EACA,SAAS,EACP,OAAO,KAAK,IAAI,IAAI,UACtB;CACF,CAAC;AACH;AAEA,SAAgB,6BAAsC;CACpD,MAAM,UAAU,IAAI,QAAQ,OAAO;CACnC,uBACE,SACA,sDACA,wLAGF;CACA,mBAAmB,SAAS;EAC1B;EACA;EACA;CACF,CAAC;CACD,iBAAiB,OAAO,CAAC,CACtB,OAAO,cAAc,4BAA4B,CAAC,CAClD,OAAO,mBAAmB,+BAA+B,CAAC,CAC1D,OAAO,mBAAmB,uDAAuD,CAAC,CAClF,OAAO,OAAO,YAAkC;EAC/C,MAAM,QAAQ,uBAAuB,OAAO;EAC5C,MAAM,KAAK,iBAAiB,KAAK;EAIjC,MAAM,WAAW,aAAa,MADT,4BAA4B,SAAS,OAAO,IAF/C,KAAK,IAEsD,CAAC,GACxC,OAAO,KAAK,UAAU;GAC1D,IAAI,MAAM,MACR,GAAG,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;EAE5C,CAAC;EAED,QAAQ,KAAK,QAAQ;CACvB,CAAC;CAEH,OAAO;AACT"}
import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, G as errorHashMismatch, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, Y as errorMarkerMissing, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, o as resolveContractPath, rt as errorRuntime, s as resolveMigrationPaths, st as errorTargetMismatch, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { o as ContractValidationError, t as createControlClient } from "./client-2kwLMBSf.mjs";
import { c as formatVerifyOutput, i as formatSchemaVerifyOutput, r as formatSchemaVerifyJson, s as formatVerifyJson } from "./verify-C4RazDE1.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { relative, resolve } from "pathe";
import { readFile } from "node:fs/promises";
import { VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, createControlStack } from "@prisma-next/framework-components/control";
//#region src/utils/combine-verify-results.ts
/**
* Collapse the aggregate verifier's per-space contract-satisfaction results
* into a single {@link VerifyDatabaseSchemaResult} for the CLI display
* surface, and carry the deduplicated unclaimed-elements list alongside it.
* Concatenates the issue lists across spaces; uses the app space's result as
* the structural envelope (storage hash, target). Extras are already
* stripped from each per-space result, so nothing here duplicates an
* unclaimed element per space.
*
* **Unclaimed disposition.** In strict mode a non-empty `unclaimed` list fails
* the combined verdict (`ok: false`); in lenient mode it is carried for
* informational rendering only. The list itself is returned unchanged for the
* renderer.
*
* **Summary policy.** Preserve the per-family phrasing whenever the
* combined `ok` flag agrees with the app space's `ok` flag — this is
* the common case (single-family deployments, single-app deployments)
* and the family's "satisfies / does not satisfy contract" phrasing
* stays user-visible. When the app passes but an extension fails (or
* vice versa) the app's summary contradicts the envelope, so fall back
* to the first failing space's summary. This keeps family phrasing
* intact and the envelope internally consistent (`ok: false` ↔ failure
* summary).
*/
function combineVerifyResults(perSpace, appSpaceId, strict, unclaimed) {
const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;
if (appResult === void 0) throw new Error("Aggregate verifier returned no per-space verify results — this is a wiring bug.");
let okAll = true;
let firstFailure;
let issues = [];
let warningIssues = [];
for (const [, result] of perSpace) {
if (!result.ok) {
okAll = false;
if (firstFailure === void 0) firstFailure = result;
}
issues = [...issues, ...result.schema.issues];
warningIssues = [...warningIssues, ...result.schema.warnings?.issues ?? []];
}
const unclaimedFails = strict && unclaimed.length > 0;
const ok = okAll && !unclaimedFails;
const summary = okAll ? unclaimedFails ? `Database schema has ${unclaimed.length} unclaimed element${unclaimed.length === 1 ? "" : "s"} (not in any contract)` : appResult.summary : appResult.ok ? firstFailure?.summary ?? appResult.summary : appResult.summary;
return {
result: {
ok,
...ok ? {} : { code: appResult.code ?? "CONTRACT.MARKER_REQUIRED" },
summary,
contract: appResult.contract,
target: appResult.target,
schema: {
issues,
warnings: { issues: warningIssues }
},
meta: { strict },
timings: { total: 0 }
},
unclaimed
};
}
//#endregion
//#region src/commands/db-verify.ts
/**
* Maps a VerifyDatabaseResult failure to a CliStructuredError.
*/
function mapVerifyFailure(verifyResult) {
if (!verifyResult.ok && verifyResult.code) {
if (verifyResult.code === VERIFY_CODE_MARKER_MISSING) return errorMarkerMissing();
if (verifyResult.code === VERIFY_CODE_HASH_MISMATCH) {
const storageMatch = verifyResult.marker?.storageHash === verifyResult.contract.storageHash;
const profileMatch = !verifyResult.contract.profileHash || verifyResult.marker?.profileHash === verifyResult.contract.profileHash;
if (!storageMatch) return errorHashMismatch({
why: "Contract storageHash does not match database marker",
expected: verifyResult.contract.storageHash,
...ifDefined("actual", verifyResult.marker?.storageHash)
});
return errorHashMismatch({
why: profileMatch ? "Contract hash does not match database marker" : "Contract profileHash does not match database marker",
...ifDefined("expected", verifyResult.contract.profileHash),
...ifDefined("actual", verifyResult.marker?.profileHash)
});
}
if (verifyResult.code === VERIFY_CODE_TARGET_MISMATCH) return errorTargetMismatch(verifyResult.target.expected, verifyResult.target.actual ?? "unknown");
}
return errorRuntime(verifyResult.summary);
}
function errorInvalidVerifyMode(options) {
return new CliStructuredError("CLI.INVALID_VERIFY_MODE", "Invalid verify mode", {
why: options.why,
fix: options.fix,
docsUrl: "https://pris.ly/db-verify"
});
}
function resolveDbVerifyMode(options) {
if (options.markerOnly && options.schemaOnly) return notOk(errorInvalidVerifyMode({
why: "`--marker-only` and `--schema-only` cannot be used together",
fix: "Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema."
}));
if (options.markerOnly && options.strict) return notOk(errorInvalidVerifyMode({
why: "`--strict` requires schema verification, but `--marker-only` skips it",
fix: "Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode."
}));
if (options.schemaOnly) return ok("schema-only");
if (options.markerOnly) return ok("marker-only");
return ok("full");
}
function formatDbVerifyModeLabel(mode, strict) {
if (mode === "marker-only") return "marker only";
if (mode === "schema-only") return `schema only (${strict ? "strict" : "tolerant"})`;
return `full (marker + schema, ${strict ? "strict" : "tolerant"})`;
}
function formatDbVerifyInvocation(mode, strict) {
const args = ["db verify"];
if (mode === "marker-only") args.push("--marker-only");
if (mode === "schema-only") args.push("--schema-only");
if (strict) args.push("--strict");
return args.join(" ");
}
function createDbVerifyConnectionRequiredError(options) {
const invocation = formatDbVerifyInvocation(options.mode, options.strict);
return errorDatabaseConnectionRequired({
why: `Database connection is required for ${invocation} (set db.connection in ${options.configPath}, or pass --db <url>)`,
retryCommand: `prisma-next ${invocation} --db <url>`
});
}
function renderVerifyHeader(paths, options, mode, flags, ui) {
if (flags.json || flags.quiet) return;
const description = mode === "schema-only" ? "Check whether the live database schema matches your contract" : mode === "marker-only" ? "Check whether the database marker matches your contract" : "Check whether the database marker and live schema match your contract";
const details = [
{
label: "config",
value: paths.configPath
},
{
label: "contract",
value: paths.contractPath
},
{
label: "mode",
value: formatDbVerifyModeLabel(mode, options.strict ?? false)
}
];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
ui.stderr(formatStyledHeader({
command: "db verify",
description,
url: "https://pris.ly/db-verify",
details,
flags
}));
}
async function resolveVerifyPaths(options) {
const config = await loadConfig(options.config);
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
const contractPathAbsolute = resolveContractPath(config);
return {
config,
configPath,
contractPathAbsolute,
contractPath: relative(process.cwd(), contractPathAbsolute)
};
}
async function resolveVerifySetup(paths, options, mode) {
const { config, configPath, contractPathAbsolute, contractPath } = paths;
let contractJsonContent;
try {
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
} catch (error) {
if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
why: `Contract file not found at ${contractPathAbsolute}`,
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
}
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
let contractJson;
try {
contractJson = familyInstance.deserializeContract(JSON.parse(contractJsonContent));
} catch (error) {
if (error instanceof ContractValidationError) return notOk(errorContractValidationFailed(`Contract validation failed: ${error.message}`, { where: { path: contractPathAbsolute } }));
return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const dbConnection = options.db ?? config.db?.connection;
if (typeof dbConnection !== "string" || dbConnection.length === 0) return notOk(createDbVerifyConnectionRequiredError({
configPath,
mode,
strict: options.strict ?? false
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${formatDbVerifyInvocation(mode, options.strict ?? false)}` }));
return ok({
...paths,
contractJson,
dbConnection
});
}
function createVerifyClient(setup) {
return createControlClient({
family: setup.config.family,
target: setup.config.target,
adapter: setup.config.adapter,
driver: setup.config.driver,
extensionPacks: setup.config.extensionPacks ?? []
});
}
function wrapVerifyError(error, contractPathAbsolute, modeLabel) {
if (CliStructuredError.is(error)) return notOk(error);
if (error instanceof ContractValidationError) return notOk(errorContractValidationFailed(`Contract validation failed: ${error.message}`, { where: { path: contractPathAbsolute } }));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during ${modeLabel}: ${error instanceof Error ? error.message : String(error)}` }));
}
/**
* Executes the db verify command and returns a structured Result.
*/
async function executeDbVerifyCommand(options, flags, ui, mode) {
const startTime = Date.now();
const paths = await resolveVerifyPaths(options);
renderVerifyHeader(paths, options, mode, flags, ui);
const setupResult = await resolveVerifySetup(paths, options, mode);
if (!setupResult.ok) return setupResult;
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
const client = createVerifyClient(setupResult.value);
const onProgress = createProgressAdapter({
ui,
flags
});
try {
const verifyResult = await client.verify({
contract: contractJson,
connection: dbConnection,
onProgress
});
if (!verifyResult.ok) return notOk(mapVerifyFailure(verifyResult));
const aggregateResult = await client.dbVerify({
contract: contractJson,
migrationsDir,
strict: options.strict ?? false,
skipSchema: mode === "marker-only",
skipMarker: false,
onProgress
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
if (mode === "marker-only") return ok({
ok: true,
mode: "marker-only",
summary: "Database marker matches contract",
contract: verifyResult.contract,
marker: verifyResult.marker,
target: verifyResult.target,
...ifDefined("missingCodecs", verifyResult.missingCodecs),
...ifDefined("codecCoverageSkipped", verifyResult.codecCoverageSkipped),
warning: "Schema verification skipped because --marker-only was provided",
meta: {
...verifyResult.meta ?? {},
schemaVerification: "skipped"
},
timings: { total: Date.now() - startTime }
});
const combined = combineVerifyResults(aggregateResult.value.schemaResults, aggregateResult.value.appSpaceId, options.strict ?? false, aggregateResult.value.unclaimed);
if (!combined.result.ok) return notOk(combined);
return ok({
ok: true,
mode: "full",
summary: "Database marker and schema match contract",
contract: verifyResult.contract,
marker: verifyResult.marker,
target: verifyResult.target,
...ifDefined("missingCodecs", verifyResult.missingCodecs),
...ifDefined("codecCoverageSkipped", verifyResult.codecCoverageSkipped),
schema: {
summary: combined.result.summary,
strict: combined.result.meta?.strict ?? false,
warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) => issue.path.join("/"))
},
unclaimed: combined.unclaimed,
meta: {
...verifyResult.meta ?? {},
schemaVerification: "performed"
},
timings: { total: Date.now() - startTime }
});
} catch (error) {
return wrapVerifyError(error, contractPathAbsolute, "db verify");
} finally {
await client.close();
}
}
async function executeDbSchemaOnlyVerifyCommand(options, flags, ui) {
const paths = await resolveVerifyPaths(options);
renderVerifyHeader(paths, options, "schema-only", flags, ui);
const setupResult = await resolveVerifySetup(paths, options, "schema-only");
if (!setupResult.ok) return setupResult;
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
const client = createVerifyClient(setupResult.value);
const onProgress = createProgressAdapter({
ui,
flags
});
try {
await client.connect(dbConnection);
const aggregateResult = await client.dbVerify({
contract: contractJson,
migrationsDir,
strict: options.strict ?? false,
skipSchema: false,
skipMarker: true,
onProgress
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
return ok(combineVerifyResults(aggregateResult.value.schemaResults, aggregateResult.value.appSpaceId, options.strict ?? false, aggregateResult.value.unclaimed));
} catch (error) {
return wrapVerifyError(error, contractPathAbsolute, "db verify --schema-only");
} finally {
await client.close();
}
}
function createDbVerifyCommand() {
const command = new Command("verify");
setCommandDescriptions(command, "Check whether the database marker and live schema match your contract", "Verifies the database marker first, then checks the database schema matches your contract.\nUse `--marker-only` for marker-only verification, `--schema-only` to skip marker checks and\ninspect only the live schema, and `--strict` to fail if the database includes elements\nnot present in the contract.");
setCommandExamples(command, [
"prisma-next db verify --db $DATABASE_URL",
"prisma-next db verify --db $DATABASE_URL --strict",
"prisma-next db verify --db $DATABASE_URL --schema-only",
"prisma-next db verify --db $DATABASE_URL --schema-only --strict",
"prisma-next db verify --db $DATABASE_URL --marker-only",
"prisma-next db verify --db $DATABASE_URL --json"
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--marker-only", "Skip schema verification and only check the database marker").option("--schema-only", "Skip marker verification and only check whether the live schema satisfies the contract").option("--strict", "Strict mode: schema elements not present in the contract are considered an error", false).action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const modeResult = resolveDbVerifyMode(options);
if (!modeResult.ok) {
const exitCode = handleResult(modeResult, flags, ui);
process.exit(exitCode);
}
const mode = modeResult.value;
if (mode === "schema-only") {
const result = await executeDbSchemaOnlyVerifyCommand(options, flags, ui);
const exitCode = handleResult(result, flags, ui, (combined) => {
if (flags.json) ui.output(formatSchemaVerifyJson(combined.result, combined.unclaimed));
else {
const renderFlags = combined.result.ok ? flags : {
...flags,
quiet: false
};
const output = formatSchemaVerifyOutput(combined.result, renderFlags, combined.unclaimed);
if (output) ui.log(output);
}
});
if (result.ok && !result.value.result.ok) process.exit(1);
process.exit(exitCode);
}
const result = await executeDbVerifyCommand(options, flags, ui, mode);
if (result.ok) {
if (flags.json) ui.output(formatVerifyJson(result.value));
else {
const output = formatVerifyOutput(result.value, flags);
if (output) ui.log(output);
}
process.exit(0);
}
if (CliStructuredError.is(result.failure)) {
const exitCode = handleResult(result, flags, ui);
process.exit(exitCode);
}
if (flags.json) ui.output(formatSchemaVerifyJson(result.failure.result, result.failure.unclaimed));
else {
const output = formatSchemaVerifyOutput(result.failure.result, {
...flags,
quiet: false
}, result.failure.unclaimed);
if (output) ui.log(output);
}
process.exit(1);
});
return command;
}
//#endregion
export { createDbVerifyCommand as t };
//# sourceMappingURL=db-verify-BpwCMyWJ.mjs.map
{"version":3,"file":"db-verify-BpwCMyWJ.mjs","names":[],"sources":["../src/utils/combine-verify-results.ts","../src/commands/db-verify.ts"],"sourcesContent":["import type { VerifyDatabaseSchemaResult } from '@prisma-next/framework-components/control';\n\n/**\n * The combined per-space contract-satisfaction result plus the standalone\n * unclaimed-elements list, reported once. The CLI renders\n * both; `unclaimed` is never folded into the combined tree or its issues.\n */\nexport interface CombinedVerifyResult {\n readonly result: VerifyDatabaseSchemaResult;\n readonly unclaimed: readonly string[];\n}\n\n/**\n * Collapse the aggregate verifier's per-space contract-satisfaction results\n * into a single {@link VerifyDatabaseSchemaResult} for the CLI display\n * surface, and carry the deduplicated unclaimed-elements list alongside it.\n * Concatenates the issue lists across spaces; uses the app space's result as\n * the structural envelope (storage hash, target). Extras are already\n * stripped from each per-space result, so nothing here duplicates an\n * unclaimed element per space.\n *\n * **Unclaimed disposition.** In strict mode a non-empty `unclaimed` list fails\n * the combined verdict (`ok: false`); in lenient mode it is carried for\n * informational rendering only. The list itself is returned unchanged for the\n * renderer.\n *\n * **Summary policy.** Preserve the per-family phrasing whenever the\n * combined `ok` flag agrees with the app space's `ok` flag — this is\n * the common case (single-family deployments, single-app deployments)\n * and the family's \"satisfies / does not satisfy contract\" phrasing\n * stays user-visible. When the app passes but an extension fails (or\n * vice versa) the app's summary contradicts the envelope, so fall back\n * to the first failing space's summary. This keeps family phrasing\n * intact and the envelope internally consistent (`ok: false` ↔ failure\n * summary).\n */\nexport function combineVerifyResults(\n perSpace: ReadonlyMap<string, VerifyDatabaseSchemaResult>,\n appSpaceId: string,\n strict: boolean,\n unclaimed: readonly string[],\n): CombinedVerifyResult {\n const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;\n if (appResult === undefined) {\n throw new Error(\n 'Aggregate verifier returned no per-space verify results — this is a wiring bug.',\n );\n }\n\n let okAll = true;\n let firstFailure: VerifyDatabaseSchemaResult | undefined;\n let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];\n let warningIssues: VerifyDatabaseSchemaResult['schema']['issues'] = [];\n for (const [, result] of perSpace) {\n if (!result.ok) {\n okAll = false;\n if (firstFailure === undefined) firstFailure = result;\n }\n issues = [...issues, ...result.schema.issues];\n warningIssues = [...warningIssues, ...(result.schema.warnings?.issues ?? [])];\n }\n\n const unclaimedFails = strict && unclaimed.length > 0;\n const ok = okAll && !unclaimedFails;\n\n // Prefer a failing space's family phrasing; else, when only the unclaimed list\n // fails the verdict, say so; else keep the app space's phrasing. When `okAll`\n // is false the loop assigned `firstFailure`, so the `?? appResult.summary`\n // fallback is unreachable — it exists only to keep the read cast-free.\n const summary = okAll\n ? unclaimedFails\n ? `Database schema has ${unclaimed.length} unclaimed element${unclaimed.length === 1 ? '' : 's'} (not in any contract)`\n : appResult.summary\n : appResult.ok\n ? (firstFailure?.summary ?? appResult.summary)\n : appResult.summary;\n\n return {\n result: {\n ok,\n ...(ok ? {} : { code: appResult.code ?? 'CONTRACT.MARKER_REQUIRED' }),\n summary,\n contract: appResult.contract,\n target: appResult.target,\n schema: {\n issues,\n warnings: { issues: warningIssues },\n },\n meta: { strict },\n timings: { total: 0 },\n },\n unclaimed,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { VerifyDatabaseResult } from '@prisma-next/framework-components/control';\nimport {\n createControlStack,\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { createControlClient } from '../control-api/client';\nimport { ContractValidationError } from '../control-api/errors';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorHashMismatch,\n errorMarkerMissing,\n errorRuntime,\n errorTargetMismatch,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { type CombinedVerifyResult, combineVerifyResults } from '../utils/combine-verify-results';\nimport {\n addGlobalOptions,\n maskConnectionUrl,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport {\n type DbVerifyCommandSuccessResult,\n formatSchemaVerifyJson,\n formatSchemaVerifyOutput,\n formatVerifyJson,\n formatVerifyOutput,\n} from '../utils/formatters/verify';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbVerifyOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly markerOnly?: boolean;\n readonly schemaOnly?: boolean;\n readonly strict?: boolean;\n}\n\ntype DbVerifyMode = 'full' | 'marker-only' | 'schema-only';\n\n/**\n * Maps a VerifyDatabaseResult failure to a CliStructuredError.\n */\nfunction mapVerifyFailure(verifyResult: VerifyDatabaseResult): CliStructuredError {\n if (!verifyResult.ok && verifyResult.code) {\n if (verifyResult.code === VERIFY_CODE_MARKER_MISSING) {\n return errorMarkerMissing();\n }\n if (verifyResult.code === VERIFY_CODE_HASH_MISMATCH) {\n const storageMatch = verifyResult.marker?.storageHash === verifyResult.contract.storageHash;\n const profileMatch =\n !verifyResult.contract.profileHash ||\n verifyResult.marker?.profileHash === verifyResult.contract.profileHash;\n\n if (!storageMatch) {\n return errorHashMismatch({\n why: 'Contract storageHash does not match database marker',\n expected: verifyResult.contract.storageHash,\n ...ifDefined('actual', verifyResult.marker?.storageHash),\n });\n }\n\n return errorHashMismatch({\n why: profileMatch\n ? 'Contract hash does not match database marker'\n : 'Contract profileHash does not match database marker',\n ...ifDefined('expected', verifyResult.contract.profileHash),\n ...ifDefined('actual', verifyResult.marker?.profileHash),\n });\n }\n if (verifyResult.code === VERIFY_CODE_TARGET_MISMATCH) {\n return errorTargetMismatch(\n verifyResult.target.expected,\n verifyResult.target.actual ?? 'unknown',\n );\n }\n // Unknown code - fall through to runtime error\n }\n return errorRuntime(verifyResult.summary);\n}\n\ntype DbVerifyFailure = CliStructuredError | CombinedVerifyResult;\n\nfunction errorInvalidVerifyMode(options: {\n readonly why: string;\n readonly fix: string;\n}): CliStructuredError {\n return new CliStructuredError('CLI.INVALID_VERIFY_MODE', 'Invalid verify mode', {\n why: options.why,\n fix: options.fix,\n docsUrl: 'https://pris.ly/db-verify',\n });\n}\n\nfunction resolveDbVerifyMode(options: DbVerifyOptions): Result<DbVerifyMode, CliStructuredError> {\n if (options.markerOnly && options.schemaOnly) {\n return notOk(\n errorInvalidVerifyMode({\n why: '`--marker-only` and `--schema-only` cannot be used together',\n fix: 'Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema.',\n }),\n );\n }\n\n if (options.markerOnly && options.strict) {\n return notOk(\n errorInvalidVerifyMode({\n why: '`--strict` requires schema verification, but `--marker-only` skips it',\n fix: 'Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode.',\n }),\n );\n }\n\n if (options.schemaOnly) {\n return ok('schema-only');\n }\n\n if (options.markerOnly) {\n return ok('marker-only');\n }\n\n return ok('full');\n}\n\nfunction formatDbVerifyModeLabel(mode: DbVerifyMode, strict: boolean): string {\n if (mode === 'marker-only') {\n return 'marker only';\n }\n\n if (mode === 'schema-only') {\n return `schema only (${strict ? 'strict' : 'tolerant'})`;\n }\n\n return `full (marker + schema, ${strict ? 'strict' : 'tolerant'})`;\n}\n\nfunction formatDbVerifyInvocation(mode: DbVerifyMode, strict: boolean): string {\n const args = ['db verify'];\n\n if (mode === 'marker-only') {\n args.push('--marker-only');\n }\n\n if (mode === 'schema-only') {\n args.push('--schema-only');\n }\n\n if (strict) {\n args.push('--strict');\n }\n\n return args.join(' ');\n}\n\nfunction createDbVerifyConnectionRequiredError(options: {\n readonly configPath: string;\n readonly mode: DbVerifyMode;\n readonly strict: boolean;\n}): CliStructuredError {\n const invocation = formatDbVerifyInvocation(options.mode, options.strict);\n return errorDatabaseConnectionRequired({\n why: `Database connection is required for ${invocation} (set db.connection in ${options.configPath}, or pass --db <url>)`,\n retryCommand: `prisma-next ${invocation} --db <url>`,\n });\n}\n\nfunction renderVerifyHeader(\n paths: { configPath: string; contractPath: string },\n options: DbVerifyOptions,\n mode: DbVerifyMode,\n flags: GlobalFlags,\n ui: TerminalUI,\n): void {\n if (flags.json || flags.quiet) return;\n\n const description =\n mode === 'schema-only'\n ? 'Check whether the live database schema matches your contract'\n : mode === 'marker-only'\n ? 'Check whether the database marker matches your contract'\n : 'Check whether the database marker and live schema match your contract';\n\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: paths.configPath },\n { label: 'contract', value: paths.contractPath },\n { label: 'mode', value: formatDbVerifyModeLabel(mode, options.strict ?? false) },\n ];\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n }\n\n ui.stderr(\n formatStyledHeader({\n command: 'db verify',\n description,\n url: 'https://pris.ly/db-verify',\n details,\n flags,\n }),\n );\n}\n\nasync function resolveVerifyPaths(options: DbVerifyOptions) {\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n return { config, configPath, contractPathAbsolute, contractPath };\n}\n\ntype VerifyPaths = Awaited<ReturnType<typeof resolveVerifyPaths>>;\n\ninterface VerifySetup extends VerifyPaths {\n readonly contractJson: Contract;\n readonly dbConnection: string;\n}\n\nasync function resolveVerifySetup(\n paths: VerifyPaths,\n options: DbVerifyOptions,\n mode: DbVerifyMode,\n): Promise<Result<VerifySetup, CliStructuredError>> {\n const { config, configPath, contractPathAbsolute, contractPath } = paths;\n\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n // Cross the family `deserializeContract` seam at the read site, just\n // like every other CLI on-disk read (TML-2536). The downstream\n // `dbVerify` op accepts the hydrated `Contract` directly and no\n // longer re-deserializes.\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n\n let contractJson: Contract;\n try {\n contractJson = familyInstance.deserializeContract(JSON.parse(contractJsonContent) as unknown);\n } catch (error) {\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n const dbConnection = options.db ?? config.db?.connection;\n if (typeof dbConnection !== 'string' || dbConnection.length === 0) {\n return notOk(\n createDbVerifyConnectionRequiredError({\n configPath,\n mode,\n strict: options.strict ?? false,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: `Config.driver is required for ${formatDbVerifyInvocation(mode, options.strict ?? false)}`,\n }),\n );\n }\n\n return ok({ ...paths, contractJson, dbConnection });\n}\n\nfunction createVerifyClient(setup: VerifySetup) {\n return createControlClient({\n family: setup.config.family,\n target: setup.config.target,\n adapter: setup.config.adapter,\n driver: setup.config.driver!,\n extensionPacks: setup.config.extensionPacks ?? [],\n });\n}\n\nfunction wrapVerifyError(\n error: unknown,\n contractPathAbsolute: string,\n modeLabel: string,\n): Result<never, CliStructuredError> {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during ${modeLabel}: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n}\n\n/**\n * Executes the db verify command and returns a structured Result.\n */\nasync function executeDbVerifyCommand(\n options: DbVerifyOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n mode: Extract<DbVerifyMode, 'full' | 'marker-only'>,\n): Promise<Result<DbVerifyCommandSuccessResult, DbVerifyFailure>> {\n const startTime = Date.now();\n const paths = await resolveVerifyPaths(options);\n renderVerifyHeader(paths, options, mode, flags, ui);\n\n const setupResult = await resolveVerifySetup(paths, options, mode);\n if (!setupResult.ok) return setupResult;\n const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;\n const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);\n\n const client = createVerifyClient(setupResult.value);\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n // Single-contract marker verification preserved for the existing\n // marker / target / hash failure surface (`CONTRACT.MARKER_MISSING/CONTRACT.MARKER_MISMATCH/CONTRACT.TARGET_MISMATCH`).\n // The aggregate verifier (run below for the per-space marker /\n // schema checks) does not duplicate this: it concerns itself with\n // marker-vs-on-disk and orphan-marker drift, not the\n // hash-mismatch-against-the-app-contract lane that today's\n // `client.verify` covers.\n const verifyResult = await client.verify({\n contract: contractJson,\n connection: dbConnection,\n onProgress,\n });\n\n if (!verifyResult.ok) {\n return notOk(mapVerifyFailure(verifyResult));\n }\n\n // Aggregate verifier (loader → verifier pipeline). Runs the layout\n // precheck, marker-aware per-space verifier, and (full mode only)\n // per-space pre-projected schema verification (closes F23).\n const aggregateResult = await client.dbVerify({\n contract: contractJson,\n migrationsDir,\n strict: options.strict ?? false,\n skipSchema: mode === 'marker-only',\n skipMarker: false,\n onProgress,\n });\n if (!aggregateResult.ok) return notOk(aggregateResult.failure);\n\n if (mode === 'marker-only') {\n return ok({\n ok: true,\n mode: 'marker-only',\n summary: 'Database marker matches contract',\n contract: verifyResult.contract,\n marker: verifyResult.marker,\n target: verifyResult.target,\n ...ifDefined('missingCodecs', verifyResult.missingCodecs),\n ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),\n warning: 'Schema verification skipped because --marker-only was provided',\n meta: {\n ...(verifyResult.meta ?? {}),\n schemaVerification: 'skipped',\n },\n timings: { total: Date.now() - startTime },\n });\n }\n\n const combined = combineVerifyResults(\n aggregateResult.value.schemaResults,\n aggregateResult.value.appSpaceId,\n options.strict ?? false,\n aggregateResult.value.unclaimed,\n );\n if (!combined.result.ok) {\n return notOk(combined);\n }\n\n return ok({\n ok: true,\n mode: 'full',\n summary: 'Database marker and schema match contract',\n contract: verifyResult.contract,\n marker: verifyResult.marker,\n target: verifyResult.target,\n ...ifDefined('missingCodecs', verifyResult.missingCodecs),\n ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),\n schema: {\n summary: combined.result.summary,\n strict: combined.result.meta?.strict ?? false,\n warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) =>\n issue.path.join('/'),\n ),\n },\n unclaimed: combined.unclaimed,\n meta: {\n ...(verifyResult.meta ?? {}),\n schemaVerification: 'performed',\n },\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n return wrapVerifyError(error, contractPathAbsolute, 'db verify');\n } finally {\n await client.close();\n }\n}\n\nasync function executeDbSchemaOnlyVerifyCommand(\n options: DbVerifyOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<CombinedVerifyResult, CliStructuredError>> {\n const paths = await resolveVerifyPaths(options);\n renderVerifyHeader(paths, options, 'schema-only', flags, ui);\n\n const setupResult = await resolveVerifySetup(paths, options, 'schema-only');\n if (!setupResult.ok) return setupResult;\n const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;\n const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);\n\n const client = createVerifyClient(setupResult.value);\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n await client.connect(dbConnection);\n const aggregateResult = await client.dbVerify({\n contract: contractJson,\n migrationsDir,\n strict: options.strict ?? false,\n skipSchema: false,\n skipMarker: true,\n onProgress,\n });\n if (!aggregateResult.ok) return notOk(aggregateResult.failure);\n\n return ok(\n combineVerifyResults(\n aggregateResult.value.schemaResults,\n aggregateResult.value.appSpaceId,\n options.strict ?? false,\n aggregateResult.value.unclaimed,\n ),\n );\n } catch (error) {\n return wrapVerifyError(error, contractPathAbsolute, 'db verify --schema-only');\n } finally {\n await client.close();\n }\n}\n\nexport function createDbVerifyCommand(): Command {\n const command = new Command('verify');\n setCommandDescriptions(\n command,\n 'Check whether the database marker and live schema match your contract',\n 'Verifies the database marker first, then checks the database schema matches your contract.\\n' +\n 'Use `--marker-only` for marker-only verification, `--schema-only` to skip marker checks and\\n' +\n 'inspect only the live schema, and `--strict` to fail if the database includes elements\\n' +\n 'not present in the contract.',\n );\n setCommandExamples(command, [\n 'prisma-next db verify --db $DATABASE_URL',\n 'prisma-next db verify --db $DATABASE_URL --strict',\n 'prisma-next db verify --db $DATABASE_URL --schema-only',\n 'prisma-next db verify --db $DATABASE_URL --schema-only --strict',\n 'prisma-next db verify --db $DATABASE_URL --marker-only',\n 'prisma-next db verify --db $DATABASE_URL --json',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--marker-only', 'Skip schema verification and only check the database marker')\n .option(\n '--schema-only',\n 'Skip marker verification and only check whether the live schema satisfies the contract',\n )\n .option(\n '--strict',\n 'Strict mode: schema elements not present in the contract are considered an error',\n false,\n )\n .action(async (options: DbVerifyOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n const modeResult = resolveDbVerifyMode(options);\n if (!modeResult.ok) {\n const exitCode = handleResult(modeResult as Result<never, CliStructuredError>, flags, ui);\n process.exit(exitCode);\n }\n\n const mode = modeResult.value;\n\n if (mode === 'schema-only') {\n const result = await executeDbSchemaOnlyVerifyCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, (combined) => {\n if (flags.json) {\n ui.output(formatSchemaVerifyJson(combined.result, combined.unclaimed));\n } else {\n // Always show schema-drift failures, even in quiet mode — exiting 1\n // without diagnostics is unhelpful (same policy as the full-mode\n // failure branch below).\n const renderFlags = combined.result.ok ? flags : { ...flags, quiet: false };\n const output = formatSchemaVerifyOutput(\n combined.result,\n renderFlags,\n combined.unclaimed,\n );\n if (output) {\n ui.log(output);\n }\n }\n });\n\n if (result.ok && !result.value.result.ok) {\n process.exit(1);\n }\n\n process.exit(exitCode);\n }\n\n const result = await executeDbVerifyCommand(options, flags, ui, mode);\n\n if (result.ok) {\n if (flags.json) {\n ui.output(formatVerifyJson(result.value));\n } else {\n const output = formatVerifyOutput(result.value, flags);\n if (output) {\n ui.log(output);\n }\n }\n process.exit(0);\n }\n\n if (CliStructuredError.is(result.failure)) {\n const exitCode = handleResult(result as Result<never, CliStructuredError>, flags, ui);\n process.exit(exitCode);\n }\n\n if (flags.json) {\n ui.output(formatSchemaVerifyJson(result.failure.result, result.failure.unclaimed));\n } else {\n // Always show schema-drift failures, even in quiet mode — exiting 1 without\n // diagnostics is unhelpful.\n const output = formatSchemaVerifyOutput(\n result.failure.result,\n { ...flags, quiet: false },\n result.failure.unclaimed,\n );\n if (output) {\n ui.log(output);\n }\n }\n process.exit(1);\n });\n\n return command;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,qBACd,UACA,YACA,QACA,WACsB;CACtB,MAAM,YAAY,SAAS,IAAI,UAAU,KAAK,SAAS,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;CACvE,IAAI,cAAc,KAAA,GAChB,MAAM,IAAI,MACR,iFACF;CAGF,IAAI,QAAQ;CACZ,IAAI;CACJ,IAAI,SAAyD,CAAC;CAC9D,IAAI,gBAAgE,CAAC;CACrE,KAAK,MAAM,GAAG,WAAW,UAAU;EACjC,IAAI,CAAC,OAAO,IAAI;GACd,QAAQ;GACR,IAAI,iBAAiB,KAAA,GAAW,eAAe;EACjD;EACA,SAAS,CAAC,GAAG,QAAQ,GAAG,OAAO,OAAO,MAAM;EAC5C,gBAAgB,CAAC,GAAG,eAAe,GAAI,OAAO,OAAO,UAAU,UAAU,CAAC,CAAE;CAC9E;CAEA,MAAM,iBAAiB,UAAU,UAAU,SAAS;CACpD,MAAM,KAAK,SAAS,CAAC;CAMrB,MAAM,UAAU,QACZ,iBACE,uBAAuB,UAAU,OAAO,oBAAoB,UAAU,WAAW,IAAI,KAAK,IAAI,0BAC9F,UAAU,UACZ,UAAU,KACP,cAAc,WAAW,UAAU,UACpC,UAAU;CAEhB,OAAO;EACL,QAAQ;GACN;GACA,GAAI,KAAK,CAAC,IAAI,EAAE,MAAM,UAAU,QAAQ,2BAA2B;GACnE;GACA,UAAU,UAAU;GACpB,QAAQ,UAAU;GAClB,QAAQ;IACN;IACA,UAAU,EAAE,QAAQ,cAAc;GACpC;GACA,MAAM,EAAE,OAAO;GACf,SAAS,EAAE,OAAO,EAAE;EACtB;EACA;CACF;AACF;;;;;;AC7BA,SAAS,iBAAiB,cAAwD;CAChF,IAAI,CAAC,aAAa,MAAM,aAAa,MAAM;EACzC,IAAI,aAAa,SAAS,4BACxB,OAAO,mBAAmB;EAE5B,IAAI,aAAa,SAAS,2BAA2B;GACnD,MAAM,eAAe,aAAa,QAAQ,gBAAgB,aAAa,SAAS;GAChF,MAAM,eACJ,CAAC,aAAa,SAAS,eACvB,aAAa,QAAQ,gBAAgB,aAAa,SAAS;GAE7D,IAAI,CAAC,cACH,OAAO,kBAAkB;IACvB,KAAK;IACL,UAAU,aAAa,SAAS;IAChC,GAAG,UAAU,UAAU,aAAa,QAAQ,WAAW;GACzD,CAAC;GAGH,OAAO,kBAAkB;IACvB,KAAK,eACD,iDACA;IACJ,GAAG,UAAU,YAAY,aAAa,SAAS,WAAW;IAC1D,GAAG,UAAU,UAAU,aAAa,QAAQ,WAAW;GACzD,CAAC;EACH;EACA,IAAI,aAAa,SAAS,6BACxB,OAAO,oBACL,aAAa,OAAO,UACpB,aAAa,OAAO,UAAU,SAChC;CAGJ;CACA,OAAO,aAAa,aAAa,OAAO;AAC1C;AAIA,SAAS,uBAAuB,SAGT;CACrB,OAAO,IAAI,mBAAmB,2BAA2B,uBAAuB;EAC9E,KAAK,QAAQ;EACb,KAAK,QAAQ;EACb,SAAS;CACX,CAAC;AACH;AAEA,SAAS,oBAAoB,SAAoE;CAC/F,IAAI,QAAQ,cAAc,QAAQ,YAChC,OAAO,MACL,uBAAuB;EACrB,KAAK;EACL,KAAK;CACP,CAAC,CACH;CAGF,IAAI,QAAQ,cAAc,QAAQ,QAChC,OAAO,MACL,uBAAuB;EACrB,KAAK;EACL,KAAK;CACP,CAAC,CACH;CAGF,IAAI,QAAQ,YACV,OAAO,GAAG,aAAa;CAGzB,IAAI,QAAQ,YACV,OAAO,GAAG,aAAa;CAGzB,OAAO,GAAG,MAAM;AAClB;AAEA,SAAS,wBAAwB,MAAoB,QAAyB;CAC5E,IAAI,SAAS,eACX,OAAO;CAGT,IAAI,SAAS,eACX,OAAO,gBAAgB,SAAS,WAAW,WAAW;CAGxD,OAAO,0BAA0B,SAAS,WAAW,WAAW;AAClE;AAEA,SAAS,yBAAyB,MAAoB,QAAyB;CAC7E,MAAM,OAAO,CAAC,WAAW;CAEzB,IAAI,SAAS,eACX,KAAK,KAAK,eAAe;CAG3B,IAAI,SAAS,eACX,KAAK,KAAK,eAAe;CAG3B,IAAI,QACF,KAAK,KAAK,UAAU;CAGtB,OAAO,KAAK,KAAK,GAAG;AACtB;AAEA,SAAS,sCAAsC,SAIxB;CACrB,MAAM,aAAa,yBAAyB,QAAQ,MAAM,QAAQ,MAAM;CACxE,OAAO,gCAAgC;EACrC,KAAK,uCAAuC,WAAW,yBAAyB,QAAQ,WAAW;EACnG,cAAc,eAAe,WAAW;CAC1C,CAAC;AACH;AAEA,SAAS,mBACP,OACA,SACA,MACA,OACA,IACM;CACN,IAAI,MAAM,QAAQ,MAAM,OAAO;CAE/B,MAAM,cACJ,SAAS,gBACL,iEACA,SAAS,gBACP,4DACA;CAER,MAAM,UAAmD;EACvD;GAAE,OAAO;GAAU,OAAO,MAAM;EAAW;EAC3C;GAAE,OAAO;GAAY,OAAO,MAAM;EAAa;EAC/C;GAAE,OAAO;GAAQ,OAAO,wBAAwB,MAAM,QAAQ,UAAU,KAAK;EAAE;CACjF;CACA,IAAI,QAAQ,IACV,QAAQ,KAAK;EAAE,OAAO;EAAY,OAAO,kBAAkB,QAAQ,EAAE;CAAE,CAAC;CAG1E,GAAG,OACD,mBAAmB;EACjB,SAAS;EACT;EACA,KAAK;EACL;EACA;CACF,CAAC,CACH;AACF;AAEA,eAAe,mBAAmB,SAA0B;CAC1D,MAAM,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC9C,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,IAAI,GAAG,QAAQ,QAAQ,MAAM,CAAC,IAC/C;CACJ,MAAM,uBAAuB,oBAAoB,MAAM;CAEvD,OAAO;EAAE;EAAQ;EAAY;EAAsB,cAD9B,SAAS,QAAQ,IAAI,GAAG,oBACiB;CAAE;AAClE;AASA,eAAe,mBACb,OACA,SACA,MACkD;CAClD,MAAM,EAAE,QAAQ,YAAY,sBAAsB,iBAAiB;CAEnE,IAAI;CACJ,IAAI;EACF,sBAAsB,MAAM,SAAS,sBAAsB,OAAO;CACpE,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAU,MAA4B,SAAS,UAClE,OAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;EACjH,CAAC,CACH;EAEF,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IAC7F,CAAC,CACH;CACF;CAMA,MAAM,QAAQ,mBAAmB,MAAM;CACvC,MAAM,iBAAiB,OAAO,OAAO,OAAO,KAAK;CAEjD,IAAI;CACJ,IAAI;EACF,eAAe,eAAe,oBAAoB,KAAK,MAAM,mBAAmB,CAAY;CAC9F,SAAS,OAAO;EACd,IAAI,iBAAiB,yBACnB,OAAO,MACL,8BAA8B,+BAA+B,MAAM,WAAW,EAC5E,OAAO,EAAE,MAAM,qBAAqB,EACtC,CAAC,CACH;EAEF,OAAO,MACL,8BACE,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAClF,EAAE,OAAO,EAAE,MAAM,qBAAqB,EAAE,CAC1C,CACF;CACF;CAEA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,OAAO,iBAAiB,YAAY,aAAa,WAAW,GAC9D,OAAO,MACL,sCAAsC;EACpC;EACA;EACA,QAAQ,QAAQ,UAAU;CAC5B,CAAC,CACH;CAGF,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAClB,KAAK,iCAAiC,yBAAyB,MAAM,QAAQ,UAAU,KAAK,IAC9F,CAAC,CACH;CAGF,OAAO,GAAG;EAAE,GAAG;EAAO;EAAc;CAAa,CAAC;AACpD;AAEA,SAAS,mBAAmB,OAAoB;CAC9C,OAAO,oBAAoB;EACzB,QAAQ,MAAM,OAAO;EACrB,QAAQ,MAAM,OAAO;EACrB,SAAS,MAAM,OAAO;EACtB,QAAQ,MAAM,OAAO;EACrB,gBAAgB,MAAM,OAAO,kBAAkB,CAAC;CAClD,CAAC;AACH;AAEA,SAAS,gBACP,OACA,sBACA,WACmC;CACnC,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;CAEpB,IAAI,iBAAiB,yBACnB,OAAO,MACL,8BAA8B,+BAA+B,MAAM,WAAW,EAC5E,OAAO,EAAE,MAAM,qBAAqB,EACtC,CAAC,CACH;CAEF,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,2BAA2B,UAAU,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IACrG,CAAC,CACH;AACF;;;;AAKA,eAAe,uBACb,SACA,OACA,IACA,MACgE;CAChE,MAAM,YAAY,KAAK,IAAI;CAC3B,MAAM,QAAQ,MAAM,mBAAmB,OAAO;CAC9C,mBAAmB,OAAO,SAAS,MAAM,OAAO,EAAE;CAElD,MAAM,cAAc,MAAM,mBAAmB,OAAO,SAAS,IAAI;CACjE,IAAI,CAAC,YAAY,IAAI,OAAO;CAC5B,MAAM,EAAE,cAAc,cAAc,yBAAyB,YAAY;CACzE,MAAM,EAAE,kBAAkB,sBAAsB,QAAQ,QAAQ,YAAY,MAAM,MAAM;CAExF,MAAM,SAAS,mBAAmB,YAAY,KAAK;CACnD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,IAAI;EAQF,MAAM,eAAe,MAAM,OAAO,OAAO;GACvC,UAAU;GACV,YAAY;GACZ;EACF,CAAC;EAED,IAAI,CAAC,aAAa,IAChB,OAAO,MAAM,iBAAiB,YAAY,CAAC;EAM7C,MAAM,kBAAkB,MAAM,OAAO,SAAS;GAC5C,UAAU;GACV;GACA,QAAQ,QAAQ,UAAU;GAC1B,YAAY,SAAS;GACrB,YAAY;GACZ;EACF,CAAC;EACD,IAAI,CAAC,gBAAgB,IAAI,OAAO,MAAM,gBAAgB,OAAO;EAE7D,IAAI,SAAS,eACX,OAAO,GAAG;GACR,IAAI;GACJ,MAAM;GACN,SAAS;GACT,UAAU,aAAa;GACvB,QAAQ,aAAa;GACrB,QAAQ,aAAa;GACrB,GAAG,UAAU,iBAAiB,aAAa,aAAa;GACxD,GAAG,UAAU,wBAAwB,aAAa,oBAAoB;GACtE,SAAS;GACT,MAAM;IACJ,GAAI,aAAa,QAAQ,CAAC;IAC1B,oBAAoB;GACtB;GACA,SAAS,EAAE,OAAO,KAAK,IAAI,IAAI,UAAU;EAC3C,CAAC;EAGH,MAAM,WAAW,qBACf,gBAAgB,MAAM,eACtB,gBAAgB,MAAM,YACtB,QAAQ,UAAU,OAClB,gBAAgB,MAAM,SACxB;EACA,IAAI,CAAC,SAAS,OAAO,IACnB,OAAO,MAAM,QAAQ;EAGvB,OAAO,GAAG;GACR,IAAI;GACJ,MAAM;GACN,SAAS;GACT,UAAU,aAAa;GACvB,QAAQ,aAAa;GACrB,QAAQ,aAAa;GACrB,GAAG,UAAU,iBAAiB,aAAa,aAAa;GACxD,GAAG,UAAU,wBAAwB,aAAa,oBAAoB;GACtE,QAAQ;IACN,SAAS,SAAS,OAAO;IACzB,QAAQ,SAAS,OAAO,MAAM,UAAU;IACxC,WAAW,SAAS,OAAO,OAAO,UAAU,UAAU,CAAC,EAAA,CAAG,KAAK,UAC7D,MAAM,KAAK,KAAK,GAAG,CACrB;GACF;GACA,WAAW,SAAS;GACpB,MAAM;IACJ,GAAI,aAAa,QAAQ,CAAC;IAC1B,oBAAoB;GACtB;GACA,SAAS,EAAE,OAAO,KAAK,IAAI,IAAI,UAAU;EAC3C,CAAC;CACH,SAAS,OAAO;EACd,OAAO,gBAAgB,OAAO,sBAAsB,WAAW;CACjE,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,eAAe,iCACb,SACA,OACA,IAC2D;CAC3D,MAAM,QAAQ,MAAM,mBAAmB,OAAO;CAC9C,mBAAmB,OAAO,SAAS,eAAe,OAAO,EAAE;CAE3D,MAAM,cAAc,MAAM,mBAAmB,OAAO,SAAS,aAAa;CAC1E,IAAI,CAAC,YAAY,IAAI,OAAO;CAC5B,MAAM,EAAE,cAAc,cAAc,yBAAyB,YAAY;CACzE,MAAM,EAAE,kBAAkB,sBAAsB,QAAQ,QAAQ,YAAY,MAAM,MAAM;CAExF,MAAM,SAAS,mBAAmB,YAAY,KAAK;CACnD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,IAAI;EACF,MAAM,OAAO,QAAQ,YAAY;EACjC,MAAM,kBAAkB,MAAM,OAAO,SAAS;GAC5C,UAAU;GACV;GACA,QAAQ,QAAQ,UAAU;GAC1B,YAAY;GACZ,YAAY;GACZ;EACF,CAAC;EACD,IAAI,CAAC,gBAAgB,IAAI,OAAO,MAAM,gBAAgB,OAAO;EAE7D,OAAO,GACL,qBACE,gBAAgB,MAAM,eACtB,gBAAgB,MAAM,YACtB,QAAQ,UAAU,OAClB,gBAAgB,MAAM,SACxB,CACF;CACF,SAAS,OAAO;EACd,OAAO,gBAAgB,OAAO,sBAAsB,yBAAyB;CAC/E,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,SAAgB,wBAAiC;CAC/C,MAAM,UAAU,IAAI,QAAQ,QAAQ;CACpC,uBACE,SACA,yEACA,+SAIF;CACA,mBAAmB,SAAS;EAC1B;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,iBAAiB,OAAO,CAAC,CACtB,OAAO,cAAc,4BAA4B,CAAC,CAClD,OAAO,mBAAmB,+BAA+B,CAAC,CAC1D,OAAO,iBAAiB,6DAA6D,CAAC,CACtF,OACC,iBACA,wFACF,CAAC,CACA,OACC,YACA,oFACA,KACF,CAAC,CACA,OAAO,OAAO,YAA6B;EAC1C,MAAM,QAAQ,uBAAuB,OAAO;EAC5C,MAAM,KAAK,iBAAiB,KAAK;EAEjC,MAAM,aAAa,oBAAoB,OAAO;EAC9C,IAAI,CAAC,WAAW,IAAI;GAClB,MAAM,WAAW,aAAa,YAAiD,OAAO,EAAE;GACxF,QAAQ,KAAK,QAAQ;EACvB;EAEA,MAAM,OAAO,WAAW;EAExB,IAAI,SAAS,eAAe;GAC1B,MAAM,SAAS,MAAM,iCAAiC,SAAS,OAAO,EAAE;GACxE,MAAM,WAAW,aAAa,QAAQ,OAAO,KAAK,aAAa;IAC7D,IAAI,MAAM,MACR,GAAG,OAAO,uBAAuB,SAAS,QAAQ,SAAS,SAAS,CAAC;SAChE;KAIL,MAAM,cAAc,SAAS,OAAO,KAAK,QAAQ;MAAE,GAAG;MAAO,OAAO;KAAM;KAC1E,MAAM,SAAS,yBACb,SAAS,QACT,aACA,SAAS,SACX;KACA,IAAI,QACF,GAAG,IAAI,MAAM;IAEjB;GACF,CAAC;GAED,IAAI,OAAO,MAAM,CAAC,OAAO,MAAM,OAAO,IACpC,QAAQ,KAAK,CAAC;GAGhB,QAAQ,KAAK,QAAQ;EACvB;EAEA,MAAM,SAAS,MAAM,uBAAuB,SAAS,OAAO,IAAI,IAAI;EAEpE,IAAI,OAAO,IAAI;GACb,IAAI,MAAM,MACR,GAAG,OAAO,iBAAiB,OAAO,KAAK,CAAC;QACnC;IACL,MAAM,SAAS,mBAAmB,OAAO,OAAO,KAAK;IACrD,IAAI,QACF,GAAG,IAAI,MAAM;GAEjB;GACA,QAAQ,KAAK,CAAC;EAChB;EAEA,IAAI,mBAAmB,GAAG,OAAO,OAAO,GAAG;GACzC,MAAM,WAAW,aAAa,QAA6C,OAAO,EAAE;GACpF,QAAQ,KAAK,QAAQ;EACvB;EAEA,IAAI,MAAM,MACR,GAAG,OAAO,uBAAuB,OAAO,QAAQ,QAAQ,OAAO,QAAQ,SAAS,CAAC;OAC5E;GAGL,MAAM,SAAS,yBACb,OAAO,QAAQ,QACf;IAAE,GAAG;IAAO,OAAO;GAAM,GACzB,OAAO,QAAQ,SACjB;GACA,IAAI,QACF,GAAG,IAAI,MAAM;EAEjB;EACA,QAAQ,KAAK,CAAC;CAChB,CAAC;CAEH,OAAO;AACT"}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

import { A as formatStyledHeader, F as CliStructuredError, U as errorDriverRequired, V as errorDatabaseConnectionRequired, c as sanitizeErrorMessage, ct as errorUnexpected, i as maskConnectionUrl } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-2kwLMBSf.mjs";
import { loadConfig } from "@prisma-next/config-loader";
import { notOk, ok } from "@prisma-next/utils/result";
import { relative, resolve } from "pathe";
//#region src/commands/inspect-live-schema.ts
async function inspectLiveSchema(options, flags, ui, startTime, context) {
let config;
try {
config = await loadConfig(options.config);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: "Failed to load config" }));
}
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
else if (config.db?.connection && typeof config.db.connection === "string") details.push({
label: "database",
value: maskConnectionUrl(config.db.connection)
});
ui.stderr(formatStyledHeader({
command: context.commandName,
description: context.description,
url: context.url,
details,
flags
}));
}
const dbConnection = options.db ?? config.db?.connection;
if (!dbConnection) return notOk(errorDatabaseConnectionRequired({
why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: context.commandName
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${context.commandName}` }));
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
const onProgress = createProgressAdapter({
ui,
flags
});
try {
const schema = await client.introspect({
connection: dbConnection,
onProgress
});
const schemaView = client.toSchemaView(schema);
const pslContractAst = client.inferPslContract(schema);
const pslBlockDescriptors = client.getPslBlockDescriptors();
const dbUrl = typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0;
return ok({
config,
schema,
schemaView,
pslContractAst,
pslBlockDescriptors,
target: {
familyId: config.family.familyId,
id: config.target.targetId
},
meta: {
configPath,
...dbUrl ? { dbUrl } : {}
},
timings: { total: Date.now() - startTime }
});
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
const safeMessage = sanitizeErrorMessage(error instanceof Error ? error.message : String(error), typeof dbConnection === "string" ? dbConnection : void 0);
return notOk(errorUnexpected(safeMessage, { why: `Unexpected error during ${context.commandName}: ${safeMessage}` }));
} finally {
await client.close();
}
}
//#endregion
export { inspectLiveSchema as t };
//# sourceMappingURL=inspect-live-schema-DxsD_a4d.mjs.map
{"version":3,"file":"inspect-live-schema-DxsD_a4d.mjs","names":[],"sources":["../src/commands/inspect-live-schema.ts"],"sourcesContent":["import { loadConfig } from '@prisma-next/config-loader';\nimport type { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';\nimport type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { relative, resolve } from 'pathe';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { maskConnectionUrl, sanitizeErrorMessage } from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions, GlobalFlags } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport type { TerminalUI } from '../utils/terminal-ui';\n\nexport interface InspectLiveSchemaOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n}\n\ninterface InspectLiveSchemaContext {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\ntype LoadedCliConfig = Awaited<ReturnType<typeof loadConfig>>;\n\nexport interface InspectLiveSchemaResult {\n readonly config: LoadedCliConfig;\n readonly schema: unknown;\n readonly schemaView: CoreSchemaView | undefined;\n /**\n * PSL AST inferred from the introspected schema, when the configured family\n * implements `PslContractInferCapable`. `undefined` for families that do not\n * support inference (e.g. Mongo today).\n */\n readonly pslContractAst: PslDocumentAst | undefined;\n /**\n * The assembled PSL block descriptors from the control stack — the full set of\n * extension-contributed top-level block descriptors. Downstream commands pass\n * this through to `printPsl` so contributed-block AST nodes round-trip back to\n * source.\n */\n readonly pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace;\n readonly target: {\n readonly familyId: string;\n readonly id: string;\n };\n readonly meta: {\n readonly configPath?: string;\n readonly dbUrl?: string;\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nexport async function inspectLiveSchema(\n options: InspectLiveSchemaOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n context: InspectLiveSchemaContext,\n): Promise<Result<InspectLiveSchemaResult, CliStructuredError>> {\n let config: LoadedCliConfig;\n try {\n config = await loadConfig(options.config);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: 'Failed to load config',\n }),\n );\n }\n\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n ];\n\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n } else if (config.db?.connection && typeof config.db.connection === 'string') {\n details.push({ label: 'database', value: maskConnectionUrl(config.db.connection) });\n }\n\n ui.stderr(\n formatStyledHeader({\n command: context.commandName,\n description: context.description,\n url: context.url,\n details,\n flags,\n }),\n );\n }\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: context.commandName,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: `Config.driver is required for ${context.commandName}`,\n }),\n );\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n const schema = await client.introspect({\n connection: dbConnection,\n onProgress,\n });\n const schemaView = client.toSchemaView(schema);\n const pslContractAst = client.inferPslContract(schema);\n const pslBlockDescriptors = client.getPslBlockDescriptors();\n\n const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;\n\n return ok({\n config,\n schema,\n schemaView,\n pslContractAst,\n pslBlockDescriptors,\n target: {\n familyId: config.family.familyId,\n id: config.target.targetId,\n },\n meta: {\n configPath,\n ...(dbUrl ? { dbUrl } : {}),\n },\n timings: {\n total: Date.now() - startTime,\n },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n const rawMessage = error instanceof Error ? error.message : String(error);\n const safeMessage = sanitizeErrorMessage(\n rawMessage,\n typeof dbConnection === 'string' ? dbConnection : undefined,\n );\n return notOk(\n errorUnexpected(safeMessage, {\n why: `Unexpected error during ${context.commandName}: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n"],"mappings":";;;;;;;AA8DA,eAAsB,kBACpB,SACA,OACA,IACA,WACA,SAC8D;CAC9D,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC1C,SAAS,OAAO;EACd,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;EAGpB,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,wBACP,CAAC,CACH;CACF;CAEA,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,IAAI,GAAG,QAAQ,QAAQ,MAAM,CAAC,IAC/C;CAEJ,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;EAAW,CACvC;EAEA,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,EAAE;EAAE,CAAC;OACnE,IAAI,OAAO,IAAI,cAAc,OAAO,OAAO,GAAG,eAAe,UAClE,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,OAAO,GAAG,UAAU;EAAE,CAAC;EAGpF,GAAG,OACD,mBAAmB;GACjB,SAAS,QAAQ;GACjB,aAAa,QAAQ;GACrB,KAAK,QAAQ;GACb;GACA;EACF,CAAC,CACH;CACF;CAEA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,QAAQ,YAAY,yBAAyB,WAAW;EACpG,aAAa,QAAQ;CACvB,CAAC,CACH;CAGF,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAClB,KAAK,iCAAiC,QAAQ,cAChD,CAAC,CACH;CAGF,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,CAAC;CAC5C,CAAC;CACD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAAW;GACrC,YAAY;GACZ;EACF,CAAC;EACD,MAAM,aAAa,OAAO,aAAa,MAAM;EAC7C,MAAM,iBAAiB,OAAO,iBAAiB,MAAM;EACrD,MAAM,sBAAsB,OAAO,uBAAuB;EAE1D,MAAM,QAAQ,OAAO,iBAAiB,WAAW,kBAAkB,YAAY,IAAI,KAAA;EAEnF,OAAO,GAAG;GACR;GACA;GACA;GACA;GACA;GACA,QAAQ;IACN,UAAU,OAAO,OAAO;IACxB,IAAI,OAAO,OAAO;GACpB;GACA,MAAM;IACJ;IACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC3B;GACA,SAAS,EACP,OAAO,KAAK,IAAI,IAAI,UACtB;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;EAIpB,MAAM,cAAc,qBADD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAGtE,OAAO,iBAAiB,WAAW,eAAe,KAAA,CACpD;EACA,OAAO,MACL,gBAAgB,aAAa,EAC3B,KAAK,2BAA2B,QAAQ,YAAY,IAAI,cAC1D,CAAC,CACH;CACF,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF"}
import { A as formatStyledHeader, K as errorInvalidSpaceId, L as errorAmbiguousMigrationRef, _ as createTerminalUI, at as errorSpaceNotFound, b as formatErrorJson, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, o as resolveContractPath, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, x as formatErrorOutput } from "./command-helpers-CVn3U9Uz.mjs";
import { n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { i as resolveTargetPathAcrossSpaces, n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-C0TQuV5n.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { join, relative } from "pathe";
import { readFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { isValidSpaceId, listContractSpaceDirectories, spaceMigrationDirectory, spaceRefsDirectory } from "@prisma-next/migration-tools/spaces";
import { existsSync, readdirSync, statSync } from "node:fs";
import { loadContractSpaceAggregate } from "@prisma-next/migration-tools/aggregate";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
import { contractSnapshotDir, readContractSnapshotJson } from "@prisma-next/migration-tools/contract-snapshot-store";
import { parseMigrationRef } from "@prisma-next/migration-tools/ref-resolution";
import { verifyMigrationHash } from "@prisma-next/migration-tools/hash";
//#region src/utils/integrity-violation-to-check-failure.ts
function migrationPathRelative$1(dirPath) {
return relative(process.cwd(), dirPath);
}
function migrationFileRelative$1(dirPath, fileName) {
return join(migrationPathRelative$1(dirPath), fileName);
}
/**
* Map one {@link IntegrityViolation} onto a `migration check` failure row.
* Sole catalogue mapping from integrity violations to `MIGRATION.CHECK_*` codes.
*/
function integrityViolationToCheckFailure(violation, migrationsDir) {
const spaceRelative = (spaceId) => migrationPathRelative$1(join(migrationsDir, spaceId));
const packageRelative = (spaceId, dirName) => migrationPathRelative$1(join(migrationsDir, spaceId, dirName));
const refRelative = (spaceId, refName) => migrationPathRelative$1(join(migrationsDir, spaceId, "refs", `${refName}.json`));
switch (violation.kind) {
case "hashMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HASH_MISMATCH",
where: migrationFileRelative$1(join(migrationsDir, violation.spaceId, violation.dirName), "migration.json"),
why: `Stored hash ${violation.stored} does not match recomputed hash ${violation.computed}`,
fix: "Re-emit the migration package or restore from version control."
};
case "providedInvariantsMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_PROVIDED_INVARIANTS_MISMATCH",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`,
fix: "Re-emit the migration package so migration.json and ops.json agree."
};
case "packageUnloadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_PACKAGE_UNLOADABLE",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`,
fix: "Re-emit the migration package or restore from version control."
};
case "sameSourceAndTarget": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_NOOP_SELF_EDGE",
where: packageRelative(violation.spaceId, violation.dirName),
why: `Migration "${violation.dirName}" in space "${violation.spaceId}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,
fix: "Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op."
};
case "orphanSpaceDir": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_ORPHAN_SPACE_DIR",
where: spaceRelative(violation.spaceId),
why: `Contract-space directory "${violation.spaceId}" exists on disk but no extension declares it.`,
fix: "Remove the orphan directory, or declare the extension in `extensionPacks`."
};
case "declaredButUnmigrated": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_DECLARED_BUT_UNMIGRATED",
where: spaceRelative(violation.spaceId),
why: `Extension "${violation.spaceId}" is declared in \`extensionPacks\` but has no on-disk migrations directory.`,
fix: "Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused."
};
case "headRefMissing": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HEAD_REF_MISSING",
where: refRelative(violation.spaceId, "head"),
why: `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`,
fix: "Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control."
};
case "headRefNotInGraph": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_HEAD_REF_NOT_IN_GRAPH",
where: refRelative(violation.spaceId, "head"),
why: `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in its migration graph.`,
fix: "Re-emit the contract space migrations, or restore the missing migration package."
};
case "refUnreadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_REF_UNREADABLE",
where: refRelative(violation.spaceId, violation.refName),
why: `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`,
fix: "Repair or remove the corrupt ref file."
};
case "targetMismatch": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_TARGET_MISMATCH",
where: spaceRelative(violation.spaceId),
why: `Contract space "${violation.spaceId}" targets "${violation.actual}" but the project targets "${violation.expected}".`,
fix: "Update the extension to target the configured database, or change the project target."
};
case "disjointness": return {
space: "app",
code: "MIGRATION.CHECK_SPACE_DISJOINTNESS_VIOLATION",
where: migrationPathRelative$1(migrationsDir),
why: `Storage element "${violation.element}" is claimed by multiple contract spaces: ${violation.claimedBy.join(", ")}.`,
fix: "Update the contracts so each storage element is owned by exactly one contract space."
};
case "contractUnreadable": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_CONTRACT_UNREADABLE",
where: migrationFileRelative$1(join(migrationsDir, violation.spaceId), "contract.json"),
why: `Contract for space "${violation.spaceId}" is unreadable: ${violation.detail}`,
fix: "Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract."
};
case "duplicateMigrationHash": return {
space: violation.spaceId,
code: "MIGRATION.CHECK_DUPLICATE_MIGRATION_HASH",
where: spaceRelative(violation.spaceId),
why: `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(", ")}).`,
fix: "Re-emit one of the conflicting packages so each migrationHash is unique."
};
}
}
//#endregion
//#region src/commands/migration-check.ts
function migrationPathRelative(dirPath) {
return relative(process.cwd(), dirPath);
}
function migrationFileRelative(dirPath, fileName) {
return join(migrationPathRelative(dirPath), fileName);
}
function checkFileExists(spaceId, dirPath, dirName, fileName) {
if (!existsSync(join(dirPath, fileName))) return {
space: spaceId,
code: "MIGRATION.CHECK_FILE_MISSING",
where: migrationFileRelative(dirPath, fileName),
why: `${fileName} is missing from ${dirName}`,
fix: "Re-emit the migration package or restore from version control."
};
return null;
}
/**
* Snapshot-store consistency check for one migration package (D6.5):
* absent store entry is not an issue (runner independence, ADR 199 — a
* package dir with only `migration.json` + `ops.json` is legitimate); a
* present entry whose inner `storage.storageHash` disagrees with
* `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store
* entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.
*/
async function checkSnapshotConsistency(spaceId, pkg, migrationsDir) {
let snapshotDir;
try {
snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);
} catch {
return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" declares to="${pkg.metadata.to}", which is not a well-formed contract snapshot hash.`,
fix: "Re-emit the migration package so migration.json declares a valid `sha256:<64hex>` to-hash."
};
}
let raw;
try {
raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);
} catch (error) {
if (MigrationToolsError.is(error) && error.code === "MIGRATION.CONTRACT_SNAPSHOT_MISSING") return null;
return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,
fix: "Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot."
};
}
const snapshotHash = (raw !== null && typeof raw === "object" ? raw : {})["storage"]?.["storageHash"];
if (typeof snapshotHash === "string" && snapshotHash !== pkg.metadata.to) return {
space: spaceId,
code: "MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,
fix: "Re-emit the migration package so migration.json and its contract snapshot agree."
};
return null;
}
/**
* Project the loaded {@link ContractSpaceAggregate} into the
* {@link CheckSpace} rows the multi-space check iterates — one per on-disk
* contract-space directory, in the aggregate's `app`-first ordering. Mirrors
* `migration list`'s `migrationSpaceListEntriesFromAggregate`: space
* membership matches the on-disk directories, package / ref / graph data come
* from `aggregate.space(id)`.
*/
async function enumerateCheckSpaces(aggregate, projectMigrationsDir) {
const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));
const spaces = [];
for (const space of aggregate.spaces()) {
const spaceId = space.spaceId;
if (!isValidSpaceId(spaceId)) continue;
if (!onDiskSpaceIds.has(spaceId)) continue;
const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
spaces.push({
spaceId,
packages: space.packages,
refs: space.refs,
graph: space.graph(),
migrationsDir,
refsDir: spaceRefsDirectory(migrationsDir),
projectMigrationsDir
});
}
return spaces;
}
function checkManifestFilesPresent(space) {
if (!existsSync(space.migrationsDir)) return [];
const loadedDirNames = new Set(space.packages.map((p) => p.dirName));
const failures = [];
let entries;
try {
entries = readdirSync(space.migrationsDir);
} catch {
return failures;
}
for (const entry of entries) {
if (entry.startsWith(".") || entry.startsWith("_") || entry === "refs") continue;
const entryPath = join(space.migrationsDir, entry);
try {
if (!statSync(entryPath).isDirectory()) continue;
} catch {
continue;
}
if (!loadedDirNames.has(entry)) for (const f of ["migration.json", "ops.json"]) {
const fail = checkFileExists(space.spaceId, entryPath, entry, f);
if (fail) failures.push(fail);
}
}
return failures;
}
function checkReachability(space) {
const allToHashes = new Set(space.packages.map((p) => p.metadata.to));
const failures = [];
for (const pkg of space.packages) if (!(pkg.metadata.from === null || allToHashes.has(pkg.metadata.from) || pkg.metadata.from === "sha256:empty")) failures.push({
space: space.spaceId,
code: "MIGRATION.CHECK_UNREACHABLE_MIGRATION",
where: migrationPathRelative(pkg.dirPath),
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
fix: "This migration is unreachable in the graph. Delete it or re-emit a connecting migration."
});
return failures;
}
function checkDanglingRefs(space) {
const failures = [];
for (const [name, entry] of Object.entries(space.refs)) if (!space.graph.nodes.has(entry.hash)) failures.push({
space: space.spaceId,
code: "MIGRATION.CHECK_DANGLING_REF",
where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`
});
return failures;
}
async function checkSpace(space) {
const snapshotFailures = await Promise.all(space.packages.map((pkg) => checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir)));
return [
...checkManifestFilesPresent(space),
...snapshotFailures.filter((f) => f !== null),
...checkReachability(space),
...checkDanglingRefs(space)
];
}
/**
* Policy core of the holistic `migration check`: validates `--space`,
* narrows the pre-enumerated spaces, and runs the per-space explicit graph
* checks (file-existence, snapshot consistency, reachability, dangling
* refs), aggregating every failure into one {@link MigrationCheckResult}.
*
* `--space` validation mirrors `migration list`: an invalid id →
* {@link errorInvalidSpaceId}; an id with no on-disk space →
* {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.
* Aggregate-integrity violations (which already span every space) are folded
* in by the caller, not here.
*/
async function runMigrationCheck(inputs) {
const { spaces, spaceFilter } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return notOk(errorInvalidSpaceId(spaceFilter));
if (spaceFilter !== void 0 && !spaces.some((s) => s.spaceId === spaceFilter)) return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();
if (failures.length === 0) return ok({
ok: true,
failures: [],
summary: "All checks passed"
});
return ok({
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
});
}
async function loadAggregateIntegrityViolations(config, migrationsDir) {
try {
const contractJsonContent = await readFile(resolveContractPath(config), "utf-8");
const familyInstance = config.family.create(createControlStack(config));
const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
const parsedAppContract = JSON.parse(contractJsonContent);
return (await loadContractSpaceAggregate({
migrationsDir,
deserializeContract: (json) => familyInstance.deserializeContract(json),
appContract: familyInstance.deserializeContract(parsedAppContract)
})).checkIntegrity({
declaredExtensions,
checkContracts: true
});
} catch {
return [];
}
}
async function executeMigrationCheckCommand(target, options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "migrations",
value: appMigrationsRelative
}];
if (target) details.push({
label: "target",
value: target
});
const header = formatStyledHeader({
command: "migration check",
description: "Verify artifact and graph integrity",
details,
flags
});
ui.stderr(header);
}
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
if (!loadedAggregate.ok) return {
error: loadedAggregate.failure,
exitCode: 2
};
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
if (target) return await checkSingleTarget(target, {
spaces,
...options.space !== void 0 ? { spaceFilter: options.space } : {},
appMigrationsDir,
appMigrationsRelative
});
const checkResult = await runMigrationCheck({
spaces,
...options.space !== void 0 ? { spaceFilter: options.space } : {}
});
if (!checkResult.ok) return {
error: checkResult.failure,
exitCode: 2
};
const failures = [...checkResult.value.failures];
const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);
const scopedViolations = options.space === void 0 ? allViolations : allViolations.filter((v) => v.kind !== "disjointness" && v.spaceId === options.space);
for (const violation of scopedViolations) failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
if (failures.length === 0) return {
result: {
ok: true,
failures: [],
summary: "All checks passed"
},
exitCode: 0
};
return {
result: {
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
},
exitCode: 4
};
}
/**
* Ranks ref-resolution failure kinds by how informative they are, so a
* single-target check surfaces the most useful failure across spaces instead of
* whichever space failed first. `not-found` (the input matched nothing here)
* says the least; a malformed input, a wrong grammar, or an in-space ambiguity
* all say more.
*/
function refFailureSpecificity(error) {
switch (error.kind) {
case "wrong-grammar": return 3;
case "ambiguous": return 2;
case "invalid-format": return 1;
case "not-found": return 0;
}
}
/**
* Single-target (`check <ref/path>`) mode — resolves a migration reference
* across all contract spaces (or the one space narrowed by `--space <id>`).
*
* Resolution:
* - filesystem path → find the owning space by checking which space's
* `migrationsDir` contains the resolved path; falls back to app-relative
* validation when the path is outside every space dir.
* - ref → `parseMigrationRef` against each in-scope space; collect every
* (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity
* error (qualify with `--space`).
*
* `--space <id>` is validated the same way the holistic path does it:
* invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.
*/
async function checkSingleTarget(target, inputs) {
const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return {
error: errorInvalidSpaceId(spaceFilter),
exitCode: 2
};
if (spaceFilter !== void 0 && !spaces.some((s) => s.spaceId === spaceFilter)) return {
error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),
exitCode: 2
};
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
let matchedSpace;
let matchedPkg;
if (looksLikePath(target)) {
const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);
if (resolvedPath !== null) for (const space of scopedSpaces) {
const found = findPackageByDirPath(space.packages, resolvedPath);
if (found) {
matchedSpace = space;
matchedPkg = found;
break;
}
}
else {
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
if (!resolved.ok) return {
error: resolved.failure,
exitCode: 2
};
const appSpace = scopedSpaces.find((s) => s.spaceId === "app");
if (appSpace) {
matchedSpace = appSpace;
matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);
}
}
} else {
const hits = [];
let bestParseFailure;
for (const space of scopedSpaces) {
const migResult = parseMigrationRef(target, {
graph: space.graph,
refs: space.refs
});
if (!migResult.ok) {
if (bestParseFailure === void 0 || refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)) bestParseFailure = migResult.failure;
continue;
}
const pkg = space.packages.find((p) => p.metadata.migrationHash === migResult.value.migrationHash);
if (pkg) hits.push({
space,
pkg
});
}
if (hits.length > 1) return {
error: errorAmbiguousMigrationRef(target, hits.map((h) => h.space.spaceId)),
exitCode: 2
};
if (hits.length === 1) {
matchedSpace = hits[0].space;
matchedPkg = hits[0].pkg;
} else if (bestParseFailure !== void 0) return {
error: mapRefResolutionError(bestParseFailure),
exitCode: 2
};
}
if (!matchedPkg || !matchedSpace) return {
result: {
ok: false,
failures: [],
summary: `Migration package for "${target}" not found on disk`
},
exitCode: 2
};
const failures = [...checkManifestFilesPresent(matchedSpace)];
for (const f of ["migration.json", "ops.json"]) {
const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);
if (fail) failures.push(fail);
}
const verification = verifyMigrationHash(matchedPkg);
if (!verification.ok) failures.push({
space: matchedSpace.spaceId,
code: "MIGRATION.CHECK_HASH_MISMATCH",
where: migrationFileRelative(matchedPkg.dirPath, "migration.json"),
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
fix: "Re-emit the migration package or restore from version control."
});
const snapshotFailure = await checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg, matchedSpace.projectMigrationsDir);
if (snapshotFailure) failures.push(snapshotFailure);
const resolvedSpaceId = matchedSpace.spaceId !== "app" ? matchedSpace.spaceId : void 0;
if (failures.length === 0) return {
result: {
ok: true,
failures: [],
summary: "All checks passed"
},
exitCode: 0,
...ifDefined("resolvedSpaceId", resolvedSpaceId)
};
return {
result: {
ok: false,
failures,
summary: `${failures.length} integrity failure(s)`
},
exitCode: 4,
...ifDefined("resolvedSpaceId", resolvedSpaceId)
};
}
function createMigrationCheckCommand() {
const command = new Command("check");
setCommandDescriptions(command, "Verify artifact and graph integrity", "Validates that on-disk migration packages are internally consistent\n(hashes match, manifests are complete) and that the graph is well-formed\n(edges connect, refs point at valid nodes). The whole-graph check spans\nevery contract space by default; pass --space <id> to narrow to one. A\nmigration reference checks a single package, resolved across all contract\nspaces (narrow with --space; an ambiguous reference is a precondition failure).\nOffline — does not consult the database.\nExit codes: 0 = all checks passed, 2 = precondition failed\n(unresolved target or unknown --space), 4 = integrity failure(s) found.");
setCommandExamples(command, [
"prisma-next migration check",
"prisma-next migration check --space app",
"prisma-next migration check 20260101-add-users",
"prisma-next migration check 20260101-add-users --space app",
"prisma-next migration check --json"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
command.exitOverride();
addGlobalOptions(command).argument("[target]", "Migration reference: directory name, hash/prefix, ref, or path").option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").action(async (target, options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
let outcome;
try {
outcome = await executeMigrationCheckCommand(target, options, flags, ui);
} catch (error) {
outcome = {
result: {
ok: false,
failures: [],
summary: error instanceof Error ? error.message : String(error)
},
exitCode: 2
};
}
if (outcome.error) {
const envelope = outcome.error.toEnvelope();
if (flags.json) ui.output(formatErrorJson(envelope));
else if (!flags.quiet) ui.error(formatErrorOutput(envelope, flags));
process.exit(outcome.exitCode);
}
const result = outcome.result ?? {
ok: false,
failures: [],
summary: "No check result produced"
};
if (flags.json) ui.output(JSON.stringify(result, null, 2));
else if (!flags.quiet) if (result.ok) {
const spaceSuffix = outcome.resolvedSpaceId !== void 0 ? ` (space: ${outcome.resolvedSpaceId})` : "";
ui.log(`✔ ${result.summary}${spaceSuffix}`);
} else {
for (const f of result.failures) {
ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);
ui.log(` fix: ${f.fix}`);
}
ui.log(`\n${result.summary}`);
}
process.exit(outcome.exitCode);
});
return command;
}
//#endregion
export { enumerateCheckSpaces as n, runMigrationCheck as r, createMigrationCheckCommand as t };
//# sourceMappingURL=migration-check-DMxPzbHl.mjs.map
{"version":3,"file":"migration-check-DMxPzbHl.mjs","names":["migrationPathRelative","migrationFileRelative"],"sources":["../src/utils/integrity-violation-to-check-failure.ts","../src/commands/migration-check.ts"],"sourcesContent":["import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';\nimport { join, relative } from 'pathe';\nimport type { CheckFailure } from '../commands/json/schemas';\n\nexport type { CheckFailure } from '../commands/json/schemas';\n\nfunction migrationPathRelative(dirPath: string): string {\n return relative(process.cwd(), dirPath);\n}\n\nfunction migrationFileRelative(dirPath: string, fileName: string): string {\n return join(migrationPathRelative(dirPath), fileName);\n}\n\n/**\n * Map one {@link IntegrityViolation} onto a `migration check` failure row.\n * Sole catalogue mapping from integrity violations to `MIGRATION.CHECK_*` codes.\n */\nexport function integrityViolationToCheckFailure(\n violation: IntegrityViolation,\n migrationsDir: string,\n): CheckFailure {\n const spaceRelative = (spaceId: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId));\n const packageRelative = (spaceId: string, dirName: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId, dirName));\n const refRelative = (spaceId: string, refName: string): string =>\n migrationPathRelative(join(migrationsDir, spaceId, 'refs', `${refName}.json`));\n\n switch (violation.kind) {\n case 'hashMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HASH_MISMATCH',\n where: migrationFileRelative(\n join(migrationsDir, violation.spaceId, violation.dirName),\n 'migration.json',\n ),\n why: `Stored hash ${violation.stored} does not match recomputed hash ${violation.computed}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n case 'providedInvariantsMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_PROVIDED_INVARIANTS_MISMATCH',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" providedInvariants in migration.json disagrees with ops.json.`,\n fix: 'Re-emit the migration package so migration.json and ops.json agree.',\n };\n case 'packageUnloadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_PACKAGE_UNLOADABLE',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" could not be loaded: ${violation.detail}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n case 'sameSourceAndTarget':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_NOOP_SELF_EDGE',\n where: packageRelative(violation.spaceId, violation.dirName),\n why: `Migration \"${violation.dirName}\" in space \"${violation.spaceId}\" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,\n fix: 'Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op.',\n };\n case 'orphanSpaceDir':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_ORPHAN_SPACE_DIR',\n where: spaceRelative(violation.spaceId),\n why: `Contract-space directory \"${violation.spaceId}\" exists on disk but no extension declares it.`,\n fix: 'Remove the orphan directory, or declare the extension in `extensionPacks`.',\n };\n case 'declaredButUnmigrated':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_DECLARED_BUT_UNMIGRATED',\n where: spaceRelative(violation.spaceId),\n why: `Extension \"${violation.spaceId}\" is declared in \\`extensionPacks\\` but has no on-disk migrations directory.`,\n fix: 'Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused.',\n };\n case 'headRefMissing':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HEAD_REF_MISSING',\n where: refRelative(violation.spaceId, 'head'),\n why: `Head ref \\`refs/head.json\\` is missing for contract space \"${violation.spaceId}\".`,\n fix: 'Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control.',\n };\n case 'headRefNotInGraph':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_HEAD_REF_NOT_IN_GRAPH',\n where: refRelative(violation.spaceId, 'head'),\n why: `Head ref ${violation.hash} for contract space \"${violation.spaceId}\" is not present in its migration graph.`,\n fix: 'Re-emit the contract space migrations, or restore the missing migration package.',\n };\n case 'refUnreadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_REF_UNREADABLE',\n where: refRelative(violation.spaceId, violation.refName),\n why: `Ref \"${violation.refName}\" for contract space \"${violation.spaceId}\" is unreadable: ${violation.detail}`,\n fix: 'Repair or remove the corrupt ref file.',\n };\n case 'targetMismatch':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_TARGET_MISMATCH',\n where: spaceRelative(violation.spaceId),\n why: `Contract space \"${violation.spaceId}\" targets \"${violation.actual}\" but the project targets \"${violation.expected}\".`,\n fix: 'Update the extension to target the configured database, or change the project target.',\n };\n case 'disjointness':\n return {\n space: 'app',\n code: 'MIGRATION.CHECK_SPACE_DISJOINTNESS_VIOLATION',\n where: migrationPathRelative(migrationsDir),\n why: `Storage element \"${violation.element}\" is claimed by multiple contract spaces: ${violation.claimedBy.join(', ')}.`,\n fix: 'Update the contracts so each storage element is owned by exactly one contract space.',\n };\n case 'contractUnreadable':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_CONTRACT_UNREADABLE',\n where: migrationFileRelative(join(migrationsDir, violation.spaceId), 'contract.json'),\n why: `Contract for space \"${violation.spaceId}\" is unreadable: ${violation.detail}`,\n fix: 'Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract.',\n };\n case 'duplicateMigrationHash':\n return {\n space: violation.spaceId,\n code: 'MIGRATION.CHECK_DUPLICATE_MIGRATION_HASH',\n where: spaceRelative(violation.spaceId),\n why: `Multiple migrations in space \"${violation.spaceId}\" share migrationHash \"${violation.migrationHash}\" (${violation.dirNames.join(', ')}).`,\n fix: 'Re-emit one of the conflicting packages so each migrationHash is unique.',\n };\n }\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport type {\n ContractSpaceAggregate,\n IntegrityViolation,\n} from '@prisma-next/migration-tools/aggregate';\nimport { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';\nimport {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { verifyMigrationHash } from '@prisma-next/migration-tools/hash';\nimport type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';\nimport {\n parseMigrationRef,\n type RefResolutionError,\n} from '@prisma-next/migration-tools/ref-resolution';\nimport type { Refs } from '@prisma-next/migration-tools/refs';\nimport {\n isValidSpaceId,\n listContractSpaceDirectories,\n spaceMigrationDirectory,\n spaceRefsDirectory,\n} from '@prisma-next/migration-tools/spaces';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative } from 'pathe';\nimport {\n type CliStructuredError,\n errorAmbiguousMigrationRef,\n errorInvalidSpaceId,\n errorSpaceNotFound,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';\nimport { formatErrorJson, formatErrorOutput } from '../utils/formatters/errors';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { integrityViolationToCheckFailure } from '../utils/integrity-violation-to-check-failure';\nimport {\n findPackageByDirPath,\n looksLikePath,\n resolveAppTargetPath,\n resolveTargetPathAcrossSpaces,\n} from '../utils/migration-path-target';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type { CheckFailure, MigrationCheckResult } from './json/schemas';\nimport { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';\n\ninterface MigrationCheckOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly space?: string;\n}\n\nexport type { CheckFailure, MigrationCheckResult } from './json/schemas';\nexport { migrationCheckResultSchema } from './json/schemas';\n\nfunction migrationPathRelative(dirPath: string): string {\n return relative(process.cwd(), dirPath);\n}\n\nfunction migrationFileRelative(dirPath: string, fileName: string): string {\n return join(migrationPathRelative(dirPath), fileName);\n}\n\nfunction checkFileExists(\n spaceId: string,\n dirPath: string,\n dirName: string,\n fileName: string,\n): CheckFailure | null {\n if (!existsSync(join(dirPath, fileName))) {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_FILE_MISSING',\n where: migrationFileRelative(dirPath, fileName),\n why: `${fileName} is missing from ${dirName}`,\n fix: 'Re-emit the migration package or restore from version control.',\n };\n }\n return null;\n}\n\n/**\n * Snapshot-store consistency check for one migration package (D6.5):\n * absent store entry is not an issue (runner independence, ADR 199 — a\n * package dir with only `migration.json` + `ops.json` is legitimate); a\n * present entry whose inner `storage.storageHash` disagrees with\n * `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store\n * entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.\n */\nasync function checkSnapshotConsistency(\n spaceId: string,\n pkg: OnDiskMigrationPackage,\n migrationsDir: string,\n): Promise<CheckFailure | null> {\n let snapshotDir: string;\n try {\n snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);\n } catch {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" declares to=\"${pkg.metadata.to}\", which is not a well-formed contract snapshot hash.`,\n fix: 'Re-emit the migration package so migration.json declares a valid `sha256:<64hex>` to-hash.',\n };\n }\n\n let raw: unknown;\n try {\n raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);\n } catch (error) {\n if (MigrationToolsError.is(error) && error.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING') {\n return null;\n }\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n };\n }\n const record = raw !== null && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};\n const storage = record['storage'] as Record<string, unknown> | undefined;\n const snapshotHash = storage?.['storageHash'];\n if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {\n return {\n space: spaceId,\n code: 'MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,\n fix: 'Re-emit the migration package so migration.json and its contract snapshot agree.',\n };\n }\n return null;\n}\n\n/**\n * One contract space's on-disk state, resolved for the explicit graph\n * checks `runMigrationCheck` runs per space: the space's migration\n * packages, its user-authored refs, its induced graph, and the absolute\n * `migrations/<space>/` + `migrations/<space>/refs/` directories the\n * file-existence and dangling-ref `where` paths are derived from.\n */\nexport interface CheckSpace {\n readonly spaceId: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly refs: Refs;\n readonly graph: MigrationGraph;\n readonly migrationsDir: string;\n readonly refsDir: string;\n /** Migrations root the contract-snapshot store lives under — shared by every space. */\n readonly projectMigrationsDir: string;\n}\n\n/**\n * Project the loaded {@link ContractSpaceAggregate} into the\n * {@link CheckSpace} rows the multi-space check iterates — one per on-disk\n * contract-space directory, in the aggregate's `app`-first ordering. Mirrors\n * `migration list`'s `migrationSpaceListEntriesFromAggregate`: space\n * membership matches the on-disk directories, package / ref / graph data come\n * from `aggregate.space(id)`.\n */\nexport async function enumerateCheckSpaces(\n aggregate: ContractSpaceAggregate,\n projectMigrationsDir: string,\n): Promise<readonly CheckSpace[]> {\n const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);\n const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));\n const spaces: CheckSpace[] = [];\n for (const space of aggregate.spaces()) {\n const spaceId = space.spaceId;\n if (!isValidSpaceId(spaceId)) continue;\n if (!onDiskSpaceIds.has(spaceId)) continue;\n const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);\n spaces.push({\n spaceId,\n packages: space.packages,\n refs: space.refs,\n graph: space.graph(),\n migrationsDir,\n refsDir: spaceRefsDirectory(migrationsDir),\n projectMigrationsDir,\n });\n }\n return spaces;\n}\n\nfunction checkManifestFilesPresent(space: CheckSpace): readonly CheckFailure[] {\n if (!existsSync(space.migrationsDir)) return [];\n const loadedDirNames = new Set(space.packages.map((p) => p.dirName));\n const failures: CheckFailure[] = [];\n let entries: string[];\n try {\n entries = readdirSync(space.migrationsDir);\n } catch {\n return failures;\n }\n for (const entry of entries) {\n if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;\n const entryPath = join(space.migrationsDir, entry);\n try {\n if (!statSync(entryPath).isDirectory()) continue;\n } catch {\n continue;\n }\n if (!loadedDirNames.has(entry)) {\n for (const f of ['migration.json', 'ops.json']) {\n const fail = checkFileExists(space.spaceId, entryPath, entry, f);\n if (fail) failures.push(fail);\n }\n }\n }\n return failures;\n}\n\nfunction checkReachability(space: CheckSpace): readonly CheckFailure[] {\n const allToHashes = new Set(space.packages.map((p) => p.metadata.to));\n const failures: CheckFailure[] = [];\n for (const pkg of space.packages) {\n const isReachable =\n pkg.metadata.from === null ||\n allToHashes.has(pkg.metadata.from) ||\n pkg.metadata.from === 'sha256:empty';\n if (!isReachable) {\n failures.push({\n space: space.spaceId,\n code: 'MIGRATION.CHECK_UNREACHABLE_MIGRATION',\n where: migrationPathRelative(pkg.dirPath),\n why: `Migration \"${pkg.dirName}\" starts from ${pkg.metadata.from} which no other migration produces`,\n fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',\n });\n }\n }\n return failures;\n}\n\nfunction checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {\n const failures: CheckFailure[] = [];\n for (const [name, entry] of Object.entries(space.refs)) {\n if (!space.graph.nodes.has(entry.hash)) {\n failures.push({\n space: space.spaceId,\n code: 'MIGRATION.CHECK_DANGLING_REF',\n where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),\n why: `Ref \"${name}\" points at ${entry.hash} which does not exist in the migration graph`,\n fix: `Update the ref with \\`prisma-next ref set ${name} <valid-hash>\\` or delete it.`,\n });\n }\n }\n return failures;\n}\n\nasync function checkSpace(space: CheckSpace): Promise<readonly CheckFailure[]> {\n const snapshotFailures = await Promise.all(\n space.packages.map((pkg) =>\n checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir),\n ),\n );\n return [\n ...checkManifestFilesPresent(space),\n ...snapshotFailures.filter((f): f is CheckFailure => f !== null),\n ...checkReachability(space),\n ...checkDanglingRefs(space),\n ];\n}\n\n/**\n * Inputs for {@link runMigrationCheck} — the multi-space policy core of\n * the holistic (no-arg) `migration check`. Enumeration is supplied by the\n * caller (the CLI shell builds it from {@link enumerateCheckSpaces}); the\n * core does not touch config, flags, or streams.\n */\nexport interface RunMigrationCheckInputs {\n readonly spaces: readonly CheckSpace[];\n readonly spaceFilter?: string;\n}\n\n/**\n * Policy core of the holistic `migration check`: validates `--space`,\n * narrows the pre-enumerated spaces, and runs the per-space explicit graph\n * checks (file-existence, snapshot consistency, reachability, dangling\n * refs), aggregating every failure into one {@link MigrationCheckResult}.\n *\n * `--space` validation mirrors `migration list`: an invalid id →\n * {@link errorInvalidSpaceId}; an id with no on-disk space →\n * {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.\n * Aggregate-integrity violations (which already span every space) are folded\n * in by the caller, not here.\n */\nexport async function runMigrationCheck(\n inputs: RunMigrationCheckInputs,\n): Promise<Result<MigrationCheckResult, CliStructuredError>> {\n const { spaces, spaceFilter } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return notOk(errorInvalidSpaceId(spaceFilter));\n }\n if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {\n return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;\n\n const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();\n if (failures.length === 0) {\n return ok({ ok: true, failures: [], summary: 'All checks passed' });\n }\n return ok({ ok: false, failures, summary: `${failures.length} integrity failure(s)` });\n}\n\nasync function loadAggregateIntegrityViolations(\n config: Awaited<ReturnType<typeof loadConfig>>,\n migrationsDir: string,\n): Promise<readonly IntegrityViolation[]> {\n try {\n const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');\n const familyInstance = config.family.create(createControlStack(config));\n const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);\n\n const parsedAppContract: unknown = JSON.parse(contractJsonContent);\n const aggregate = await loadContractSpaceAggregate({\n migrationsDir,\n deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),\n appContract: familyInstance.deserializeContract(parsedAppContract),\n });\n return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });\n } catch {\n return [];\n }\n}\n\ninterface MigrationCheckOutcome {\n readonly result?: MigrationCheckResult;\n readonly error?: CliStructuredError;\n readonly exitCode: number;\n readonly resolvedSpaceId?: string;\n}\n\nasync function executeMigrationCheckCommand(\n target: string | undefined,\n options: MigrationCheckOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<MigrationCheckOutcome> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =\n resolveMigrationPaths(options.config, config);\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: appMigrationsRelative },\n ];\n if (target) {\n details.push({ label: 'target', value: target });\n }\n const header = formatStyledHeader({\n command: 'migration check',\n description: 'Verify artifact and graph integrity',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n const loadedAggregate = await buildReadAggregate(config, { migrationsDir });\n if (!loadedAggregate.ok) {\n return { error: loadedAggregate.failure, exitCode: PRECONDITION };\n }\n\n const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);\n\n if (target) {\n return await checkSingleTarget(target, {\n spaces,\n ...(options.space !== undefined ? { spaceFilter: options.space } : {}),\n appMigrationsDir,\n appMigrationsRelative,\n });\n }\n\n const checkResult = await runMigrationCheck({\n spaces,\n ...(options.space !== undefined ? { spaceFilter: options.space } : {}),\n });\n if (!checkResult.ok) {\n return { error: checkResult.failure, exitCode: PRECONDITION };\n }\n\n const failures: CheckFailure[] = [...checkResult.value.failures];\n const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);\n const scopedViolations =\n options.space === undefined\n ? allViolations\n : allViolations.filter((v) => v.kind !== 'disjointness' && v.spaceId === options.space);\n for (const violation of scopedViolations) {\n failures.push(integrityViolationToCheckFailure(violation, migrationsDir));\n }\n\n if (failures.length === 0) {\n return {\n result: { ok: true, failures: [], summary: 'All checks passed' },\n exitCode: OK,\n };\n }\n\n return {\n result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },\n exitCode: INTEGRITY_FAILED,\n };\n}\n\ninterface SingleTargetInputs {\n readonly spaces: readonly CheckSpace[];\n readonly spaceFilter?: string;\n readonly appMigrationsDir: string;\n readonly appMigrationsRelative: string;\n}\n\n/**\n * Ranks ref-resolution failure kinds by how informative they are, so a\n * single-target check surfaces the most useful failure across spaces instead of\n * whichever space failed first. `not-found` (the input matched nothing here)\n * says the least; a malformed input, a wrong grammar, or an in-space ambiguity\n * all say more.\n */\nfunction refFailureSpecificity(error: RefResolutionError): number {\n switch (error.kind) {\n case 'wrong-grammar':\n return 3;\n case 'ambiguous':\n return 2;\n case 'invalid-format':\n return 1;\n case 'not-found':\n return 0;\n }\n}\n\n/**\n * Single-target (`check <ref/path>`) mode — resolves a migration reference\n * across all contract spaces (or the one space narrowed by `--space <id>`).\n *\n * Resolution:\n * - filesystem path → find the owning space by checking which space's\n * `migrationsDir` contains the resolved path; falls back to app-relative\n * validation when the path is outside every space dir.\n * - ref → `parseMigrationRef` against each in-scope space; collect every\n * (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity\n * error (qualify with `--space`).\n *\n * `--space <id>` is validated the same way the holistic path does it:\n * invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.\n */\nasync function checkSingleTarget(\n target: string,\n inputs: SingleTargetInputs,\n): Promise<MigrationCheckOutcome> {\n const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return { error: errorInvalidSpaceId(spaceFilter), exitCode: PRECONDITION };\n }\n if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {\n return {\n error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),\n exitCode: PRECONDITION,\n };\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;\n\n let matchedSpace: CheckSpace | undefined;\n let matchedPkg: OnDiskMigrationPackage | undefined;\n\n if (looksLikePath(target)) {\n const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);\n if (resolvedPath !== null) {\n for (const space of scopedSpaces) {\n const found = findPackageByDirPath(space.packages, resolvedPath);\n if (found) {\n matchedSpace = space;\n matchedPkg = found;\n break;\n }\n }\n } else {\n // Path outside every space dir — fall back to app-relative validation\n const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);\n if (!resolved.ok) {\n return { error: resolved.failure, exitCode: PRECONDITION };\n }\n const appSpace = scopedSpaces.find((s) => s.spaceId === 'app');\n if (appSpace) {\n matchedSpace = appSpace;\n matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);\n }\n }\n } else {\n // Ref resolution: try each in-scope space, collect all hits.\n const hits: Array<{ space: CheckSpace; pkg: OnDiskMigrationPackage }> = [];\n let bestParseFailure: RefResolutionError | undefined;\n for (const space of scopedSpaces) {\n const migResult = parseMigrationRef(target, { graph: space.graph, refs: space.refs });\n if (!migResult.ok) {\n // Keep scanning — a later space may hold a hit that must not be discarded.\n // When no space yields a hit, keep the most informative failure rather than\n // whichever space failed first (the kind is space-dependent).\n if (\n bestParseFailure === undefined ||\n refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)\n ) {\n bestParseFailure = migResult.failure;\n }\n continue;\n }\n const pkg = space.packages.find(\n (p) => p.metadata.migrationHash === migResult.value.migrationHash,\n );\n if (pkg) {\n hits.push({ space, pkg });\n }\n }\n\n if (hits.length > 1) {\n const spaceIds = hits.map((h) => h.space.spaceId);\n return {\n error: errorAmbiguousMigrationRef(target, spaceIds),\n exitCode: PRECONDITION,\n };\n }\n\n if (hits.length === 1) {\n matchedSpace = hits[0]!.space;\n matchedPkg = hits[0]!.pkg;\n } else if (bestParseFailure !== undefined) {\n // The ref didn't resolve in any in-scope space — surface the most informative\n // parse failure through the shared ref-resolution envelope (CONTRACT.VERIFY_FAILED) the\n // earlier work established, rather than a bespoke string. (Ref-resolved-but-\n // no-package falls through to the \"not found on disk\" result below.)\n return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };\n }\n }\n\n if (!matchedPkg || !matchedSpace) {\n return {\n result: {\n ok: false,\n failures: [],\n summary: `Migration package for \"${target}\" not found on disk`,\n },\n exitCode: PRECONDITION,\n };\n }\n\n const failures: CheckFailure[] = [...checkManifestFilesPresent(matchedSpace)];\n\n for (const f of ['migration.json', 'ops.json']) {\n const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);\n if (fail) failures.push(fail);\n }\n\n const verification = verifyMigrationHash(matchedPkg);\n if (!verification.ok) {\n failures.push({\n space: matchedSpace.spaceId,\n code: 'MIGRATION.CHECK_HASH_MISMATCH',\n where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),\n why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,\n fix: 'Re-emit the migration package or restore from version control.',\n });\n }\n\n const snapshotFailure = await checkSnapshotConsistency(\n matchedSpace.spaceId,\n matchedPkg,\n matchedSpace.projectMigrationsDir,\n );\n if (snapshotFailure) failures.push(snapshotFailure);\n\n const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;\n\n if (failures.length === 0) {\n return {\n result: { ok: true, failures: [], summary: 'All checks passed' },\n exitCode: OK,\n ...ifDefined('resolvedSpaceId', resolvedSpaceId),\n };\n }\n return {\n result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },\n exitCode: INTEGRITY_FAILED,\n ...ifDefined('resolvedSpaceId', resolvedSpaceId),\n };\n}\n\nexport function createMigrationCheckCommand(): Command {\n const command = new Command('check');\n setCommandDescriptions(\n command,\n 'Verify artifact and graph integrity',\n 'Validates that on-disk migration packages are internally consistent\\n' +\n '(hashes match, manifests are complete) and that the graph is well-formed\\n' +\n '(edges connect, refs point at valid nodes). The whole-graph check spans\\n' +\n 'every contract space by default; pass --space <id> to narrow to one. A\\n' +\n 'migration reference checks a single package, resolved across all contract\\n' +\n 'spaces (narrow with --space; an ambiguous reference is a precondition failure).\\n' +\n 'Offline — does not consult the database.\\n' +\n 'Exit codes: 0 = all checks passed, 2 = precondition failed\\n' +\n '(unresolved target or unknown --space), 4 = integrity failure(s) found.',\n );\n setCommandExamples(command, [\n 'prisma-next migration check',\n 'prisma-next migration check --space app',\n 'prisma-next migration check 20260101-add-users',\n 'prisma-next migration check 20260101-add-users --space app',\n 'prisma-next migration check --json',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n command.exitOverride();\n addGlobalOptions(command)\n .argument('[target]', 'Migration reference: directory name, hash/prefix, ref, or path')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .action(async (target: string | undefined, options: MigrationCheckOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n let outcome: MigrationCheckOutcome;\n try {\n outcome = await executeMigrationCheckCommand(target, options, flags, ui);\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n outcome = {\n result: { ok: false, failures: [], summary: msg },\n exitCode: PRECONDITION,\n };\n }\n\n if (outcome.error) {\n const envelope = outcome.error.toEnvelope();\n if (flags.json) {\n ui.output(formatErrorJson(envelope));\n } else if (!flags.quiet) {\n ui.error(formatErrorOutput(envelope, flags));\n }\n process.exit(outcome.exitCode);\n }\n\n const result = outcome.result ?? {\n ok: false,\n failures: [],\n summary: 'No check result produced',\n };\n\n if (flags.json) {\n ui.output(JSON.stringify(result, null, 2));\n } else if (!flags.quiet) {\n if (result.ok) {\n const spaceSuffix =\n outcome.resolvedSpaceId !== undefined ? ` (space: ${outcome.resolvedSpaceId})` : '';\n ui.log(`✔ ${result.summary}${spaceSuffix}`);\n } else {\n for (const f of result.failures) {\n ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);\n ui.log(` fix: ${f.fix}`);\n }\n ui.log(`\\n${result.summary}`);\n }\n }\n\n process.exit(outcome.exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { A as formatStyledHeader, B as errorContractValidationFailed, U as errorDriverRequired, V as errorDatabaseConnectionRequired, W as errorFileNotFound, ct as errorUnexpected, i as maskConnectionUrl, o as resolveContractPath, ot as errorTargetMigrationNotSupported, t as addGlobalOptions } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-2kwLMBSf.mjs";
import { loadConfig } from "@prisma-next/config-loader";
import { notOk, ok } from "@prisma-next/utils/result";
import { readFile } from "node:fs/promises";
import { hasMigrations } from "@prisma-next/framework-components/control";
import { relative, resolve } from "node:path";
//#region src/utils/migration-command-scaffold.ts
/**
* Prepares the shared context for migration commands (db init, db update).
*
* Handles: config loading, contract file reading, JSON parsing, connection resolution,
* driver/migration-support validation, client creation, and header output.
*
* Returns a Result with either the resolved context or a structured error.
*/
async function prepareMigrationContext(options, flags, ui, descriptor) {
const config = await loadConfig(options.config);
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
const contractPathAbsolute = resolveContractPath(config);
const contractPath = relative(process.cwd(), contractPathAbsolute);
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "contract",
value: contractPath
}];
if (options.db) details.push({
label: "database",
value: maskConnectionUrl(options.db)
});
if (options.dryRun) details.push({
label: "mode",
value: "dry run"
});
const header = formatStyledHeader({
command: descriptor.commandName,
description: descriptor.description,
url: descriptor.url,
details,
flags
});
ui.stderr(header);
}
let contractJsonContent;
try {
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
} catch (error) {
if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
why: `Contract file not found at ${contractPathAbsolute}`,
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
}
let contractJson;
try {
contractJson = JSON.parse(contractJsonContent);
} catch (error) {
return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const dbConnection = options.db ?? config.db?.connection;
if (!dbConnection) return notOk(errorDatabaseConnectionRequired({
why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: descriptor.commandName
}));
if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }));
if (!hasMigrations(config.target)) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
const onProgress = createProgressAdapter({
ui,
flags
});
return ok({
client,
contractJson,
dbConnection,
onProgress,
configPath,
contractPath,
contractPathAbsolute,
config
});
}
/**
* Registers the shared CLI options for migration commands (db init, db update).
*/
function addMigrationCommandOptions(command) {
addGlobalOptions(command);
return command.option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--dry-run", "Preview planned operations without applying", false);
}
//#endregion
export { prepareMigrationContext as n, addMigrationCommandOptions as t };
//# sourceMappingURL=migration-command-scaffold-DB_i9nBC.mjs.map
{"version":3,"file":"migration-command-scaffold-DB_i9nBC.mjs","names":[],"sources":["../src/utils/migration-command-scaffold.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { hasMigrations } from '@prisma-next/framework-components/control';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport type { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport type { ControlClient } from '../control-api/types';\nimport {\n type CliStructuredError,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from './cli-errors';\nimport { addGlobalOptions, maskConnectionUrl, resolveContractPath } from './command-helpers';\nimport { formatStyledHeader } from './formatters/styled';\nimport type { GlobalFlags } from './global-flags';\nimport { createProgressAdapter } from './progress-adapter';\nimport type { TerminalUI } from './terminal-ui';\n\n/**\n * Resolved context for a migration command.\n * Contains everything needed to invoke a control-api operation.\n */\nexport interface MigrationContext {\n readonly client: ControlClient;\n readonly contractJson: Record<string, unknown>;\n readonly dbConnection: unknown;\n readonly onProgress: ReturnType<typeof createProgressAdapter>;\n readonly configPath: string;\n readonly contractPath: string;\n readonly contractPathAbsolute: string;\n readonly config: Awaited<ReturnType<typeof loadConfig>>;\n}\n\n/**\n * Command-specific configuration for the shared scaffold.\n */\nexport interface MigrationCommandDescriptor {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\n/**\n * Prepares the shared context for migration commands (db init, db update).\n *\n * Handles: config loading, contract file reading, JSON parsing, connection resolution,\n * driver/migration-support validation, client creation, and header output.\n *\n * Returns a Result with either the resolved context or a structured error.\n */\nexport async function prepareMigrationContext(\n options: { readonly db?: string; readonly config?: string; readonly dryRun?: boolean },\n flags: GlobalFlags,\n ui: TerminalUI,\n descriptor: MigrationCommandDescriptor,\n): Promise<Result<MigrationContext, CliStructuredError>> {\n // Load config\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header to stderr (decoration)\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n }\n if (options.dryRun) {\n details.push({ label: 'mode', value: 'dry run' });\n }\n const header = formatStyledHeader({\n command: descriptor.commandName,\n description: descriptor.description,\n url: descriptor.url,\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Load contract file\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n // Parse contract JSON\n let contractJson: Record<string, unknown>;\n try {\n contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n // Resolve database connection (--db flag or config.db.connection)\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: descriptor.commandName,\n }),\n );\n }\n\n // Check for driver\n if (!config.driver) {\n return notOk(\n errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),\n );\n }\n\n if (!hasMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n // Create control client\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n // Create progress adapter\n const onProgress = createProgressAdapter({ ui, flags });\n\n return ok({\n client,\n contractJson,\n dbConnection,\n onProgress,\n configPath,\n contractPath,\n contractPathAbsolute,\n config,\n });\n}\n\n/**\n * Registers the shared CLI options for migration commands (db init, db update).\n */\nexport function addMigrationCommandOptions(command: Command): Command {\n addGlobalOptions(command);\n return command\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--dry-run', 'Preview planned operations without applying', false);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,eAAsB,wBACpB,SACA,OACA,IACA,YACuD;CAEvD,MAAM,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC9C,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,IAAI,GAAG,QAAQ,QAAQ,MAAM,CAAC,IAC/C;CACJ,MAAM,uBAAuB,oBAAoB,MAAM;CACvD,MAAM,eAAe,SAAS,QAAQ,IAAI,GAAG,oBAAoB;CAGjE,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;EAAW,GACrC;GAAE,OAAO;GAAY,OAAO;EAAa,CAC3C;EACA,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,EAAE;EAAE,CAAC;EAE1E,IAAI,QAAQ,QACV,QAAQ,KAAK;GAAE,OAAO;GAAQ,OAAO;EAAU,CAAC;EAElD,MAAM,SAAS,mBAAmB;GAChC,SAAS,WAAW;GACpB,aAAa,WAAW;GACxB,KAAK,WAAW;GAChB;GACA;EACF,CAAC;EACD,GAAG,OAAO,MAAM;CAClB;CAGA,IAAI;CACJ,IAAI;EACF,sBAAsB,MAAM,SAAS,sBAAsB,OAAO;CACpE,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAU,MAA4B,SAAS,UAClE,OAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;EACjH,CAAC,CACH;EAEF,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IAC7F,CAAC,CACH;CACF;CAGA,IAAI;CACJ,IAAI;EACF,eAAe,KAAK,MAAM,mBAAmB;CAC/C,SAAS,OAAO;EACd,OAAO,MACL,8BACE,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAClF,EAAE,OAAO,EAAE,MAAM,qBAAqB,EAAE,CAC1C,CACF;CACF;CAGA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,WAAW,YAAY,yBAAyB,WAAW;EACvG,aAAa,WAAW;CAC1B,CAAC,CACH;CAIF,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAAE,KAAK,iCAAiC,WAAW,cAAc,CAAC,CACxF;CAGF,IAAI,CAAC,cAAc,OAAO,MAAM,GAC9B,OAAO,MACL,iCAAiC,EAC/B,KAAK,WAAW,OAAO,OAAO,GAAG,+BACnC,CAAC,CACH;CAIF,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,CAAC;CAC5C,CAAC;CAGD,MAAM,aAAa,sBAAsB;EAAE;EAAI;CAAM,CAAC;CAEtD,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH;;;;AAKA,SAAgB,2BAA2B,SAA2B;CACpE,iBAAiB,OAAO;CACxB,OAAO,QACJ,OAAO,cAAc,4BAA4B,CAAC,CAClD,OAAO,mBAAmB,+BAA+B,CAAC,CAC1D,OAAO,aAAa,+CAA+C,KAAK;AAC7E"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, d as setCommandSeeAlso, dt as requireLiveDatabase, f as targetSupportsMigrations, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createControlClient } from "./client-2kwLMBSf.mjs";
import { _ as migrationListEmptySource, g as abbreviateContractHash, o as createAnsiMigrationListStyler, s as IDENTITY_MIGRATION_LIST_STYLER, v as migrationListForwardArrow } from "./migration-graph-command-render-B83xrnNy.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import stringWidth from "string-width";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/formatters/migration-log-table.ts
const HEADING_APPLIED_AT = "Applied at";
const HEADING_SPACE = "Space";
const HEADING_MIGRATION = "Migration";
const HEADING_CHANGE = "Change";
const HEADING_OPS = "Ops";
const COLUMN_SEPARATOR = " ";
const DIVIDER_CHAR = "─";
const ASCII_DIVIDER_CHAR = "-";
function sortLedgerEntries(entries) {
return [...entries].sort((left, right) => {
const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();
if (timeDiff !== 0) return timeDiff;
const spaceDiff = left.space.localeCompare(right.space);
if (spaceDiff !== 0) return spaceDiff;
return left.migrationName.localeCompare(right.migrationName);
});
}
function pad2(value) {
return String(value).padStart(2, "0");
}
function formatLedgerAppliedAt(date, mode) {
if (mode === "iso") return date.toISOString();
if (mode === "utc") return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;
const offsetMinutes = -date.getTimezoneOffset();
const sign = offsetMinutes >= 0 ? "+" : "-";
const absoluteOffset = Math.abs(offsetMinutes);
const offsetHours = pad2(Math.floor(absoluteOffset / 60));
const offsetMins = pad2(absoluteOffset % 60);
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;
}
function formatHashEndpoint(hash, glyphMode = "unicode") {
if (hash === null) return migrationListEmptySource(glyphMode);
return abbreviateContractHash(hash);
}
function formatHashTransition(from, to, glyphMode = "unicode") {
return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;
}
function styleHashTransition(from, to, styler, glyphMode = "unicode") {
return `${from === null ? styler.glyph(migrationListEmptySource(glyphMode)) : styler.sourceHash(abbreviateContractHash(from))} ${styler.glyph(migrationListForwardArrow(glyphMode))} ${styler.destHash(abbreviateContractHash(to))}`;
}
function padVisible(text, targetWidth) {
const padding = Math.max(0, targetWidth - stringWidth(text));
return text + " ".repeat(padding);
}
function columnWidth(values) {
return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);
}
function padDividerCell(valueWidth, dividerChar) {
return dividerChar.repeat(valueWidth + 2);
}
function padTextCell(value, valueWidth) {
return ` ${padVisible(value, valueWidth)} `;
}
function padOpsCell(value, valueWidth) {
const padding = Math.max(0, valueWidth - stringWidth(value));
return ` ${" ".repeat(padding)}${value} `;
}
function renderMigrationLogTable(entries, options = {}) {
const sorted = sortLedgerEntries(entries);
if (sorted.length === 0) return "";
const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;
const glyphMode = options.glyphMode ?? "unicode";
const dividerChar = glyphMode === "ascii" ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;
const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;
const timestampMode = options.utc ? "utc" : "local";
const rows = sorted.map((entry) => ({
appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),
space: entry.space,
migrationName: entry.migrationName,
transition: formatHashTransition(entry.from, entry.to, glyphMode),
ops: `${entry.operationCount} ops`,
from: entry.from,
to: entry.to
}));
const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);
const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;
const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);
const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);
const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);
const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];
if (showSpace) headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));
headingParts.push(padTextCell(HEADING_MIGRATION, nameWidth), padTextCell(HEADING_CHANGE, transitionWidth), padOpsCell(HEADING_OPS, opsWidth));
const heading = headingParts.join(COLUMN_SEPARATOR);
const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];
if (showSpace) dividerParts.push(padDividerCell(spaceWidth, dividerChar));
dividerParts.push(padDividerCell(nameWidth, dividerChar), padDividerCell(transitionWidth, dividerChar), padDividerCell(opsWidth, dividerChar));
return [
heading,
dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR),
...rows.map((row) => {
const parts = [padTextCell(row.appliedAt, appliedAtWidth)];
if (showSpace) parts.push(padTextCell(row.space, spaceWidth));
parts.push(padTextCell(styler.dirName(row.migrationName), nameWidth), padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth), padOpsCell(row.ops, opsWidth));
return parts.join(COLUMN_SEPARATOR);
})
].join("\n");
}
function serializeLedgerEntriesForJson(entries) {
return sortLedgerEntries(entries).map((entry) => ({
space: entry.space,
name: entry.migrationName,
hash: entry.migrationHash,
fromContract: entry.from,
toContract: entry.to,
appliedAt: formatLedgerAppliedAt(entry.appliedAt, "iso"),
operationCount: entry.operationCount
}));
}
const MIGRATION_LOG_EMPTY_MESSAGE = "No migrations have been applied to this database.";
//#endregion
//#region src/commands/migration-log.ts
async function executeMigrationLogCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath } = resolveMigrationPaths(options.config, config);
const dbConnection = options.db ?? config.db?.connection;
const missingDb = requireLiveDatabase({
dbConnection,
hasDriver: !!config.driver,
why: `migration log needs a database connection and driver to read the ledger (set db.connection in ${configPath}, or pass --db <url>)`,
commandName: "migration log"
});
if (missingDb) return notOk(missingDb);
if (!targetSupportsMigrations(config.target)) return notOk(errorUnexpected("Target does not support migrations"));
if (!flags.json && !flags.quiet) {
const header = formatStyledHeader({
command: "migration log",
description: "Show executed migration history from the database ledger",
details: [{
label: "config",
value: configPath
}, ...typeof dbConnection === "string" ? [{
label: "database",
value: maskConnectionUrl(dbConnection)
}] : []],
flags
});
ui.stderr(header);
}
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
...ifDefined("driver", config.driver),
extensionPacks: config.extensionPacks ?? []
});
try {
await client.connect(dbConnection);
return ok(await client.readLedger());
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` }));
} finally {
await client.close();
}
}
function createMigrationLogCommand() {
const command = new Command("log");
setCommandDescriptions(command, "Show executed migration history", "Reads the database ledger and displays every applied migration edge\nin chronological order, including rollbacks and re-applies, merged\nacross all contract spaces. Requires a database connection.");
setCommandExamples(command, [
"prisma-next migration log --db $DATABASE_URL",
"prisma-next migration log --utc --db $DATABASE_URL",
"prisma-next migration log --json --db $DATABASE_URL"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--utc", "Render human timestamps in UTC instead of local time").option("--ascii", "Use ASCII glyphs (pipe-friendly)").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeMigrationLogCommand(options, flags, ui), flags, ui, (entries) => {
if (flags.json) {
const records = serializeLedgerEntriesForJson(entries);
const result = {
ok: true,
records,
summary: `${records.length} migration(s) applied`
};
ui.output(JSON.stringify(result, null, 2));
} else if (!flags.quiet) if (entries.length === 0) ui.output(MIGRATION_LOG_EMPTY_MESSAGE);
else {
const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });
ui.output(renderMigrationLogTable(entries, {
utc: options.utc === true,
styler,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
}
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { executeMigrationLogCommand as n, createMigrationLogCommand as t };
//# sourceMappingURL=migration-log-CEqFiez9.mjs.map
{"version":3,"file":"migration-log-CEqFiez9.mjs","names":[],"sources":["../src/utils/formatters/migration-log-table.ts","../src/commands/migration-log.ts"],"sourcesContent":["import type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport stringWidth from 'string-width';\nimport type { GlyphMode } from '../glyph-mode';\nimport {\n abbreviateContractHash,\n migrationListEmptySource,\n migrationListForwardArrow,\n} from './migration-list-data-column';\nimport { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';\n\nexport type LedgerTimestampMode = 'local' | 'utc' | 'iso';\n\nexport interface RenderMigrationLogTableOptions {\n readonly utc?: boolean;\n readonly styler?: MigrationListStyler;\n readonly glyphMode?: GlyphMode;\n}\n\nexport interface SerializedLedgerEntryRecord {\n readonly space: string;\n readonly name: string;\n readonly hash: string;\n readonly fromContract: string | null;\n readonly toContract: string;\n readonly appliedAt: string;\n readonly operationCount: number;\n}\n\nconst HEADING_APPLIED_AT = 'Applied at';\nconst HEADING_SPACE = 'Space';\nconst HEADING_MIGRATION = 'Migration';\nconst HEADING_CHANGE = 'Change';\nconst HEADING_OPS = 'Ops';\nconst COLUMN_SEPARATOR = ' ';\nconst DIVIDER_CHAR = '─';\nconst ASCII_DIVIDER_CHAR = '-';\n\nexport function sortLedgerEntries(entries: readonly LedgerEntryRecord[]): LedgerEntryRecord[] {\n return [...entries].sort((left, right) => {\n const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();\n if (timeDiff !== 0) {\n return timeDiff;\n }\n const spaceDiff = left.space.localeCompare(right.space);\n if (spaceDiff !== 0) {\n return spaceDiff;\n }\n return left.migrationName.localeCompare(right.migrationName);\n });\n}\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, '0');\n}\n\nexport function formatLedgerAppliedAt(date: Date, mode: LedgerTimestampMode): string {\n if (mode === 'iso') {\n return date.toISOString();\n }\n if (mode === 'utc') {\n return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;\n }\n const offsetMinutes = -date.getTimezoneOffset();\n const sign = offsetMinutes >= 0 ? '+' : '-';\n const absoluteOffset = Math.abs(offsetMinutes);\n const offsetHours = pad2(Math.floor(absoluteOffset / 60));\n const offsetMins = pad2(absoluteOffset % 60);\n return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;\n}\n\nexport function formatHashEndpoint(hash: string | null, glyphMode: GlyphMode = 'unicode'): string {\n if (hash === null) {\n return migrationListEmptySource(glyphMode);\n }\n return abbreviateContractHash(hash);\n}\n\nexport function formatHashTransition(\n from: string | null,\n to: string,\n glyphMode: GlyphMode = 'unicode',\n): string {\n return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;\n}\n\nexport function styleHashTransition(\n from: string | null,\n to: string,\n styler: MigrationListStyler,\n glyphMode: GlyphMode = 'unicode',\n): string {\n const fromPart =\n from === null\n ? styler.glyph(migrationListEmptySource(glyphMode))\n : styler.sourceHash(abbreviateContractHash(from));\n const arrow = styler.glyph(migrationListForwardArrow(glyphMode));\n const dest = styler.destHash(abbreviateContractHash(to));\n return `${fromPart} ${arrow} ${dest}`;\n}\n\nfunction padVisible(text: string, targetWidth: number): string {\n const padding = Math.max(0, targetWidth - stringWidth(text));\n return text + ' '.repeat(padding);\n}\n\nfunction columnWidth(values: readonly string[]): number {\n return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);\n}\n\nfunction padDividerCell(valueWidth: number, dividerChar: string): string {\n return dividerChar.repeat(valueWidth + 2);\n}\n\nfunction padTextCell(value: string, valueWidth: number): string {\n return ` ${padVisible(value, valueWidth)} `;\n}\n\nfunction padOpsCell(value: string, valueWidth: number): string {\n const padding = Math.max(0, valueWidth - stringWidth(value));\n return ` ${' '.repeat(padding)}${value} `;\n}\n\nexport function renderMigrationLogTable(\n entries: readonly LedgerEntryRecord[],\n options: RenderMigrationLogTableOptions = {},\n): string {\n const sorted = sortLedgerEntries(entries);\n if (sorted.length === 0) {\n return '';\n }\n\n const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;\n const glyphMode = options.glyphMode ?? 'unicode';\n const dividerChar = glyphMode === 'ascii' ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;\n const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;\n const timestampMode: LedgerTimestampMode = options.utc ? 'utc' : 'local';\n const rows = sorted.map((entry) => ({\n appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),\n space: entry.space,\n migrationName: entry.migrationName,\n transition: formatHashTransition(entry.from, entry.to, glyphMode),\n ops: `${entry.operationCount} ops`,\n from: entry.from,\n to: entry.to,\n }));\n\n const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);\n const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;\n const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);\n const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);\n const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);\n\n const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];\n if (showSpace) {\n headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));\n }\n headingParts.push(\n padTextCell(HEADING_MIGRATION, nameWidth),\n padTextCell(HEADING_CHANGE, transitionWidth),\n padOpsCell(HEADING_OPS, opsWidth),\n );\n const heading = headingParts.join(COLUMN_SEPARATOR);\n\n const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];\n if (showSpace) {\n dividerParts.push(padDividerCell(spaceWidth, dividerChar));\n }\n dividerParts.push(\n padDividerCell(nameWidth, dividerChar),\n padDividerCell(transitionWidth, dividerChar),\n padDividerCell(opsWidth, dividerChar),\n );\n const divider = dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR);\n\n const dataRows = rows.map((row) => {\n const parts = [padTextCell(row.appliedAt, appliedAtWidth)];\n if (showSpace) {\n parts.push(padTextCell(row.space, spaceWidth));\n }\n parts.push(\n padTextCell(styler.dirName(row.migrationName), nameWidth),\n padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth),\n padOpsCell(row.ops, opsWidth),\n );\n return parts.join(COLUMN_SEPARATOR);\n });\n\n return [heading, divider, ...dataRows].join('\\n');\n}\n\nexport function serializeLedgerEntriesForJson(\n entries: readonly LedgerEntryRecord[],\n): SerializedLedgerEntryRecord[] {\n return sortLedgerEntries(entries).map((entry) => ({\n space: entry.space,\n name: entry.migrationName,\n hash: entry.migrationHash,\n fromContract: entry.from,\n toContract: entry.to,\n appliedAt: formatLedgerAppliedAt(entry.appliedAt, 'iso'),\n operationCount: entry.operationCount,\n }));\n}\n\nexport const MIGRATION_LOG_EMPTY_MESSAGE = 'No migrations have been applied to this database.';\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorUnexpected,\n mapMigrationToolsError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n maskConnectionUrl,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n targetSupportsMigrations,\n} from '../utils/command-helpers';\nimport { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';\nimport {\n MIGRATION_LOG_EMPTY_MESSAGE,\n renderMigrationLogTable,\n serializeLedgerEntriesForJson,\n} from '../utils/formatters/migration-log-table';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type { MigrationLogResult } from './json/schemas';\n\nexport type { MigrationLogResult };\n\ninterface MigrationLogOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly utc?: boolean;\n readonly ascii?: boolean;\n}\n\nexport async function executeMigrationLogCommand(\n options: MigrationLogOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<readonly LedgerEntryRecord[], CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath } = resolveMigrationPaths(options.config, config);\n\n const dbConnection = options.db ?? config.db?.connection;\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver: !!config.driver,\n why: `migration log needs a database connection and driver to read the ledger (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration log',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n if (!targetSupportsMigrations(config.target)) {\n return notOk(errorUnexpected('Target does not support migrations'));\n }\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration log',\n description: 'Show executed migration history from the database ledger',\n details: [\n { label: 'config', value: configPath },\n ...(typeof dbConnection === 'string'\n ? [{ label: 'database', value: maskConnectionUrl(dbConnection) }]\n : []),\n ],\n flags,\n });\n ui.stderr(header);\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n ...ifDefined('driver', config.driver),\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const ledger = await client.readLedger();\n return ok(ledger);\n } catch (error) {\n if (CliStructuredError.is(error)) return notOk(error);\n if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationLogCommand(): Command {\n const command = new Command('log');\n setCommandDescriptions(\n command,\n 'Show executed migration history',\n 'Reads the database ledger and displays every applied migration edge\\n' +\n 'in chronological order, including rollbacks and re-applies, merged\\n' +\n 'across all contract spaces. Requires a database connection.',\n );\n setCommandExamples(command, [\n 'prisma-next migration log --db $DATABASE_URL',\n 'prisma-next migration log --utc --db $DATABASE_URL',\n 'prisma-next migration log --json --db $DATABASE_URL',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--utc', 'Render human timestamps in UTC instead of local time')\n .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')\n .action(async (options: MigrationLogOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeMigrationLogCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, (entries) => {\n if (flags.json) {\n const records = serializeLedgerEntriesForJson(entries);\n const result: MigrationLogResult = {\n ok: true,\n records,\n summary: `${records.length} migration(s) applied`,\n };\n ui.output(JSON.stringify(result, null, 2));\n } else if (!flags.quiet) {\n if (entries.length === 0) {\n ui.output(MIGRATION_LOG_EMPTY_MESSAGE);\n } else {\n const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });\n ui.output(\n renderMigrationLogTable(entries, {\n utc: options.utc === true,\n styler,\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n }),\n );\n }\n }\n });\n process.exit(exitCode);\n });\n return command;\n}\n"],"mappings":"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"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, a as readContractEnvelope, ct as errorUnexpected, d as setCommandSeeAlso, dt as requireLiveDatabase, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, n as collectDeclaredInvariants, p as toStructuralEdge, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as createControlClient } from "./client-2kwLMBSf.mjs";
import { n as buildReadAggregate, o as refusePackageCorruptionOnAggregate, r as loadContractRawSafely } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { a as renderMigrationGraphLegend, f as indentMigrationGraphTreeBlock, l as computeGlobalMaxDirNameWidth, p as renderMigrationGraphSpaceTree, u as computeGlobalMaxEdgeTreePrefixWidth } from "./migration-graph-command-render-B83xrnNy.mjs";
import { c as validateLegendOptions, i as migrationSpaceListEntriesFromAggregate, o as runMigrationList, r as listRefsByContractHash, s as shouldShowLegend } from "./migration-list-cnSKilYH.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { ifDefined } from "@prisma-next/utils/defined";
import { notOk, ok } from "@prisma-next/utils/result";
import { dim, yellow } from "colorette";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { MigrationToolsError, errorNoInvariantPath, errorUnknownInvariant } from "@prisma-next/migration-tools/errors";
import { findPath, findPathWithDecision } from "@prisma-next/migration-tools/migration-graph";
import { readRefs } from "@prisma-next/migration-tools/refs";
import { parseContractRef } from "@prisma-next/migration-tools/ref-resolution";
//#region src/commands/migration-status-overlay.ts
function deriveStatusEdgeAnnotations(input) {
const annotations = /* @__PURE__ */ new Map();
if (input.showAppliedOverlay) {
for (const edge of input.graph.migrationByHash.values()) if (input.appliedMigrationHashes.has(edge.migrationHash)) annotations.set(edge.migrationHash, { status: "applied" });
}
if (!input.graph.nodes.has(input.originHash)) return annotations;
const pendingPath = findPath(input.graph, input.originHash, input.targetHash);
if (!pendingPath) return annotations;
for (const edge of pendingPath) {
if (input.appliedMigrationHashes.has(edge.migrationHash)) continue;
if (annotations.get(edge.migrationHash)?.status === "applied") continue;
annotations.set(edge.migrationHash, { status: "pending" });
}
return annotations;
}
function appliedHashesFromLedger(ledgerEntries) {
return new Set(ledgerEntries.map((entry) => entry.migrationHash));
}
function statusForMigrationHash(migrationHash, annotations) {
return annotations.get(migrationHash)?.status ?? null;
}
//#endregion
//#region src/commands/migration-status.ts
function shortDisplayHash(hash) {
return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, 12);
}
function resolveTarget(contractHash, activeRefHash) {
return activeRefHash ?? contractHash;
}
function buildStatusMigrations(listMigrations, annotations) {
return listMigrations.map((migration) => ({
...migration,
status: statusForMigrationHash(migration.hash, annotations)
}));
}
function renderSpaceTree(args) {
const graph = args.space.graph();
if (graph.nodes.size === 0) return "";
return renderMigrationGraphSpaceTree({
graph,
migrations: args.migrations,
liveContractHash: args.liveContractHash,
refsByHash: listRefsByContractHash(args.space),
statusOverlayByHash: args.statusOverlay,
colorize: args.colorize,
glyphMode: args.glyphMode,
isAppSpace: args.isAppSpace,
...args.showDbMarker && args.markerHash !== void 0 ? { dbHash: args.markerHash } : {},
...args.globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth } : {},
...args.globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth: args.globalMaxDirNameWidth } : {}
});
}
function countPending(migrations) {
return migrations.filter((m) => m.status === "pending").length;
}
function buildNoPathSummary(args) {
const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
const targetShort = shortDisplayHash(args.targetHash);
if (!args.explicitTarget) return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
return `No migration path from ${markerPart} to ${args.refName !== void 0 ? `the target (${targetShort} via \`${args.refName}\`)` : `the target (${targetShort})`}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
}
function buildStatusHeadline(args) {
if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
if (args.pendingCount === 0) return "Up to date";
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
}
function formatStatusSummary(result, colorize) {
const c = (fn, s) => colorize ? fn(s) : s;
const lines = [];
const pendingTotal = result.spaces.reduce((sum, space) => sum + countPending(space.migrations), 0);
if (result.diagnostics.some((d) => d.code === "MIGRATION.MARKER_NOT_IN_HISTORY") || pendingTotal > 0) lines.push(c(yellow, result.summary));
else lines.push(result.summary);
const missingInvariantsDiagnostic = result.diagnostics.find((d) => d.code === "MIGRATION.MISSING_INVARIANTS");
if (missingInvariantsDiagnostic !== void 0) lines.push(c(dim, missingInvariantsDiagnostic.message));
return lines.join("\n");
}
function formatStatusHumanOutput(result, colorize) {
const sections = [];
for (const section of result.treeSections) {
if (section.showHeading) sections.push(`${section.space}:`);
if (section.tree.length > 0) sections.push(section.tree);
else sections.push("(no migrations)");
sections.push("");
}
sections.push(formatStatusSummary(result, colorize));
return sections.join("\n").trimEnd();
}
async function readMarkersAndLedgers(args) {
const markersBySpace = /* @__PURE__ */ new Map();
const all = await args.client.readAllMarkers();
for (const [spaceId, marker] of all) markersBySpace.set(spaceId, marker);
const ledgersBySpace = /* @__PURE__ */ new Map();
for (const spaceId of args.spaceIds) ledgersBySpace.set(spaceId, await args.client.readLedger(spaceId));
return {
markersBySpace,
ledgersBySpace
};
}
async function executeMigrationStatusCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(options.config, config);
const dbConnection = options.db ?? config.db?.connection;
const hasDriver = !!config.driver;
const usingFromOverride = options.from !== void 0;
if (!usingFromOverride) {
const missingDb = requireLiveDatabase({
dbConnection,
hasDriver,
why: "migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)",
retryCommand: "prisma-next migration status --from <contract>"
});
if (missingDb) return notOk(missingDb);
}
let allRefs = {};
try {
allRefs = await readRefs(refsDir);
} catch (error) {
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
throw error;
}
const diagnostics = [];
let contractHash = EMPTY_CONTRACT_HASH;
try {
contractHash = (await readContractEnvelope(config)).storageHash;
} catch (error) {
diagnostics.push({
code: "CONTRACT.UNREADABLE",
severity: "warn",
message: `Could not read contract: ${error instanceof Error ? error.message : "unknown error"}`,
hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
});
}
const loaded = await buildReadAggregate(config, { migrationsDir });
if (!loaded.ok) return notOk(loaded.failure);
const { aggregate } = loaded.value;
if (await loadContractRawSafely(config) !== null) {
const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
if (corruptionFailure) return notOk(corruptionFailure);
}
const appGraph = aggregate.app.graph();
let activeRefHash;
let activeRefName;
let activeRefEntry;
let fromOverrideHash;
if (options.to) {
const refResult = parseContractRef(options.to, {
graph: appGraph,
refs: allRefs
});
if (!refResult.ok) return notOk(mapRefResolutionError(refResult.failure));
activeRefHash = refResult.value.hash;
if (refResult.value.provenance.kind === "ref") {
activeRefName = refResult.value.provenance.refName;
activeRefEntry = allRefs[activeRefName];
}
}
if (options.from) {
const fromResult = parseContractRef(options.from, {
graph: appGraph,
refs: allRefs
});
if (!fromResult.ok) return notOk(mapRefResolutionError(fromResult.failure));
fromOverrideHash = fromResult.value.hash;
}
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
if (!flags.json && !flags.quiet) {
const details = [{
label: "config",
value: configPath
}, {
label: "migrations",
value: migrationsRelative
}];
if (dbConnection && hasDriver) details.push({
label: "database",
value: maskConnectionUrl(String(dbConnection))
});
if (activeRefName) details.push({
label: "ref",
value: activeRefName
});
if (options.from) details.push({
label: "from",
value: options.from
});
if (options.space) details.push({
label: "space",
value: options.space
});
const header = formatStyledHeader({
command: "migration status",
description: "Show migration history and applied status",
details,
flags
});
ui.stderr(header);
if (shouldShowLegend(options, flags)) {
ui.stderr(renderMigrationGraphLegend({
colorize: flags.color !== false,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
ui.stderr("");
}
}
const listResult = runMigrationList({
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
...ifDefined("spaceFilter", options.space)
});
if (!listResult.ok) return listResult;
const scopedSpaces = listResult.value.spaces;
const showSpaceHeadings = scopedSpaces.length > 1;
let markersBySpace = /* @__PURE__ */ new Map();
let ledgersBySpace = /* @__PURE__ */ new Map();
let connected = false;
if (dbConnection && hasDriver && !usingFromOverride) {
const client = createControlClient({
family: config.family,
target: config.target,
adapter: config.adapter,
driver: config.driver,
extensionPacks: config.extensionPacks ?? []
});
try {
await client.connect(dbConnection);
connected = true;
const read = await readMarkersAndLedgers({
client,
spaceIds: scopedSpaces.map((s) => s.space)
});
markersBySpace = new Map(read.markersBySpace);
ledgersBySpace = new Map(read.ledgersBySpace);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}` }));
} finally {
await client.close();
}
}
if (activeRefEntry && activeRefEntry.invariants.length > 0 && connected) {
const declared = collectDeclaredInvariants(appGraph);
const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];
const known = new Set(declared);
for (const id of markerInvariants) known.add(id);
const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
if (unknown.length > 0) return notOk(mapMigrationToolsError(errorUnknownInvariant({
...ifDefined("refName", activeRefName),
unknown,
declared: [...declared].sort()
})));
}
const showAppliedOverlay = connected && !usingFromOverride;
const showDbMarker = connected && !usingFromOverride;
const glyphMode = ui.resolveGlyphMode(options.ascii === true);
const colorize = flags.color !== false;
const statusSpaces = [];
const treeSections = [];
let markerDiverged = false;
let markerCannotReachTarget = false;
let headlineTargetHash = activeRefHash ?? contractHash;
let totalPending = 0;
const globalLayoutInputs = showSpaceHeadings ? scopedSpaces.filter((spaceEntry) => spaceEntry.migrations.length > 0).map((spaceEntry) => ({
graph: aggregate.space(spaceEntry.space).graph(),
liveContractHash: contractHash
})) : [];
const globalMaxEdgeTreePrefixWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs) : void 0;
const globalMaxDirNameWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : void 0;
for (const spaceEntry of scopedSpaces) {
const space = aggregate.space(spaceEntry.space);
if (space === void 0) continue;
const graph = space.graph();
const spaceContractHash = space.contract().storage.storageHash;
const targetHash = resolveTarget(spaceContractHash, activeRefHash);
if (spaceEntry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
const markerRecord = markersBySpace.get(spaceEntry.space);
const markerHash = usingFromOverride ? fromOverrideHash : markerRecord?.storageHash ?? void 0;
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
if (connected && !usingFromOverride && markerInGraph && originHash !== targetHash && findPath(graph, originHash, targetHash) === null) markerCannotReachTarget = true;
if (connected && !usingFromOverride && markerHash !== void 0 && !markerInGraph) {
markerDiverged = true;
diagnostics.push({
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
severity: "warn",
message: "Database was updated outside the migration system (marker does not match any migration)",
hints: ["Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database"]
});
}
const ledger = ledgersBySpace.get(spaceEntry.space) ?? [];
const annotations = deriveStatusEdgeAnnotations({
graph,
targetHash,
originHash,
appliedMigrationHashes: showAppliedOverlay ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
showAppliedOverlay
});
const isAppSpace = spaceEntry.space === aggregate.app.spaceId;
const tree = renderSpaceTree({
space,
liveContractHash: contractHash,
migrations: spaceEntry.migrations,
markerHash,
showDbMarker,
statusOverlay: annotations,
colorize,
glyphMode,
isAppSpace,
...globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth } : {},
...globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth } : {}
});
const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);
const pending = countPending(migrations);
totalPending += pending;
statusSpaces.push({
space: spaceEntry.space,
currentContract: markerHash ?? null,
targetContract: targetHash,
migrations: [...migrations]
});
const displayTree = showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree;
treeSections.push({
space: spaceEntry.space,
tree: displayTree,
showHeading: showSpaceHeadings
});
}
if (connected && requiredInvariants.length > 0) {
const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];
const markerSet = new Set(markerInvariants);
const missing = requiredInvariants.filter((id) => !markerSet.has(id));
if (missing.length > 0) {
diagnostics.push({
code: "MIGRATION.MISSING_INVARIANTS",
...ifDefined("ref", activeRefName),
invariants: missing,
message: `missing invariant(s): ${missing.join(", ")}`
});
if (activeRefHash !== void 0) {
const outcome = findPathWithDecision(appGraph, markersBySpace.get(aggregate.app.spaceId)?.storageHash ?? EMPTY_CONTRACT_HASH, activeRefHash, {
...ifDefined("refName", activeRefName),
required: new Set(missing)
});
if (outcome.kind === "unsatisfiable") return notOk(mapMigrationToolsError(errorNoInvariantPath({
...ifDefined("refName", activeRefName),
required: [...missing].sort(),
missing: outcome.missing,
structuralPath: outcome.structuralPath.map(toStructuralEdge)
})));
}
}
}
const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;
const summary = markerCannotReachTarget ? buildNoPathSummary({
markerHash: appMarkerHash,
targetHash: headlineTargetHash,
explicitTarget: options.to !== void 0,
refName: activeRefName
}) : buildStatusHeadline({
pendingCount: totalPending,
targetHash: headlineTargetHash,
markerDiverged,
markerHash: appMarkerHash
});
if (scopedSpaces.every((s) => s.migrations.length === 0)) return ok({
ok: true,
spaces: statusSpaces,
summary: "No migrations found",
diagnostics,
treeSections
});
return ok({
ok: true,
spaces: statusSpaces,
summary,
diagnostics,
treeSections
});
}
function createMigrationStatusCommand() {
const command = new Command("status");
setCommandDescriptions(command, "Show migration path and pending status", "Shows which migrations are pending between the database marker and\nthe target contract. Requires a database connection.\nPass --from for an offline path preview without a database.\nUse `migration graph` for topology, `migration log` for history,\nand `migration list` for on-disk enumeration.");
setCommandExamples(command, [
"prisma-next migration status --db $DATABASE_URL",
"prisma-next migration status --to production --db $DATABASE_URL",
"prisma-next migration status --from sha256:abc --to production",
"prisma-next migration status --from sha256:abc --to production --json",
"prisma-next migration status --ascii --from sha256:abc --to production",
"prisma-next migration status --legend --from sha256:abc --to production"
]);
setCommandSeeAlso(command, [
{
verb: "migration log",
oneLiner: "Show executed migration history"
},
{
verb: "migration list",
oneLiner: "List on-disk migrations"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").option("--to <contract>", "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)").option("--from <contract>", "Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.").option("--legend", "Print a key for the tree glyphs and lane colors").option("--ascii", "Use ASCII glyphs (pipe-friendly)").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const legendValidation = validateLegendOptions(options, flags);
if (!legendValidation.ok) process.exit(handleResult(legendValidation, flags, ui));
const exitCode = handleResult(await executeMigrationStatusCommand(options, flags, ui), flags, ui, (statusResult) => {
if (flags.json) {
const jsonResult = {
ok: true,
spaces: [...statusResult.spaces],
summary: statusResult.summary,
diagnostics: [...statusResult.diagnostics]
};
ui.output(JSON.stringify(jsonResult, null, 2));
} else if (!flags.quiet) ui.output(formatStatusHumanOutput(statusResult, flags.color !== false));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { formatStatusHumanOutput as a, executeMigrationStatusCommand as i, buildStatusHeadline as n, formatStatusSummary as o, createMigrationStatusCommand as r, buildNoPathSummary as t };
//# sourceMappingURL=migration-status-ugWwGk1V.mjs.map
{"version":3,"file":"migration-status-ugWwGk1V.mjs","names":[],"sources":["../src/commands/migration-status-overlay.ts","../src/commands/migration-status.ts"],"sourcesContent":["import type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { findPath } from '@prisma-next/migration-tools/migration-graph';\nimport type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-labels';\n\nexport interface DeriveStatusEdgeAnnotationsInput {\n readonly graph: MigrationGraph;\n readonly targetHash: string;\n readonly originHash: string;\n readonly appliedMigrationHashes: ReadonlySet<string>;\n readonly showAppliedOverlay: boolean;\n}\n\nexport function deriveStatusEdgeAnnotations(\n input: DeriveStatusEdgeAnnotationsInput,\n): ReadonlyMap<string, MigrationEdgeAnnotation> {\n const annotations = new Map<string, MigrationEdgeAnnotation>();\n\n if (input.showAppliedOverlay) {\n for (const edge of input.graph.migrationByHash.values()) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n annotations.set(edge.migrationHash, { status: 'applied' });\n }\n }\n }\n\n if (!input.graph.nodes.has(input.originHash)) {\n return annotations;\n }\n\n const pendingPath = findPath(input.graph, input.originHash, input.targetHash);\n if (!pendingPath) {\n return annotations;\n }\n\n for (const edge of pendingPath) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n continue;\n }\n const existing = annotations.get(edge.migrationHash);\n if (existing?.status === 'applied') {\n continue;\n }\n annotations.set(edge.migrationHash, { status: 'pending' });\n }\n\n return annotations;\n}\n\nexport function appliedHashesFromLedger(\n ledgerEntries: ReadonlyArray<{ readonly migrationHash: string }>,\n): ReadonlySet<string> {\n return new Set(ledgerEntries.map((entry) => entry.migrationHash));\n}\n\nexport function statusForMigrationHash(\n migrationHash: string,\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): 'applied' | 'pending' | null {\n const status = annotations.get(migrationHash)?.status;\n return status ?? null;\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport type {\n AggregateContractSpace,\n ContractMarkerRecordLike,\n} from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n errorNoInvariantPath,\n errorUnknownInvariant,\n MigrationToolsError,\n} from '@prisma-next/migration-tools/errors';\nimport { findPath, findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';\nimport { readRefs } from '@prisma-next/migration-tools/refs';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { dim, yellow } from 'colorette';\nimport { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n collectDeclaredInvariants,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n toStructuralEdge,\n} from '../utils/command-helpers';\nimport {\n buildReadAggregate,\n loadContractRawSafely,\n refusePackageCorruptionOnAggregate,\n} from '../utils/contract-space-aggregate-loader';\nimport {\n type MigrationEdgeAnnotation,\n renderMigrationGraphLegend,\n} from '../utils/formatters/migration-graph-labels';\nimport {\n computeGlobalMaxDirNameWidth,\n computeGlobalMaxEdgeTreePrefixWidth,\n indentMigrationGraphTreeBlock,\n renderMigrationGraphSpaceTree,\n} from '../utils/formatters/migration-graph-space-render';\nimport type { MigrationListEntry } from '../utils/formatters/migration-list-types';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { shouldShowLegend, validateLegendOptions } from '../utils/legend';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n} from './json/schemas';\nimport { migrationStatusJsonResultSchema } from './json/schemas';\nimport {\n listRefsByContractHash,\n migrationSpaceListEntriesFromAggregate,\n runMigrationList,\n} from './migration-list';\nimport {\n appliedHashesFromLedger,\n deriveStatusEdgeAnnotations,\n statusForMigrationHash,\n} from './migration-status-overlay';\n\nexport type { StatusRef } from '../utils/migration-types';\nexport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n};\nexport { migrationStatusJsonResultSchema };\n\nexport interface MigrationStatusOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly to?: string;\n readonly from?: string;\n readonly space?: string;\n readonly legend?: boolean;\n readonly ascii?: boolean;\n}\n\nexport interface MigrationStatusTreeSection {\n readonly space: string;\n readonly tree: string;\n readonly showHeading: boolean;\n}\n\ninterface MigrationStatusCommandResult {\n readonly ok: true;\n readonly spaces: readonly MigrationStatusSpace[];\n readonly summary: string;\n readonly diagnostics: readonly StatusDiagnosticJson[];\n readonly treeSections: readonly MigrationStatusTreeSection[];\n}\n\nfunction shortDisplayHash(hash: string): string {\n const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;\n return stripped.slice(0, 12);\n}\n\nfunction resolveTarget(contractHash: string, activeRefHash: string | undefined): string {\n return activeRefHash ?? contractHash;\n}\n\nfunction buildStatusMigrations(\n listMigrations: readonly MigrationListEntry[],\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): readonly MigrationStatusEntry[] {\n return listMigrations.map((migration) => ({\n ...migration,\n status: statusForMigrationHash(migration.hash, annotations),\n }));\n}\n\nfunction renderSpaceTree(args: {\n readonly space: AggregateContractSpace;\n readonly liveContractHash: string;\n readonly migrations: readonly MigrationListEntry[];\n readonly markerHash: string | undefined;\n readonly showDbMarker: boolean;\n readonly statusOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>;\n readonly colorize: boolean;\n readonly glyphMode: 'unicode' | 'ascii';\n readonly isAppSpace: boolean;\n readonly globalMaxEdgeTreePrefixWidth?: number;\n readonly globalMaxDirNameWidth?: number;\n}): string {\n const graph = args.space.graph();\n if (graph.nodes.size === 0) {\n return '';\n }\n return renderMigrationGraphSpaceTree({\n graph,\n migrations: args.migrations,\n liveContractHash: args.liveContractHash,\n refsByHash: listRefsByContractHash(args.space),\n statusOverlayByHash: args.statusOverlay,\n colorize: args.colorize,\n glyphMode: args.glyphMode,\n isAppSpace: args.isAppSpace,\n ...(args.showDbMarker && args.markerHash !== undefined ? { dbHash: args.markerHash } : {}),\n ...(args.globalMaxEdgeTreePrefixWidth !== undefined\n ? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth }\n : {}),\n ...(args.globalMaxDirNameWidth !== undefined\n ? { globalMaxDirNameWidth: args.globalMaxDirNameWidth }\n : {}),\n });\n}\n\nfunction countPending(migrations: readonly MigrationStatusEntry[]): number {\n return migrations.filter((m) => m.status === 'pending').length;\n}\n\nexport function buildNoPathSummary(args: {\n readonly markerHash: string | undefined;\n readonly targetHash: string;\n readonly explicitTarget: boolean;\n readonly refName: string | undefined;\n}): string {\n const markerPart =\n args.markerHash !== undefined\n ? `the database state (${shortDisplayHash(args.markerHash)})`\n : 'the database state';\n const targetShort = shortDisplayHash(args.targetHash);\n if (!args.explicitTarget) {\n return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \\`prisma-next migration plan --name <name>\\` to author one.`;\n }\n const targetLabel =\n args.refName !== undefined\n ? `the target (${targetShort} via \\`${args.refName}\\`)`\n : `the target (${targetShort})`;\n return `No migration path from ${markerPart} to ${targetLabel}. Run \\`prisma-next migration plan --name <name>\\` to author one, or pass \\`--to <contract>\\` to pick a reachable target.`;\n}\n\nexport function buildStatusHeadline(args: {\n readonly pendingCount: number;\n readonly targetHash: string;\n readonly markerDiverged: boolean;\n readonly markerHash: string | undefined;\n}): string {\n if (args.markerDiverged && args.markerHash !== undefined) {\n return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;\n }\n if (args.pendingCount === 0) {\n return 'Up to date';\n }\n return `${args.pendingCount} pending — run \\`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\\``;\n}\n\nexport function formatStatusSummary(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);\n const lines: string[] = [];\n const pendingTotal = result.spaces.reduce(\n (sum, space) => sum + countPending(space.migrations),\n 0,\n );\n const hasDivergence = result.diagnostics.some(\n (d) => d.code === 'MIGRATION.MARKER_NOT_IN_HISTORY',\n );\n if (hasDivergence || pendingTotal > 0) {\n lines.push(c(yellow, result.summary));\n } else {\n lines.push(result.summary);\n }\n const missingInvariantsDiagnostic = result.diagnostics.find(\n (d) => d.code === 'MIGRATION.MISSING_INVARIANTS',\n );\n if (missingInvariantsDiagnostic !== undefined) {\n lines.push(c(dim, missingInvariantsDiagnostic.message));\n }\n return lines.join('\\n');\n}\n\nexport function formatStatusHumanOutput(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const sections: string[] = [];\n for (const section of result.treeSections) {\n if (section.showHeading) {\n sections.push(`${section.space}:`);\n }\n if (section.tree.length > 0) {\n sections.push(section.tree);\n } else {\n sections.push('(no migrations)');\n }\n sections.push('');\n }\n sections.push(formatStatusSummary(result, colorize));\n return sections.join('\\n').trimEnd();\n}\n\nasync function readMarkersAndLedgers(args: {\n readonly client: ReturnType<typeof createControlClient>;\n readonly spaceIds: readonly string[];\n}): Promise<{\n readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike>;\n readonly ledgersBySpace: ReadonlyMap<string, readonly LedgerEntryRecord[]>;\n}> {\n const markersBySpace = new Map<string, ContractMarkerRecordLike>();\n const all = await args.client.readAllMarkers();\n for (const [spaceId, marker] of all) {\n markersBySpace.set(spaceId, marker);\n }\n const ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n for (const spaceId of args.spaceIds) {\n ledgersBySpace.set(spaceId, await args.client.readLedger(spaceId));\n }\n return { markersBySpace, ledgersBySpace };\n}\n\nexport async function executeMigrationStatusCommand(\n options: MigrationStatusOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationStatusCommandResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n const hasDriver = !!config.driver;\n const usingFromOverride = options.from !== undefined;\n\n if (!usingFromOverride) {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: 'migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)',\n retryCommand: 'prisma-next migration status --from <contract>',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n }\n\n let allRefs: Refs = {};\n try {\n allRefs = await readRefs(refsDir);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n const diagnostics: StatusDiagnosticJson[] = [];\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch (error) {\n diagnostics.push({\n code: 'CONTRACT.UNREADABLE',\n severity: 'warn',\n message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,\n hints: [\"Run 'prisma-next contract emit' to generate a valid contract\"],\n });\n }\n\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n\n const { aggregate } = loaded.value;\n const contractRawForAggregate = await loadContractRawSafely(config);\n if (contractRawForAggregate !== null) {\n const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);\n if (corruptionFailure) {\n return notOk(corruptionFailure);\n }\n }\n const appGraph = aggregate.app.graph();\n\n let activeRefHash: string | undefined;\n let activeRefName: string | undefined;\n let activeRefEntry: RefEntry | undefined;\n let fromOverrideHash: string | undefined;\n\n if (options.to) {\n const refResult = parseContractRef(options.to, { graph: appGraph, refs: allRefs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n activeRefHash = refResult.value.hash;\n if (refResult.value.provenance.kind === 'ref') {\n activeRefName = refResult.value.provenance.refName;\n activeRefEntry = allRefs[activeRefName];\n }\n }\n\n if (options.from) {\n const fromResult = parseContractRef(options.from, { graph: appGraph, refs: allRefs });\n if (!fromResult.ok) {\n return notOk(mapRefResolutionError(fromResult.failure));\n }\n fromOverrideHash = fromResult.value.hash;\n }\n\n const requiredInvariants: readonly string[] = [...(activeRefEntry?.invariants ?? [])].sort();\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (dbConnection && hasDriver) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (activeRefName) {\n details.push({ label: 'ref', value: activeRefName });\n }\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.space) {\n details.push({ label: 'space', value: options.space });\n }\n const header = formatStyledHeader({\n command: 'migration status',\n description: 'Show migration history and applied status',\n details,\n flags,\n });\n ui.stderr(header);\n if (shouldShowLegend(options, flags)) {\n ui.stderr(\n renderMigrationGraphLegend({\n colorize: flags.color !== false,\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n }),\n );\n ui.stderr('');\n }\n }\n\n const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);\n const listResult = runMigrationList({\n spaces: listSpaces,\n ...ifDefined('spaceFilter', options.space),\n });\n if (!listResult.ok) {\n return listResult;\n }\n\n const scopedSpaces = listResult.value.spaces;\n const showSpaceHeadings = scopedSpaces.length > 1;\n\n let markersBySpace = new Map<string, ContractMarkerRecordLike>();\n let ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n let connected = false;\n\n if (dbConnection && hasDriver && !usingFromOverride) {\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n try {\n await client.connect(dbConnection);\n connected = true;\n const read = await readMarkersAndLedgers({\n client,\n spaceIds: scopedSpaces.map((s) => s.space),\n });\n markersBySpace = new Map(read.markersBySpace);\n ledgersBySpace = new Map(read.ledgersBySpace);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n }\n\n if (activeRefEntry && activeRefEntry.invariants.length > 0 && connected) {\n const declared = collectDeclaredInvariants(appGraph);\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const known = new Set<string>(declared);\n for (const id of markerInvariants) known.add(id);\n const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));\n if (unknown.length > 0) {\n return notOk(\n mapMigrationToolsError(\n errorUnknownInvariant({\n ...ifDefined('refName', activeRefName),\n unknown,\n declared: [...declared].sort(),\n }),\n ),\n );\n }\n }\n\n const showAppliedOverlay = connected && !usingFromOverride;\n const showDbMarker = connected && !usingFromOverride;\n const glyphMode = ui.resolveGlyphMode(options.ascii === true);\n const colorize = flags.color !== false;\n\n const statusSpaces: MigrationStatusSpace[] = [];\n const treeSections: MigrationStatusTreeSection[] = [];\n let markerDiverged = false;\n let markerCannotReachTarget = false;\n let headlineTargetHash = activeRefHash ?? contractHash;\n let totalPending = 0;\n\n const globalLayoutInputs = showSpaceHeadings\n ? scopedSpaces\n .filter((spaceEntry) => spaceEntry.migrations.length > 0)\n .map((spaceEntry) => ({\n graph: aggregate.space(spaceEntry.space)!.graph(),\n liveContractHash: contractHash,\n }))\n : [];\n const globalMaxEdgeTreePrefixWidth =\n globalLayoutInputs.length > 0\n ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)\n : undefined;\n const globalMaxDirNameWidth =\n globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;\n\n for (const spaceEntry of scopedSpaces) {\n const space = aggregate.space(spaceEntry.space);\n if (space === undefined) {\n continue;\n }\n const graph = space.graph();\n const spaceContractHash = space.contract().storage.storageHash;\n const targetHash = resolveTarget(spaceContractHash, activeRefHash);\n if (spaceEntry.space === aggregate.app.spaceId) {\n headlineTargetHash = targetHash;\n }\n\n const markerRecord = markersBySpace.get(spaceEntry.space);\n const markerHash = usingFromOverride\n ? fromOverrideHash\n : (markerRecord?.storageHash ?? undefined);\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n const markerInGraph =\n markerHash === undefined || graph.nodes.has(markerHash) || markerHash === spaceContractHash;\n if (\n connected &&\n !usingFromOverride &&\n markerInGraph &&\n originHash !== targetHash &&\n findPath(graph, originHash, targetHash) === null\n ) {\n markerCannotReachTarget = true;\n }\n\n if (connected && !usingFromOverride && markerHash !== undefined && !markerInGraph) {\n markerDiverged = true;\n diagnostics.push({\n code: 'MIGRATION.MARKER_NOT_IN_HISTORY',\n severity: 'warn',\n message:\n 'Database was updated outside the migration system (marker does not match any migration)',\n hints: [\n \"Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract\",\n \"Run 'prisma-next db update' to push the current contract to the database\",\n ],\n });\n }\n\n const ledger = ledgersBySpace.get(spaceEntry.space) ?? [];\n const appliedHashes = showAppliedOverlay ? appliedHashesFromLedger(ledger) : new Set<string>();\n\n const annotations = deriveStatusEdgeAnnotations({\n graph,\n targetHash,\n originHash,\n appliedMigrationHashes: appliedHashes,\n showAppliedOverlay,\n });\n const isAppSpace = spaceEntry.space === aggregate.app.spaceId;\n const tree = renderSpaceTree({\n space,\n liveContractHash: contractHash,\n migrations: spaceEntry.migrations,\n markerHash,\n showDbMarker,\n statusOverlay: annotations,\n colorize,\n glyphMode,\n isAppSpace,\n ...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),\n ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),\n });\n const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);\n const pending = countPending(migrations);\n totalPending += pending;\n\n statusSpaces.push({\n space: spaceEntry.space,\n currentContract: markerHash ?? null,\n targetContract: targetHash,\n migrations: [...migrations],\n });\n const displayTree =\n showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;\n treeSections.push({\n space: spaceEntry.space,\n tree: displayTree,\n showHeading: showSpaceHeadings,\n });\n }\n\n if (connected && requiredInvariants.length > 0) {\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const markerSet = new Set(markerInvariants);\n const missing = requiredInvariants.filter((id) => !markerSet.has(id));\n if (missing.length > 0) {\n diagnostics.push({\n code: 'MIGRATION.MISSING_INVARIANTS',\n ...ifDefined('ref', activeRefName),\n invariants: missing,\n message: `missing invariant(s): ${missing.join(', ')}`,\n });\n if (activeRefHash !== undefined) {\n const originHash =\n markersBySpace.get(aggregate.app.spaceId)?.storageHash ?? EMPTY_CONTRACT_HASH;\n const outcome = findPathWithDecision(appGraph, originHash, activeRefHash, {\n ...ifDefined('refName', activeRefName),\n required: new Set(missing),\n });\n if (outcome.kind === 'unsatisfiable') {\n return notOk(\n mapMigrationToolsError(\n errorNoInvariantPath({\n ...ifDefined('refName', activeRefName),\n required: [...missing].sort(),\n missing: outcome.missing,\n structuralPath: outcome.structuralPath.map(toStructuralEdge),\n }),\n ),\n );\n }\n }\n }\n }\n\n const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;\n const summary = markerCannotReachTarget\n ? buildNoPathSummary({\n markerHash: appMarkerHash,\n targetHash: headlineTargetHash,\n explicitTarget: options.to !== undefined,\n refName: activeRefName,\n })\n : buildStatusHeadline({\n pendingCount: totalPending,\n targetHash: headlineTargetHash,\n markerDiverged,\n markerHash: appMarkerHash,\n });\n\n if (scopedSpaces.every((s) => s.migrations.length === 0)) {\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary: 'No migrations found',\n diagnostics,\n treeSections,\n });\n }\n\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary,\n diagnostics,\n treeSections,\n });\n}\n\nexport function createMigrationStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show migration path and pending status',\n 'Shows which migrations are pending between the database marker and\\n' +\n 'the target contract. Requires a database connection.\\n' +\n 'Pass --from for an offline path preview without a database.\\n' +\n 'Use `migration graph` for topology, `migration log` for history,\\n' +\n 'and `migration list` for on-disk enumeration.',\n );\n setCommandExamples(command, [\n 'prisma-next migration status --db $DATABASE_URL',\n 'prisma-next migration status --to production --db $DATABASE_URL',\n 'prisma-next migration status --from sha256:abc --to production',\n 'prisma-next migration status --from sha256:abc --to production --json',\n 'prisma-next migration status --ascii --from sha256:abc --to production',\n 'prisma-next migration status --legend --from sha256:abc --to production',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration log', oneLiner: 'Show executed migration history' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option(\n '--from <contract>',\n 'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',\n )\n .option('--legend', 'Print a key for the tree glyphs and lane colors')\n .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')\n .action(async (options: MigrationStatusOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n const legendValidation = validateLegendOptions(options, flags);\n if (!legendValidation.ok) {\n process.exit(handleResult(legendValidation, flags, ui));\n }\n\n const result = await executeMigrationStatusCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (statusResult) => {\n if (flags.json) {\n const jsonResult: MigrationStatusResult = {\n ok: true,\n spaces: [...statusResult.spaces],\n summary: statusResult.summary,\n diagnostics: [...statusResult.diagnostics],\n };\n ui.output(JSON.stringify(jsonResult, null, 2));\n } else if (!flags.quiet) {\n ui.output(formatStatusHumanOutput(statusResult, flags.color !== false));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}