🎩 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
994
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.5
to
0.16.0-dev.6
+1628
dist/client-2kwLMBSf.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,
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

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

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

import { B as errorContractValidationFailed, F as CliStructuredError, W as errorFileNotFound, ct as errorUnexpected, it as errorSnapshotMissing, lt as mapMigrationToolsError } from "./command-helpers-CVn3U9Uz.mjs";
import { notOk } from "@prisma-next/utils/result";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/contract-at-errors.ts
function mapContractAtError(error, options) {
if (MigrationToolsError.is(error)) switch (error.code) {
case "MIGRATION.REF_NOT_RESOLVABLE": return notOk(errorSnapshotMissing(typeof error.details?.["refName"] === "string" ? error.details["refName"] : typeof error.details?.["identifier"] === "string" ? error.details["identifier"] : "unknown"));
case "MIGRATION.CONTRACT_DESERIALIZATION_FAILED": {
const filePath = typeof error.details?.["filePath"] === "string" ? error.details["filePath"] : "unknown";
return notOk(errorContractValidationFailed(`Predecessor contract at ${filePath} failed to deserialize: ${typeof error.details?.["message"] === "string" ? error.details["message"] : error.message}`, { where: { path: filePath } }));
}
case "MIGRATION.INVALID_JSON": {
const filePath = typeof error.details?.["filePath"] === "string" ? error.details["filePath"] : "unknown";
const message = typeof error.details?.["parseError"] === "string" ? error.details["parseError"] : error.message;
return notOk(errorContractValidationFailed((options?.artifactRole ?? "from") === "to" ? `Target contract at ${filePath} failed to deserialize: ${message}` : `Predecessor contract at ${filePath} failed to deserialize: ${message}`, { where: { path: filePath } }));
}
case "MIGRATION.BUNDLE_NOT_FOUND_FOR_GRAPH_NODE": return notOk(errorUnexpected(error.message, {
why: error.why,
fix: error.fix
}));
case "MIGRATION.CONTRACT_SNAPSHOT_MISSING": {
const expectedPath = typeof error.details?.["expectedPath"] === "string" ? error.details["expectedPath"] : "migrations/snapshots/";
return notOk(errorFileNotFound(expectedPath, {
why: (options?.artifactRole ?? "from") === "to" ? `Target migration is missing its contract snapshot at ${expectedPath}` : `Predecessor migration is missing its contract snapshot at ${expectedPath}`,
fix: "Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot."
}));
}
default: return notOk(mapMigrationToolsError(error));
}
if (CliStructuredError.is(error)) return notOk(error);
throw error;
}
//#endregion
export { mapContractAtError as t };
//# sourceMappingURL=contract-at-errors-C-l4-JEY.mjs.map
{"version":3,"file":"contract-at-errors-C-l4-JEY.mjs","names":[],"sources":["../src/utils/contract-at-errors.ts"],"sourcesContent":["import { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { notOk, type Result } from '@prisma-next/utils/result';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorSnapshotMissing,\n errorUnexpected,\n mapMigrationToolsError,\n} from './cli-errors';\n\nexport function mapContractAtError(\n error: unknown,\n options?: { readonly artifactRole?: 'from' | 'to' },\n): Result<never, CliStructuredError> {\n if (MigrationToolsError.is(error)) {\n switch (error.code) {\n case 'MIGRATION.REF_NOT_RESOLVABLE': {\n const refName =\n typeof error.details?.['refName'] === 'string'\n ? error.details['refName']\n : typeof error.details?.['identifier'] === 'string'\n ? error.details['identifier']\n : 'unknown';\n return notOk(errorSnapshotMissing(refName));\n }\n case 'MIGRATION.CONTRACT_DESERIALIZATION_FAILED': {\n const filePath =\n typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';\n const message =\n typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;\n return notOk(\n errorContractValidationFailed(\n `Predecessor contract at ${filePath} failed to deserialize: ${message}`,\n { where: { path: filePath } },\n ),\n );\n }\n case 'MIGRATION.INVALID_JSON': {\n const filePath =\n typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';\n const message =\n typeof error.details?.['parseError'] === 'string'\n ? error.details['parseError']\n : error.message;\n const role = options?.artifactRole ?? 'from';\n return notOk(\n errorContractValidationFailed(\n role === 'to'\n ? `Target contract at ${filePath} failed to deserialize: ${message}`\n : `Predecessor contract at ${filePath} failed to deserialize: ${message}`,\n { where: { path: filePath } },\n ),\n );\n }\n case 'MIGRATION.BUNDLE_NOT_FOUND_FOR_GRAPH_NODE':\n return notOk(\n errorUnexpected(error.message, {\n why: error.why,\n fix: error.fix,\n }),\n );\n case 'MIGRATION.CONTRACT_SNAPSHOT_MISSING': {\n const expectedPath =\n typeof error.details?.['expectedPath'] === 'string'\n ? error.details['expectedPath']\n : 'migrations/snapshots/';\n const role = options?.artifactRole ?? 'from';\n return notOk(\n errorFileNotFound(expectedPath, {\n why:\n role === 'to'\n ? `Target migration is missing its contract snapshot at ${expectedPath}`\n : `Predecessor migration is missing its contract snapshot at ${expectedPath}`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n }),\n );\n }\n default:\n return notOk(mapMigrationToolsError(error));\n }\n }\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n throw error;\n}\n"],"mappings":";;;;AAWA,SAAgB,mBACd,OACA,SACmC;CACnC,IAAI,oBAAoB,GAAG,KAAK,GAC9B,QAAQ,MAAM,MAAd;EACE,KAAK,gCAOH,OAAO,MAAM,qBALX,OAAO,MAAM,UAAU,eAAe,WAClC,MAAM,QAAQ,aACd,OAAO,MAAM,UAAU,kBAAkB,WACvC,MAAM,QAAQ,gBACd,SACiC,CAAC;EAE5C,KAAK,6CAA6C;GAChD,MAAM,WACJ,OAAO,MAAM,UAAU,gBAAgB,WAAW,MAAM,QAAQ,cAAc;GAGhF,OAAO,MACL,8BACE,2BAA2B,SAAS,0BAHtC,OAAO,MAAM,UAAU,eAAe,WAAW,MAAM,QAAQ,aAAa,MAAM,WAIhF,EAAE,OAAO,EAAE,MAAM,SAAS,EAAE,CAC9B,CACF;EACF;EACA,KAAK,0BAA0B;GAC7B,MAAM,WACJ,OAAO,MAAM,UAAU,gBAAgB,WAAW,MAAM,QAAQ,cAAc;GAChF,MAAM,UACJ,OAAO,MAAM,UAAU,kBAAkB,WACrC,MAAM,QAAQ,gBACd,MAAM;GAEZ,OAAO,MACL,+BAFW,SAAS,gBAAgB,YAGzB,OACL,sBAAsB,SAAS,0BAA0B,YACzD,2BAA2B,SAAS,0BAA0B,WAClE,EAAE,OAAO,EAAE,MAAM,SAAS,EAAE,CAC9B,CACF;EACF;EACA,KAAK,6CACH,OAAO,MACL,gBAAgB,MAAM,SAAS;GAC7B,KAAK,MAAM;GACX,KAAK,MAAM;EACb,CAAC,CACH;EACF,KAAK,uCAAuC;GAC1C,MAAM,eACJ,OAAO,MAAM,UAAU,oBAAoB,WACvC,MAAM,QAAQ,kBACd;GAEN,OAAO,MACL,kBAAkB,cAAc;IAC9B,MAHS,SAAS,gBAAgB,YAIvB,OACL,wDAAwD,iBACxD,6DAA6D;IACnE,KAAK;GACP,CAAC,CACH;EACF;EACA,SACE,OAAO,MAAM,uBAAuB,KAAK,CAAC;CAC9C;CAEF,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;CAEpB,MAAM;AACR"}
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 { F as CliStructuredError, a as readContractEnvelope, ct as errorUnexpected, lt as mapMigrationToolsError, o as resolveContractPath } from "./command-helpers-CVn3U9Uz.mjs";
import { notOk, ok } from "@prisma-next/utils/result";
import { readFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { blindCast } from "@prisma-next/utils/casts";
import { loadContractSpaceAggregate } from "@prisma-next/migration-tools/aggregate";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/extension-pack-inputs.ts
/**
* Project the CLI's `Config.extensionPacks` array into the canonical
* {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`
* structural cast in the CLI lives inside this function.
*/
function toExtensionInputs(extensionPacks) {
return extensionPacks.map((raw) => {
const pack = raw;
if (pack.contractSpace === void 0) return {
id: pack.id,
targetId: pack.targetId
};
return {
id: pack.id,
targetId: pack.targetId,
contractSpace: {
contractJson: pack.contractSpace.contractJson,
headRef: pack.contractSpace.headRef,
migrations: pack.contractSpace.migrations ?? []
}
};
});
}
/**
* Minimal aggregate-loader projection that extracts `id` + `targetId`
* from raw extension pack descriptors **without invoking any
* `contractSpace` accessor**. Inspects the own-property descriptor so
* that getter-backed `contractSpace` declarations are detected but
* never called.
*
* Inclusion semantics match {@link toDeclaredExtensions}: a data
* property whose value is explicitly `undefined` is treated as "no
* contract-space declaration" and skipped, mirroring the
* `pack.contractSpace === undefined` check used on canonicalised
* inputs. Prototype-chain `contractSpace` properties (no own
* descriptor) are also skipped.
*
* This variant must be used by `buildContractSpaceAggregate` so that
* the aggregate path (including `db verify`) never reads
* `contractSpace.contractJson` from extension descriptors — the loader
* always reads the contract from on-disk artefacts instead.
*/
function toDeclaredExtensionsFromRaw(extensionPacks) {
const entries = [];
for (const raw of extensionPacks) {
if (typeof raw !== "object" || raw === null) continue;
const descriptor = Object.getOwnPropertyDescriptor(raw, "contractSpace");
if (descriptor === void 0) continue;
if ("value" in descriptor && descriptor.value === void 0) continue;
const pack = raw;
entries.push({
id: pack.id,
targetId: pack.targetId
});
}
return entries;
}
//#endregion
//#region src/utils/contract-space-aggregate-loader.ts
const CONTRACT_SPACES_DOCS_URL = "https://pris.ly/contract-spaces";
function contractSpaceViolationError(summary, options) {
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", summary, {
why: options.why,
fix: options.fix,
docsUrl: CONTRACT_SPACES_DOCS_URL,
meta: { violations: options.violations }
});
}
/**
* Build the `MIGRATION.CONTRACT_SPACE_VIOLATION` structured-error envelope for a contract-space
* target mismatch. Shared between the declared-extension precheck (the
* descriptor's configured target disagrees with the project target) and
* the on-disk-contract check surfaced by `checkIntegrity`.
*/
function targetMismatchError(spaceId, expected, actual) {
return contractSpaceViolationError(`Contract-space target mismatch for "${spaceId}"`, {
why: `Space "${spaceId}" targets "${actual}" but the project's adapter targets "${expected}".`,
fix: "Update the extension descriptor to target the configured database, or change the project adapter.",
violations: [{
kind: "targetMismatch",
spaceId,
expected,
actual
}]
});
}
/**
* Human-readable detail for an integrity violation, used as the `why`
* of the `integrityFailure` envelope. Mirrors the messages the prior
* throw-on-load loader produced so downstream consumers see the same
* text for the same on-disk state.
*/
function describeIntegrityViolation(violation) {
switch (violation.kind) {
case "hashMismatch": return `Migration "${violation.dirName}" stored hash "${violation.stored}" does not match computed hash "${violation.computed}".`;
case "providedInvariantsMismatch": return `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`;
case "packageUnloadable": return `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`;
case "sameSourceAndTarget": return `Migration "${violation.dirName}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`;
case "headRefMissing": return `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`;
case "headRefNotInGraph": return `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in the migration graph.`;
case "refUnreadable": return `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`;
case "duplicateMigrationHash": return `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(", ")}).`;
default: {
const spaceId = "spaceId" in violation ? violation.spaceId : "*";
return `Integrity violation "${violation.kind}" for contract space "${spaceId}".`;
}
}
}
/**
* Map the integrity violations `checkIntegrity` reports into a single
* CLI structured-error envelope, preserving the error codes the prior
* throw-on-load loader emitted: `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`
* (layout drift, bundled) and `MIGRATION.CONTRACT_SPACE_VIOLATION` (target /
* disjointness / contract-validation / structural integrity). Returns
* `null` when there is nothing to refuse on.
*
* Precedence reproduces the prior loader's first-failure ordering:
* layout drift first (every offence bundled into one envelope), then
* target mismatch, then disjointness, then a contract-validation
* failure, then any remaining structural integrity violation.
*/
function mapIntegrityViolations(violations) {
if (violations.length === 0) return null;
const layout = violations.filter((v) => v.kind === "orphanSpaceDir" || v.kind === "declaredButUnmigrated");
if (layout.length > 0) {
const lines = layout.map((v) => `- [${v.kind}] ${v.spaceId}`);
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION", layout.length === 1 ? "Contract-space layout violation detected" : `Contract-space layout violations detected (${layout.length})`, {
why: `The on-disk \`migrations/\` directory and your \`extensionPacks\` declaration are not in agreement.\n${lines.join("\n")}`,
fix: "Declare the extension in `extensionPacks` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.",
docsUrl: CONTRACT_SPACES_DOCS_URL,
meta: { violations: layout }
});
}
const targetMismatch = violations.find((v) => v.kind === "targetMismatch");
if (targetMismatch && targetMismatch.kind === "targetMismatch") return targetMismatchError(targetMismatch.spaceId, targetMismatch.expected, targetMismatch.actual);
const disjointness = violations.find((v) => v.kind === "disjointness");
if (disjointness && disjointness.kind === "disjointness") return contractSpaceViolationError(`Contract-space disjointness violation: storage element "${disjointness.element}" claimed by multiple spaces`, {
why: `Spaces ${disjointness.claimedBy.map((s) => `"${s}"`).join(", ")} all claim the storage element "${disjointness.element}". Each storage element must be owned by exactly one contract space.`,
fix: "Update the conflicting contracts so each storage element is claimed by exactly one space.",
violations: [disjointness]
});
const contractUnreadable = violations.find((v) => v.kind === "contractUnreadable");
if (contractUnreadable && contractUnreadable.kind === "contractUnreadable") return contractSpaceViolationError(`Contract-space contract validation failed for "${contractUnreadable.spaceId}"`, {
why: contractUnreadable.detail,
fix: "Re-emit the extension contract with `prisma-next contract emit`, or fix the extension pack descriptor producing the invalid contract.",
violations: [contractUnreadable]
});
const structural = violations[0];
return contractSpaceViolationError(`Contract-space integrity failure for "${"spaceId" in structural ? structural.spaceId : "*"}"`, {
why: describeIntegrityViolation(structural),
fix: "Re-emit the affected migration package(s) or restore the on-disk `migrations/` directory from version control.",
violations: [structural]
});
}
function declaredExtensionsFromInputs(extensionPacks) {
return toDeclaredExtensionsFromRaw(extensionPacks);
}
/**
* Reject extension descriptors whose configured target disagrees with
* the project target before any on-disk read.
*/
function refuseDeclaredExtensionTargetMismatch(inputs) {
for (const declared of declaredExtensionsFromInputs(inputs.extensionPacks)) if (declared.targetId !== inputs.targetId) return targetMismatchError(declared.id, inputs.targetId, declared.targetId);
return null;
}
/**
* Load the tolerant {@link ContractSpaceAggregate} once at the CLI
* surface. Construction never throws on disk content; callers query
* {@link ContractSpaceAggregate.app} / extension facets instead of
* re-reading `migrations/`.
*/
async function loadContractSpaceAggregateForCli(inputs) {
const targetFailure = refuseDeclaredExtensionTargetMismatch(inputs);
if (targetFailure) return notOk(targetFailure);
return ok(await loadContractSpaceAggregate({
migrationsDir: inputs.migrationsDir,
deserializeContract: inputs.deserializeContract,
appContract: inputs.appContract
}));
}
/**
* Run `checkIntegrity` on a loaded aggregate and map violations into
* the contract-space refusal envelope, or return `null` when the model
* is acceptable for the requested check scope.
*/
function refuseContractSpaceIntegrity(aggregate, options) {
return mapIntegrityViolations(aggregate.checkIntegrity(options));
}
const PACKAGE_CORRUPTION_KINDS = /* @__PURE__ */ new Set([
"hashMismatch",
"providedInvariantsMismatch",
"packageUnloadable"
]);
/**
* Reader-subset integrity refusal for `migration status`: package
* corruptions only (`hashMismatch`, `providedInvariantsMismatch`,
* `packageUnloadable`).
*/
function refusePackageCorruptionOnAggregate(aggregate) {
return mapIntegrityViolations(aggregate.checkIntegrity().filter((v) => PACKAGE_CORRUPTION_KINDS.has(v.kind)));
}
/**
* Construct the tolerant {@link ContractSpaceAggregate} at the CLI
* surface and apply the explicit integrity refusal.
*
* App-space migration packages are read from `migrations/<app>/` by the
* loader itself; callers no longer thread them through.
*/
async function buildContractSpaceAggregate(inputs) {
const declaredExtensions = declaredExtensionsFromInputs(inputs.extensionPacks);
const loaded = await loadContractSpaceAggregateForCli(inputs);
if (!loaded.ok) return loaded;
const failure = refuseContractSpaceIntegrity(loaded.value, {
declaredExtensions,
checkContracts: true
});
if (failure) return notOk(failure);
return ok(loaded.value);
}
/**
* Build a minimal app {@link Contract} carrying only the project's
* contract-identity (`storage.storageHash` + `target` / `targetFamily`)
* when the real `contract.json` is absent or undeserializable.
*
* `loadContractSpaceAggregate` requires an `appContract` to synthesise the
* app space's head ref from its storage hash. Read commands query only that
* hash and the target — never `models` — so an empty-`models` stand-in is
* sufficient for them. It is *not* a valid contract for any consumer that
* reads schema, which is why this is confined to the read-aggregate path.
*/
function appContractStandInFromIdentity(args) {
return blindCast({
storage: { storageHash: args.contractHash },
schemaVersion: "0.0.0",
target: args.targetId,
targetFamily: args.targetFamily,
models: {},
profileHash: EMPTY_CONTRACT_HASH
});
}
async function loadContractRawSafely(config) {
try {
const raw = await readFile(resolveContractPath(config), "utf-8");
return JSON.parse(raw);
} catch {
return null;
}
}
/**
* Tolerant {@link ContractSpaceAggregate} assembly for read-only CLI
* commands. No integrity gate — callers query `aggregate.app` (or other
* facets) without re-reading `migrations/`. When `contract.json` is absent
* or undeserializable, the app contract falls back to an identity-only
* stand-in ({@link appContractStandInFromIdentity}), so these commands
* load without requiring a readable contract.
*/
async function buildReadAggregate(config, options) {
let contractHash = EMPTY_CONTRACT_HASH;
try {
contractHash = (await readContractEnvelope(config)).storageHash;
} catch {}
try {
const contractRawForAggregate = await loadContractRawSafely(config);
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
const deserializeContract = (json) => familyInstance.deserializeContract(json);
let appContractForLoad = appContractStandInFromIdentity({
contractHash,
targetId: config.target.id,
targetFamily: config.target.familyId
});
if (contractRawForAggregate !== null) try {
appContractForLoad = deserializeContract(contractRawForAggregate);
} catch {}
const loaded = await loadContractSpaceAggregateForCli({
targetId: config.target.id,
migrationsDir: options.migrationsDir,
appContract: appContractForLoad,
extensionPacks: config.extensionPacks ?? [],
deserializeContract
});
if (!loaded.ok) return loaded;
return ok({
aggregate: loaded.value,
contractHash
});
} catch (error) {
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}` }));
}
}
//#endregion
export { refuseContractSpaceIntegrity as a, toExtensionInputs as c, loadContractSpaceAggregateForCli as i, buildReadAggregate as n, refusePackageCorruptionOnAggregate as o, loadContractRawSafely as r, toDeclaredExtensionsFromRaw as s, buildContractSpaceAggregate as t };
//# sourceMappingURL=contract-space-aggregate-loader-hPVymNLw.mjs.map
{"version":3,"file":"contract-space-aggregate-loader-hPVymNLw.mjs","names":[],"sources":["../src/utils/extension-pack-inputs.ts","../src/utils/contract-space-aggregate-loader.ts"],"sourcesContent":["/**\n * Single descriptor-import boundary for CLI consumers of `Config.extensionPacks`.\n *\n * Every CLI command / utility that reads an extension descriptor's\n * `contractSpace` projection (loader, migrate-pass, extension-migrations\n * pass, migration commands) goes through {@link toExtensionInputs}. The\n * structural cast `pack as { contractSpace?: ... }` lives **only** here —\n * downstream code consumes the canonical shape and maps it to its own\n * narrower shape via the per-consumer adapters below.\n *\n * The CLI receives extension descriptors typed against the SQL family\n * (or any other family in the future); this helper only depends on the\n * structural shape of `contractSpace`. SQL-family callers pass the same\n * `contractJson` / `headRef.hash` value through unchanged.\n */\nimport type { DeclaredExtensionEntry } from '@prisma-next/migration-tools/aggregate';\nimport type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';\nimport type { MigrationOps } from '@prisma-next/migration-tools/package';\n\n/**\n * In-memory authored migration package shipped by an extension descriptor.\n * Mirrors the `MigrationPackage` shape from\n * `@prisma-next/framework-components/control` minus `dirPath`; redeclared\n * structurally here so the helper does not couple to the SQL family's\n * `ExtensionMigrationPackage` type.\n */\nexport interface DescriptorMigrationPackage {\n readonly dirName: string;\n readonly metadata: MigrationMetadata;\n readonly ops: MigrationOps;\n}\n\n/**\n * The most-general projection of a single declared extension pack\n * needed by the CLI's descriptor-import boundary.\n *\n * - `id` / `targetId` are always present.\n * - `contractSpace` is present only when the extension declares one.\n * When present, it carries the canonical inputs every downstream\n * consumer needs — `contractJson`, `headRef`, and the descriptor's\n * pre-built migration packages.\n */\nexport interface ExtensionPackInput {\n readonly id: string;\n readonly targetId: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly headRef: {\n readonly hash: string;\n readonly invariants: readonly string[];\n };\n readonly migrations: readonly DescriptorMigrationPackage[];\n };\n}\n\n/**\n * Structural shape we read off each `Config.extensionPacks` entry.\n *\n * The CLI is the descriptor-import boundary; `extensionPacks` is the only\n * surface where the SQL-family-typed `ControlExtensionDescriptor` flows\n * into framework-neutral helpers. The structural cast lives here, and\n * here alone — every other CLI consumer reads the canonical\n * {@link ExtensionPackInput} shape produced by {@link toExtensionInputs}.\n */\ntype ExtensionPackLike = {\n readonly id: string;\n readonly targetId: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly headRef: {\n readonly hash: string;\n readonly invariants: readonly string[];\n };\n readonly migrations?: readonly DescriptorMigrationPackage[];\n };\n};\n\n/**\n * Project the CLI's `Config.extensionPacks` array into the canonical\n * {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`\n * structural cast in the CLI lives inside this function.\n */\nexport function toExtensionInputs(\n extensionPacks: ReadonlyArray<unknown>,\n): readonly ExtensionPackInput[] {\n return extensionPacks.map((raw) => {\n const pack = raw as ExtensionPackLike;\n if (pack.contractSpace === undefined) {\n return { id: pack.id, targetId: pack.targetId };\n }\n return {\n id: pack.id,\n targetId: pack.targetId,\n contractSpace: {\n contractJson: pack.contractSpace.contractJson,\n headRef: pack.contractSpace.headRef,\n migrations: pack.contractSpace.migrations ?? [],\n },\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-consumer adapters: take the canonical `ExtensionPackInput[]` and\n// project to whatever narrower shape the downstream primitive needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate-loader projection. Surfaces `id` + `targetId` per\n * contract-space-bearing extension to\n * {@link import('./contract-space-aggregate-loader').buildContractSpaceAggregate}.\n *\n * Codec-only extensions (no `contractSpace` declaration) are filtered\n * out: they contribute no contract space, so the aggregate loader\n * has nothing to do with them. Filtering happens at this descriptor-\n * import boundary so the loader stays oblivious to that distinction —\n * every entry it sees expects an on-disk `migrations/<id>/` directory.\n */\nexport function toDeclaredExtensions(\n inputs: ReadonlyArray<ExtensionPackInput>,\n): readonly DeclaredExtensionEntry[] {\n const entries: DeclaredExtensionEntry[] = [];\n for (const pack of inputs) {\n if (pack.contractSpace === undefined) continue;\n entries.push({ id: pack.id, targetId: pack.targetId });\n }\n return entries;\n}\n\n/**\n * Minimal aggregate-loader projection that extracts `id` + `targetId`\n * from raw extension pack descriptors **without invoking any\n * `contractSpace` accessor**. Inspects the own-property descriptor so\n * that getter-backed `contractSpace` declarations are detected but\n * never called.\n *\n * Inclusion semantics match {@link toDeclaredExtensions}: a data\n * property whose value is explicitly `undefined` is treated as \"no\n * contract-space declaration\" and skipped, mirroring the\n * `pack.contractSpace === undefined` check used on canonicalised\n * inputs. Prototype-chain `contractSpace` properties (no own\n * descriptor) are also skipped.\n *\n * This variant must be used by `buildContractSpaceAggregate` so that\n * the aggregate path (including `db verify`) never reads\n * `contractSpace.contractJson` from extension descriptors — the loader\n * always reads the contract from on-disk artefacts instead.\n */\nexport function toDeclaredExtensionsFromRaw(\n extensionPacks: ReadonlyArray<unknown>,\n): readonly DeclaredExtensionEntry[] {\n const entries: DeclaredExtensionEntry[] = [];\n for (const raw of extensionPacks) {\n if (typeof raw !== 'object' || raw === null) continue;\n const descriptor = Object.getOwnPropertyDescriptor(raw, 'contractSpace');\n if (descriptor === undefined) continue;\n if ('value' in descriptor && descriptor.value === undefined) continue;\n const pack = raw as { readonly id: string; readonly targetId: string };\n entries.push({ id: pack.id, targetId: pack.targetId });\n }\n return entries;\n}\n","import { readFile } from 'node:fs/promises';\nimport type { PrismaNextConfig } from '@prisma-next/config/config-types';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ControlExtensionDescriptor } from '@prisma-next/framework-components/control';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport type {\n ContractSpaceAggregate,\n DeclaredExtensionEntry,\n IntegrityQueryOptions,\n IntegrityViolation,\n} from '@prisma-next/migration-tools/aggregate';\nimport { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { CliStructuredError, errorUnexpected, mapMigrationToolsError } from './cli-errors';\nimport { readContractEnvelope, resolveContractPath } from './command-helpers';\nimport { toDeclaredExtensionsFromRaw } from './extension-pack-inputs';\n\nconst CONTRACT_SPACES_DOCS_URL = 'https://pris.ly/contract-spaces';\n\nfunction contractSpaceViolationError(\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly violations: readonly IntegrityViolation[];\n },\n): CliStructuredError {\n return new CliStructuredError('MIGRATION.CONTRACT_SPACE_VIOLATION', summary, {\n why: options.why,\n fix: options.fix,\n docsUrl: CONTRACT_SPACES_DOCS_URL,\n meta: { violations: options.violations },\n });\n}\n\n/**\n * Build the `MIGRATION.CONTRACT_SPACE_VIOLATION` structured-error envelope for a contract-space\n * target mismatch. Shared between the declared-extension precheck (the\n * descriptor's configured target disagrees with the project target) and\n * the on-disk-contract check surfaced by `checkIntegrity`.\n */\nfunction targetMismatchError(\n spaceId: string,\n expected: string,\n actual: string,\n): CliStructuredError {\n return contractSpaceViolationError(`Contract-space target mismatch for \"${spaceId}\"`, {\n why: `Space \"${spaceId}\" targets \"${actual}\" but the project's adapter targets \"${expected}\".`,\n fix: 'Update the extension descriptor to target the configured database, or change the project adapter.',\n violations: [{ kind: 'targetMismatch', spaceId, expected, actual }],\n });\n}\n\n/**\n * Human-readable detail for an integrity violation, used as the `why`\n * of the `integrityFailure` envelope. Mirrors the messages the prior\n * throw-on-load loader produced so downstream consumers see the same\n * text for the same on-disk state.\n */\nfunction describeIntegrityViolation(violation: IntegrityViolation): string {\n switch (violation.kind) {\n case 'hashMismatch':\n return `Migration \"${violation.dirName}\" stored hash \"${violation.stored}\" does not match computed hash \"${violation.computed}\".`;\n case 'providedInvariantsMismatch':\n return `Migration \"${violation.dirName}\" providedInvariants in migration.json disagrees with ops.json.`;\n case 'packageUnloadable':\n return `Migration \"${violation.dirName}\" could not be loaded: ${violation.detail}`;\n case 'sameSourceAndTarget':\n return `Migration \"${violation.dirName}\" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`;\n case 'headRefMissing':\n return `Head ref \\`refs/head.json\\` is missing for contract space \"${violation.spaceId}\".`;\n case 'headRefNotInGraph':\n return `Head ref ${violation.hash} for contract space \"${violation.spaceId}\" is not present in the migration graph.`;\n case 'refUnreadable':\n return `Ref \"${violation.refName}\" for contract space \"${violation.spaceId}\" is unreadable: ${violation.detail}`;\n case 'duplicateMigrationHash':\n return `Multiple migrations in space \"${violation.spaceId}\" share migrationHash \"${violation.migrationHash}\" (${violation.dirNames.join(', ')}).`;\n default: {\n const spaceId = 'spaceId' in violation ? violation.spaceId : '*';\n return `Integrity violation \"${violation.kind}\" for contract space \"${spaceId}\".`;\n }\n }\n}\n\n/**\n * Map the integrity violations `checkIntegrity` reports into a single\n * CLI structured-error envelope, preserving the error codes the prior\n * throw-on-load loader emitted: `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`\n * (layout drift, bundled) and `MIGRATION.CONTRACT_SPACE_VIOLATION` (target /\n * disjointness / contract-validation / structural integrity). Returns\n * `null` when there is nothing to refuse on.\n *\n * Precedence reproduces the prior loader's first-failure ordering:\n * layout drift first (every offence bundled into one envelope), then\n * target mismatch, then disjointness, then a contract-validation\n * failure, then any remaining structural integrity violation.\n */\nexport function mapIntegrityViolations(\n violations: readonly IntegrityViolation[],\n): CliStructuredError | null {\n if (violations.length === 0) return null;\n\n const layout = violations.filter(\n (v): v is Extract<IntegrityViolation, { kind: 'orphanSpaceDir' | 'declaredButUnmigrated' }> =>\n v.kind === 'orphanSpaceDir' || v.kind === 'declaredButUnmigrated',\n );\n if (layout.length > 0) {\n const lines = layout.map((v) => `- [${v.kind}] ${v.spaceId}`);\n const summary =\n layout.length === 1\n ? 'Contract-space layout violation detected'\n : `Contract-space layout violations detected (${layout.length})`;\n return new CliStructuredError('MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION', summary, {\n why: `The on-disk \\`migrations/\\` directory and your \\`extensionPacks\\` declaration are not in agreement.\\n${lines.join('\\n')}`,\n fix: 'Declare the extension in `extensionPacks` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.',\n docsUrl: CONTRACT_SPACES_DOCS_URL,\n meta: { violations: layout },\n });\n }\n\n const targetMismatch = violations.find((v) => v.kind === 'targetMismatch');\n if (targetMismatch && targetMismatch.kind === 'targetMismatch') {\n return targetMismatchError(\n targetMismatch.spaceId,\n targetMismatch.expected,\n targetMismatch.actual,\n );\n }\n\n const disjointness = violations.find((v) => v.kind === 'disjointness');\n if (disjointness && disjointness.kind === 'disjointness') {\n return contractSpaceViolationError(\n `Contract-space disjointness violation: storage element \"${disjointness.element}\" claimed by multiple spaces`,\n {\n why: `Spaces ${disjointness.claimedBy.map((s) => `\"${s}\"`).join(', ')} all claim the storage element \"${disjointness.element}\". Each storage element must be owned by exactly one contract space.`,\n fix: 'Update the conflicting contracts so each storage element is claimed by exactly one space.',\n violations: [disjointness],\n },\n );\n }\n\n const contractUnreadable = violations.find((v) => v.kind === 'contractUnreadable');\n if (contractUnreadable && contractUnreadable.kind === 'contractUnreadable') {\n return contractSpaceViolationError(\n `Contract-space contract validation failed for \"${contractUnreadable.spaceId}\"`,\n {\n why: contractUnreadable.detail,\n fix: 'Re-emit the extension contract with `prisma-next contract emit`, or fix the extension pack descriptor producing the invalid contract.',\n violations: [contractUnreadable],\n },\n );\n }\n\n // Any remaining recoverable structural violation refuses as an\n // integrity failure, surfacing the first one's detail (every violation\n // is still computed; the gate just renders one envelope).\n const structural = violations[0]!;\n const spaceId = 'spaceId' in structural ? structural.spaceId : '*';\n return contractSpaceViolationError(`Contract-space integrity failure for \"${spaceId}\"`, {\n why: describeIntegrityViolation(structural),\n fix: 'Re-emit the affected migration package(s) or restore the on-disk `migrations/` directory from version control.',\n violations: [structural],\n });\n}\n\n/**\n * Inputs needed to compose the aggregate loader at the CLI surface.\n *\n * Keeps the loader framework-neutral (no `Config` import) by accepting\n * already-resolved structural inputs: validated app contract, target\n * id, migrations root directory, and the set of extension descriptors.\n */\nexport interface BuildAggregateInputs<TFamilyId extends string, TTargetId extends string> {\n readonly targetId: TTargetId;\n readonly migrationsDir: string;\n readonly appContract: Contract;\n readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;\n readonly deserializeContract: (contractJson: unknown) => Contract;\n}\n\nfunction declaredExtensionsFromInputs(\n extensionPacks: BuildAggregateInputs<string, string>['extensionPacks'],\n): readonly DeclaredExtensionEntry[] {\n return toDeclaredExtensionsFromRaw(extensionPacks as ReadonlyArray<unknown>);\n}\n\n/**\n * Reject extension descriptors whose configured target disagrees with\n * the project target before any on-disk read.\n */\nexport function refuseDeclaredExtensionTargetMismatch<\n TFamilyId extends string,\n TTargetId extends string,\n>(inputs: BuildAggregateInputs<TFamilyId, TTargetId>): CliStructuredError | null {\n for (const declared of declaredExtensionsFromInputs(inputs.extensionPacks)) {\n if (declared.targetId !== inputs.targetId) {\n return targetMismatchError(declared.id, inputs.targetId, declared.targetId);\n }\n }\n return null;\n}\n\n/**\n * Load the tolerant {@link ContractSpaceAggregate} once at the CLI\n * surface. Construction never throws on disk content; callers query\n * {@link ContractSpaceAggregate.app} / extension facets instead of\n * re-reading `migrations/`.\n */\nexport async function loadContractSpaceAggregateForCli<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n inputs: BuildAggregateInputs<TFamilyId, TTargetId>,\n): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {\n const targetFailure = refuseDeclaredExtensionTargetMismatch(inputs);\n if (targetFailure) {\n return notOk(targetFailure);\n }\n\n const aggregate = await loadContractSpaceAggregate({\n migrationsDir: inputs.migrationsDir,\n deserializeContract: inputs.deserializeContract,\n appContract: inputs.appContract,\n });\n return ok(aggregate);\n}\n\n/**\n * Run `checkIntegrity` on a loaded aggregate and map violations into\n * the contract-space refusal envelope, or return `null` when the model\n * is acceptable for the requested check scope.\n */\nexport function refuseContractSpaceIntegrity(\n aggregate: ContractSpaceAggregate,\n options: IntegrityQueryOptions,\n): CliStructuredError | null {\n return mapIntegrityViolations(aggregate.checkIntegrity(options));\n}\n\nconst PACKAGE_CORRUPTION_KINDS = new Set<IntegrityViolation['kind']>([\n 'hashMismatch',\n 'providedInvariantsMismatch',\n 'packageUnloadable',\n]);\n\n/**\n * Reader-subset integrity refusal for `migration status`: package\n * corruptions only (`hashMismatch`, `providedInvariantsMismatch`,\n * `packageUnloadable`).\n */\nexport function refusePackageCorruptionOnAggregate(\n aggregate: ContractSpaceAggregate,\n): CliStructuredError | null {\n const corruption = aggregate.checkIntegrity().filter((v) => PACKAGE_CORRUPTION_KINDS.has(v.kind));\n return mapIntegrityViolations(corruption);\n}\n\n/**\n * Construct the tolerant {@link ContractSpaceAggregate} at the CLI\n * surface and apply the explicit integrity refusal.\n *\n * App-space migration packages are read from `migrations/<app>/` by the\n * loader itself; callers no longer thread them through.\n */\nexport async function buildContractSpaceAggregate<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n inputs: BuildAggregateInputs<TFamilyId, TTargetId>,\n): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {\n const declaredExtensions = declaredExtensionsFromInputs(inputs.extensionPacks);\n const loaded = await loadContractSpaceAggregateForCli(inputs);\n if (!loaded.ok) {\n return loaded;\n }\n const failure = refuseContractSpaceIntegrity(loaded.value, {\n declaredExtensions,\n checkContracts: true,\n });\n if (failure) {\n return notOk(failure);\n }\n return ok(loaded.value);\n}\n\n/**\n * Build a minimal app {@link Contract} carrying only the project's\n * contract-identity (`storage.storageHash` + `target` / `targetFamily`)\n * when the real `contract.json` is absent or undeserializable.\n *\n * `loadContractSpaceAggregate` requires an `appContract` to synthesise the\n * app space's head ref from its storage hash. Read commands query only that\n * hash and the target — never `models` — so an empty-`models` stand-in is\n * sufficient for them. It is *not* a valid contract for any consumer that\n * reads schema, which is why this is confined to the read-aggregate path.\n */\nexport function appContractStandInFromIdentity(args: {\n readonly contractHash: string;\n readonly targetId: string;\n readonly targetFamily: string;\n}): Contract {\n return blindCast<\n Contract,\n 'read-aggregate consumers query only storage.storageHash and target; empty models stand in for an unreadable contract.json'\n >({\n storage: { storageHash: args.contractHash },\n schemaVersion: '0.0.0',\n target: args.targetId,\n targetFamily: args.targetFamily,\n models: {},\n profileHash: EMPTY_CONTRACT_HASH,\n });\n}\n\nexport async function loadContractRawSafely(config: {\n contract?: { output?: string };\n}): Promise<unknown | null> {\n try {\n const path = resolveContractPath(config);\n const raw = await readFile(path, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Tolerant {@link ContractSpaceAggregate} assembly for read-only CLI\n * commands. No integrity gate — callers query `aggregate.app` (or other\n * facets) without re-reading `migrations/`. When `contract.json` is absent\n * or undeserializable, the app contract falls back to an identity-only\n * stand-in ({@link appContractStandInFromIdentity}), so these commands\n * load without requiring a readable contract.\n */\nexport async function buildReadAggregate(\n config: PrismaNextConfig,\n options: { readonly migrationsDir: string },\n): Promise<\n Result<\n { readonly aggregate: ContractSpaceAggregate; readonly contractHash: string },\n CliStructuredError\n >\n> {\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch {\n // Contract unreadable — marker uses EMPTY_CONTRACT_HASH\n }\n\n try {\n const contractRawForAggregate = await loadContractRawSafely(config);\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const deserializeContract = (json: unknown): Contract =>\n familyInstance.deserializeContract(json);\n let appContractForLoad: Contract = appContractStandInFromIdentity({\n contractHash,\n targetId: config.target.id,\n targetFamily: config.target.familyId,\n });\n if (contractRawForAggregate !== null) {\n try {\n appContractForLoad = deserializeContract(contractRawForAggregate);\n } catch {\n // Deserialization failed — identity-only stand-in fallback\n }\n }\n\n const loaded = await loadContractSpaceAggregateForCli({\n targetId: config.target.id,\n migrationsDir: options.migrationsDir,\n appContract: appContractForLoad,\n extensionPacks: config.extensionPacks ?? [],\n deserializeContract,\n });\n if (!loaded.ok) {\n return loaded;\n }\n return ok({ aggregate: loaded.value, contractHash });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n}\n"],"mappings":"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"}
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":"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"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, rt as errorRuntime, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.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, writeFile } from "node:fs/promises";
import { EOL } from "node:os";
import { PslFormatError, format } from "@prisma-next/psl-parser/format";
//#region src/control-api/operations/format.ts
function resolveNewline(formatterNewline, eol) {
if (formatterNewline !== void 0) return formatterNewline;
return eol === "\r\n" ? "CRLF" : "LF";
}
async function executeFormat(options) {
const eol = options.eol ?? EOL;
let config;
try {
config = await loadConfig(options.configPath);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
}
const source = config.contract?.source;
if (source?.sourceFormat !== "psl") return ok({ formatted: false });
const inputPath = source.inputs?.[0];
if (inputPath === void 0) return ok({ formatted: false });
let contents;
try {
contents = await readFile(inputPath, "utf-8");
} catch (error) {
return notOk(errorRuntime("Failed to read contract source file", {
why: error instanceof Error ? error.message : String(error),
fix: `Check that ${inputPath} exists and is readable.`
}));
}
const formatOptions = {
indent: config.formatter?.indent ?? 2,
newline: resolveNewline(config.formatter?.newline, eol)
};
let formatted;
try {
formatted = format(contents, formatOptions);
} catch (error) {
if (error instanceof PslFormatError) return notOk(errorRuntime("Cannot format PSL with parse errors", {
why: error.message,
fix: "Fix the parse errors in your schema and try again.",
meta: { diagnostics: error.diagnostics }
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
}
try {
await writeFile(inputPath, formatted, "utf-8");
} catch (error) {
return notOk(errorRuntime("Failed to write formatted contract source file", {
why: error instanceof Error ? error.message : String(error),
fix: `Check that ${inputPath} is writable.`
}));
}
return ok({
formatted: true,
path: inputPath
});
}
//#endregion
//#region src/commands/format.ts
function createFormatCommand() {
const command = new Command("format");
setCommandDescriptions(command, "Format your PSL contract source", "Formats the Prisma schema (PSL) contract source declared in your config\n(contract.source.inputs[0]) in place. Only runs when contract.source.sourceFormat\nis 'psl'; a TypeScript or unset source is left untouched. Indent and newline are\nread from the optional formatter config section, defaulting to two spaces and the\nsystem newline.");
setCommandExamples(command, ["prisma-next format", "prisma-next format --config ./custom-config.ts"]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
if (!flags.json && !flags.quiet) {
const displayConfigPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
ui.stderr(formatStyledHeader({
command: "format",
description: "Format your PSL contract source",
details: [{
label: "config",
value: displayConfigPath
}],
flags
}));
}
const exitCode = handleResult(await executeFormat({ ...ifDefined("configPath", options.config) }), flags, ui, (value) => {
if (flags.json) {
ui.output(JSON.stringify(value));
return;
}
if (flags.quiet) return;
if (value.formatted) ui.success(`Formatted ${relative(process.cwd(), value.path ?? "")}`);
else ui.info("Nothing to format (contract source is not PSL).");
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createFormatCommand as t };
//# sourceMappingURL=format-CefCDzkw.mjs.map
{"version":3,"file":"format-CefCDzkw.mjs","names":[],"sources":["../src/control-api/operations/format.ts","../src/commands/format.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises';\nimport { EOL } from 'node:os';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { type FormatOptions, format, PslFormatError } from '@prisma-next/psl-parser/format';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { CliStructuredError, errorRuntime, errorUnexpected } from '../../utils/cli-errors';\n\nexport interface FormatOperationOptions {\n readonly configPath?: string;\n readonly eol?: string;\n}\n\nexport interface FormatOperationResult {\n readonly formatted: boolean;\n readonly path?: string;\n}\n\nexport function resolveNewline(\n formatterNewline: 'LF' | 'CRLF' | undefined,\n eol: string,\n): 'LF' | 'CRLF' {\n if (formatterNewline !== undefined) {\n return formatterNewline;\n }\n return eol === '\\r\\n' ? 'CRLF' : 'LF';\n}\n\nexport async function executeFormat(\n options: FormatOperationOptions,\n): Promise<Result<FormatOperationResult, CliStructuredError>> {\n const eol = options.eol ?? EOL;\n\n let config: Awaited<ReturnType<typeof loadConfig>>;\n try {\n config = await loadConfig(options.configPath);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));\n }\n\n const source = config.contract?.source;\n if (source?.sourceFormat !== 'psl') {\n return ok({ formatted: false });\n }\n\n const inputPath = source.inputs?.[0];\n if (inputPath === undefined) {\n return ok({ formatted: false });\n }\n\n let contents: string;\n try {\n contents = await readFile(inputPath, 'utf-8');\n } catch (error) {\n return notOk(\n errorRuntime('Failed to read contract source file', {\n why: error instanceof Error ? error.message : String(error),\n fix: `Check that ${inputPath} exists and is readable.`,\n }),\n );\n }\n\n const formatOptions: FormatOptions = {\n indent: config.formatter?.indent ?? 2,\n newline: resolveNewline(config.formatter?.newline, eol),\n };\n\n let formatted: string;\n try {\n formatted = format(contents, formatOptions);\n } catch (error) {\n if (error instanceof PslFormatError) {\n return notOk(\n errorRuntime('Cannot format PSL with parse errors', {\n why: error.message,\n fix: 'Fix the parse errors in your schema and try again.',\n meta: { diagnostics: error.diagnostics },\n }),\n );\n }\n return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));\n }\n\n try {\n await writeFile(inputPath, formatted, 'utf-8');\n } catch (error) {\n return notOk(\n errorRuntime('Failed to write formatted contract source file', {\n why: error instanceof Error ? error.message : String(error),\n fix: `Check that ${inputPath} is writable.`,\n }),\n );\n }\n\n return ok({ formatted: true, path: inputPath });\n}\n","import { ifDefined } from '@prisma-next/utils/defined';\nimport { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { executeFormat } from '../control-api/operations/format';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI } from '../utils/terminal-ui';\n\ninterface FormatCommandOptions extends CommonCommandOptions {\n readonly config?: string;\n}\n\nexport function createFormatCommand(): Command {\n const command = new Command('format');\n setCommandDescriptions(\n command,\n 'Format your PSL contract source',\n 'Formats the Prisma schema (PSL) contract source declared in your config\\n' +\n '(contract.source.inputs[0]) in place. Only runs when contract.source.sourceFormat\\n' +\n \"is 'psl'; a TypeScript or unset source is left untouched. Indent and newline are\\n\" +\n 'read from the optional formatter config section, defaulting to two spaces and the\\n' +\n 'system newline.',\n );\n setCommandExamples(command, [\n 'prisma-next format',\n 'prisma-next format --config ./custom-config.ts',\n ]);\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: FormatCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n if (!flags.json && !flags.quiet) {\n const displayConfigPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n ui.stderr(\n formatStyledHeader({\n command: 'format',\n description: 'Format your PSL contract source',\n details: [{ label: 'config', value: displayConfigPath }],\n flags,\n }),\n );\n }\n\n const result = await executeFormat({ ...ifDefined('configPath', options.config) });\n\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n return;\n }\n if (flags.quiet) {\n return;\n }\n if (value.formatted) {\n ui.success(`Formatted ${relative(process.cwd(), value.path ?? '')}`);\n } else {\n ui.info('Nothing to format (contract source is not PSL).');\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { R as errorConfigValidation } from "./command-helpers-CVn3U9Uz.mjs";
import "@prisma-next/framework-components/components";
//#region src/utils/framework-components.ts
/**
* Asserts that all framework components are compatible with the expected family and target.
*
* This function validates that each component in the framework components array:
* - Has kind 'target', 'adapter', 'extension', or 'driver'
* - Has familyId matching expectedFamilyId
* - Has targetId matching expectedTargetId
*
* This validation happens at the CLI composition boundary, before passing components
* to typed planner/runner instances. It fills the gap between runtime validation
* (via `validateConfig()`) and compile-time type enforcement.
*
* @param expectedFamilyId - The expected family ID (e.g., 'sql')
* @param expectedTargetId - The expected target ID (e.g., 'postgres')
* @param frameworkComponents - Array of framework components to validate
* @returns The same array typed as TargetBoundComponentDescriptor
* @throws CliStructuredError if any component is incompatible
*
* @example
* ```ts
* const config = await loadConfig();
* const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
*
* // Validate and type-narrow components before passing to planner
* const typedComponents = assertFrameworkComponentsCompatible(
* config.family.familyId,
* config.target.targetId,
* frameworkComponents
* );
*
* const planner = target.migrations.createPlanner(familyInstance);
* planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });
* ```
*/
function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
for (let i = 0; i < frameworkComponents.length; i++) {
const component = frameworkComponents[i];
if (typeof component !== "object" || component === null) throw errorConfigValidation("frameworkComponents[]", { why: `Framework component at index ${i} must be an object` });
const record = component;
if (!Object.hasOwn(record, "kind")) throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} must have 'kind' property` });
const kind = record["kind"];
if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')` });
if (!Object.hasOwn(record, "familyId")) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property` });
const familyId = record["familyId"];
if (familyId !== expectedFamilyId) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'` });
if (!Object.hasOwn(record, "targetId")) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property` });
const targetId = record["targetId"];
if (targetId !== expectedTargetId) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'` });
}
return frameworkComponents;
}
//#endregion
export { assertFrameworkComponentsCompatible as t };
//# sourceMappingURL=framework-components-VMQJbAwl.mjs.map
{"version":3,"file":"framework-components-VMQJbAwl.mjs","names":[],"sources":["../src/utils/framework-components.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport {\n checkContractComponentRequirements,\n type TargetBoundComponentDescriptor,\n} from '@prisma-next/framework-components/components';\nimport type { ControlStack } from '@prisma-next/framework-components/control';\nimport { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';\n\n/**\n * Asserts that all framework components are compatible with the expected family and target.\n *\n * This function validates that each component in the framework components array:\n * - Has kind 'target', 'adapter', 'extension', or 'driver'\n * - Has familyId matching expectedFamilyId\n * - Has targetId matching expectedTargetId\n *\n * This validation happens at the CLI composition boundary, before passing components\n * to typed planner/runner instances. It fills the gap between runtime validation\n * (via `validateConfig()`) and compile-time type enforcement.\n *\n * @param expectedFamilyId - The expected family ID (e.g., 'sql')\n * @param expectedTargetId - The expected target ID (e.g., 'postgres')\n * @param frameworkComponents - Array of framework components to validate\n * @returns The same array typed as TargetBoundComponentDescriptor\n * @throws CliStructuredError if any component is incompatible\n *\n * @example\n * ```ts\n * const config = await loadConfig();\n * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n *\n * // Validate and type-narrow components before passing to planner\n * const typedComponents = assertFrameworkComponentsCompatible(\n * config.family.familyId,\n * config.target.targetId,\n * frameworkComponents\n * );\n *\n * const planner = target.migrations.createPlanner(familyInstance);\n * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });\n * ```\n */\nexport function assertFrameworkComponentsCompatible<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n expectedFamilyId: TFamilyId,\n expectedTargetId: TTargetId,\n frameworkComponents: ReadonlyArray<unknown>,\n): ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>> {\n for (let i = 0; i < frameworkComponents.length; i++) {\n const component = frameworkComponents[i];\n\n // Check that component is an object\n if (typeof component !== 'object' || component === null) {\n throw errorConfigValidation('frameworkComponents[]', {\n why: `Framework component at index ${i} must be an object`,\n });\n }\n\n const record = component as Record<string, unknown>;\n\n // Check kind\n if (!Object.hasOwn(record, 'kind')) {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} must have 'kind' property`,\n });\n }\n\n const kind = record['kind'];\n if (kind !== 'target' && kind !== 'adapter' && kind !== 'extension' && kind !== 'driver') {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`,\n });\n }\n\n // Check familyId\n if (!Object.hasOwn(record, 'familyId')) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`,\n });\n }\n\n const familyId = record['familyId'];\n if (familyId !== expectedFamilyId) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`,\n });\n }\n\n // Check targetId\n if (!Object.hasOwn(record, 'targetId')) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`,\n });\n }\n\n const targetId = record['targetId'];\n if (targetId !== expectedTargetId) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`,\n });\n }\n }\n\n // Type assertion is safe because we've validated all components above\n return frameworkComponents as ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Validates that a contract is compatible with the configured target, adapter,\n * and extension packs. Throws on family/target mismatches or missing extension packs.\n *\n * This check ensures the emitted contract matches the CLI config before running\n * commands that depend on the contract (e.g., db verify, db sign).\n *\n * @param contract - The contract to validate (must include targetFamily, target, extensionPacks).\n * @param stack - The control plane stack (target, adapter, driver, extensionPacks).\n *\n * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target\n * doesn't match the configured family/target.\n * @throws {CliStructuredError} errorContractMissingExtensionPacks when the contract requires\n * extension packs that are not provided in the config (includes all missing packs in error.meta).\n *\n * @example\n * ```ts\n * import { assertContractRequirementsSatisfied } from './framework-components';\n *\n * const config = await loadConfig();\n * const contract = await loadContractJson(config.contract.output);\n * const stack = createControlStack({ family: config.family, target: config.target, adapter: config.adapter, ... });\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({ contract, stack });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n stack,\n}: {\n readonly contract: Pick<Contract, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly stack: ControlStack<TFamilyId, TTargetId>;\n}): void {\n const providedComponentIds = new Set<string>([\n stack.target.id,\n ...(stack.adapter ? [stack.adapter.id] : []),\n ]);\n for (const extension of stack.extensionPacks) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: stack.target.familyId,\n expectedTargetId: stack.target.targetId,\n providedComponentIds,\n });\n\n if (result.familyMismatch) {\n throw errorConfigValidation('contract.targetFamily', {\n why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`,\n });\n }\n\n if (result.targetMismatch) {\n throw errorConfigValidation('contract.target', {\n why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`,\n });\n }\n\n if (result.missingExtensionPackIds.length > 0) {\n throw errorContractMissingExtensionPacks({\n missingExtensionPacks: result.missingExtensionPackIds,\n providedComponentIds: [...providedComponentIds],\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,oCAId,kBACA,kBACA,qBACqE;CACrE,KAAK,IAAI,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;EACnD,MAAM,YAAY,oBAAoB;EAGtC,IAAI,OAAO,cAAc,YAAY,cAAc,MACjD,MAAM,sBAAsB,yBAAyB,EACnD,KAAK,gCAAgC,EAAE,oBACzC,CAAC;EAGH,MAAM,SAAS;EAGf,IAAI,CAAC,OAAO,OAAO,QAAQ,MAAM,GAC/B,MAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,4BACzC,CAAC;EAGH,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,YAAY,SAAS,aAAa,SAAS,eAAe,SAAS,UAC9E,MAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,qBAAqB,OAAO,IAAI,EAAE,2DAC3E,CAAC;EAIH,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GACnC,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,iCAChE,CAAC;EAGH,MAAM,WAAW,OAAO;EACxB,IAAI,aAAa,kBACf,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,kBAAkB,OAAO,QAAQ,EAAE,kBAAkB,iBAAiB,GACtI,CAAC;EAIH,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GACnC,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,iCAChE,CAAC;EAGH,MAAM,WAAW,OAAO;EACxB,IAAI,aAAa,kBACf,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,kBAAkB,OAAO,QAAQ,EAAE,kBAAkB,iBAAiB,GACtI,CAAC;CAEL;CAGA,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 "./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-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, K as errorInvalidSpaceId, _ as createTerminalUI, at as errorSpaceNotFound, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, q as errorLegendHumanOnly, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { n as buildReadAggregate } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { a as renderMigrationGraphLegend, c as renderMigrationListWithStyle, o as createAnsiMigrationListStyler } 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 { APP_SPACE_ID, isValidSpaceId, listContractSpaceDirectories } from "@prisma-next/migration-tools/spaces";
import { HEAD_REF_NAME, refsByContractHash } from "@prisma-next/migration-tools/refs";
//#region src/utils/legend.ts
/**
* The legend is decoration printed alongside the command header on stderr, so
* it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
* `--quiet`) exactly as the header is.
*/
function shouldShowLegend(options, flags) {
return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
}
function validateLegendOptions(options, flags) {
if (options.legend !== true) return ok(void 0);
if (flags.json === true) return notOk(errorLegendHumanOnly("--json"));
if (flags.quiet === true) return notOk(errorLegendHumanOnly("--quiet"));
if (options.dot === true) return notOk(errorLegendHumanOnly("--dot"));
return ok(void 0);
}
//#endregion
//#region src/commands/migration-list.ts
function compareSpaceIds(a, b) {
if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;
if (b === APP_SPACE_ID) return 1;
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
function compareDirNamesDescending(a, b) {
if (a.name < b.name) return 1;
if (a.name > b.name) return -1;
return 0;
}
/**
* Ref names decorating a space's destination contract hashes. The
* tolerant `space.refs` deliberately omits the structural `head.json`;
* for extension spaces the old enumerator surfaced it as a `head`
* decoration on the tip migration, so fold `space.headRef` back in to
* keep that output. The app space synthesises its head, so it carries
* no on-disk `head` ref to restore.
*/
function listRefsByContractHash(space) {
const byHash = new Map(refsByContractHash(space.refs));
if (space.spaceId !== APP_SPACE_ID && space.headRef !== null) {
const hash = space.headRef.hash;
const bucket = byHash.get(hash) ?? [];
if (!bucket.includes(HEAD_REF_NAME)) byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());
}
return byHash;
}
async function orderedOnDiskSpaceIds(projectMigrationsDir) {
return (await listContractSpaceDirectories(projectMigrationsDir)).filter(isValidSpaceId).sort(compareSpaceIds);
}
/**
* Project the loaded {@link ContractSpaceAggregate} into the render-ready
* {@link MigrationSpaceListEntry} rows `migration list` displays.
*
* Space membership matches the on-disk contract-space directories (not the
* aggregate's always-present synthesized app space when `migrations/app/`
* is absent); package and ref data come from `aggregate.space(id)`.
*/
async function migrationSpaceListEntriesFromAggregate(aggregate, projectMigrationsDir) {
const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);
const spaces = [];
for (const spaceId of spaceIds) {
const space = aggregate.space(spaceId);
if (space === void 0) continue;
const refsByHash = listRefsByContractHash(space);
const migrations = space.packages.map((pkg) => ({
name: pkg.dirName,
hash: pkg.metadata.migrationHash,
fromContract: pkg.metadata.from,
toContract: pkg.metadata.to,
operationCount: pkg.ops.length,
createdAt: pkg.metadata.createdAt,
refs: [...refsByHash.get(pkg.metadata.to) ?? []],
providedInvariants: [...pkg.metadata.providedInvariants]
})).sort(compareDirNamesDescending);
spaces.push({
space: spaceId,
migrations
});
}
return spaces;
}
function renderMigrationListHumanOutput(result, options) {
return renderMigrationListWithStyle(result, createAnsiMigrationListStyler({ useColor: options.useColor }), options.glyphMode, {
colorize: options.useColor,
liveContractHash: options.liveContractHash,
graphForSpace: options.graphForSpace,
...options.appSpaceId !== void 0 ? { appSpaceId: options.appSpaceId } : {}
});
}
function computeSummary(spaces) {
const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);
if (spaces.length <= 1) return `${totalMigrations} migration(s) on disk`;
return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;
}
/**
* Policy core of `migration list`: validates `--space`, narrows the
* pre-enumerated spaces, and assembles a {@link MigrationListResult}.
*
* - `migrations/` missing or contains no valid space directories →
* caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.
* - `--space <id>` on an existing-but-empty space → `{ spaceId, migrations: [] }` in the input.
* - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.
*/
function runMigrationList(inputs) {
const { spaces, spaceFilter } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return notOk(errorInvalidSpaceId(spaceFilter));
if (spaceFilter !== void 0 && !spaces.some((s) => s.space === spaceFilter)) return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.space).sort()));
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.space === spaceFilter) : spaces;
const resultSpaces = scopedSpaces.length === 0 ? [{
space: APP_SPACE_ID,
migrations: []
}] : scopedSpaces;
return ok({
ok: true,
spaces: [...resultSpaces],
summary: computeSummary(resultSpaces)
});
}
/**
* CLI shell: loads config, resolves paths, prints the styled header on
* stderr (interactive mode only), and delegates to {@link runMigrationList}.
* Kept intentionally thin so the unit-testable surface lives in the core.
*/
async function executeMigrationListCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);
if (!flags.json && !flags.quiet) {
const header = formatStyledHeader({
command: "migration list",
description: "List on-disk migrations per contract space",
details: [
{
label: "config",
value: configPath
},
{
label: "migrations",
value: migrationsRelative
},
...options.space !== void 0 ? [{
label: "space",
value: options.space
}] : []
],
flags
});
ui.stderr(header);
if (shouldShowLegend(options, flags)) {
ui.stderr(renderMigrationGraphLegend({
colorize: flags.color !== false,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
ui.stderr("");
}
}
const loaded = await buildReadAggregate(config, { migrationsDir });
if (!loaded.ok) return notOk(loaded.failure);
const { aggregate, contractHash: liveContractHash } = loaded.value;
const listResult = runMigrationList({
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
...ifDefined("spaceFilter", options.space)
});
if (!listResult.ok) return listResult;
return ok({
list: listResult.value,
liveContractHash,
aggregate
});
}
function createMigrationListCommand() {
const command = new Command("list");
setCommandDescriptions(command, "List on-disk migrations per contract space", "Enumerates every on-disk migration under migrations/<space>/ for every\ncontract space found on disk. Offline — does not consult the database.\nHuman output draws the shared migration graph tree with operation counts,\ninvariants on each migration row, and refs on destination contract nodes.\nPass --space <id> to narrow to one contract space. --ascii forces ASCII\ntree glyphs (orthogonal to --no-color).");
setCommandExamples(command, [
"prisma-next migration list",
"prisma-next migration list --space app",
"prisma-next migration list --ascii",
"prisma-next migration list --legend",
"prisma-next migration list --json"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration log",
oneLiner: "Show executed migration history"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").option("--ascii", "Use ASCII kind glyphs (pipe-friendly)").option("--legend", "Print a key for the tree glyphs and lane colors").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 executeMigrationListCommand(options, flags, ui), flags, ui, ({ list, liveContractHash, aggregate }) => {
if (flags.json) ui.output(JSON.stringify(list, null, 2));
else if (!flags.quiet) ui.output(renderMigrationListHumanOutput(list, {
glyphMode: ui.resolveGlyphMode(options.ascii === true),
useColor: ui.useColor,
liveContractHash,
graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),
appSpaceId: aggregate.app.spaceId
}));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { renderMigrationListHumanOutput as a, validateLegendOptions as c, migrationSpaceListEntriesFromAggregate as i, executeMigrationListCommand as n, runMigrationList as o, listRefsByContractHash as r, shouldShowLegend as s, createMigrationListCommand as t };
//# sourceMappingURL=migration-list-cnSKilYH.mjs.map
{"version":3,"file":"migration-list-cnSKilYH.mjs","names":[],"sources":["../src/utils/legend.ts","../src/commands/migration-list.ts"],"sourcesContent":["import { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { type CliStructuredError, errorLegendHumanOnly } from './cli-errors';\nimport type { GlobalFlags } from './global-flags';\n\nexport interface LegendCliOptions {\n readonly legend?: boolean;\n readonly dot?: boolean;\n}\n\n/**\n * The legend is decoration printed alongside the command header on stderr, so\n * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,\n * `--quiet`) exactly as the header is.\n */\nexport function shouldShowLegend(options: LegendCliOptions, flags: GlobalFlags): boolean {\n return (\n options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true\n );\n}\n\nexport function validateLegendOptions(\n options: LegendCliOptions,\n flags: GlobalFlags,\n): Result<void, CliStructuredError> {\n if (options.legend !== true) {\n return ok(undefined);\n }\n if (flags.json === true) {\n return notOk(errorLegendHumanOnly('--json'));\n }\n if (flags.quiet === true) {\n return notOk(errorLegendHumanOnly('--quiet'));\n }\n if (options.dot === true) {\n return notOk(errorLegendHumanOnly('--dot'));\n }\n return ok(undefined);\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type {\n AggregateContractSpace,\n ContractSpaceAggregate,\n} from '@prisma-next/migration-tools/aggregate';\nimport type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';\nimport {\n APP_SPACE_ID,\n isValidSpaceId,\n listContractSpaceDirectories,\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 {\n type CliStructuredError,\n errorInvalidSpaceId,\n errorSpaceNotFound,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-labels';\nimport { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';\nimport { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';\nimport type {\n MigrationListEntry,\n MigrationListResult,\n MigrationSpaceListEntry,\n} 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 type { GlyphMode } from '../utils/glyph-mode';\nimport { shouldShowLegend, validateLegendOptions } from '../utils/legend';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\nfunction compareSpaceIds(a: string, b: string): number {\n if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;\n if (b === APP_SPACE_ID) return 1;\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n\nfunction compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry): number {\n if (a.name < b.name) return 1;\n if (a.name > b.name) return -1;\n return 0;\n}\n\n/**\n * Ref names decorating a space's destination contract hashes. The\n * tolerant `space.refs` deliberately omits the structural `head.json`;\n * for extension spaces the old enumerator surfaced it as a `head`\n * decoration on the tip migration, so fold `space.headRef` back in to\n * keep that output. The app space synthesises its head, so it carries\n * no on-disk `head` ref to restore.\n */\nexport function listRefsByContractHash(\n space: AggregateContractSpace,\n): ReadonlyMap<string, readonly string[]> {\n const byHash = new Map(refsByContractHash(space.refs));\n if (space.spaceId !== APP_SPACE_ID && space.headRef !== null) {\n const hash = space.headRef.hash;\n const bucket = byHash.get(hash) ?? [];\n if (!bucket.includes(HEAD_REF_NAME)) {\n byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());\n }\n }\n return byHash;\n}\n\nasync function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<readonly string[]> {\n const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);\n return candidateDirs.filter(isValidSpaceId).sort(compareSpaceIds);\n}\n\n/**\n * Project the loaded {@link ContractSpaceAggregate} into the render-ready\n * {@link MigrationSpaceListEntry} rows `migration list` displays.\n *\n * Space membership matches the on-disk contract-space directories (not the\n * aggregate's always-present synthesized app space when `migrations/app/`\n * is absent); package and ref data come from `aggregate.space(id)`.\n */\nexport async function migrationSpaceListEntriesFromAggregate(\n aggregate: ContractSpaceAggregate,\n projectMigrationsDir: string,\n): Promise<readonly MigrationSpaceListEntry[]> {\n const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);\n const spaces: MigrationSpaceListEntry[] = [];\n\n for (const spaceId of spaceIds) {\n const space = aggregate.space(spaceId);\n if (space === undefined) {\n continue;\n }\n const refsByHash = listRefsByContractHash(space);\n const migrations: MigrationListEntry[] = space.packages\n .map((pkg) => ({\n name: pkg.dirName,\n hash: pkg.metadata.migrationHash,\n fromContract: pkg.metadata.from,\n toContract: pkg.metadata.to,\n operationCount: pkg.ops.length,\n createdAt: pkg.metadata.createdAt,\n refs: [...(refsByHash.get(pkg.metadata.to) ?? [])],\n providedInvariants: [...pkg.metadata.providedInvariants],\n }))\n .sort(compareDirNamesDescending);\n\n spaces.push({ space: spaceId, migrations });\n }\n\n return spaces;\n}\n\ninterface MigrationListOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly space?: string;\n readonly ascii?: boolean;\n readonly legend?: boolean;\n}\n\nexport interface MigrationListExecuteResult {\n readonly list: MigrationListResult;\n readonly liveContractHash: string;\n readonly aggregate: ContractSpaceAggregate;\n}\n\nexport interface MigrationListHumanRenderOptions {\n readonly glyphMode: GlyphMode;\n readonly useColor: boolean;\n readonly liveContractHash: string;\n readonly graphForSpace: (spaceId: string) => MigrationGraph | undefined;\n readonly appSpaceId?: string;\n}\n\nexport function renderMigrationListHumanOutput(\n result: MigrationListResult,\n options: MigrationListHumanRenderOptions,\n): string {\n const styler = createAnsiMigrationListStyler({ useColor: options.useColor });\n return renderMigrationListWithStyle(result, styler, options.glyphMode, {\n colorize: options.useColor,\n liveContractHash: options.liveContractHash,\n graphForSpace: options.graphForSpace,\n ...(options.appSpaceId !== undefined ? { appSpaceId: options.appSpaceId } : {}),\n });\n}\n\n/**\n * Inputs for {@link runMigrationList} — the policy core of `migration list`\n * that tests exercise directly.\n *\n * The core does not call `loadConfig`, parse CLI flags, render a styled\n * header, or write to any stream. Enumeration is supplied by the caller\n * (the CLI shell builds it from {@link migrationSpaceListEntriesFromAggregate}).\n */\nexport interface RunMigrationListInputs {\n readonly spaces: readonly MigrationSpaceListEntry[];\n readonly spaceFilter?: string;\n}\n\nfunction computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {\n const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);\n if (spaces.length <= 1) {\n return `${totalMigrations} migration(s) on disk`;\n }\n return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;\n}\n\n/**\n * Policy core of `migration list`: validates `--space`, narrows the\n * pre-enumerated spaces, and assembles a {@link MigrationListResult}.\n *\n * - `migrations/` missing or contains no valid space directories →\n * caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.\n * - `--space <id>` on an existing-but-empty space → `{ spaceId, migrations: [] }` in the input.\n * - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.\n */\nexport function runMigrationList(\n inputs: RunMigrationListInputs,\n): Result<MigrationListResult, CliStructuredError> {\n const { spaces, spaceFilter } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return notOk(errorInvalidSpaceId(spaceFilter));\n }\n\n if (spaceFilter !== undefined && !spaces.some((s) => s.space === spaceFilter)) {\n return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.space).sort()));\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.space === spaceFilter) : spaces;\n\n const resultSpaces: readonly MigrationSpaceListEntry[] =\n scopedSpaces.length === 0 ? [{ space: APP_SPACE_ID, migrations: [] }] : scopedSpaces;\n\n return ok({\n ok: true,\n spaces: [...resultSpaces],\n summary: computeSummary(resultSpaces),\n });\n}\n\n/**\n * CLI shell: loads config, resolves paths, prints the styled header on\n * stderr (interactive mode only), and delegates to {@link runMigrationList}.\n * Kept intentionally thin so the unit-testable surface lives in the core.\n */\nexport async function executeMigrationListCommand(\n options: MigrationListOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationListExecuteResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration list',\n description: 'List on-disk migrations per contract space',\n details: [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),\n ],\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 loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n\n const { aggregate, contractHash: liveContractHash } = loaded.value;\n\n const spaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);\n\n const listResult = runMigrationList({\n spaces,\n ...ifDefined('spaceFilter', options.space),\n });\n if (!listResult.ok) {\n return listResult;\n }\n return ok({ list: listResult.value, liveContractHash, aggregate });\n}\n\nexport function createMigrationListCommand(): Command {\n const command = new Command('list');\n setCommandDescriptions(\n command,\n 'List on-disk migrations per contract space',\n 'Enumerates every on-disk migration under migrations/<space>/ for every\\n' +\n 'contract space found on disk. Offline — does not consult the database.\\n' +\n 'Human output draws the shared migration graph tree with operation counts,\\n' +\n 'invariants on each migration row, and refs on destination contract nodes.\\n' +\n 'Pass --space <id> to narrow to one contract space. --ascii forces ASCII\\n' +\n 'tree glyphs (orthogonal to --no-color).',\n );\n setCommandExamples(command, [\n 'prisma-next migration list',\n 'prisma-next migration list --space app',\n 'prisma-next migration list --ascii',\n 'prisma-next migration list --legend',\n 'prisma-next migration list --json',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration log', oneLiner: 'Show executed migration history' },\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('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .option('--ascii', 'Use ASCII kind glyphs (pipe-friendly)')\n .option('--legend', 'Print a key for the tree glyphs and lane colors')\n .action(async (options: MigrationListOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const legendValidation = validateLegendOptions(options, flags);\n if (!legendValidation.ok) {\n process.exit(handleResult(legendValidation, flags, ui));\n }\n const result = await executeMigrationListCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, ({ list, liveContractHash, aggregate }) => {\n if (flags.json) {\n ui.output(JSON.stringify(list, null, 2));\n } else if (!flags.quiet) {\n ui.output(\n renderMigrationListHumanOutput(list, {\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n useColor: ui.useColor,\n liveContractHash,\n graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),\n appSpaceId: aggregate.app.spaceId,\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, 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 { rt as errorRuntime } from "./command-helpers-CVn3U9Uz.mjs";
import { notOk, ok } from "@prisma-next/utils/result";
import { isAbsolute, relative, resolve } from "pathe";
//#region src/utils/migration-path-target.ts
function looksLikePath(target) {
return target.includes("/") || target.includes("\\");
}
function resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative) {
const targetPath = resolve(target);
const relativeToApp = relative(appMigrationsDir, targetPath);
if (relativeToApp === "" || relativeToApp === "." || relativeToApp.startsWith("..") || isAbsolute(relativeToApp)) return notOk(errorRuntime("Target must point to an app-space migration", {
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
fix: "Pass an app-space migration directory or use a hash prefix."
}));
return ok(targetPath);
}
/**
* Resolve a filesystem-path target to the migration dir that contains it,
* searching each in-scope space's `migrationsDir`. A path is explicit, so
* it can belong to at most one space — returns the first match, or `null`
* when the path falls outside every space dir.
*/
function resolveTargetPathAcrossSpaces(target, spaces) {
const targetPath = resolve(target);
for (const space of spaces) {
const rel = relative(space.migrationsDir, targetPath);
if (!(rel === "" || rel === "." || rel.startsWith("..") || isAbsolute(rel))) return targetPath;
}
return null;
}
function findPackageByDirPath(packages, resolvedDirPath) {
const normalized = resolve(resolvedDirPath);
return packages.find((p) => resolve(p.dirPath) === normalized);
}
//#endregion
export { resolveTargetPathAcrossSpaces as i, looksLikePath as n, resolveAppTargetPath as r, findPackageByDirPath as t };
//# sourceMappingURL=migration-path-target-C0TQuV5n.mjs.map
{"version":3,"file":"migration-path-target-C0TQuV5n.mjs","names":[],"sources":["../src/utils/migration-path-target.ts"],"sourcesContent":["import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { isAbsolute, relative, resolve } from 'pathe';\nimport { type CliStructuredError, errorRuntime } from './cli-errors';\n\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || target.includes('\\\\');\n}\n\nexport function resolveAppTargetPath(\n target: string,\n appMigrationsDir: string,\n appMigrationsRelative: string,\n): Result<string, CliStructuredError> {\n const targetPath = resolve(target);\n const relativeToApp = relative(appMigrationsDir, targetPath);\n const isOutsideAppDir =\n relativeToApp === '' ||\n relativeToApp === '.' ||\n relativeToApp.startsWith('..') ||\n isAbsolute(relativeToApp);\n if (isOutsideAppDir) {\n return notOk(\n errorRuntime('Target must point to an app-space migration', {\n why: `Expected a path under ${appMigrationsRelative}, got ${target}`,\n fix: 'Pass an app-space migration directory or use a hash prefix.',\n }),\n );\n }\n return ok(targetPath);\n}\n\n/**\n * Resolve a filesystem-path target to the migration dir that contains it,\n * searching each in-scope space's `migrationsDir`. A path is explicit, so\n * it can belong to at most one space — returns the first match, or `null`\n * when the path falls outside every space dir.\n */\nexport function resolveTargetPathAcrossSpaces(\n target: string,\n spaces: ReadonlyArray<{ readonly migrationsDir: string }>,\n): string | null {\n const targetPath = resolve(target);\n for (const space of spaces) {\n const rel = relative(space.migrationsDir, targetPath);\n const isOutside = rel === '' || rel === '.' || rel.startsWith('..') || isAbsolute(rel);\n if (!isOutside) {\n return targetPath;\n }\n }\n return null;\n}\n\nexport function findPackageByDirPath(\n packages: readonly OnDiskMigrationPackage[],\n resolvedDirPath: string,\n): OnDiskMigrationPackage | undefined {\n const normalized = resolve(resolvedDirPath);\n return packages.find((p) => resolve(p.dirPath) === normalized);\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,QAAyB;CACrD,OAAO,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,IAAI;AACrD;AAEA,SAAgB,qBACd,QACA,kBACA,uBACoC;CACpC,MAAM,aAAa,QAAQ,MAAM;CACjC,MAAM,gBAAgB,SAAS,kBAAkB,UAAU;CAM3D,IAJE,kBAAkB,MAClB,kBAAkB,OAClB,cAAc,WAAW,IAAI,KAC7B,WAAW,aAAa,GAExB,OAAO,MACL,aAAa,+CAA+C;EAC1D,KAAK,yBAAyB,sBAAsB,QAAQ;EAC5D,KAAK;CACP,CAAC,CACH;CAEF,OAAO,GAAG,UAAU;AACtB;;;;;;;AAQA,SAAgB,8BACd,QACA,QACe;CACf,MAAM,aAAa,QAAQ,MAAM;CACjC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,SAAS,MAAM,eAAe,UAAU;EAEpD,IAAI,EADc,QAAQ,MAAM,QAAQ,OAAO,IAAI,WAAW,IAAI,KAAK,WAAW,GAAG,IAEnF,OAAO;CAEX;CACA,OAAO;AACT;AAEA,SAAgB,qBACd,UACA,iBACoC;CACpC,MAAM,aAAa,QAAQ,eAAe;CAC1C,OAAO,SAAS,MAAM,MAAM,QAAQ,EAAE,OAAO,MAAM,UAAU;AAC/D"}
import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, Q as errorPlanForgotTheFlag, W as errorFileNotFound, X as errorMigrationPlanningFailed, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, it as errorSnapshotMissing, l as setCommandDescriptions, lt as mapMigrationToolsError, o as resolveContractPath, ot as errorTargetMigrationNotSupported, r as getTargetMigrations, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "./command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-VMQJbAwl.mjs";
import { c as toExtensionInputs, i as loadContractSpaceAggregateForCli, t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-hPVymNLw.mjs";
import { t as mapContractAtError } from "./contract-at-errors-C-l4-JEY.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
import { notOk, ok } from "@prisma-next/utils/result";
import { join, relative } from "pathe";
import { readFile } from "node:fs/promises";
import { createControlStack, hasOperationPreview } from "@prisma-next/framework-components/control";
import { emitContractSpaceArtifacts, planAllSpaces, readContractSpaceHeadRef, spaceMigrationDirectory } from "@prisma-next/migration-tools/spaces";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
import { assertHashIsGraphNode, findLatestMigration, isGraphNode } from "@prisma-next/migration-tools/migration-graph";
import { snapshotsImportPathFrom, writeContractSnapshot } from "@prisma-next/migration-tools/contract-snapshot-store";
import { parseContractRef } from "@prisma-next/migration-tools/ref-resolution";
import { computeMigrationHash } from "@prisma-next/migration-tools/hash";
import { formatMigrationDirName, materialiseExtensionMigrationPackageIfMissing, writeMigrationPackage } from "@prisma-next/migration-tools/io";
import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
import { deriveProvidedInvariants } from "@prisma-next/migration-tools/invariants";
//#region src/utils/contract-space-seed-phase.ts
/**
* Phase-1 of the two-phase `migration plan` pipeline (sub-spec § 4).
*
* For every extension that exposes a `contractSpace`:
*
* 1. Read the on-disk head ref (returns `null` on first emit).
* 2. Write the head contract into the migrations-root snapshot store
* (write-if-absent, keyed by hash) and unconditionally re-emit
* `refs/head.json`, via {@link emitContractSpaceArtifacts}. The
* framework owns `refs/head.json`; re-emit is the contract for that
* file. The snapshot itself is only written once per distinct hash.
* 3. Materialise any descriptor-shipped migration packages not yet on
* disk via {@link materialiseExtensionMigrationPackageIfMissing}.
* Existing packages are left untouched (by-existence skip).
*
* The return value lets the caller render a per-space status line and
* lets the phase-2 aggregate loader run on a now-consistent disk state
* (every loaded extension is guaranteed to have its head ref pinned
* to the descriptor's hash and to ship every package the descriptor
* declares).
*
* Output ordering is deterministic and alphabetical by spaceId (via
* {@link planAllSpaces}, which also detects duplicate spaceIds). This
* matches the canonical sort order used by every other aggregate
* surface (`migrate`, `migration status`, the runner).
*/
async function runContractSpaceSeedPhase(inputs) {
const planned = planAllSpaces(inputs.extensionPacks.filter((pack) => pack.contractSpace !== void 0).map((pack) => ({
spaceId: pack.id,
priorContract: null,
newContract: pack.contractSpace.contractJson,
__pack: pack.contractSpace
})), (input) => input.__pack.migrations);
const descriptorBySpace = /* @__PURE__ */ new Map();
for (const pack of inputs.extensionPacks) if (pack.contractSpace !== void 0) descriptorBySpace.set(pack.id, pack.contractSpace);
const seeded = [];
for (const space of planned) {
const descriptor = descriptorBySpace.get(space.spaceId);
if (descriptor === void 0) continue;
const priorHash = (await readContractSpaceHeadRef(inputs.migrationsDir, space.spaceId))?.hash ?? null;
await emitContractSpaceArtifacts(inputs.migrationsDir, space.spaceId, {
contract: descriptor.contractJson,
contractDts: buildPlaceholderContractDts(space.spaceId),
headRef: {
hash: descriptor.headRef.hash,
invariants: descriptor.headRef.invariants
}
});
const spaceDir = spaceMigrationDirectory(inputs.migrationsDir, space.spaceId);
const newMigrationDirs = [];
for (const pkg of space.migrationPackages) {
const { written } = await materialiseExtensionMigrationPackageIfMissing(spaceDir, pkg);
if (written) newMigrationDirs.push(pkg.dirName);
}
const action = priorHash !== descriptor.headRef.hash || newMigrationDirs.length > 0 ? "updated" : "unchanged";
seeded.push({
spaceId: space.spaceId,
action,
priorHash,
newHash: descriptor.headRef.hash,
newMigrationDirs
});
}
return { seeded };
}
/**
* Placeholder `.d.ts` content for an extension space's on-disk mirror.
*
* Rendering a fully-typed `.d.ts` for an extension contract requires
* the SQL-family renderer with the codec / typemap registry threaded
* through; until that integration ships, the on-disk `.d.ts` is a
* stub `export {};` module that documents how consumers should
* validate the sibling `contract.json`. The stub typechecks on its
* own and does not need any TypeScript suppressions.
*/
function buildPlaceholderContractDts(spaceId) {
return [
"/**",
` * Placeholder \`.d.ts\` for extension space "${spaceId}".`,
" *",
" * The framework re-emits this file on every `migration plan` run",
" * alongside `contract.json` and `refs/head.json`. A typed `.d.ts`",
" * rendering pass for extension contracts is tracked separately;",
" * until that ships, consumers should import `contract.json`",
" * and pass it through the target descriptor’s `contractSerializer`.",
" */",
"export {};",
""
].join("\n");
}
//#endregion
//#region src/utils/plan-resolution.ts
const FULL_HASH_PATTERN = /^sha256:([0-9a-f]{64}|empty)$/;
function looksLikeFullHash(input) {
return FULL_HASH_PATTERN.test(input);
}
function graphIsEmpty(space) {
return space.packages.length === 0;
}
function getReachableRefs(refs, graph) {
return Object.entries(refs).flatMap(([name, entry]) => entry && isGraphNode(entry.hash, graph) ? [{
name,
hash: entry.hash
}] : []).sort((a, b) => a.name.localeCompare(b.name));
}
function assertFromIsGraphNode(fromHash, graph, refs, graphTipHash) {
try {
assertHashIsGraphNode(fromHash, graph);
} catch (error) {
if (MigrationToolsError.is(error) && error.code === "MIGRATION.HASH_NOT_IN_GRAPH") throw errorPlanForgotTheFlag(fromHash, getReachableRefs(refs, graph), graphTipHash);
throw error;
}
}
async function resolveContractRef(parsed, space, options) {
const { hash, provenance } = parsed;
const refName = provenance.kind === "ref" ? provenance.refName : void 0;
try {
const at = await space.contractAt(hash, refName !== void 0 ? { refName } : void 0);
if (at.provenance === "ref") return ok({
kind: "ref",
hash: at.hash,
contract: at.contract,
contractJson: at.contractJson,
contractDts: at.contractDts
});
return ok({
kind: "graph-node",
hash: at.hash,
contract: at.contract,
contractJson: at.contractJson,
contractDts: at.contractDts
});
} catch (error) {
return mapContractAtError(error, options?.artifactRole !== void 0 ? { artifactRole: options.artifactRole } : void 0);
}
}
async function resolveFromPolicy(parsed, input, refs, explicitFromLabel) {
const resolution = await resolveContractRef(parsed, input.space, {
...explicitFromLabel !== void 0 ? { explicitLabel: explicitFromLabel } : {},
artifactRole: "from"
});
if (!resolution.ok) return resolution;
if (resolution.value.kind === "graph-node") return ok({
kind: "graph-node",
fromHash: resolution.value.hash,
fromContract: resolution.value.contract
});
const { hash, contract, contractJson, contractDts } = resolution.value;
if (graphIsEmpty(input.space)) return ok({
kind: "auto-baseline",
fromHash: hash,
fromContract: contract,
contractDts,
contractJson
});
const graph = input.space.graph();
const graphTip = findLatestMigration(graph)?.to ?? null;
try {
assertFromIsGraphNode(hash, graph, refs, graphTip);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
throw error;
}
return ok({
kind: "ref",
fromHash: hash,
fromContract: contract,
contractDts,
contractJson
});
}
async function resolveFromForPlan(input) {
const { optionsFrom, space } = input;
const graph = space.graph();
const refs = space.refs;
if (optionsFrom === void 0) {
const dbRef = refs["db"];
if (!dbRef) return ok({
kind: "greenfield",
fromHash: null,
fromContract: null
});
return resolveFromPolicy({
hash: dbRef.hash,
provenance: {
kind: "ref",
refName: "db"
}
}, input, refs);
}
const refResult = parseContractRef(optionsFrom, {
graph,
refs
});
if (!refResult.ok) {
if (looksLikeFullHash(optionsFrom)) {
const empty = graphIsEmpty(space);
const graphTip = findLatestMigration(graph)?.to ?? null;
if (empty) return notOk(errorSnapshotMissing(optionsFrom, { viaRef: false }));
return notOk(errorPlanForgotTheFlag(optionsFrom, getReachableRefs(refs, graph), graphTip));
}
return notOk(mapRefResolutionError(refResult.failure));
}
return resolveFromPolicy(refResult.value, input, refs, optionsFrom);
}
async function resolveToForPlan(optionsTo, input) {
const { space } = input;
const graph = space.graph();
const refs = space.refs;
const refResult = parseContractRef(optionsTo, {
graph,
refs
});
if (!refResult.ok) return notOk(mapRefResolutionError(refResult.failure));
const resolution = await resolveContractRef(refResult.value, space, {
explicitLabel: optionsTo,
artifactRole: "to"
});
if (!resolution.ok) return resolution;
const { hash, contract, contractJson, contractDts } = resolution.value;
return ok({
hash,
contract,
contractJson,
contractDts
});
}
//#endregion
//#region src/commands/migration-plan.ts
async function runPlannerLeg(planner, migrations, frameworkComponents, contract, fromContract, spaceId, ownership, snapshotsImportPath) {
const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
const plannerResult = planner.plan({
contract,
schema: fromSchema,
policy: { allowedOperationClasses: [
"additive",
"widening",
"destructive",
"data"
] },
fromContract,
frameworkComponents,
spaceId,
ownership,
snapshotsImportPath
});
if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
let plannedOps = [];
let hasPlaceholders = false;
try {
plannedOps = await Promise.all(plannerResult.plan.operations);
if (plannedOps.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
kind: "unsupportedChange",
summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
}] }));
} catch (e) {
if (CliStructuredError.is(e) && e.code === "MIGRATION.UNFILLED_PLACEHOLDER") hasPlaceholders = true;
else throw e;
}
return ok({
plannedOps,
migrationTsContent: plannerResult.plan.renderTypeScript(),
hasPlaceholders
});
}
async function writePlannedMigrationPackage(packageDir, fromHash, toHash, createdAt, leg) {
const opsForWrite = leg.hasPlaceholders ? [] : leg.plannedOps;
const metadataWithInvariants = {
from: fromHash,
to: toHash,
providedInvariants: deriveProvidedInvariants(opsForWrite),
createdAt: createdAt.toISOString()
};
await writeMigrationPackage(packageDir, {
...metadataWithInvariants,
migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite)
}, opsForWrite);
await writeMigrationTs(packageDir, leg.migrationTsContent);
}
async function executeMigrationPlanCommand(options, flags, ui, startTime) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
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
},
{
label: "migrations",
value: appMigrationsRelative
}
];
if (options.from) details.push({
label: "from",
value: options.from
});
if (options.to) details.push({
label: "to",
value: options.to
});
if (options.name) details.push({
label: "name",
value: options.name
});
const header = formatStyledHeader({
command: "migration plan",
description: "Plan a migration from contract changes",
url: "https://pris.ly/migration-plan",
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)}` }));
}
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
const controlAdapter = config.adapter.create(stack);
let toContract;
try {
toContract = familyInstance.deserializeContract(JSON.parse(contractJsonContent));
} catch (error) {
return notOk(errorContractValidationFailed(`Contract at ${contractPathAbsolute} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const rawStorageHash = toContract.storage?.storageHash;
if (typeof rawStorageHash !== "string") return notOk(errorContractValidationFailed("Contract is missing storageHash", { where: { path: contractPathAbsolute } }));
let toStorageHash = rawStorageHash;
let toArtifacts = null;
let fromContract = null;
let fromHash = null;
let snapshotStartContract = null;
let isAutoBaseline = false;
const tolerantAggregateResult = await loadContractSpaceAggregateForCli({
targetId: config.target.targetId,
migrationsDir,
appContract: toContract,
extensionPacks: config.extensionPacks ?? [],
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!tolerantAggregateResult.ok) return notOk(tolerantAggregateResult.failure);
const resolutionSpace = tolerantAggregateResult.value.app;
const resolutionResult = await resolveFromForPlan({
optionsFrom: options.from,
space: resolutionSpace
});
if (!resolutionResult.ok) return notOk(resolutionResult.failure);
switch (resolutionResult.value.kind) {
case "greenfield": break;
case "graph-node":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
break;
case "ref":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
snapshotStartContract = {
fromHash: resolutionResult.value.fromHash,
contractJson: resolutionResult.value.contractJson,
contractDts: resolutionResult.value.contractDts
};
break;
case "auto-baseline":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
snapshotStartContract = {
fromHash: resolutionResult.value.fromHash,
contractJson: resolutionResult.value.contractJson,
contractDts: resolutionResult.value.contractDts
};
isAutoBaseline = true;
break;
}
if (options.to !== void 0) {
const toResolution = await resolveToForPlan(options.to, { space: resolutionSpace });
if (!toResolution.ok) return notOk(toResolution.failure);
toContract = toResolution.value.contract;
toStorageHash = toResolution.value.hash;
toArtifacts = {
contractJson: toResolution.value.contractJson,
contractDts: toResolution.value.contractDts
};
}
const seedResult = await runContractSpaceSeedPhase({
migrationsDir,
extensionPacks: toExtensionInputs(config.extensionPacks ?? [])
});
if (!flags.json && !flags.quiet) {
for (const record of seedResult.seeded) if (record.action === "updated") {
const pkgSuffix = record.newMigrationDirs.length > 0 ? `; ${record.newMigrationDirs.length} new migration package(s) materialised` : "";
ui.step(`Updated ${record.spaceId} to ${record.newHash}${pkgSuffix}`);
}
}
const emittedExtensionDirs = seedResult.seeded.flatMap((r) => r.newMigrationDirs.map((dirName) => ({
spaceId: r.spaceId,
dirName
})));
if (fromHash === toStorageHash && !isAutoBaseline) return ok({
ok: true,
noOp: true,
from: fromHash,
to: toStorageHash,
operations: [],
emittedExtensionDirs,
summary: "No changes detected between contracts",
timings: { total: Date.now() - startTime }
});
const migrations = getTargetMigrations(config.target);
if (!migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
const aggregateResult = await buildContractSpaceAggregate({
targetId: config.target.targetId,
migrationsDir,
appContract: toContract,
extensionPacks: config.extensionPacks ?? [],
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
const aggregate = aggregateResult.value;
const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
config.target,
config.adapter,
...config.extensionPacks ?? []
]);
async function writeDestinationSnapshot(destHash) {
if (toArtifacts !== null) {
await writeContractSnapshot(migrationsDir, destHash, {
contractJson: toArtifacts.contractJson,
contractDts: toArtifacts.contractDts
});
return;
}
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
const [contractJsonRaw, contractDts] = await Promise.all([readFile(destinationArtifacts.jsonPath, "utf-8"), readFile(destinationArtifacts.dtsPath, "utf-8")]);
await writeContractSnapshot(migrationsDir, destHash, {
contractJson: JSON.parse(contractJsonRaw),
contractDts
});
}
try {
const planner = migrations.createPlanner(controlAdapter);
if (isAutoBaseline && fromHash !== null && fromContract !== null && snapshotStartContract !== null) {
const baselineTimestamp = /* @__PURE__ */ new Date();
const deltaTimestamp = new Date(baselineTimestamp.getTime() + 6e4);
const baselineDirName = formatMigrationDirName(baselineTimestamp, "baseline");
const deltaDirName = formatMigrationDirName(deltaTimestamp, options.name ?? "migration");
const baselinePackageDir = join(appMigrationsDir, baselineDirName);
const deltaPackageDir = join(appMigrationsDir, deltaDirName);
const baselineLeg = await runPlannerLeg(planner, migrations, frameworkComponents, fromContract, null, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(baselinePackageDir, migrationsDir));
if (!baselineLeg.ok) return notOk(baselineLeg.failure);
await writePlannedMigrationPackage(baselinePackageDir, null, fromHash, baselineTimestamp, baselineLeg.value);
await writeContractSnapshot(migrationsDir, fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
if (fromHash === toStorageHash) {
const baselineOps = baselineLeg.value.hasPlaceholders ? [] : baselineLeg.value.plannedOps;
if (baselineLeg.value.hasPlaceholders) {
const baselineDir = relative(process.cwd(), baselinePackageDir);
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: baselineDir,
baselineDir,
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned baseline with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
}
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(baselineOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: baselineOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildAutoBaselinePlanSummary(0, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
}
const deltaLeg = await runPlannerLeg(planner, migrations, frameworkComponents, aggregate.app.contract(), fromContract, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(deltaPackageDir, migrationsDir));
if (!deltaLeg.ok) return notOk(deltaLeg.failure);
await writePlannedMigrationPackage(deltaPackageDir, fromHash, toStorageHash, deltaTimestamp, deltaLeg.value);
await writeDestinationSnapshot(toStorageHash);
await writeContractSnapshot(migrationsDir, fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
const deltaOps = deltaLeg.value.hasPlaceholders ? [] : deltaLeg.value.plannedOps;
if (deltaLeg.value.hasPlaceholders) return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), deltaPackageDir),
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned baseline + migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(deltaOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), deltaPackageDir),
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: deltaOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildAutoBaselinePlanSummary(deltaOps.length, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
}
const timestamp = /* @__PURE__ */ new Date();
const packageDir = join(appMigrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
const deltaLeg = await runPlannerLeg(planner, migrations, frameworkComponents, aggregate.app.contract(), fromContract, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(packageDir, migrationsDir));
if (!deltaLeg.ok) return notOk(deltaLeg.failure);
await writePlannedMigrationPackage(packageDir, fromHash, toStorageHash, timestamp, deltaLeg.value);
await writeDestinationSnapshot(toStorageHash);
if (snapshotStartContract !== null) await writeContractSnapshot(migrationsDir, snapshotStartContract.fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
if (deltaLeg.value.hasPlaceholders) return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), packageDir),
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
const plannedOps = deltaLeg.value.plannedOps;
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(plannedOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), packageDir),
operations: plannedOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildPlanSummary(plannedOps.length, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
const message = error instanceof Error ? error.message : String(error);
return notOk(errorUnexpected(message, { why: `Unexpected error during migration plan: ${message}` }));
}
}
function createMigrationPlanCommand() {
const command = new Command("plan");
setCommandDescriptions(command, "Plan a migration from contract changes", "Compares the emitted contract against the latest on-disk migration state and\nproduces a new migration package with the required operations. No database\nconnection is needed — this is a fully offline operation.");
setCommandExamples(command, [
"prisma-next migration plan",
"prisma-next migration plan --name add-users-table",
"prisma-next migration plan --to <migration-dir>^ --name rollback"
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--name <slug>", "Name slug for the migration directory", "migration").option("--from <contract>", "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)").option("--to <contract>", "Destination contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path); defaults to the emitted contract").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const startTime = Date.now();
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeMigrationPlanCommand(options, flags, ui, startTime), flags, ui, (planResult) => {
if (flags.json) ui.output(JSON.stringify(planResult, null, 2));
else if (!flags.quiet) ui.log(formatMigrationPlanOutput(planResult, flags));
});
process.exit(exitCode);
});
return command;
}
/**
* Compose the success-line summary so the cross-space side effect
* (extension-space migration packages materialised on disk during
* this `plan` run) is visible in the top line — not just in the
* step log above it.
*
* Example outputs:
* - `Planned 3 operation(s)` (app-space-only project)
* - `Planned 3 operation(s); materialised 1 extension-space migration` (one extension)
* - `Planned 3 operation(s); materialised 2 extension-space migrations` (two extensions)
*
* Locks AC3 at the summary-line level: a reader of the success line
* can tell that something happened beyond the app space.
*/
function buildPlanSummary(plannedOpsCount, emittedExtensionDirsCount) {
const base = `Planned ${plannedOpsCount} operation(s)`;
if (emittedExtensionDirsCount === 0) return base;
return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
}
function buildAutoBaselinePlanSummary(deltaOpsCount, emittedExtensionDirsCount) {
const base = `Planned baseline + ${deltaOpsCount} operation(s)`;
if (emittedExtensionDirsCount === 0) return base;
return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
}
function formatMigrationPlanOutput(result, flags) {
const lines = [];
const useColor = flags.color !== false;
const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
function appendEmittedExtensions() {
if (result.emittedExtensionDirs.length === 0) return;
lines.push("");
lines.push(dim_("Emitted extension migrations:"));
for (const entry of result.emittedExtensionDirs) lines.push(dim_(` ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
lines.push("");
lines.push(`Next: review the extension migrations above, then run ${green_("prisma-next migrate")}.`);
}
if (result.noOp) {
lines.push(`${green_("✔")} No changes detected`);
lines.push(dim_(` from: ${result.from}`));
lines.push(dim_(` to: ${result.to}`));
appendEmittedExtensions();
return lines.join("\n");
}
if (result.pendingPlaceholders) {
lines.push(`${yellow_("⚠")} ${result.summary}`);
lines.push("");
lines.push(dim_(`from: ${result.from}`));
lines.push(dim_(`to: ${result.to}`));
if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
lines.push("");
lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
appendEmittedExtensions();
return lines.join("\n");
}
lines.push(`${green_("✔")} ${result.summary}`);
lines.push("");
if (result.operations.length > 0) {
lines.push(dim_("│"));
for (let i = 0; i < result.operations.length; i++) {
const op = result.operations[i];
const treeChar = i === result.operations.length - 1 ? "└" : "├";
const destructiveMarker = op.operationClass === "destructive" ? ` ${yellow_("(destructive)")}` : "";
lines.push(`${dim_(treeChar)}─ ${op.label}${destructiveMarker}`);
}
if (result.operations.some((op) => op.operationClass === "destructive")) {
lines.push("");
lines.push(`${yellow_("⚠")} This migration contains destructive operations that may cause data loss.`);
}
lines.push("");
}
lines.push(dim_(`from: ${result.from}`));
lines.push(dim_(`to: ${result.to}`));
if (result.baselineDir) lines.push(dim_(`Baseline → ${result.baselineDir}`));
if (result.dir) lines.push(dim_(`App space → ${result.dir}`));
for (const entry of result.emittedExtensionDirs) lines.push(dim_(`Extension space ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
lines.push("");
const reviewTarget = result.baselineDir !== void 0 && result.dir !== void 0 ? `${result.baselineDir} and ${result.dir}` : result.baselineDir ?? result.dir ?? "<dir>";
lines.push(`Next: review ${green_(reviewTarget)} if needed, then run ${green_("prisma-next migrate")}.`);
if (result.preview && result.preview.statements.length > 0) {
const allSql = result.preview.statements.every((s) => s.language === "sql");
lines.push("");
lines.push(dim_(allSql ? "DDL preview" : "Operation preview"));
lines.push("");
for (const statement of result.preview.statements) {
const trimmed = statement.text.trim();
if (!trimmed) continue;
const line = statement.language === "sql" && !trimmed.endsWith(";") ? `${trimmed};` : trimmed;
lines.push(line);
}
}
if (flags.verbose && result.timings) {
lines.push("");
lines.push(dim_(`Total time: ${result.timings.total}ms`));
}
return lines.join("\n");
}
/**
* Resolve a migration package by **target contract hash** (`metadata.to`)
* using exact match or prefix match.
*
* Note: matches `metadata.to` (the contract hash this migration produces),
* not `metadata.migrationHash` (the package's content-addressed identity).
* Tries exact match first, then prefix match (auto-prepending `sha256:` when
* the needle omits the scheme). Returns the matched package on success, or a
* discriminated failure indicating whether the prefix was ambiguous or simply
* not found.
*
* @internal Exported for testing only.
*/
function resolveBundleByPrefix(bundles, needle) {
const exact = bundles.find((p) => p.metadata.to === needle);
if (exact) return ok(exact);
const prefixWithScheme = needle.startsWith("sha256:") ? needle : `sha256:${needle}`;
const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
if (candidates.length === 1) return ok(candidates[0]);
if (candidates.length > 1) return notOk({
reason: "ambiguous",
count: candidates.length
});
return notOk({ reason: "not-found" });
}
//#endregion
export { formatMigrationPlanOutput as n, resolveBundleByPrefix as r, createMigrationPlanCommand as t };
//# sourceMappingURL=migration-plan-BJHulFZC.mjs.map

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

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 "./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-Df4zK5lO.mjs.map
{"version":3,"file":"migration-status-Df4zK5lO.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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"}
import { ifDefined } from "@prisma-next/utils/defined";
import { readFile } from "node:fs/promises";
import { errorInvalidRefName } from "@prisma-next/migration-tools/errors";
import { writeContractSnapshot } from "@prisma-next/migration-tools/contract-snapshot-store";
import { validateRefName, writeRef } from "@prisma-next/migration-tools/refs";
//#region src/utils/ref-advancement.ts
function computeRefAdvancementName(options) {
if (options.advanceRef !== void 0) return options.advanceRef;
if (options.db === void 0) return "db";
return null;
}
async function readContractIR(contractJson, contractJsonPath) {
return {
contract: contractJson,
contractDts: await readFile(contractJsonPath.replace(/\.json$/i, ".d.ts"), "utf-8")
};
}
async function executeRefAdvancement(refsDir, migrationsDir, name, hash, contractIR) {
if (!validateRefName(name)) throw errorInvalidRefName(name);
await writeContractSnapshot(migrationsDir, hash, {
contractJson: contractIR.contract,
contractDts: contractIR.contractDts
});
await writeRef(refsDir, name, {
hash,
invariants: []
});
return {
name,
hash
};
}
async function buildRefAdvancementFields(options) {
const name = computeRefAdvancementName({
...ifDefined("advanceRef", options.advanceRef),
...ifDefined("db", options.db)
});
if (name === null) return {
advancedRef: null,
plannedAdvanceRef: null
};
if (options.mode === "plan") return {
advancedRef: null,
plannedAdvanceRef: {
name,
hash: options.hash
}
};
return {
advancedRef: await executeRefAdvancement(options.refsDir, options.migrationsDir, name, options.hash, options.contractIR),
plannedAdvanceRef: null
};
}
//#endregion
export { readContractIR as i, computeRefAdvancementName as n, executeRefAdvancement as r, buildRefAdvancementFields as t };
//# sourceMappingURL=ref-advancement-BCz7X7qJ.mjs.map
{"version":3,"file":"ref-advancement-BCz7X7qJ.mjs","names":[],"sources":["../src/utils/ref-advancement.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { writeContractSnapshot } from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { errorInvalidRefName } from '@prisma-next/migration-tools/errors';\nimport { validateRefName, writeRef } from '@prisma-next/migration-tools/refs';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport interface ContractIR {\n readonly contract: unknown;\n readonly contractDts: string;\n}\n\nexport interface RefAdvancementFields {\n readonly advancedRef: { readonly name: string; readonly hash: string } | null;\n readonly plannedAdvanceRef: { readonly name: string; readonly hash: string } | null;\n}\n\nexport function computeRefAdvancementName(options: {\n readonly advanceRef?: string;\n readonly db?: string;\n}): string | null {\n if (options.advanceRef !== undefined) {\n return options.advanceRef;\n }\n if (options.db === undefined) {\n return 'db';\n }\n return null;\n}\n\nexport async function readContractIR(\n contractJson: Record<string, unknown>,\n contractJsonPath: string,\n): Promise<ContractIR> {\n const contractDtsPath = contractJsonPath.replace(/\\.json$/i, '.d.ts');\n const contractDts = await readFile(contractDtsPath, 'utf-8');\n return { contract: contractJson, contractDts };\n}\n\nexport async function executeRefAdvancement(\n refsDir: string,\n migrationsDir: string,\n name: string,\n hash: string,\n contractIR: ContractIR,\n): Promise<{ name: string; hash: string }> {\n // Validate the ref name before writing anything: writeRef validates it too,\n // but only after the store write below, which would otherwise leave a\n // (harmless, but pointless) orphan store entry on an invalid name.\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n await writeContractSnapshot(migrationsDir, hash, {\n contractJson: contractIR.contract,\n contractDts: contractIR.contractDts,\n });\n await writeRef(refsDir, name, { hash, invariants: [] });\n return { name, hash };\n}\n\nexport async function buildRefAdvancementFields(options: {\n readonly advanceRef?: string;\n readonly db?: string;\n readonly refsDir: string;\n readonly migrationsDir: string;\n readonly contractIR: ContractIR;\n readonly mode: 'plan' | 'apply';\n readonly hash: string;\n}): Promise<RefAdvancementFields> {\n const name = computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n });\n if (name === null) {\n return { advancedRef: null, plannedAdvanceRef: null };\n }\n if (options.mode === 'plan') {\n return { advancedRef: null, plannedAdvanceRef: { name, hash: options.hash } };\n }\n const advancedRef = await executeRefAdvancement(\n options.refsDir,\n options.migrationsDir,\n name,\n options.hash,\n options.contractIR,\n );\n return { advancedRef, plannedAdvanceRef: null };\n}\n"],"mappings":";;;;;;AAgBA,SAAgB,0BAA0B,SAGxB;CAChB,IAAI,QAAQ,eAAe,KAAA,GACzB,OAAO,QAAQ;CAEjB,IAAI,QAAQ,OAAO,KAAA,GACjB,OAAO;CAET,OAAO;AACT;AAEA,eAAsB,eACpB,cACA,kBACqB;CAGrB,OAAO;EAAE,UAAU;EAAc,aAAA,MADP,SADF,iBAAiB,QAAQ,YAAY,OACZ,GAAG,OAAO;CACd;AAC/C;AAEA,eAAsB,sBACpB,SACA,eACA,MACA,MACA,YACyC;CAIzC,IAAI,CAAC,gBAAgB,IAAI,GACvB,MAAM,oBAAoB,IAAI;CAEhC,MAAM,sBAAsB,eAAe,MAAM;EAC/C,cAAc,WAAW;EACzB,aAAa,WAAW;CAC1B,CAAC;CACD,MAAM,SAAS,SAAS,MAAM;EAAE;EAAM,YAAY,CAAC;CAAE,CAAC;CACtD,OAAO;EAAE;EAAM;CAAK;AACtB;AAEA,eAAsB,0BAA0B,SAQd;CAChC,MAAM,OAAO,0BAA0B;EACrC,GAAG,UAAU,cAAc,QAAQ,UAAU;EAC7C,GAAG,UAAU,MAAM,QAAQ,EAAE;CAC/B,CAAC;CACD,IAAI,SAAS,MACX,OAAO;EAAE,aAAa;EAAM,mBAAmB;CAAK;CAEtD,IAAI,QAAQ,SAAS,QACnB,OAAO;EAAE,aAAa;EAAM,mBAAmB;GAAE;GAAM,MAAM,QAAQ;EAAK;CAAE;CAS9E,OAAO;EAAE,aAAA,MAPiB,sBACxB,QAAQ,SACR,QAAQ,eACR,MACA,QAAQ,MACR,QAAQ,UACV;EACsB,mBAAmB;CAAK;AAChD"}
import { D as isCI, O as formatCommandHelp, _ as createTerminalUI, g as parseGlobalFlagsOrExit, h as parseGlobalFlags, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples } from "./command-helpers-CVn3U9Uz.mjs";
import { Command } from "commander";
import { readUserConfig, resolveGating, userConfigPath, writeUserConfig } from "@prisma-next/cli-telemetry";
//#region src/commands/telemetry/status.ts
/**
* Resolves the same gate the runtime uses (CI check + `resolveGating`) and
* projects it into a user-facing status. Pure read: never mints, never
* writes. The `installationId` value itself is never surfaced — only its
* presence — so `status` discloses nothing identifying.
*/
function resolveTelemetryStatus(inputs) {
const config = readUserConfig();
const configPath = userConfigPath();
const installationIdStored = typeof config.installationId === "string" && config.installationId.length > 0;
if (inputs.inCI) return {
enabled: false,
reason: "ci",
configPath,
installationIdStored
};
const gating = resolveGating({
env: inputs.env,
config
});
if (!gating.enabled) return {
enabled: false,
reason: gating.reason === "env-override" ? "env-opt-out" : "stored-opt-out",
configPath,
installationIdStored
};
return {
enabled: true,
reason: config.enableTelemetry === true ? "stored-opt-in" : "default-on",
configPath,
installationIdStored
};
}
const REASON_EXPLANATION = {
ci: "CI environment detected — telemetry is hard-disabled.",
"env-opt-out": "an environment opt-out is set (DO_NOT_TRACK / PRISMA_NEXT_DISABLE_TELEMETRY).",
"stored-opt-out": "\"enableTelemetry\": false is stored in your config.",
"stored-opt-in": "\"enableTelemetry\": true is stored in your config.",
"default-on": "no explicit choice is stored, so the opt-out default applies."
};
function formatTelemetryStatusLines(status) {
return [
`Telemetry is ${status.enabled ? "enabled" : "disabled"}: ${REASON_EXPLANATION[status.reason]}`,
`Config file: ${status.configPath}`,
`Installation ID: ${status.installationIdStored ? "stored" : "not stored"}`
];
}
//#endregion
//#region src/commands/telemetry/index.ts
function createTelemetryStatusCommand() {
const command = new Command("status");
setCommandDescriptions(command, "Show whether anonymous CLI telemetry is enabled and why", "Reports whether telemetry is currently enabled or disabled and the reason\n(default-on, stored opt-out, environment opt-out, or CI), the path to your\nuser-level config file, and whether an installation ID has been stored.\nRead-only: never sends an event, never mints an ID, never writes anything.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const status = resolveTelemetryStatus({
env: process.env,
inCI: isCI()
});
if (flags.json) ui.output(JSON.stringify(status));
else for (const line of formatTelemetryStatusLines(status)) ui.output(line);
process.exit(0);
});
}
function createTelemetryEnableCommand() {
const command = new Command("enable");
setCommandDescriptions(command, "Enable anonymous CLI telemetry", "Stores \"enableTelemetry\": true in your user-level config and mints an\ninstallation ID if one is not already stored.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
writeUserConfig({ enableTelemetry: true });
const ui = createTerminalUI(flags);
if (flags.json) ui.output(JSON.stringify({
enableTelemetry: true,
configPath: userConfigPath()
}));
else ui.output(`Telemetry enabled. Preference stored in ${userConfigPath()}.`);
process.exit(0);
});
}
function createTelemetryDisableCommand() {
const command = new Command("disable");
setCommandDescriptions(command, "Disable anonymous CLI telemetry", "Stores \"enableTelemetry\": false in your user-level config. No installation\nID is minted and no event is sent.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
writeUserConfig({ enableTelemetry: false });
const ui = createTerminalUI(flags);
if (flags.json) ui.output(JSON.stringify({
enableTelemetry: false,
configPath: userConfigPath()
}));
else ui.output(`Telemetry disabled. Preference stored in ${userConfigPath()}.`);
process.exit(0);
});
}
function createTelemetryCommand() {
const command = new Command("telemetry");
setCommandDescriptions(command, "Inspect and change anonymous CLI telemetry", "Show telemetry status, or enable / disable anonymous CLI usage data.\nTelemetry is on by default (opt-out); see https://prisma-next.dev/docs/cli/telemetry\nfor what is collected and why.");
setCommandExamples(command, [
"prisma-next telemetry status",
"prisma-next telemetry disable",
"prisma-next telemetry enable"
]);
command.configureHelp({
formatHelp: (cmd) => formatCommandHelp({
command: cmd,
flags: parseGlobalFlags({})
}),
subcommandDescription: () => ""
});
command.addCommand(createTelemetryStatusCommand());
command.addCommand(createTelemetryEnableCommand());
command.addCommand(createTelemetryDisableCommand());
return command;
}
//#endregion
export { createTelemetryCommand as t };
//# sourceMappingURL=telemetry-BEyNMfco.mjs.map
{"version":3,"file":"telemetry-BEyNMfco.mjs","names":[],"sources":["../src/commands/telemetry/status.ts","../src/commands/telemetry/index.ts"],"sourcesContent":["import { readUserConfig, resolveGating, userConfigPath } from '@prisma-next/cli-telemetry';\n\n/**\n * Why telemetry resolves the way it does, in the order the CLI's\n * `resolveTelemetryGate` evaluates: CI hard-disables first, then the env\n * opt-outs, then the stored `enableTelemetry`, then the opt-out default.\n */\nexport type TelemetryStatusReason =\n | 'ci'\n | 'env-opt-out'\n | 'stored-opt-out'\n | 'stored-opt-in'\n | 'default-on';\n\nexport interface TelemetryStatus {\n readonly enabled: boolean;\n readonly reason: TelemetryStatusReason;\n readonly configPath: string;\n readonly installationIdStored: boolean;\n}\n\n/**\n * Resolves the same gate the runtime uses (CI check + `resolveGating`) and\n * projects it into a user-facing status. Pure read: never mints, never\n * writes. The `installationId` value itself is never surfaced — only its\n * presence — so `status` discloses nothing identifying.\n */\nexport function resolveTelemetryStatus(inputs: {\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly inCI: boolean;\n}): TelemetryStatus {\n const config = readUserConfig();\n const configPath = userConfigPath();\n const installationIdStored =\n typeof config.installationId === 'string' && config.installationId.length > 0;\n\n if (inputs.inCI) {\n return { enabled: false, reason: 'ci', configPath, installationIdStored };\n }\n\n const gating = resolveGating({ env: inputs.env, config });\n if (!gating.enabled) {\n const reason: TelemetryStatusReason =\n gating.reason === 'env-override' ? 'env-opt-out' : 'stored-opt-out';\n return { enabled: false, reason, configPath, installationIdStored };\n }\n\n const reason: TelemetryStatusReason =\n config.enableTelemetry === true ? 'stored-opt-in' : 'default-on';\n return { enabled: true, reason, configPath, installationIdStored };\n}\n\nconst REASON_EXPLANATION: Record<TelemetryStatusReason, string> = {\n ci: 'CI environment detected — telemetry is hard-disabled.',\n 'env-opt-out': 'an environment opt-out is set (DO_NOT_TRACK / PRISMA_NEXT_DISABLE_TELEMETRY).',\n 'stored-opt-out': '\"enableTelemetry\": false is stored in your config.',\n 'stored-opt-in': '\"enableTelemetry\": true is stored in your config.',\n 'default-on': 'no explicit choice is stored, so the opt-out default applies.',\n};\n\nexport function formatTelemetryStatusLines(status: TelemetryStatus): string[] {\n return [\n `Telemetry is ${status.enabled ? 'enabled' : 'disabled'}: ${REASON_EXPLANATION[status.reason]}`,\n `Config file: ${status.configPath}`,\n `Installation ID: ${status.installationIdStored ? 'stored' : 'not stored'}`,\n ];\n}\n","import { userConfigPath, writeUserConfig } from '@prisma-next/cli-telemetry';\nimport { Command } from 'commander';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../../utils/command-helpers';\nimport { formatCommandHelp } from '../../utils/formatters/help';\nimport {\n type CommonCommandOptions,\n parseGlobalFlags,\n parseGlobalFlagsOrExit,\n} from '../../utils/global-flags';\nimport { isCI } from '../../utils/is-ci';\nimport { createTerminalUI } from '../../utils/terminal-ui';\nimport { formatTelemetryStatusLines, resolveTelemetryStatus } from './status';\n\nfunction createTelemetryStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show whether anonymous CLI telemetry is enabled and why',\n 'Reports whether telemetry is currently enabled or disabled and the reason\\n' +\n '(default-on, stored opt-out, environment opt-out, or CI), the path to your\\n' +\n 'user-level config file, and whether an installation ID has been stored.\\n' +\n 'Read-only: never sends an event, never mints an ID, never writes anything.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const status = resolveTelemetryStatus({ env: process.env, inCI: isCI() });\n if (flags.json) {\n ui.output(JSON.stringify(status));\n } else {\n for (const line of formatTelemetryStatusLines(status)) {\n ui.output(line);\n }\n }\n process.exit(0);\n });\n}\n\nfunction createTelemetryEnableCommand(): Command {\n const command = new Command('enable');\n setCommandDescriptions(\n command,\n 'Enable anonymous CLI telemetry',\n 'Stores \"enableTelemetry\": true in your user-level config and mints an\\n' +\n 'installation ID if one is not already stored.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n writeUserConfig({ enableTelemetry: true });\n const ui = createTerminalUI(flags);\n if (flags.json) {\n ui.output(JSON.stringify({ enableTelemetry: true, configPath: userConfigPath() }));\n } else {\n ui.output(`Telemetry enabled. Preference stored in ${userConfigPath()}.`);\n }\n process.exit(0);\n });\n}\n\nfunction createTelemetryDisableCommand(): Command {\n const command = new Command('disable');\n setCommandDescriptions(\n command,\n 'Disable anonymous CLI telemetry',\n 'Stores \"enableTelemetry\": false in your user-level config. No installation\\n' +\n 'ID is minted and no event is sent.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n writeUserConfig({ enableTelemetry: false });\n const ui = createTerminalUI(flags);\n if (flags.json) {\n ui.output(JSON.stringify({ enableTelemetry: false, configPath: userConfigPath() }));\n } else {\n ui.output(`Telemetry disabled. Preference stored in ${userConfigPath()}.`);\n }\n process.exit(0);\n });\n}\n\nexport function createTelemetryCommand(): Command {\n const command = new Command('telemetry');\n setCommandDescriptions(\n command,\n 'Inspect and change anonymous CLI telemetry',\n 'Show telemetry status, or enable / disable anonymous CLI usage data.\\n' +\n 'Telemetry is on by default (opt-out); see https://prisma-next.dev/docs/cli/telemetry\\n' +\n 'for what is collected and why.',\n );\n setCommandExamples(command, [\n 'prisma-next telemetry status',\n 'prisma-next telemetry disable',\n 'prisma-next telemetry enable',\n ]);\n command.configureHelp({\n formatHelp: (cmd) => formatCommandHelp({ command: cmd, flags: parseGlobalFlags({}) }),\n subcommandDescription: () => '',\n });\n command.addCommand(createTelemetryStatusCommand());\n command.addCommand(createTelemetryEnableCommand());\n command.addCommand(createTelemetryDisableCommand());\n return command;\n}\n"],"mappings":"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"}
import { M as createColorFormatter, N as formatDim, P as isVerbose } from "./command-helpers-CVn3U9Uz.mjs";
import { ifDefined } from "@prisma-next/utils/defined";
import { issueOutcome } from "@prisma-next/framework-components/control";
import { bold, cyan, dim, green, magenta, red, yellow } from "colorette";
//#region src/utils/formatters/verify.ts
/** Human-readable label for each outcome, prefixed onto an issue's message for display. */
const OUTCOME_LABEL = {
"not-found": "missing",
"not-expected": "extra",
"not-equal": "mismatch"
};
/**
* The issue's display text: its own path, prefixed with a human label for
* why it's flagged. Turning the presence-derived outcome (and the path) into
* prose is this formatter's job, not the differ's — the differ's issue is
* data (`path` + nodes), not prose.
*/
function formatIssueMessage(issue) {
return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join("/")}`;
}
/**
* Formats human-readable output for database verify.
*/
function formatVerifyOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatYellow = createColorFormatter(useColor, yellow);
const formatDimText = (text) => formatDim(useColor, text);
const verificationMode = result.mode === "full" ? `marker + schema${result.schema?.strict ? " (strict)" : " (tolerant)"}` : "marker only (--marker-only)";
lines.push(`${formatGreen("✔")} ${result.summary}`);
lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);
lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);
if (result.contract.profileHash) lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
if (result.schema?.warnings && result.schema.warnings.length > 0) {
lines.push("");
lines.push(formatYellow("Schema warnings:"));
for (const message of result.schema.warnings) lines.push(` ${formatYellow("⚠")} ${message}`);
}
if (result.unclaimed && result.unclaimed.length > 0) {
lines.push("");
lines.push(formatYellow("Unclaimed elements (declared by no contract):"));
for (const name of result.unclaimed) lines.push(` ${formatYellow("⚠")} ${name}`);
}
if (result.warning) {
lines.push("");
lines.push(`${formatYellow("⚠")} ${result.warning}`);
}
if (isVerbose(flags, 1)) {
if (result.codecCoverageSkipped) lines.push(`${formatDimText(" Codec coverage check skipped (helper returned no supported types)")}`);
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
}
return lines.join("\n");
}
/**
* Formats JSON output for database verify.
*/
function formatVerifyJson(result) {
const output = {
ok: result.ok,
summary: result.summary,
mode: result.mode,
contract: result.contract,
...ifDefined("marker", result.marker),
target: result.target,
...ifDefined("missingCodecs", result.missingCodecs),
...ifDefined("codecCoverageSkipped", result.codecCoverageSkipped),
...ifDefined("schema", result.schema),
unclaimed: result.unclaimed ?? [],
...ifDefined("warning", result.warning),
...ifDefined("meta", result.meta),
timings: result.timings
};
return JSON.stringify(output, null, 2);
}
/**
* Formats JSON output for database introspection.
*/
function formatIntrospectJson(result) {
return JSON.stringify(result, null, 2);
}
/**
* Renders a schema tree structure from CoreSchemaView.
* Status-glyph tree styling shared with the retired verification-tree renderer.
*/
function renderSchemaTree(node, flags, options) {
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
const lines = [];
let formattedLabel = node.label;
if (useColor) switch (node.kind) {
case "root":
formattedLabel = bold(node.label);
break;
case "entity": {
const tableMatch = node.label.match(/^table\s+(.+)$/);
if (tableMatch?.[1]) {
const tableName = tableMatch[1];
formattedLabel = `${dim("table")} ${cyan(tableName)}`;
} else formattedLabel = cyan(node.label);
break;
}
case "collection":
formattedLabel = dim(node.label);
break;
case "field": {
const columnMatch = node.label.match(/^([^:]+):\s*(.+)$/);
if (columnMatch?.[1] && columnMatch[2]) {
const columnName = columnMatch[1];
const rest = columnMatch[2];
const typeMatch = rest.match(/^([^\s(]+)\s*(\([^)]+\))$/);
if (typeMatch?.[1] && typeMatch[2]) {
const typeDisplay = typeMatch[1];
const nullability = typeMatch[2];
formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;
} else formattedLabel = `${cyan(columnName)}: ${rest}`;
} else formattedLabel = node.label;
break;
}
case "index": {
const pkMatch = node.label.match(/^primary key:\s*(.+)$/);
if (pkMatch?.[1]) {
const columnNames = pkMatch[1];
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
} else {
const uniqueMatch = node.label.match(/^unique\s+(.+)$/);
if (uniqueMatch?.[1]) {
const name = uniqueMatch[1];
formattedLabel = `${dim("unique")} ${cyan(name)}`;
} else {
const indexMatch = node.label.match(/^(unique\s+)?index\s+(.+)$/);
if (indexMatch?.[2]) {
const indexPrefix = indexMatch[1] ? `${dim("unique")} ` : "";
const name = indexMatch[2];
formattedLabel = `${indexPrefix}${dim("index")} ${cyan(name)}`;
} else formattedLabel = dim(node.label);
}
}
break;
}
case "dependency": {
const extMatch = node.label.match(/^([^\s]+)\s+(extension is enabled)$/);
if (extMatch?.[1] && extMatch[2]) {
const extName = extMatch[1];
const rest = extMatch[2];
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
} else formattedLabel = magenta(node.label);
break;
}
default:
formattedLabel = node.label;
break;
}
if (isRoot) lines.push(formattedLabel);
else {
const treePrefix = `${formatDimText(isLast ? "└" : "├")}─ `;
lines.push(`${prefix}${treePrefix}${formattedLabel}`);
}
if (node.children && node.children.length > 0) {
const childPrefix = isRoot ? "" : `${prefix}${isLast ? " " : `${formatDimText("│")} `}`;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (!child) continue;
const childLines = renderSchemaTree(child, flags, {
isLast: i === node.children.length - 1,
prefix: childPrefix,
useColor,
formatDimText,
isRoot: false
});
lines.push(...childLines);
}
}
return lines;
}
/**
* Formats human-readable output for database introspection.
*/
function formatIntrospectOutput(result, schemaView, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatDimText = (text) => formatDim(useColor, text);
if (schemaView) {
const treeLines = renderSchemaTree(schemaView.root, flags, {
isLast: true,
prefix: "",
useColor,
formatDimText,
isRoot: true
});
lines.push(...treeLines);
} else {
lines.push(`✔ ${result.summary}`);
if (isVerbose(flags, 1)) {
lines.push(` Target: ${result.target.familyId}/${result.target.id}`);
if (result.meta?.dbUrl) lines.push(` Database: ${result.meta.dbUrl}`);
}
}
if (isVerbose(flags, 1)) lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
return lines.join("\n");
}
/**
* Formats human-readable output for database schema verification.
*/
function formatSchemaVerifyOutput(result, flags, unclaimed = []) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatRed = createColorFormatter(useColor, red);
const formatYellow = createColorFormatter(useColor, yellow);
const formatDimText = (text) => formatDim(useColor, text);
const issueMessages = result.schema.issues.map(formatIssueMessage);
if (issueMessages.length > 0) {
lines.push(formatRed("Schema issues:"));
for (const message of issueMessages) lines.push(` ${formatRed("✖")} ${message}`);
}
const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);
if (warningMessages.length > 0) {
if (lines.length > 0) lines.push("");
lines.push(formatYellow("Schema warnings:"));
for (const message of warningMessages) lines.push(` ${formatYellow("⚠")} ${message}`);
}
if (unclaimed.length > 0) {
const strict = result.meta?.strict ?? false;
if (lines.length > 0) lines.push("");
lines.push((strict ? formatRed : formatYellow)("Unclaimed elements (declared by no contract):"));
for (const name of unclaimed) lines.push(` ${(strict ? formatRed : formatYellow)(strict ? "✖" : "⚠")} ${name}`);
}
if (isVerbose(flags, 1)) lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
if (lines.length > 0) lines.push("");
if (result.ok) lines.push(`${formatGreen("✔")} ${result.summary}`);
else {
const codeText = result.code ? ` (${result.code})` : "";
lines.push(`${formatRed("✖")} ${result.summary}${codeText}`);
}
return lines.join("\n");
}
/**
* Formats JSON output for database schema verification. The unclaimed-elements
* list is a top-level field alongside the combined result, reported once for
* the whole database.
*/
function formatSchemaVerifyJson(result, unclaimed = []) {
return JSON.stringify({
...result,
unclaimed
}, null, 2);
}
/**
* Formats human-readable output for database sign.
*/
function formatSignOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatDimText = (text) => formatDim(useColor, text);
if (result.ok) {
lines.push(`${formatGreen("✔")} Database signed`);
const previousHash = result.marker.previous?.storageHash ?? "none";
const currentHash = result.contract.storageHash;
lines.push(`${formatDimText(` from: ${previousHash}`)}`);
lines.push(`${formatDimText(` to: ${currentHash}`)}`);
if (isVerbose(flags, 1)) {
if (result.contract.profileHash) lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
if (result.marker.previous?.profileHash) lines.push(`${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`);
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
}
}
return lines.join("\n");
}
/**
* Formats JSON output for database sign.
*/
function formatSignJson(result) {
return JSON.stringify(result, null, 2);
}
//#endregion
export { formatSignJson as a, formatVerifyOutput as c, formatSchemaVerifyOutput as i, formatIntrospectOutput as n, formatSignOutput as o, formatSchemaVerifyJson as r, formatVerifyJson as s, formatIntrospectJson as t };
//# sourceMappingURL=verify-C4RazDE1.mjs.map
{"version":3,"file":"verify-C4RazDE1.mjs","names":[],"sources":["../src/utils/formatters/verify.ts"],"sourcesContent":["import type {\n CoreSchemaView,\n ExpectationFailureReason,\n IntrospectSchemaResult,\n SchemaDiffIssue,\n SchemaTreeNode,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { issueOutcome } from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n/** Human-readable label for each outcome, prefixed onto an issue's message for display. */\nconst OUTCOME_LABEL: Record<ExpectationFailureReason, string> = {\n 'not-found': 'missing',\n 'not-expected': 'extra',\n 'not-equal': 'mismatch',\n};\n\n/**\n * The issue's display text: its own path, prefixed with a human label for\n * why it's flagged. Turning the presence-derived outcome (and the path) into\n * prose is this formatter's job, not the differ's — the differ's issue is\n * data (`path` + nodes), not prose.\n */\nfunction formatIssueMessage(issue: SchemaDiffIssue): string {\n return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join('/')}`;\n}\n\n// ============================================================================\n// Verify Output Formatters\n// ============================================================================\n\nexport interface DbVerifyCommandSuccessResult {\n readonly ok: true;\n readonly mode: 'full' | 'marker-only';\n readonly summary: string;\n readonly contract: VerifyDatabaseResult['contract'];\n readonly marker?: VerifyDatabaseResult['marker'];\n readonly target: VerifyDatabaseResult['target'];\n readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];\n readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];\n readonly schema?: {\n readonly summary: string;\n readonly strict: boolean;\n /**\n * Warn-graded finding messages (observed-policy drift). Informational —\n * present on a passing verify; the full-mode result summarizes them as a\n * flat message list.\n */\n readonly warnings?: readonly string[];\n };\n /**\n * Live element names no contract space declares. In full success this is\n * only ever non-empty in lenient mode — strict mode fails on it — and is\n * rendered informationally.\n */\n readonly unclaimed?: readonly string[];\n readonly warning?: string;\n readonly meta?:\n | (NonNullable<VerifyDatabaseResult['meta']> & {\n readonly schemaVerification: 'performed' | 'skipped';\n })\n | {\n readonly schemaVerification: 'performed' | 'skipped';\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for database verify.\n */\nexport function formatVerifyOutput(\n result: DbVerifyCommandSuccessResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n const verificationMode =\n result.mode === 'full'\n ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`\n : 'marker only (--marker-only)';\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);\n lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.schema?.warnings && result.schema.warnings.length > 0) {\n lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of result.schema.warnings) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (result.unclaimed && result.unclaimed.length > 0) {\n lines.push('');\n lines.push(formatYellow('Unclaimed elements (declared by no contract):'));\n for (const name of result.unclaimed) {\n lines.push(` ${formatYellow('⚠')} ${name}`);\n }\n }\n\n if (result.warning) {\n lines.push('');\n lines.push(`${formatYellow('⚠')} ${result.warning}`);\n }\n\n if (isVerbose(flags, 1)) {\n if (result.codecCoverageSkipped) {\n lines.push(\n `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database verify.\n */\nexport function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {\n const output = {\n ok: result.ok,\n summary: result.summary,\n mode: result.mode,\n contract: result.contract,\n ...ifDefined('marker', result.marker),\n target: result.target,\n ...ifDefined('missingCodecs', result.missingCodecs),\n ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),\n ...ifDefined('schema', result.schema),\n unclaimed: result.unclaimed ?? [],\n ...ifDefined('warning', result.warning),\n ...ifDefined('meta', result.meta),\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Formats JSON output for database introspection.\n */\nexport function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Renders a schema tree structure from CoreSchemaView.\n * Status-glyph tree styling shared with the retired verification-tree renderer.\n */\nfunction renderSchemaTree(\n node: SchemaTreeNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format node label with color based on kind (matching schema-verify style)\n let formattedLabel: string = node.label;\n\n if (useColor) {\n switch (node.kind) {\n case 'root':\n formattedLabel = bold(node.label);\n break;\n case 'entity': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.label.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n // Fallback: color entire label with cyan\n formattedLabel = cyan(node.label);\n }\n break;\n }\n case 'collection': {\n // \"columns\" grouping node - dim the label\n formattedLabel = dim(node.label);\n break;\n }\n case 'field': {\n // Parse column name format: \"columnName: typeDisplay (nullability)\"\n // Color code: column name (cyan), type (default), nullability (dim)\n const columnMatch = node.label.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"typeDisplay (nullability)\"\n const typeMatch = rest.match(/^([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2]) {\n const typeDisplay = typeMatch[1];\n const nullability = typeMatch[2];\n formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.label;\n }\n break;\n }\n case 'index': {\n // Parse index/unique constraint/primary key formats\n // \"primary key: columnName\" -> dim \"primary key\", cyan columnName\n const pkMatch = node.label.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n // \"unique name\" -> dim \"unique\", cyan \"name\"\n const uniqueMatch = node.label.match(/^unique\\s+(.+)$/);\n if (uniqueMatch?.[1]) {\n const name = uniqueMatch[1];\n formattedLabel = `${dim('unique')} ${cyan(name)}`;\n } else {\n // \"index name\" or \"unique index name\" -> dim label prefix, cyan name\n const indexMatch = node.label.match(/^(unique\\s+)?index\\s+(.+)$/);\n if (indexMatch?.[2]) {\n const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';\n const name = indexMatch[2];\n formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;\n } else {\n formattedLabel = dim(node.label);\n }\n }\n }\n break;\n }\n case 'dependency': {\n // Parse extension message formats similar to schema-verify\n // \"extensionName extension is enabled\" -> cyan extensionName, dim rest\n const extMatch = node.label.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire label with magenta\n formattedLabel = magenta(node.label);\n }\n break;\n }\n default:\n formattedLabel = node.label;\n break;\n }\n }\n\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(formattedLabel);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${formattedLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database introspection.\n */\nexport function formatIntrospectOutput(\n result: IntrospectSchemaResult<unknown>,\n schemaView: CoreSchemaView | undefined,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (schemaView) {\n // Render tree structure - root node is special (no tree characters)\n const treeLines = renderSchemaTree(schemaView.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n } else {\n // Fallback: print summary when toSchemaView is not available\n lines.push(`✔ ${result.summary}`);\n if (isVerbose(flags, 1)) {\n lines.push(` Target: ${result.target.familyId}/${result.target.id}`);\n if (result.meta?.dbUrl) {\n lines.push(` Database: ${result.meta.dbUrl}`);\n }\n }\n }\n\n // Add timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats human-readable output for database schema verification.\n */\nexport function formatSchemaVerifyOutput(\n result: VerifyDatabaseSchemaResult,\n flags: GlobalFlags,\n unclaimed: readonly string[] = [],\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatRed = createColorFormatter(useColor, red);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n const issueMessages = result.schema.issues.map(formatIssueMessage);\n if (issueMessages.length > 0) {\n lines.push(formatRed('Schema issues:'));\n for (const message of issueMessages) {\n lines.push(` ${formatRed('✖')} ${message}`);\n }\n }\n\n const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);\n if (warningMessages.length > 0) {\n if (lines.length > 0) lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of warningMessages) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (unclaimed.length > 0) {\n const strict = result.meta?.strict ?? false;\n if (lines.length > 0) lines.push('');\n lines.push(\n (strict ? formatRed : formatYellow)('Unclaimed elements (declared by no contract):'),\n );\n for (const name of unclaimed) {\n lines.push(` ${(strict ? formatRed : formatYellow)(strict ? '✖' : '⚠')} ${name}`);\n }\n }\n\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n // Blank line before summary\n if (lines.length > 0) lines.push('');\n\n // Summary line at the end: verdict with status glyph\n if (result.ok) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n } else {\n const codeText = result.code ? ` (${result.code})` : '';\n lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database schema verification. The unclaimed-elements\n * list is a top-level field alongside the combined result, reported once for\n * the whole database.\n */\nexport function formatSchemaVerifyJson(\n result: VerifyDatabaseSchemaResult,\n unclaimed: readonly string[] = [],\n): string {\n return JSON.stringify({ ...result, unclaimed }, null, 2);\n}\n\n// ============================================================================\n// Sign Output Formatters\n// ============================================================================\n\n/**\n * Formats human-readable output for database sign.\n */\nexport function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Main success message in white (not dimmed)\n lines.push(`${formatGreen('✔')} Database signed`);\n\n // Show from -> to hashes with clear labels\n const previousHash = result.marker.previous?.storageHash ?? 'none';\n const currentHash = result.contract.storageHash;\n\n lines.push(`${formatDimText(` from: ${previousHash}`)}`);\n lines.push(`${formatDimText(` to: ${currentHash}`)}`);\n\n if (isVerbose(flags, 1)) {\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.marker.previous?.profileHash) {\n lines.push(\n `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database sign.\n */\nexport function formatSignJson(result: SignDatabaseResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}
+13
-13
#!/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-Cpq44aVa.mjs";
import { t as createContractEmitCommand } from "./contract-emit-CgnXSANN.mjs";
import { t as createContractInferCommand } from "./contract-infer-Cg8evbeg.mjs";
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 { createDbInitCommand } from "./commands/db-init.mjs";

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

import { createDbUpdateCommand } from "./commands/db-update.mjs";
import { t as createDbVerifyCommand } from "./db-verify-BEclUzki.mjs";
import { t as createFormatCommand } from "./format-DAUAZqPt.mjs";
import { t as createMigrationListCommand } from "./migration-list-6z6vuXHa.mjs";
import { t as createDbVerifyCommand } from "./db-verify-BpwCMyWJ.mjs";
import { t as createFormatCommand } from "./format-CefCDzkw.mjs";
import { t as createMigrationListCommand } from "./migration-list-cnSKilYH.mjs";
import { createMigrateCommand } from "./commands/migrate.mjs";
import { t as createMigrationCheckCommand } from "./migration-check-CcuayFB2.mjs";
import { t as createMigrationCheckCommand } from "./migration-check-Biu1YZXu.mjs";
import { createMigrationGraphCommand } from "./commands/migration-graph.mjs";
import { t as createMigrationLogCommand } from "./migration-log-C_xam2HZ.mjs";
import { t as createMigrationLogCommand } from "./migration-log-CEqFiez9.mjs";
import { createMigrationNewCommand } from "./commands/migration-new.mjs";
import { t as createMigrationPlanCommand } from "./migration-plan-DuMEUejG.mjs";
import { t as createMigrationPlanCommand } from "./migration-plan-BJHulFZC.mjs";
import { createMigrationShowCommand } from "./commands/migration-show.mjs";
import { r as createMigrationStatusCommand } from "./migration-status-B61YOaMl.mjs";
import { r as createMigrationStatusCommand } from "./migration-status-Df4zK5lO.mjs";
import { createRefCommand } from "./commands/ref.mjs";
import { t as createTelemetryCommand } from "./telemetry-DVr6RFnq.mjs";
import { t as createTelemetryCommand } from "./telemetry-BEyNMfco.mjs";
import { Command } from "commander";

@@ -29,3 +29,3 @@ import { DEFAULT_CONTRACT_SOURCE_DIR } from "@prisma-next/config/config-types";

//#region package.json
var version = "0.16.0-dev.5";
var version = "0.16.0-dev.6";
//#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-CgKEi_t5.mjs");
const { runInit } = await import("./init-BSgPxVXA.mjs");
const flags = parseGlobalFlagsOrExit(options);

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

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

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

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

import { t as createContractInferCommand } from "../contract-infer-Cg8evbeg.mjs";
import { t as createContractInferCommand } from "../contract-infer-BVqworBB.mjs";
export { createContractInferCommand };

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

{"version":3,"file":"db-init.d.mts","names":[],"sources":["../../src/commands/db-init.ts"],"mappings":";;iBAoQgB,uBAAuB"}
{"version":3,"file":"db-init.d.mts","names":[],"sources":["../../src/commands/db-init.ts"],"mappings":";;iBAqQgB,uBAAuB"}

@@ -1,5 +0,5 @@

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-Cpq44aVa.mjs";
import { o as ContractValidationError } from "../client-KuBQftxz.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-CRyYrHZ9.mjs";
import { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BkXlikCA.mjs";
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 { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BCz7X7qJ.mjs";
import { Command } from "commander";

@@ -80,2 +80,3 @@ import { ifDefined } from "@prisma-next/utils/defined";

refsDir,
migrationsDir,
contractIR,

@@ -82,0 +83,0 @@ mode: result.value.mode,

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

{"version":3,"file":"db-init.mjs","names":[],"sources":["../../src/commands/db-init.ts"],"sourcesContent":["import { 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 { ContractValidationError } from '../control-api/errors';\nimport type { DbInitFailure } from '../control-api/types';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorMigrationPlanningFailed,\n errorRunnerFailed,\n errorRuntime,\n errorUnexpected,\n mapMigrationToolsError,\n} from '../utils/cli-errors';\nimport type { MigrationCommandOptions } from '../utils/command-helpers';\nimport {\n resolveMigrationPaths,\n sanitizeErrorMessage,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport {\n formatMigrationApplyOutput,\n formatMigrationJson,\n formatMigrationPlanOutput,\n type MigrationCommandResult,\n} from '../utils/formatters/migrations';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport {\n addMigrationCommandOptions,\n prepareMigrationContext,\n} from '../utils/migration-command-scaffold';\nimport {\n buildRefAdvancementFields,\n computeRefAdvancementName,\n type RefAdvancementFields,\n readContractIR,\n} from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbInitOptions extends MigrationCommandOptions {\n readonly advanceRef?: string;\n}\n\n/**\n * Maps a DbInitFailure to a CliStructuredError for consistent error handling.\n */\nfunction mapDbInitFailure(failure: DbInitFailure): CliStructuredError {\n if (failure.code === 'PLANNING_FAILED') {\n return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });\n }\n\n if (failure.code === 'MIGRATION.MARKER_ORIGIN_MISMATCH') {\n const mismatchParts: string[] = [];\n if (\n failure.marker?.storageHash !== failure.destination?.storageHash &&\n failure.marker?.storageHash &&\n failure.destination?.storageHash\n ) {\n mismatchParts.push(\n `storageHash (marker: ${failure.marker.storageHash}, destination: ${failure.destination.storageHash})`,\n );\n }\n if (\n failure.marker?.profileHash !== failure.destination?.profileHash &&\n failure.marker?.profileHash &&\n failure.destination?.profileHash\n ) {\n mismatchParts.push(\n `profileHash (marker: ${failure.marker.profileHash}, destination: ${failure.destination.profileHash})`,\n );\n }\n\n return errorRuntime(\n `Existing database signature does not match plan destination.${mismatchParts.length > 0 ? ` Mismatch in ${mismatchParts.join(' and ')}.` : ''}`,\n {\n why: 'Database has an existing signature (marker) that does not match the target contract',\n fix: 'If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow',\n meta: {\n code: 'MIGRATION.MARKER_ORIGIN_MISMATCH',\n ...ifDefined('markerStorageHash', failure.marker?.storageHash),\n ...ifDefined('destinationStorageHash', failure.destination?.storageHash),\n ...ifDefined('markerProfileHash', failure.marker?.profileHash),\n ...ifDefined('destinationProfileHash', failure.destination?.profileHash),\n },\n },\n );\n }\n\n if (failure.code === 'RUNNER_FAILED') {\n const runnerCode =\n typeof failure.meta?.['runnerErrorCode'] === 'string'\n ? failure.meta['runnerErrorCode']\n : undefined;\n const fix =\n runnerCode === 'MIGRATION.LEGACY_MARKER_SHAPE'\n ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'\n : 'Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`';\n return errorRunnerFailed(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix,\n ...(failure.meta\n ? { meta: { code: 'RUNNER_FAILED', ...failure.meta } }\n : { meta: { code: 'RUNNER_FAILED' } }),\n });\n }\n\n // Exhaustive check - TypeScript will error if a new code is added but not handled\n const exhaustive: never = failure.code;\n throw new Error(`Unhandled DbInitFailure code: ${exhaustive}`);\n}\n\n/**\n * Executes the db init command and returns a structured Result.\n */\nasync function executeDbInitCommand(\n options: DbInitOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationCommandResult, CliStructuredError>> {\n // Prepare shared migration context (config, contract, connection, client)\n const ctxResult = await prepareMigrationContext(options, flags, ui, {\n commandName: 'db init',\n description: 'Bootstrap a database to match the current contract',\n url: 'https://pris.ly/db-init',\n });\n if (!ctxResult.ok) {\n return ctxResult;\n }\n const { client, config, contractJson, dbConnection, onProgress, contractPathAbsolute } =\n ctxResult.value;\n\n // The aggregate loader (loader → planner → runner pipeline) catches\n // layout / drift / disjointness violations on its own; the legacy\n // per-space precheck + marker-check helpers are no longer needed at\n // this surface. Marker-vs-on-disk drift surfaces through the planner's\n // graph-walk strategy.\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n\n try {\n await client.connect(dbConnection);\n\n const result = await client.dbInit({\n contract: contractJson,\n mode: options.dryRun ? 'plan' : 'apply',\n migrationsDir,\n onProgress,\n });\n\n // Handle failures by mapping to CLI structured error\n if (!result.ok) {\n return notOk(mapDbInitFailure(result.failure));\n }\n\n const advancementHash =\n result.value.mode === 'apply'\n ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)\n : result.value.destination.storageHash;\n\n let refAdvancementFields: RefAdvancementFields = {\n advancedRef: null,\n plannedAdvanceRef: null,\n };\n if (\n computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n }) !== null\n ) {\n try {\n const contractIR = await readContractIR(contractJson, contractPathAbsolute);\n refAdvancementFields = await buildRefAdvancementFields({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n refsDir,\n contractIR,\n mode: result.value.mode,\n hash: advancementHash,\n });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n // Convert success result to CLI output format\n const dbInitResult: MigrationCommandResult = {\n ok: true,\n mode: result.value.mode,\n plan: {\n targetId: ctxResult.value.config.target.targetId,\n destination: {\n storageHash: result.value.destination.storageHash,\n ...ifDefined('profileHash', result.value.destination.profileHash),\n },\n operations: result.value.plan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n ...ifDefined('preview', result.value.plan.preview),\n },\n ...(result.value.execution\n ? {\n execution: {\n operationsPlanned: result.value.execution.operationsPlanned,\n operationsExecuted: result.value.execution.operationsExecuted,\n },\n }\n : {}),\n ...(result.value.marker\n ? {\n marker: {\n storageHash: result.value.marker.storageHash,\n ...ifDefined('profileHash', result.value.marker.profileHash),\n },\n }\n : {}),\n ...ifDefined('perSpace', result.value.perSpace),\n advancedRef: refAdvancementFields.advancedRef,\n plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,\n summary: result.value.summary,\n timings: { total: Date.now() - startTime },\n };\n\n return ok(dbInitResult);\n } catch (error) {\n // Driver already throws CliStructuredError for connection failures\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\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 db init: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createDbInitCommand(): Command {\n const command = new Command('init');\n setCommandDescriptions(\n command,\n 'Bootstrap a database to match the current contract and sign it',\n 'Initializes a database to match your emitted contract using additive-only operations.\\n' +\n 'Creates any missing tables, columns, indexes, and constraints defined in your contract.\\n' +\n 'Leaves existing compatible structures in place, surfaces conflicts when destructive changes\\n' +\n 'would be required, and signs the database to track contract state. Use --dry-run to\\n' +\n 'preview changes without applying.',\n );\n setCommandExamples(command, [\n 'prisma-next db init --db $DATABASE_URL',\n 'prisma-next db init --db $DATABASE_URL --dry-run',\n ]);\n addMigrationCommandOptions(command);\n command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');\n command.action(async (options: DbInitOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n const result = await executeDbInitCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (dbInitResult) => {\n if (flags.json) {\n ui.output(formatMigrationJson(dbInitResult));\n } else {\n const output =\n dbInitResult.mode === 'plan'\n ? formatMigrationPlanOutput(dbInitResult, flags)\n : formatMigrationApplyOutput(dbInitResult, flags);\n if (output) {\n ui.log(output);\n }\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
{"version":3,"file":"db-init.mjs","names":[],"sources":["../../src/commands/db-init.ts"],"sourcesContent":["import { 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 { ContractValidationError } from '../control-api/errors';\nimport type { DbInitFailure } from '../control-api/types';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorMigrationPlanningFailed,\n errorRunnerFailed,\n errorRuntime,\n errorUnexpected,\n mapMigrationToolsError,\n} from '../utils/cli-errors';\nimport type { MigrationCommandOptions } from '../utils/command-helpers';\nimport {\n resolveMigrationPaths,\n sanitizeErrorMessage,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport {\n formatMigrationApplyOutput,\n formatMigrationJson,\n formatMigrationPlanOutput,\n type MigrationCommandResult,\n} from '../utils/formatters/migrations';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport {\n addMigrationCommandOptions,\n prepareMigrationContext,\n} from '../utils/migration-command-scaffold';\nimport {\n buildRefAdvancementFields,\n computeRefAdvancementName,\n type RefAdvancementFields,\n readContractIR,\n} from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbInitOptions extends MigrationCommandOptions {\n readonly advanceRef?: string;\n}\n\n/**\n * Maps a DbInitFailure to a CliStructuredError for consistent error handling.\n */\nfunction mapDbInitFailure(failure: DbInitFailure): CliStructuredError {\n if (failure.code === 'PLANNING_FAILED') {\n return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });\n }\n\n if (failure.code === 'MIGRATION.MARKER_ORIGIN_MISMATCH') {\n const mismatchParts: string[] = [];\n if (\n failure.marker?.storageHash !== failure.destination?.storageHash &&\n failure.marker?.storageHash &&\n failure.destination?.storageHash\n ) {\n mismatchParts.push(\n `storageHash (marker: ${failure.marker.storageHash}, destination: ${failure.destination.storageHash})`,\n );\n }\n if (\n failure.marker?.profileHash !== failure.destination?.profileHash &&\n failure.marker?.profileHash &&\n failure.destination?.profileHash\n ) {\n mismatchParts.push(\n `profileHash (marker: ${failure.marker.profileHash}, destination: ${failure.destination.profileHash})`,\n );\n }\n\n return errorRuntime(\n `Existing database signature does not match plan destination.${mismatchParts.length > 0 ? ` Mismatch in ${mismatchParts.join(' and ')}.` : ''}`,\n {\n why: 'Database has an existing signature (marker) that does not match the target contract',\n fix: 'If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow',\n meta: {\n code: 'MIGRATION.MARKER_ORIGIN_MISMATCH',\n ...ifDefined('markerStorageHash', failure.marker?.storageHash),\n ...ifDefined('destinationStorageHash', failure.destination?.storageHash),\n ...ifDefined('markerProfileHash', failure.marker?.profileHash),\n ...ifDefined('destinationProfileHash', failure.destination?.profileHash),\n },\n },\n );\n }\n\n if (failure.code === 'RUNNER_FAILED') {\n const runnerCode =\n typeof failure.meta?.['runnerErrorCode'] === 'string'\n ? failure.meta['runnerErrorCode']\n : undefined;\n const fix =\n runnerCode === 'MIGRATION.LEGACY_MARKER_SHAPE'\n ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'\n : 'Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`';\n return errorRunnerFailed(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix,\n ...(failure.meta\n ? { meta: { code: 'RUNNER_FAILED', ...failure.meta } }\n : { meta: { code: 'RUNNER_FAILED' } }),\n });\n }\n\n // Exhaustive check - TypeScript will error if a new code is added but not handled\n const exhaustive: never = failure.code;\n throw new Error(`Unhandled DbInitFailure code: ${exhaustive}`);\n}\n\n/**\n * Executes the db init command and returns a structured Result.\n */\nasync function executeDbInitCommand(\n options: DbInitOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationCommandResult, CliStructuredError>> {\n // Prepare shared migration context (config, contract, connection, client)\n const ctxResult = await prepareMigrationContext(options, flags, ui, {\n commandName: 'db init',\n description: 'Bootstrap a database to match the current contract',\n url: 'https://pris.ly/db-init',\n });\n if (!ctxResult.ok) {\n return ctxResult;\n }\n const { client, config, contractJson, dbConnection, onProgress, contractPathAbsolute } =\n ctxResult.value;\n\n // The aggregate loader (loader → planner → runner pipeline) catches\n // layout / drift / disjointness violations on its own; the legacy\n // per-space precheck + marker-check helpers are no longer needed at\n // this surface. Marker-vs-on-disk drift surfaces through the planner's\n // graph-walk strategy.\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n\n try {\n await client.connect(dbConnection);\n\n const result = await client.dbInit({\n contract: contractJson,\n mode: options.dryRun ? 'plan' : 'apply',\n migrationsDir,\n onProgress,\n });\n\n // Handle failures by mapping to CLI structured error\n if (!result.ok) {\n return notOk(mapDbInitFailure(result.failure));\n }\n\n const advancementHash =\n result.value.mode === 'apply'\n ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)\n : result.value.destination.storageHash;\n\n let refAdvancementFields: RefAdvancementFields = {\n advancedRef: null,\n plannedAdvanceRef: null,\n };\n if (\n computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n }) !== null\n ) {\n try {\n const contractIR = await readContractIR(contractJson, contractPathAbsolute);\n refAdvancementFields = await buildRefAdvancementFields({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n refsDir,\n migrationsDir,\n contractIR,\n mode: result.value.mode,\n hash: advancementHash,\n });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n // Convert success result to CLI output format\n const dbInitResult: MigrationCommandResult = {\n ok: true,\n mode: result.value.mode,\n plan: {\n targetId: ctxResult.value.config.target.targetId,\n destination: {\n storageHash: result.value.destination.storageHash,\n ...ifDefined('profileHash', result.value.destination.profileHash),\n },\n operations: result.value.plan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n ...ifDefined('preview', result.value.plan.preview),\n },\n ...(result.value.execution\n ? {\n execution: {\n operationsPlanned: result.value.execution.operationsPlanned,\n operationsExecuted: result.value.execution.operationsExecuted,\n },\n }\n : {}),\n ...(result.value.marker\n ? {\n marker: {\n storageHash: result.value.marker.storageHash,\n ...ifDefined('profileHash', result.value.marker.profileHash),\n },\n }\n : {}),\n ...ifDefined('perSpace', result.value.perSpace),\n advancedRef: refAdvancementFields.advancedRef,\n plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,\n summary: result.value.summary,\n timings: { total: Date.now() - startTime },\n };\n\n return ok(dbInitResult);\n } catch (error) {\n // Driver already throws CliStructuredError for connection failures\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\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 db init: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createDbInitCommand(): Command {\n const command = new Command('init');\n setCommandDescriptions(\n command,\n 'Bootstrap a database to match the current contract and sign it',\n 'Initializes a database to match your emitted contract using additive-only operations.\\n' +\n 'Creates any missing tables, columns, indexes, and constraints defined in your contract.\\n' +\n 'Leaves existing compatible structures in place, surfaces conflicts when destructive changes\\n' +\n 'would be required, and signs the database to track contract state. Use --dry-run to\\n' +\n 'preview changes without applying.',\n );\n setCommandExamples(command, [\n 'prisma-next db init --db $DATABASE_URL',\n 'prisma-next db init --db $DATABASE_URL --dry-run',\n ]);\n addMigrationCommandOptions(command);\n command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');\n command.action(async (options: DbInitOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n const result = await executeDbInitCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (dbInitResult) => {\n if (flags.json) {\n ui.output(formatMigrationJson(dbInitResult));\n } else {\n const output =\n dbInitResult.mode === 'plan'\n ? formatMigrationPlanOutput(dbInitResult, flags)\n : formatMigrationApplyOutput(dbInitResult, flags);\n if (output) {\n ui.log(output);\n }\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}

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

import { _ as createTerminalUI, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-Cpq44aVa.mjs";
import { t as inspectLiveSchema } from "../inspect-live-schema-CX7EA5C3.mjs";
import { n as formatIntrospectOutput, t as formatIntrospectJson } from "../verify-CgDVZjhc.mjs";
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 { n as formatIntrospectOutput, t as formatIntrospectJson } from "../verify-C4RazDE1.mjs";
import { Command } from "commander";

@@ -5,0 +5,0 @@ //#region src/commands/db-schema.ts

@@ -1,6 +0,6 @@

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-Cpq44aVa.mjs";
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-KuBQftxz.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { a as formatSignJson, i as formatSchemaVerifyOutput, o as formatSignOutput, r as formatSchemaVerifyJson } from "../verify-CgDVZjhc.mjs";
import { o as ContractValidationError, t as createControlClient } from "../client-2kwLMBSf.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-hPVymNLw.mjs";
import { a as formatSignJson, i as formatSchemaVerifyOutput, o as formatSignOutput, r as formatSchemaVerifyJson } from "../verify-C4RazDE1.mjs";
import { Command } from "commander";

@@ -7,0 +7,0 @@ import { loadConfig } from "@prisma-next/config-loader";

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

{"version":3,"file":"db-update.d.mts","names":[],"sources":["../../src/commands/db-update.ts"],"mappings":";;iBA8RgB,yBAAyB"}
{"version":3,"file":"db-update.d.mts","names":[],"sources":["../../src/commands/db-update.ts"],"mappings":";;iBA+RgB,yBAAyB"}

@@ -1,6 +0,6 @@

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-Cpq44aVa.mjs";
import { o as ContractValidationError } from "../client-KuBQftxz.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { n as prepareMigrationContext, t as addMigrationCommandOptions } from "../migration-command-scaffold-CRyYrHZ9.mjs";
import { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BkXlikCA.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 { 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 { i as readContractIR, n as computeRefAdvancementName, t as buildRefAdvancementFields } from "../ref-advancement-BCz7X7qJ.mjs";
import { Command } from "commander";

@@ -99,2 +99,3 @@ import { ifDefined } from "@prisma-next/utils/defined";

refsDir,
migrationsDir,
contractIR,

@@ -101,0 +102,0 @@ mode: result.value.mode,

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

{"version":3,"file":"db-update.mjs","names":[],"sources":["../../src/commands/db-update.ts"],"sourcesContent":["import {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join } from 'pathe';\nimport { ContractValidationError } from '../control-api/errors';\nimport type { DbUpdateFailure } from '../control-api/types';\nimport {\n CliStructuredError,\n ERROR_CODE_DESTRUCTIVE_CHANGES,\n errorContractValidationFailed,\n errorDestructiveChanges,\n errorMigrationPlanningFailed,\n errorRunnerFailed,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport type { MigrationCommandOptions } from '../utils/command-helpers';\nimport {\n resolveMigrationPaths,\n sanitizeErrorMessage,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport {\n formatMigrationApplyOutput,\n formatMigrationJson,\n formatMigrationPlanOutput,\n type MigrationCommandResult,\n} from '../utils/formatters/migrations';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport {\n addMigrationCommandOptions,\n prepareMigrationContext,\n} from '../utils/migration-command-scaffold';\nimport {\n buildRefAdvancementFields,\n computeRefAdvancementName,\n type RefAdvancementFields,\n readContractIR,\n} from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbUpdateOptions extends MigrationCommandOptions {\n readonly to?: string;\n readonly advanceRef?: string;\n}\n\n/**\n * Maps a DbUpdateFailure to a CliStructuredError for consistent error handling.\n */\nfunction mapDbUpdateFailure(failure: DbUpdateFailure): CliStructuredError {\n if (failure.code === 'PLANNING_FAILED') {\n return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });\n }\n\n if (failure.code === 'RUNNER_FAILED') {\n const runnerCode =\n typeof failure.meta?.['runnerErrorCode'] === 'string'\n ? failure.meta['runnerErrorCode']\n : undefined;\n const fix =\n runnerCode === 'MIGRATION.LEGACY_MARKER_SHAPE'\n ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'\n : 'Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`';\n return errorRunnerFailed(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix,\n meta: {\n ...failure.meta,\n ...(failure.warnings && failure.warnings.length > 0\n ? { plannerWarnings: failure.warnings }\n : {}),\n },\n });\n }\n\n if (failure.code === 'DESTRUCTIVE_CHANGES') {\n return errorDestructiveChanges(failure.summary, {\n ...ifDefined('why', failure.why),\n fix: 'Re-run with `-y` to apply destructive changes, or use `--dry-run` to preview first',\n ...ifDefined('meta', failure.meta),\n });\n }\n\n const exhaustive: never = failure.code;\n throw new Error(`Unhandled DbUpdateFailure code: ${exhaustive}`);\n}\n\n/**\n * Executes the db update command and returns a structured Result.\n */\nasync function executeDbUpdateCommand(\n options: DbUpdateOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationCommandResult, CliStructuredError>> {\n // Prepare shared migration context (config, contract, connection, client)\n const ctxResult = await prepareMigrationContext(options, flags, ui, {\n commandName: 'db update',\n description: 'Update your database schema to match your contract',\n url: 'https://pris.ly/db-update',\n });\n if (!ctxResult.ok) {\n return ctxResult;\n }\n const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;\n let { contractJson } = ctxResult.value;\n let contractJsonPathForSnapshot = contractPathAbsolute;\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n\n if (options.to) {\n try {\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const graph = loaded.value.aggregate.app.graph();\n const bundles = loaded.value.aggregate.app.packages;\n const refs = loaded.value.aggregate.app.refs;\n const refResult = parseContractRef(options.to, { graph, refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n const targetHash = refResult.value.hash;\n const matchingBundle = bundles.find((p) => p.metadata.to === targetHash);\n if (!matchingBundle) {\n return notOk(\n errorUnexpected(\n `No migration bundle found for --to \"${options.to}\" (resolved hash: ${targetHash})`,\n {\n why: `The ref resolved successfully but no on-disk migration package has a destination (\\`to\\`) hash matching ${targetHash}.`,\n fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',\n },\n ),\n );\n }\n contractJson = (await readContractSnapshotJson(migrationsDir, targetHash)) as Record<\n string,\n unknown\n >;\n contractJsonPathForSnapshot = join(\n contractSnapshotDir(migrationsDir, targetHash),\n 'contract.json',\n );\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n throw error;\n }\n }\n\n try {\n await client.connect(dbConnection);\n\n const result = await client.dbUpdate({\n contract: contractJson,\n mode: options.dryRun ? 'plan' : 'apply',\n migrationsDir,\n ...(flags.yes ? { acceptDataLoss: true } : {}),\n onProgress,\n });\n\n // Handle failures by mapping to CLI structured error\n if (!result.ok) {\n return notOk(mapDbUpdateFailure(result.failure));\n }\n\n const advancementHash =\n result.value.mode === 'apply'\n ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)\n : result.value.destination.storageHash;\n\n let refAdvancementFields: RefAdvancementFields = {\n advancedRef: null,\n plannedAdvanceRef: null,\n };\n if (\n computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n }) !== null\n ) {\n try {\n const contractIR = await readContractIR(contractJson, contractJsonPathForSnapshot);\n refAdvancementFields = await buildRefAdvancementFields({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n refsDir,\n contractIR,\n mode: result.value.mode,\n hash: advancementHash,\n });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n // Convert success result to CLI output format\n const dbUpdateResult: MigrationCommandResult = {\n ok: true,\n mode: result.value.mode,\n plan: {\n targetId: ctxResult.value.config.target.targetId,\n destination: {\n storageHash: result.value.destination.storageHash,\n ...ifDefined('profileHash', result.value.destination.profileHash),\n },\n operations: result.value.plan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n ...ifDefined('preview', result.value.plan.preview),\n },\n ...ifDefined(\n 'execution',\n result.value.execution\n ? {\n operationsPlanned: result.value.execution.operationsPlanned,\n operationsExecuted: result.value.execution.operationsExecuted,\n }\n : undefined,\n ),\n ...ifDefined(\n 'marker',\n result.value.marker\n ? {\n storageHash: result.value.marker.storageHash,\n ...ifDefined('profileHash', result.value.marker.profileHash),\n }\n : undefined,\n ),\n ...ifDefined('perSpace', result.value.perSpace),\n ...ifDefined('warnings', result.value.warnings),\n advancedRef: refAdvancementFields.advancedRef,\n plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,\n summary: result.value.summary,\n timings: { total: Date.now() - startTime },\n };\n\n return ok(dbUpdateResult);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\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 db update: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createDbUpdateCommand(): Command {\n const command = new Command('update');\n setCommandDescriptions(\n command,\n 'Update your database schema to match your contract',\n 'Compares your database schema to the emitted contract and applies the necessary\\n' +\n 'changes. Works on any database, whether or not it has been initialized with `db init`.\\n' +\n 'Destructive operations prompt for confirmation in interactive mode. Use -y to\\n' +\n 'auto-accept or --dry-run to preview first.',\n );\n setCommandExamples(command, [\n 'prisma-next db update --db $DATABASE_URL',\n 'prisma-next db update --db $DATABASE_URL --dry-run',\n ]);\n addMigrationCommandOptions(command);\n command.option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n );\n command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');\n command.action(async (options: DbUpdateOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n let result = await executeDbUpdateCommand(options, flags, ui, startTime);\n\n // Interactive confirmation for destructive operations:\n // When the control API rejects destructive changes, prompt the user instead of failing.\n // In non-interactive mode (CI, piped, --no-interactive, --json), the error is returned as-is.\n if (\n !result.ok &&\n result.failure.code === ERROR_CODE_DESTRUCTIVE_CHANGES &&\n flags.interactive &&\n !flags.json &&\n !flags.yes\n ) {\n const meta = result.failure.meta as\n | { destructiveOperations?: readonly { id: string; label: string }[] }\n | undefined;\n const destructiveOps = meta?.destructiveOperations ?? [];\n\n if (destructiveOps.length > 0) {\n ui.warn(\n `${destructiveOps.length} destructive operation(s) that may cause data loss:\\n${destructiveOps.map((op) => ` ${ui.yellow('▸')} ${op.label}`).join('\\n')}`,\n );\n }\n\n const confirmed = await ui.confirm('Apply destructive changes? This cannot be undone.');\n\n if (confirmed) {\n result = await executeDbUpdateCommand(options, { ...flags, yes: true }, ui, Date.now());\n }\n }\n\n const exitCode = handleResult(result, flags, ui, (dbUpdateResult) => {\n if (flags.json) {\n ui.output(formatMigrationJson(dbUpdateResult));\n } else {\n const output =\n dbUpdateResult.mode === 'plan'\n ? formatMigrationPlanOutput(dbUpdateResult, flags)\n : formatMigrationApplyOutput(dbUpdateResult, flags);\n if (output) {\n ui.log(output);\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
{"version":3,"file":"db-update.mjs","names":[],"sources":["../../src/commands/db-update.ts"],"sourcesContent":["import {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join } from 'pathe';\nimport { ContractValidationError } from '../control-api/errors';\nimport type { DbUpdateFailure } from '../control-api/types';\nimport {\n CliStructuredError,\n ERROR_CODE_DESTRUCTIVE_CHANGES,\n errorContractValidationFailed,\n errorDestructiveChanges,\n errorMigrationPlanningFailed,\n errorRunnerFailed,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport type { MigrationCommandOptions } from '../utils/command-helpers';\nimport {\n resolveMigrationPaths,\n sanitizeErrorMessage,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport {\n formatMigrationApplyOutput,\n formatMigrationJson,\n formatMigrationPlanOutput,\n type MigrationCommandResult,\n} from '../utils/formatters/migrations';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport {\n addMigrationCommandOptions,\n prepareMigrationContext,\n} from '../utils/migration-command-scaffold';\nimport {\n buildRefAdvancementFields,\n computeRefAdvancementName,\n type RefAdvancementFields,\n readContractIR,\n} from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface DbUpdateOptions extends MigrationCommandOptions {\n readonly to?: string;\n readonly advanceRef?: string;\n}\n\n/**\n * Maps a DbUpdateFailure to a CliStructuredError for consistent error handling.\n */\nfunction mapDbUpdateFailure(failure: DbUpdateFailure): CliStructuredError {\n if (failure.code === 'PLANNING_FAILED') {\n return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });\n }\n\n if (failure.code === 'RUNNER_FAILED') {\n const runnerCode =\n typeof failure.meta?.['runnerErrorCode'] === 'string'\n ? failure.meta['runnerErrorCode']\n : undefined;\n const fix =\n runnerCode === 'MIGRATION.LEGACY_MARKER_SHAPE'\n ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'\n : 'Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`';\n return errorRunnerFailed(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix,\n meta: {\n ...failure.meta,\n ...(failure.warnings && failure.warnings.length > 0\n ? { plannerWarnings: failure.warnings }\n : {}),\n },\n });\n }\n\n if (failure.code === 'DESTRUCTIVE_CHANGES') {\n return errorDestructiveChanges(failure.summary, {\n ...ifDefined('why', failure.why),\n fix: 'Re-run with `-y` to apply destructive changes, or use `--dry-run` to preview first',\n ...ifDefined('meta', failure.meta),\n });\n }\n\n const exhaustive: never = failure.code;\n throw new Error(`Unhandled DbUpdateFailure code: ${exhaustive}`);\n}\n\n/**\n * Executes the db update command and returns a structured Result.\n */\nasync function executeDbUpdateCommand(\n options: DbUpdateOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationCommandResult, CliStructuredError>> {\n // Prepare shared migration context (config, contract, connection, client)\n const ctxResult = await prepareMigrationContext(options, flags, ui, {\n commandName: 'db update',\n description: 'Update your database schema to match your contract',\n url: 'https://pris.ly/db-update',\n });\n if (!ctxResult.ok) {\n return ctxResult;\n }\n const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;\n let { contractJson } = ctxResult.value;\n let contractJsonPathForSnapshot = contractPathAbsolute;\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n\n if (options.to) {\n try {\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const graph = loaded.value.aggregate.app.graph();\n const bundles = loaded.value.aggregate.app.packages;\n const refs = loaded.value.aggregate.app.refs;\n const refResult = parseContractRef(options.to, { graph, refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n const targetHash = refResult.value.hash;\n const matchingBundle = bundles.find((p) => p.metadata.to === targetHash);\n if (!matchingBundle) {\n return notOk(\n errorUnexpected(\n `No migration bundle found for --to \"${options.to}\" (resolved hash: ${targetHash})`,\n {\n why: `The ref resolved successfully but no on-disk migration package has a destination (\\`to\\`) hash matching ${targetHash}.`,\n fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',\n },\n ),\n );\n }\n contractJson = (await readContractSnapshotJson(migrationsDir, targetHash)) as Record<\n string,\n unknown\n >;\n contractJsonPathForSnapshot = join(\n contractSnapshotDir(migrationsDir, targetHash),\n 'contract.json',\n );\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n throw error;\n }\n }\n\n try {\n await client.connect(dbConnection);\n\n const result = await client.dbUpdate({\n contract: contractJson,\n mode: options.dryRun ? 'plan' : 'apply',\n migrationsDir,\n ...(flags.yes ? { acceptDataLoss: true } : {}),\n onProgress,\n });\n\n // Handle failures by mapping to CLI structured error\n if (!result.ok) {\n return notOk(mapDbUpdateFailure(result.failure));\n }\n\n const advancementHash =\n result.value.mode === 'apply'\n ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)\n : result.value.destination.storageHash;\n\n let refAdvancementFields: RefAdvancementFields = {\n advancedRef: null,\n plannedAdvanceRef: null,\n };\n if (\n computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n }) !== null\n ) {\n try {\n const contractIR = await readContractIR(contractJson, contractJsonPathForSnapshot);\n refAdvancementFields = await buildRefAdvancementFields({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n refsDir,\n migrationsDir,\n contractIR,\n mode: result.value.mode,\n hash: advancementHash,\n });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n // Convert success result to CLI output format\n const dbUpdateResult: MigrationCommandResult = {\n ok: true,\n mode: result.value.mode,\n plan: {\n targetId: ctxResult.value.config.target.targetId,\n destination: {\n storageHash: result.value.destination.storageHash,\n ...ifDefined('profileHash', result.value.destination.profileHash),\n },\n operations: result.value.plan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n ...ifDefined('preview', result.value.plan.preview),\n },\n ...ifDefined(\n 'execution',\n result.value.execution\n ? {\n operationsPlanned: result.value.execution.operationsPlanned,\n operationsExecuted: result.value.execution.operationsExecuted,\n }\n : undefined,\n ),\n ...ifDefined(\n 'marker',\n result.value.marker\n ? {\n storageHash: result.value.marker.storageHash,\n ...ifDefined('profileHash', result.value.marker.profileHash),\n }\n : undefined,\n ),\n ...ifDefined('perSpace', result.value.perSpace),\n ...ifDefined('warnings', result.value.warnings),\n advancedRef: refAdvancementFields.advancedRef,\n plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,\n summary: result.value.summary,\n timings: { total: Date.now() - startTime },\n };\n\n return ok(dbUpdateResult);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n if (error instanceof ContractValidationError) {\n return notOk(\n errorContractValidationFailed(`Contract validation failed: ${error.message}`, {\n where: { path: contractPathAbsolute },\n }),\n );\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 db update: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createDbUpdateCommand(): Command {\n const command = new Command('update');\n setCommandDescriptions(\n command,\n 'Update your database schema to match your contract',\n 'Compares your database schema to the emitted contract and applies the necessary\\n' +\n 'changes. Works on any database, whether or not it has been initialized with `db init`.\\n' +\n 'Destructive operations prompt for confirmation in interactive mode. Use -y to\\n' +\n 'auto-accept or --dry-run to preview first.',\n );\n setCommandExamples(command, [\n 'prisma-next db update --db $DATABASE_URL',\n 'prisma-next db update --db $DATABASE_URL --dry-run',\n ]);\n addMigrationCommandOptions(command);\n command.option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n );\n command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');\n command.action(async (options: DbUpdateOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n let result = await executeDbUpdateCommand(options, flags, ui, startTime);\n\n // Interactive confirmation for destructive operations:\n // When the control API rejects destructive changes, prompt the user instead of failing.\n // In non-interactive mode (CI, piped, --no-interactive, --json), the error is returned as-is.\n if (\n !result.ok &&\n result.failure.code === ERROR_CODE_DESTRUCTIVE_CHANGES &&\n flags.interactive &&\n !flags.json &&\n !flags.yes\n ) {\n const meta = result.failure.meta as\n | { destructiveOperations?: readonly { id: string; label: string }[] }\n | undefined;\n const destructiveOps = meta?.destructiveOperations ?? [];\n\n if (destructiveOps.length > 0) {\n ui.warn(\n `${destructiveOps.length} destructive operation(s) that may cause data loss:\\n${destructiveOps.map((op) => ` ${ui.yellow('▸')} ${op.label}`).join('\\n')}`,\n );\n }\n\n const confirmed = await ui.confirm('Apply destructive changes? This cannot be undone.');\n\n if (confirmed) {\n result = await executeDbUpdateCommand(options, { ...flags, yes: true }, ui, Date.now());\n }\n }\n\n const exitCode = handleResult(result, flags, ui, (dbUpdateResult) => {\n if (flags.json) {\n ui.output(formatMigrationJson(dbUpdateResult));\n } else {\n const output =\n dbUpdateResult.mode === 'plan'\n ? formatMigrationPlanOutput(dbUpdateResult, flags)\n : formatMigrationApplyOutput(dbUpdateResult, flags);\n if (output) {\n ui.log(output);\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}

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

import { t as createDbVerifyCommand } from "../db-verify-BEclUzki.mjs";
import { t as createDbVerifyCommand } from "../db-verify-BpwCMyWJ.mjs";
export { createDbVerifyCommand };

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

{"version":3,"file":"migrate.d.mts","names":[],"sources":["../../src/commands/migrate.ts"],"mappings":";;;;;;UA6FiB;WACN;WACA;WACA;WACA;WACA;;;UAIM;WACN;WACA,qBAAqB;WACrB;;;;;;WAMA;;;;;WAKA;;;;;;;WAOA;;UAGM;WACN;WACA;WACA;WACA;WACA;aACE;aACA;aACA;aACA;aACA;aACA;;WAEF;WACA,mBAAmB;WACnB,eAAe;WACf;aACE;;WAEF;aAAyB;aAAuB;;;iBAytB3C,wBAAwB"}
{"version":3,"file":"migrate.d.mts","names":[],"sources":["../../src/commands/migrate.ts"],"mappings":";;;;;;UA6FiB;WACN;WACA;WACA;WACA;WACA;;;UAIM;WACN;WACA,qBAAqB;WACrB;;;;;;WAMA;;;;;WAKA;;;;;;;WAOA;;UAGM;WACN;WACA;WACA;WACA;WACA;aACE;aACA;aACA;aACA;aACA;aACA;;WAEF;WACA,mBAAmB;WACnB,eAAe;WACf;aACE;;WAEF;aAAyB;aAAuB;;;iBA0tB3C,wBAAwB"}

@@ -1,8 +0,8 @@

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-Cpq44aVa.mjs";
import { n as planSpacePath, t as createControlClient } from "../client-KuBQftxz.mjs";
import { a as refuseContractSpaceIntegrity, i as loadContractSpaceAggregateForCli, n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { i as readContractIR, r as executeRefAdvancement } from "../ref-advancement-BkXlikCA.mjs";
import { t as mapContractAtError } from "../contract-at-errors-CTtzKR6q.mjs";
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 { a as refuseContractSpaceIntegrity, i as loadContractSpaceAggregateForCli, n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "../contract-space-aggregate-loader-hPVymNLw.mjs";
import { i as readContractIR, r as executeRefAdvancement } from "../ref-advancement-BCz7X7qJ.mjs";
import { t as mapContractAtError } from "../contract-at-errors-C-l4-JEY.mjs";
import { d as highlightFromEdgeAnnotations, f as indentMigrationGraphTreeBlock, h as buildGrid, i as formatOnPathMigrationRow, m as buildMigrationGraphRows, n as computeMaxDirNameWidth, r as renderMigrationGraphCommand, t as computeLabelColumn } from "../migration-graph-command-render-B83xrnNy.mjs";
import { r as listRefsByContractHash } from "../migration-list-6z6vuXHa.mjs";
import { r as listRefsByContractHash } from "../migration-list-cnSKilYH.mjs";
import { Command } from "commander";

@@ -441,3 +441,3 @@ import { loadConfig } from "@prisma-next/config-loader";

} : await readContractIR(snapshotContractJson, contractPathAbsolute);
advancedRef = await executeRefAdvancement(refsDir, options.advanceRef, value.markerHash, contractIR);
advancedRef = await executeRefAdvancement(refsDir, migrationsDir, options.advanceRef, value.markerHash, contractIR);
} catch (error) {

@@ -444,0 +444,0 @@ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));

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

{"version":3,"file":"migrate.mjs","names":[],"sources":["../../src/commands/migrate.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport {\n type AggregateContractSpace,\n requireHeadRef,\n} from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { findLatestMigration, isGraphNode } 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 { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport { planSpacePath } from '../control-api/operations/migrate';\nimport type {\n MigrateFailure,\n MigratePathDecision,\n PerSpaceExecutionEntry,\n} from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorMarkerMismatch,\n errorPathUnreachable,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n collectDeclaredInvariants,\n maskConnectionUrl,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n targetSupportsMigrations,\n} from '../utils/command-helpers';\nimport { mapContractAtError } from '../utils/contract-at-errors';\nimport {\n buildReadAggregate,\n loadContractSpaceAggregateForCli,\n refuseContractSpaceIntegrity,\n} from '../utils/contract-space-aggregate-loader';\nimport { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';\nimport {\n computeLabelColumn,\n computeMaxDirNameWidth,\n renderMigrationGraphCommand,\n} from '../utils/formatters/migration-graph-command-render';\nimport { buildGrid } from '../utils/formatters/migration-graph-grid-layout';\nimport {\n formatOnPathMigrationRow,\n type MigrationEdgeAnnotation,\n} from '../utils/formatters/migration-graph-labels';\nimport { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';\nimport {\n highlightFromEdgeAnnotations,\n indentMigrationGraphTreeBlock,\n} from '../utils/formatters/migration-graph-space-render';\nimport { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { executeRefAdvancement, readContractIR } from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport { listRefsByContractHash } from './migration-list';\n\ninterface MigrateCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly to?: string;\n readonly advanceRef?: string;\n readonly show?: boolean;\n readonly from?: string;\n}\n\n/**\n * One migration that will run in a `migrate --show` preview, in execution order.\n */\nexport interface MigrateShowMigration {\n readonly spaceId: string;\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** Result returned by `migrate --show`. Read-only; no writes performed. */\nexport interface MigrateShowResult {\n readonly ok: true;\n readonly migrations: readonly MigrateShowMigration[];\n readonly summary: string;\n /**\n * Pre-rendered Tier-3 graph tree for human output. Off-path migrations render\n * dim; on-path migrations render in ordinary colours. Only present in human\n * (non-JSON) mode.\n */\n readonly graphOutput?: string;\n /**\n * Name column width for the \"Will run, in order:\" list — globally aligned with\n * every graph-tree section. Only present in human (non-JSON) mode.\n */\n readonly runListDirNameWidth?: number;\n /**\n * Left-pad offset (number of blank spaces) matching the graph's data-column\n * offset (`globalMaxEdgeTreePrefixWidth`). Used to align list rows with graph\n * rows so every `→` in the output (graph + list) lands at the same column.\n * Only present in human (non-JSON) mode when multiple spaces are rendered.\n */\n readonly runListLeftPad?: number;\n}\n\nexport interface MigrateResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly spaceId: string;\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly perSpace: readonly PerSpaceExecutionEntry[];\n readonly pathDecision?: MigratePathDecision;\n readonly timings: {\n readonly total: number;\n };\n readonly advancedRef?: { readonly name: string; readonly hash: string } | null;\n}\n\n/**\n * Read-only preview of the migration path `migrate` will take.\n *\n * Computes the path through the SAME seam as `executeMigrate`:\n * - `readAllMarkers()` for the from-state (when no `--from` is given), preserving\n * the full marker including `invariants` (not just `storageHash`).\n * - `planSpacePath()` (shared with `executeMigrate`) for per-space path selection,\n * which feeds `resolveRecordedPath()` with the same target hash, target invariants,\n * and current marker as the real apply path uses.\n *\n * Returns BEFORE any write boundary (`runMigration` / marker / DDL). No\n * DB state is mutated.\n */\nasync function executeMigrateShowCommand(\n options: MigrateCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrateShowResult, CliStructuredErrorType>> {\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 hasExplicitFrom = options.from !== undefined;\n\n // When --from is omitted we read the live DB marker (same as migrate's default).\n // When --from is given, we're in offline hypothetical mode — no connection needed.\n if (!hasExplicitFrom) {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: 'migrate --show needs a database connection to read the live marker (or pass --from <contract> for an offline preview)',\n retryCommand: 'prisma-next migrate --show --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 loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const { aggregate, contractHash } = loaded.value;\n const appGraph = aggregate.app.graph();\n\n // Resolve the --to target (defaults to the on-disk contract, same as migrate).\n // Also capture the ref's invariants so planSpacePath feeds resolveRecordedPath the\n // same target invariants that real migrate would use (refInvariants ?? headRef.invariants).\n let targetHash: string = contractHash;\n let refInvariants: readonly string[] | undefined;\n if (options.to) {\n const toResult = parseContractRef(options.to, {\n graph: appGraph,\n refs: allRefs,\n contractHash,\n });\n if (!toResult.ok) {\n return notOk(mapRefResolutionError(toResult.failure));\n }\n if (toResult.value.provenance.kind === 'reserved-db') {\n return notOk(\n errorDatabaseConnectionRequired({\n why: '@db is not valid as a --to target; it names the live database state, not a target contract.',\n commandName: 'migrate --show',\n }),\n );\n }\n targetHash = toResult.value.hash;\n if (toResult.value.provenance.kind === 'ref') {\n const refEntry = allRefs[toResult.value.provenance.refName];\n if (refEntry) refInvariants = refEntry.invariants;\n }\n }\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 && !hasExplicitFrom) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.to) {\n details.push({ label: 'to', value: options.to });\n }\n const header = formatStyledHeader({\n command: 'migrate --show',\n description: 'Preview the migration path migrate will take (read-only)',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Resolve the from-state.\n // - Explicit --from: parse it offline (no connection).\n // - Omitted: read the live DB marker via readAllMarkers() — the same source migrate uses.\n //\n // Full marker records (storageHash + invariants) are preserved so planSpacePath\n // can feed resolveRecordedPath the complete currentMarker — exactly as executeMigrate\n // does via familyInstance.readAllMarkers(). A stripped { storageHash, invariants: [] }\n // marker would produce a different `required` set and a different (incorrect) path.\n type LiveMarker = { readonly storageHash: string; readonly invariants: readonly string[] };\n const markerBySpace = new Map<string, LiveMarker | null>();\n const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];\n\n if (hasExplicitFrom) {\n // @db with explicit --from requires a connection\n if (options.from === '@db') {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: '@db resolves to the live database marker and requires a --db connection',\n retryCommand: 'prisma-next migrate --show --from @db --db $DATABASE_URL',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n // Fall through to the connection path below\n } else {\n const fromResult = parseContractRef(options.from, {\n graph: appGraph,\n refs: allRefs,\n contractHash,\n });\n if (!fromResult.ok) {\n return notOk(mapRefResolutionError(fromResult.failure));\n }\n if (fromResult.value.provenance.kind === 'reserved-db') {\n // Unreachable given the @db branch above, but guard for safety\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: '@db resolves to the live database marker and requires a --db connection',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n } else {\n // Offline hypothetical: the --from ref only carries a hash (no live invariants).\n // Apply the from-hash marker to the APP space only. Extension spaces are left\n // absent from markerBySpace (treated as null / greenfield by planSpacePath),\n // so they plan from their own marker → own head — exactly as executeMigrate does.\n const fromHash = fromResult.value.hash;\n const offlineMarker: LiveMarker | null =\n fromHash === EMPTY_CONTRACT_HASH ? null : { storageHash: fromHash, invariants: [] };\n markerBySpace.set(aggregate.app.spaceId, offlineMarker);\n }\n }\n }\n\n // If we need the live DB marker (no --from, or --from @db), connect and read.\n const needsLiveMarker = !hasExplicitFrom || options.from === '@db';\n if (needsLiveMarker) {\n if (!dbConnection || !hasDriver) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: 'A database connection is required to read the live marker for migrate --show',\n commandName: 'migrate --show',\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 try {\n await client.connect(dbConnection);\n const allMarkers = await client.readAllMarkers();\n // Store the full marker record (storageHash + invariants) per space.\n // This is the same data executeMigrate uses via familyInstance.readAllMarkers().\n for (const space of allSpaces) {\n const marker = allMarkers.get(space.spaceId);\n markerBySpace.set(space.spaceId, marker ?? null);\n }\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read live DB marker: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n }\n\n // Walk the path via planSpacePath — the same helper executeMigrate uses.\n // planSpacePath feeds resolveRecordedPath identical inputs (targetHash, targetInvariants,\n // currentMarker with full invariants), so the preview path is always the path migrate runs.\n //\n // Canonical schedule order: extensions alphabetically first, then app — mirroring the\n // runner's `applyOrder` in operations/migrate.ts so the \"Will run, in order:\" list\n // reflects the actual execution sequence (extensions install first, app last).\n const canonicalOrderSpaces: ReadonlyArray<AggregateContractSpace> = [\n ...aggregate.extensions,\n aggregate.app,\n ];\n const orderedMigrations: MigrateShowMigration[] = [];\n for (const space of canonicalOrderSpaces) {\n const isAppSpace = space.spaceId === aggregate.app.spaceId;\n const headRef = requireHeadRef(space);\n const spaceTargetHash = isAppSpace ? targetHash : headRef.hash;\n const spaceRefInvariants = isAppSpace ? refInvariants : undefined;\n const liveMarker = markerBySpace.get(space.spaceId) ?? null;\n\n const outcome = planSpacePath({\n space,\n aggregate,\n targetHash: spaceTargetHash,\n refInvariants: spaceRefInvariants,\n liveMarker,\n });\n\n if (outcome.kind === 'at-head') {\n // Empty-graph space already at target — nothing to run for this space.\n continue;\n }\n if (outcome.kind === 'never-planned') {\n return notOk(\n errorPathUnreachable({\n code: 'MIGRATION_PATH_NOT_FOUND',\n summary: `No on-disk migrations for contract space \"${outcome.spaceId}\"`,\n why: `migrate is replay-only: space \"${outcome.spaceId}\" has no on-disk migrations but its head ref targets \"${outcome.targetHash}\".`,\n meta: { spaceId: outcome.spaceId, target: outcome.targetHash, kind: 'neverPlanned' },\n }),\n );\n }\n if (outcome.kind === 'unreachable') {\n const fromHash = outcome.liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n return notOk(\n errorPathUnreachable({\n code: 'MIGRATION_PATH_NOT_FOUND',\n summary: `No migration path from ${fromHash.slice(0, 14)} to ${outcome.targetHash.slice(0, 14)} in space \"${outcome.spaceId}\".`,\n why: `The migration graph has no path from the from-state to the target in space \"${outcome.spaceId}\".`,\n meta: { spaceId: outcome.spaceId, from: fromHash, to: outcome.targetHash },\n }),\n );\n }\n if (outcome.kind === 'unsatisfiable') {\n return notOk(\n errorRuntime(`Missing required invariants for space \"${outcome.spaceId}\"`, {\n why: `The path requires invariants not available on disk: ${outcome.missing.join(', ')}`,\n }),\n );\n }\n\n for (const edge of outcome.plan.migrationEdges) {\n orderedMigrations.push({\n spaceId: space.spaceId,\n dirName: edge.dirName,\n migrationHash: edge.migrationHash,\n from: edge.from,\n to: edge.to,\n });\n }\n }\n\n const count = orderedMigrations.length;\n const summary =\n count === 0\n ? 'Already up to date — nothing to run'\n : `${count} migration${count === 1 ? '' : 's'} will run`;\n\n // Build the Tier-3 graph visualization (human mode only; skipped for --json).\n // Reuses the existing annotation hook — no parallel renderer.\n let graphOutput: string | undefined;\n let runListDirNameWidth: number | undefined;\n let runListLeftPad: number | undefined;\n if (!flags.json) {\n const onPathHashes = new Set(orderedMigrations.map((m) => m.migrationHash));\n const colorize = flags.color !== false;\n\n // Build layouts for all spaces first so we can compute global column widths\n // before rendering. This ensures the name column, hash column, and ops column\n // start at the same horizontal offset across every space section AND the\n // \"Will run, in order:\" list below.\n const spaceLayouts = allSpaces.map((space) => {\n const isApp = space.spaceId === aggregate.app.spaceId;\n const spaceGraph = space.graph();\n const rowModel = buildMigrationGraphRows(spaceGraph, isApp ? { contractHash } : {});\n const edgeAnnotations = new Map<string, MigrationEdgeAnnotation>();\n for (const edge of spaceGraph.migrationByHash.values()) {\n edgeAnnotations.set(edge.migrationHash, {\n pathHighlight: onPathHashes.has(edge.migrationHash) ? 'on-path' : 'off-path',\n });\n }\n // The on-path migration set lifts to focus mode so the chosen route draws\n // green/continuous; off-path lanes dim. Rows, gutter, and labels all come\n // from this one grid.\n const grid = buildGrid(rowModel, {}, highlightFromEdgeAnnotations(edgeAnnotations));\n return { space, isApp, spaceGraph, rowModel, grid, edgeAnnotations };\n });\n\n // Global max across all space grids so every section's labels share columns.\n const globalLabelColumn =\n spaceLayouts.length > 1\n ? Math.max(...spaceLayouts.map(({ grid }) => computeLabelColumn(grid, 'unicode')))\n : undefined;\n const globalMaxDirNameWidthFromLayouts =\n spaceLayouts.length > 1\n ? Math.max(...spaceLayouts.map(({ rowModel }) => computeMaxDirNameWidth(rowModel)))\n : undefined;\n // The run-list name column width must be at least as wide as the global tree dirName\n // width so that tree sections and the list align at the hash column.\n const runListMaxFromMigrations =\n orderedMigrations.length > 0\n ? Math.max(...orderedMigrations.map((m) => m.dirName.length))\n : 0;\n const globalMaxDirNameWidth =\n globalMaxDirNameWidthFromLayouts !== undefined\n ? Math.max(globalMaxDirNameWidthFromLayouts, runListMaxFromMigrations)\n : undefined;\n runListDirNameWidth = globalMaxDirNameWidth ?? runListMaxFromMigrations;\n runListLeftPad = globalLabelColumn;\n\n // Render each space section with globally computed widths.\n const showSpaceHeadings = allSpaces.length > 1;\n const sections: string[] = [];\n for (const { space, isApp, rowModel, grid, edgeAnnotations } of spaceLayouts) {\n const liveMarker = markerBySpace.get(space.spaceId) ?? null;\n const liveMarkerHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n const tree = renderMigrationGraphCommand({\n grid,\n rowModel,\n contractHash,\n isAppSpace: isApp,\n ...(needsLiveMarker ? { dbHash: liveMarkerHash } : {}),\n refsByHash: listRefsByContractHash(space),\n edgeAnnotationsByHash: edgeAnnotations,\n colorize,\n glyphMode: 'unicode',\n ...(globalLabelColumn !== undefined ? { globalLabelColumn } : {}),\n ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),\n });\n if (tree.length === 0) continue;\n if (showSpaceHeadings) {\n sections.push(`${space.spaceId}:\\n${indentMigrationGraphTreeBlock(tree, ' ')}`);\n } else {\n sections.push(tree);\n }\n }\n graphOutput = sections.join('\\n\\n');\n }\n\n return ok({\n ok: true,\n migrations: orderedMigrations,\n summary,\n ...(graphOutput !== undefined ? { graphOutput } : {}),\n ...(runListDirNameWidth !== undefined ? { runListDirNameWidth } : {}),\n ...(runListLeftPad !== undefined ? { runListLeftPad } : {}),\n });\n}\n\nfunction formatMigrateShowOutput(result: MigrateShowResult, flags: GlobalFlags): string {\n if (flags.quiet) return '';\n const colorize = flags.color !== false;\n const lines: string[] = [];\n // Graph tree first (shows the full topology with on-path highlighted).\n if (result.graphOutput !== undefined && result.graphOutput.length > 0) {\n lines.push(result.graphOutput);\n lines.push('');\n }\n const n = result.migrations.length;\n if (n > 0) {\n // Consolidated header: one line replaces the old separate summary + blank +\n // \"Will run, in order:\" header.\n lines.push(`The following ${n} migration${n === 1 ? '' : 's'} will run:`);\n // Ordered list rendered through the SAME on-path row renderer as the tree.\n // `formatOnPathMigrationRow` uses PATH_HIGHLIGHT_STYLES.onPath so the list and\n // graph-tree rows are styled identically — changing the on-path colour in future\n // is a one-line edit in PATH_HIGHLIGHT_STYLES.\n //\n // Alignment anchor: the `→` arrow (source-hash onward) must land at the SAME\n // absolute column as in graph edge rows, across every graph section and this list.\n //\n // Multi-space output layout (space headings + 2-space indented tree sections):\n // Graph edge row: [2 heading][G gutter][D dirName][7 source] [→] [dest]\n // List row: [2 spaces][L dirName][ ][7 source] [→] [dest]\n // Alignment: 2 + G + D + 9 = 2 + L + 2 + 9 => L = G + D - 2\n //\n // Single-space output layout (flat tree, no heading indent):\n // Graph edge row: [G gutter][D dirName][7 source] [→] [dest]\n // List row: [2 spaces][L dirName][ ][7 source] [→] [dest]\n // Alignment: G + D + 9 = 2 + L + 2 + 9 => L = G + D - 4\n //\n // D (edgeDirNameWidth) = max(rawDirNameWidth + LABEL_GAP, MIN_HASH_DATA_COLUMN - G)\n // where LABEL_GAP = 2 and MIN_HASH_DATA_COLUMN = 25 (same constants as the renderer).\n //\n // runListLeftPad is set only for multi-space; undefined means single-space.\n const isMultiSpace = result.runListLeftPad !== undefined;\n const gutter = result.runListLeftPad ?? 0;\n const rawDirNameWidth =\n result.runListDirNameWidth ?? Math.max(...result.migrations.map((m) => m.dirName.length));\n const edgeDirNameWidth = Math.max(rawDirNameWidth + 2, 25 - gutter);\n const listDirNameWidth = gutter + edgeDirNameWidth - (isMultiSpace ? 2 : 4);\n for (const m of result.migrations) {\n lines.push(\n ` ${formatOnPathMigrationRow(m.dirName, m.from, m.to, listDirNameWidth, colorize, 'unicode')}`,\n );\n }\n } else {\n lines.push(result.summary);\n }\n return lines.join('\\n');\n}\n\nfunction mapApplyFailure(failure: MigrateFailure): CliStructuredErrorType {\n if (failure.code === 'MIGRATION_PATH_NOT_FOUND') {\n return errorPathUnreachable(failure);\n }\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nasync function executeMigrateCommand(\n options: MigrateCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrateResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(\n options.config,\n config,\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 migrate (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migrate',\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: 'Config.driver is required for migrate',\n }),\n );\n }\n\n if (!targetSupportsMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n const toArg = options.to;\n\n // Construct the family instance up-front so the on-disk contract read\n // crosses the serializer seam (`familyInstance.deserializeContract`) at\n // the read site. The downstream `client.migrate({ contract })`\n // re-validates internally (no harm — validation is idempotent), but\n // closing the gap at the read site is what makes the cast-pattern\n // lint enforceable and matches the other CLI commands. See TML-2536.\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n\n const contractPathAbsolute = resolveContractPath(config);\n let contractRaw: Contract;\n let contractContent: string;\n try {\n contractContent = 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 a valid contract.json, then retry.',\n }),\n );\n }\n return notOk(\n errorContractValidationFailed(\n `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n try {\n contractRaw = familyInstance.deserializeContract(JSON.parse(contractContent) as unknown);\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract at ${contractPathAbsolute} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n const loadedAggregate = await loadContractSpaceAggregateForCli({\n targetId: config.target.targetId,\n migrationsDir,\n appContract: contractRaw,\n extensionPacks: config.extensionPacks ?? [],\n deserializeContract: (json) => familyInstance.deserializeContract(json),\n });\n if (!loadedAggregate.ok) {\n return notOk(loadedAggregate.failure);\n }\n const aggregate = loadedAggregate.value;\n const integrityFailure = refuseContractSpaceIntegrity(aggregate, {\n declaredExtensions: toDeclaredExtensionsFromRaw(\n (config.extensionPacks ?? []) as ReadonlyArray<unknown>,\n ),\n checkContracts: true,\n });\n if (integrityFailure) {\n return notOk(integrityFailure);\n }\n\n let refEntry: RefEntry | undefined;\n let refName: string | undefined;\n if (toArg) {\n const refs = aggregate.app.refs;\n const refResult = parseContractRef(toArg, { graph: aggregate.app.graph(), refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n if (refResult.value.provenance.kind === 'ref') {\n refName = refResult.value.provenance.refName;\n const resolved = refs[refName];\n if (resolved) refEntry = resolved;\n } else {\n refEntry = { hash: refResult.value.hash, invariants: [] };\n }\n }\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 (typeof dbConnection === 'string') {\n details.push({\n label: 'database',\n value: maskConnectionUrl(dbConnection),\n });\n }\n if (toArg) {\n details.push({ label: 'to', value: toArg });\n }\n const header = formatStyledHeader({\n command: 'migrate',\n description: 'Apply planned migrations to advance the database',\n url: 'https://pris.ly/migrate',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n const appGraph = aggregate.app.graph();\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\n try {\n await client.connect(dbConnection);\n\n const allMarkers = await client.readAllMarkers();\n const appMarker = allMarkers.get('app') ?? null;\n\n if (appMarker !== null && !isGraphNode(appMarker.storageHash, appGraph)) {\n return notOk(\n errorMarkerMismatch(\n appMarker.storageHash,\n [...appGraph.nodes].sort(),\n findLatestMigration(appGraph)?.to ?? null,\n ),\n );\n }\n\n if (refEntry && refEntry.invariants.length > 0) {\n const declared = collectDeclaredInvariants(appGraph);\n const known = new Set<string>(declared);\n for (const id of appMarker?.invariants ?? []) known.add(id);\n const unknown = refEntry.invariants.filter((id) => !known.has(id));\n if (unknown.length > 0) {\n return notOk(\n mapMigrationToolsError(\n errorUnknownInvariant({\n ...ifDefined('refName', toArg),\n unknown,\n declared: [...declared].sort(),\n }),\n ),\n );\n }\n }\n\n if (!flags.quiet && !flags.json) {\n ui.step('Loading contract spaces…');\n }\n\n // When `--to` resolves to an on-disk graph node with a matching bundle,\n // verify and apply against THAT bundle's destination contract via\n // `contractAt` — not the emitted `contract.json`. With `--to` omitted,\n // or a target with no matching bundle, the emitted contract stays the\n // apply contract (the only migrate-specific default). The same\n // `contractAt` artifacts feed the optional ref-advancement snapshot.\n let applyContract: Contract = contractRaw;\n let snapshotContractJson: Record<string, unknown> = JSON.parse(contractContent);\n let snapshotContractDts: string | undefined;\n if (toArg && refEntry) {\n const targetHash = refEntry.hash;\n const matchingBundle = aggregate.app.packages.find((p) => p.metadata.to === targetHash);\n if (matchingBundle) {\n try {\n const at = await aggregate.app.contractAt(\n targetHash,\n refName !== undefined ? { refName } : undefined,\n );\n applyContract = at.contract;\n snapshotContractJson = at.contractJson as Record<string, unknown>;\n snapshotContractDts = at.contractDts;\n } catch (error) {\n return mapContractAtError(error, { artifactRole: 'to' });\n }\n }\n }\n\n const applyResult = await client.migrate({\n contract: applyContract,\n migrationsDir,\n ...ifDefined('refHash', refEntry?.hash),\n ...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),\n ...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n let advancedRef: { name: string; hash: string } | null = null;\n if (options.advanceRef !== undefined) {\n try {\n const contractIR =\n snapshotContractDts !== undefined\n ? { contract: snapshotContractJson, contractDts: snapshotContractDts }\n : await readContractIR(snapshotContractJson, contractPathAbsolute);\n advancedRef = await executeRefAdvancement(\n refsDir,\n options.advanceRef,\n value.markerHash,\n contractIR,\n );\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: value.perSpace.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n perSpace: value.perSpace,\n ...ifDefined('pathDecision', value.pathDecision),\n timings: { total: Date.now() - startTime },\n advancedRef,\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migrate: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrateCommand(): Command {\n const command = new Command('migrate');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to advance the database',\n 'Walks every contract space (app + extensions) and applies pending\\n' +\n 'on-disk migrations in canonical order (extensions alphabetically,\\n' +\n 'then app). Graph-walks the on-disk migration graph for every space.\\n' +\n 'Use --to to target a specific contract (hash, ref name, migration dir).\\n' +\n 'Use --show for a read-only preview of the path that would run.',\n );\n setCommandExamples(command, [\n 'prisma-next migrate --db $DATABASE_URL',\n 'prisma-next migrate --to production --db $DATABASE_URL',\n 'prisma-next migrate --to sha256:abc123 --db $DATABASE_URL',\n 'prisma-next migrate --show --db $DATABASE_URL',\n 'prisma-next migrate --show --from @contract --to production',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option('--advance-ref <name>', 'Advance the named ref to the post-apply marker after success')\n .option('--show', 'Preview the migration path without applying (read-only)')\n .option(\n '--from <contract>',\n 'From-state for --show preview (@contract, @db, hash, ref name, or migration dir)',\n )\n .action(async (options: MigrateCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n if (options.show) {\n // Read-only path: compute the migration plan and print the ordered list.\n // NEVER reaches runMigration() or any write boundary.\n const result = await executeMigrateShowCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (showResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(showResult, null, 2));\n } else {\n // Print directly to stdout — not via ui.log() which injects Clack's │ gutter.\n ui.output(formatMigrateShowOutput(showResult, flags));\n }\n });\n\n process.exit(exitCode);\n return;\n }\n\n const result = await executeMigrateCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (migrateResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(migrateResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationApplyCommandOutput(migrateResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
{"version":3,"file":"migrate.mjs","names":[],"sources":["../../src/commands/migrate.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport {\n type AggregateContractSpace,\n requireHeadRef,\n} from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { findLatestMigration, isGraphNode } 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 { Command } from 'commander';\nimport { createControlClient } from '../control-api/client';\nimport { planSpacePath } from '../control-api/operations/migrate';\nimport type {\n MigrateFailure,\n MigratePathDecision,\n PerSpaceExecutionEntry,\n} from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorMarkerMismatch,\n errorPathUnreachable,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n collectDeclaredInvariants,\n maskConnectionUrl,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n targetSupportsMigrations,\n} from '../utils/command-helpers';\nimport { mapContractAtError } from '../utils/contract-at-errors';\nimport {\n buildReadAggregate,\n loadContractSpaceAggregateForCli,\n refuseContractSpaceIntegrity,\n} from '../utils/contract-space-aggregate-loader';\nimport { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';\nimport {\n computeLabelColumn,\n computeMaxDirNameWidth,\n renderMigrationGraphCommand,\n} from '../utils/formatters/migration-graph-command-render';\nimport { buildGrid } from '../utils/formatters/migration-graph-grid-layout';\nimport {\n formatOnPathMigrationRow,\n type MigrationEdgeAnnotation,\n} from '../utils/formatters/migration-graph-labels';\nimport { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';\nimport {\n highlightFromEdgeAnnotations,\n indentMigrationGraphTreeBlock,\n} from '../utils/formatters/migration-graph-space-render';\nimport { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { executeRefAdvancement, readContractIR } from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport { listRefsByContractHash } from './migration-list';\n\ninterface MigrateCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly to?: string;\n readonly advanceRef?: string;\n readonly show?: boolean;\n readonly from?: string;\n}\n\n/**\n * One migration that will run in a `migrate --show` preview, in execution order.\n */\nexport interface MigrateShowMigration {\n readonly spaceId: string;\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** Result returned by `migrate --show`. Read-only; no writes performed. */\nexport interface MigrateShowResult {\n readonly ok: true;\n readonly migrations: readonly MigrateShowMigration[];\n readonly summary: string;\n /**\n * Pre-rendered Tier-3 graph tree for human output. Off-path migrations render\n * dim; on-path migrations render in ordinary colours. Only present in human\n * (non-JSON) mode.\n */\n readonly graphOutput?: string;\n /**\n * Name column width for the \"Will run, in order:\" list — globally aligned with\n * every graph-tree section. Only present in human (non-JSON) mode.\n */\n readonly runListDirNameWidth?: number;\n /**\n * Left-pad offset (number of blank spaces) matching the graph's data-column\n * offset (`globalMaxEdgeTreePrefixWidth`). Used to align list rows with graph\n * rows so every `→` in the output (graph + list) lands at the same column.\n * Only present in human (non-JSON) mode when multiple spaces are rendered.\n */\n readonly runListLeftPad?: number;\n}\n\nexport interface MigrateResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly spaceId: string;\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly perSpace: readonly PerSpaceExecutionEntry[];\n readonly pathDecision?: MigratePathDecision;\n readonly timings: {\n readonly total: number;\n };\n readonly advancedRef?: { readonly name: string; readonly hash: string } | null;\n}\n\n/**\n * Read-only preview of the migration path `migrate` will take.\n *\n * Computes the path through the SAME seam as `executeMigrate`:\n * - `readAllMarkers()` for the from-state (when no `--from` is given), preserving\n * the full marker including `invariants` (not just `storageHash`).\n * - `planSpacePath()` (shared with `executeMigrate`) for per-space path selection,\n * which feeds `resolveRecordedPath()` with the same target hash, target invariants,\n * and current marker as the real apply path uses.\n *\n * Returns BEFORE any write boundary (`runMigration` / marker / DDL). No\n * DB state is mutated.\n */\nasync function executeMigrateShowCommand(\n options: MigrateCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrateShowResult, CliStructuredErrorType>> {\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 hasExplicitFrom = options.from !== undefined;\n\n // When --from is omitted we read the live DB marker (same as migrate's default).\n // When --from is given, we're in offline hypothetical mode — no connection needed.\n if (!hasExplicitFrom) {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: 'migrate --show needs a database connection to read the live marker (or pass --from <contract> for an offline preview)',\n retryCommand: 'prisma-next migrate --show --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 loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const { aggregate, contractHash } = loaded.value;\n const appGraph = aggregate.app.graph();\n\n // Resolve the --to target (defaults to the on-disk contract, same as migrate).\n // Also capture the ref's invariants so planSpacePath feeds resolveRecordedPath the\n // same target invariants that real migrate would use (refInvariants ?? headRef.invariants).\n let targetHash: string = contractHash;\n let refInvariants: readonly string[] | undefined;\n if (options.to) {\n const toResult = parseContractRef(options.to, {\n graph: appGraph,\n refs: allRefs,\n contractHash,\n });\n if (!toResult.ok) {\n return notOk(mapRefResolutionError(toResult.failure));\n }\n if (toResult.value.provenance.kind === 'reserved-db') {\n return notOk(\n errorDatabaseConnectionRequired({\n why: '@db is not valid as a --to target; it names the live database state, not a target contract.',\n commandName: 'migrate --show',\n }),\n );\n }\n targetHash = toResult.value.hash;\n if (toResult.value.provenance.kind === 'ref') {\n const refEntry = allRefs[toResult.value.provenance.refName];\n if (refEntry) refInvariants = refEntry.invariants;\n }\n }\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 && !hasExplicitFrom) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.to) {\n details.push({ label: 'to', value: options.to });\n }\n const header = formatStyledHeader({\n command: 'migrate --show',\n description: 'Preview the migration path migrate will take (read-only)',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Resolve the from-state.\n // - Explicit --from: parse it offline (no connection).\n // - Omitted: read the live DB marker via readAllMarkers() — the same source migrate uses.\n //\n // Full marker records (storageHash + invariants) are preserved so planSpacePath\n // can feed resolveRecordedPath the complete currentMarker — exactly as executeMigrate\n // does via familyInstance.readAllMarkers(). A stripped { storageHash, invariants: [] }\n // marker would produce a different `required` set and a different (incorrect) path.\n type LiveMarker = { readonly storageHash: string; readonly invariants: readonly string[] };\n const markerBySpace = new Map<string, LiveMarker | null>();\n const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];\n\n if (hasExplicitFrom) {\n // @db with explicit --from requires a connection\n if (options.from === '@db') {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: '@db resolves to the live database marker and requires a --db connection',\n retryCommand: 'prisma-next migrate --show --from @db --db $DATABASE_URL',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n // Fall through to the connection path below\n } else {\n const fromResult = parseContractRef(options.from, {\n graph: appGraph,\n refs: allRefs,\n contractHash,\n });\n if (!fromResult.ok) {\n return notOk(mapRefResolutionError(fromResult.failure));\n }\n if (fromResult.value.provenance.kind === 'reserved-db') {\n // Unreachable given the @db branch above, but guard for safety\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: '@db resolves to the live database marker and requires a --db connection',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n } else {\n // Offline hypothetical: the --from ref only carries a hash (no live invariants).\n // Apply the from-hash marker to the APP space only. Extension spaces are left\n // absent from markerBySpace (treated as null / greenfield by planSpacePath),\n // so they plan from their own marker → own head — exactly as executeMigrate does.\n const fromHash = fromResult.value.hash;\n const offlineMarker: LiveMarker | null =\n fromHash === EMPTY_CONTRACT_HASH ? null : { storageHash: fromHash, invariants: [] };\n markerBySpace.set(aggregate.app.spaceId, offlineMarker);\n }\n }\n }\n\n // If we need the live DB marker (no --from, or --from @db), connect and read.\n const needsLiveMarker = !hasExplicitFrom || options.from === '@db';\n if (needsLiveMarker) {\n if (!dbConnection || !hasDriver) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: 'A database connection is required to read the live marker for migrate --show',\n commandName: 'migrate --show',\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 try {\n await client.connect(dbConnection);\n const allMarkers = await client.readAllMarkers();\n // Store the full marker record (storageHash + invariants) per space.\n // This is the same data executeMigrate uses via familyInstance.readAllMarkers().\n for (const space of allSpaces) {\n const marker = allMarkers.get(space.spaceId);\n markerBySpace.set(space.spaceId, marker ?? null);\n }\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read live DB marker: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n }\n\n // Walk the path via planSpacePath — the same helper executeMigrate uses.\n // planSpacePath feeds resolveRecordedPath identical inputs (targetHash, targetInvariants,\n // currentMarker with full invariants), so the preview path is always the path migrate runs.\n //\n // Canonical schedule order: extensions alphabetically first, then app — mirroring the\n // runner's `applyOrder` in operations/migrate.ts so the \"Will run, in order:\" list\n // reflects the actual execution sequence (extensions install first, app last).\n const canonicalOrderSpaces: ReadonlyArray<AggregateContractSpace> = [\n ...aggregate.extensions,\n aggregate.app,\n ];\n const orderedMigrations: MigrateShowMigration[] = [];\n for (const space of canonicalOrderSpaces) {\n const isAppSpace = space.spaceId === aggregate.app.spaceId;\n const headRef = requireHeadRef(space);\n const spaceTargetHash = isAppSpace ? targetHash : headRef.hash;\n const spaceRefInvariants = isAppSpace ? refInvariants : undefined;\n const liveMarker = markerBySpace.get(space.spaceId) ?? null;\n\n const outcome = planSpacePath({\n space,\n aggregate,\n targetHash: spaceTargetHash,\n refInvariants: spaceRefInvariants,\n liveMarker,\n });\n\n if (outcome.kind === 'at-head') {\n // Empty-graph space already at target — nothing to run for this space.\n continue;\n }\n if (outcome.kind === 'never-planned') {\n return notOk(\n errorPathUnreachable({\n code: 'MIGRATION_PATH_NOT_FOUND',\n summary: `No on-disk migrations for contract space \"${outcome.spaceId}\"`,\n why: `migrate is replay-only: space \"${outcome.spaceId}\" has no on-disk migrations but its head ref targets \"${outcome.targetHash}\".`,\n meta: { spaceId: outcome.spaceId, target: outcome.targetHash, kind: 'neverPlanned' },\n }),\n );\n }\n if (outcome.kind === 'unreachable') {\n const fromHash = outcome.liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n return notOk(\n errorPathUnreachable({\n code: 'MIGRATION_PATH_NOT_FOUND',\n summary: `No migration path from ${fromHash.slice(0, 14)} to ${outcome.targetHash.slice(0, 14)} in space \"${outcome.spaceId}\".`,\n why: `The migration graph has no path from the from-state to the target in space \"${outcome.spaceId}\".`,\n meta: { spaceId: outcome.spaceId, from: fromHash, to: outcome.targetHash },\n }),\n );\n }\n if (outcome.kind === 'unsatisfiable') {\n return notOk(\n errorRuntime(`Missing required invariants for space \"${outcome.spaceId}\"`, {\n why: `The path requires invariants not available on disk: ${outcome.missing.join(', ')}`,\n }),\n );\n }\n\n for (const edge of outcome.plan.migrationEdges) {\n orderedMigrations.push({\n spaceId: space.spaceId,\n dirName: edge.dirName,\n migrationHash: edge.migrationHash,\n from: edge.from,\n to: edge.to,\n });\n }\n }\n\n const count = orderedMigrations.length;\n const summary =\n count === 0\n ? 'Already up to date — nothing to run'\n : `${count} migration${count === 1 ? '' : 's'} will run`;\n\n // Build the Tier-3 graph visualization (human mode only; skipped for --json).\n // Reuses the existing annotation hook — no parallel renderer.\n let graphOutput: string | undefined;\n let runListDirNameWidth: number | undefined;\n let runListLeftPad: number | undefined;\n if (!flags.json) {\n const onPathHashes = new Set(orderedMigrations.map((m) => m.migrationHash));\n const colorize = flags.color !== false;\n\n // Build layouts for all spaces first so we can compute global column widths\n // before rendering. This ensures the name column, hash column, and ops column\n // start at the same horizontal offset across every space section AND the\n // \"Will run, in order:\" list below.\n const spaceLayouts = allSpaces.map((space) => {\n const isApp = space.spaceId === aggregate.app.spaceId;\n const spaceGraph = space.graph();\n const rowModel = buildMigrationGraphRows(spaceGraph, isApp ? { contractHash } : {});\n const edgeAnnotations = new Map<string, MigrationEdgeAnnotation>();\n for (const edge of spaceGraph.migrationByHash.values()) {\n edgeAnnotations.set(edge.migrationHash, {\n pathHighlight: onPathHashes.has(edge.migrationHash) ? 'on-path' : 'off-path',\n });\n }\n // The on-path migration set lifts to focus mode so the chosen route draws\n // green/continuous; off-path lanes dim. Rows, gutter, and labels all come\n // from this one grid.\n const grid = buildGrid(rowModel, {}, highlightFromEdgeAnnotations(edgeAnnotations));\n return { space, isApp, spaceGraph, rowModel, grid, edgeAnnotations };\n });\n\n // Global max across all space grids so every section's labels share columns.\n const globalLabelColumn =\n spaceLayouts.length > 1\n ? Math.max(...spaceLayouts.map(({ grid }) => computeLabelColumn(grid, 'unicode')))\n : undefined;\n const globalMaxDirNameWidthFromLayouts =\n spaceLayouts.length > 1\n ? Math.max(...spaceLayouts.map(({ rowModel }) => computeMaxDirNameWidth(rowModel)))\n : undefined;\n // The run-list name column width must be at least as wide as the global tree dirName\n // width so that tree sections and the list align at the hash column.\n const runListMaxFromMigrations =\n orderedMigrations.length > 0\n ? Math.max(...orderedMigrations.map((m) => m.dirName.length))\n : 0;\n const globalMaxDirNameWidth =\n globalMaxDirNameWidthFromLayouts !== undefined\n ? Math.max(globalMaxDirNameWidthFromLayouts, runListMaxFromMigrations)\n : undefined;\n runListDirNameWidth = globalMaxDirNameWidth ?? runListMaxFromMigrations;\n runListLeftPad = globalLabelColumn;\n\n // Render each space section with globally computed widths.\n const showSpaceHeadings = allSpaces.length > 1;\n const sections: string[] = [];\n for (const { space, isApp, rowModel, grid, edgeAnnotations } of spaceLayouts) {\n const liveMarker = markerBySpace.get(space.spaceId) ?? null;\n const liveMarkerHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n const tree = renderMigrationGraphCommand({\n grid,\n rowModel,\n contractHash,\n isAppSpace: isApp,\n ...(needsLiveMarker ? { dbHash: liveMarkerHash } : {}),\n refsByHash: listRefsByContractHash(space),\n edgeAnnotationsByHash: edgeAnnotations,\n colorize,\n glyphMode: 'unicode',\n ...(globalLabelColumn !== undefined ? { globalLabelColumn } : {}),\n ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),\n });\n if (tree.length === 0) continue;\n if (showSpaceHeadings) {\n sections.push(`${space.spaceId}:\\n${indentMigrationGraphTreeBlock(tree, ' ')}`);\n } else {\n sections.push(tree);\n }\n }\n graphOutput = sections.join('\\n\\n');\n }\n\n return ok({\n ok: true,\n migrations: orderedMigrations,\n summary,\n ...(graphOutput !== undefined ? { graphOutput } : {}),\n ...(runListDirNameWidth !== undefined ? { runListDirNameWidth } : {}),\n ...(runListLeftPad !== undefined ? { runListLeftPad } : {}),\n });\n}\n\nfunction formatMigrateShowOutput(result: MigrateShowResult, flags: GlobalFlags): string {\n if (flags.quiet) return '';\n const colorize = flags.color !== false;\n const lines: string[] = [];\n // Graph tree first (shows the full topology with on-path highlighted).\n if (result.graphOutput !== undefined && result.graphOutput.length > 0) {\n lines.push(result.graphOutput);\n lines.push('');\n }\n const n = result.migrations.length;\n if (n > 0) {\n // Consolidated header: one line replaces the old separate summary + blank +\n // \"Will run, in order:\" header.\n lines.push(`The following ${n} migration${n === 1 ? '' : 's'} will run:`);\n // Ordered list rendered through the SAME on-path row renderer as the tree.\n // `formatOnPathMigrationRow` uses PATH_HIGHLIGHT_STYLES.onPath so the list and\n // graph-tree rows are styled identically — changing the on-path colour in future\n // is a one-line edit in PATH_HIGHLIGHT_STYLES.\n //\n // Alignment anchor: the `→` arrow (source-hash onward) must land at the SAME\n // absolute column as in graph edge rows, across every graph section and this list.\n //\n // Multi-space output layout (space headings + 2-space indented tree sections):\n // Graph edge row: [2 heading][G gutter][D dirName][7 source] [→] [dest]\n // List row: [2 spaces][L dirName][ ][7 source] [→] [dest]\n // Alignment: 2 + G + D + 9 = 2 + L + 2 + 9 => L = G + D - 2\n //\n // Single-space output layout (flat tree, no heading indent):\n // Graph edge row: [G gutter][D dirName][7 source] [→] [dest]\n // List row: [2 spaces][L dirName][ ][7 source] [→] [dest]\n // Alignment: G + D + 9 = 2 + L + 2 + 9 => L = G + D - 4\n //\n // D (edgeDirNameWidth) = max(rawDirNameWidth + LABEL_GAP, MIN_HASH_DATA_COLUMN - G)\n // where LABEL_GAP = 2 and MIN_HASH_DATA_COLUMN = 25 (same constants as the renderer).\n //\n // runListLeftPad is set only for multi-space; undefined means single-space.\n const isMultiSpace = result.runListLeftPad !== undefined;\n const gutter = result.runListLeftPad ?? 0;\n const rawDirNameWidth =\n result.runListDirNameWidth ?? Math.max(...result.migrations.map((m) => m.dirName.length));\n const edgeDirNameWidth = Math.max(rawDirNameWidth + 2, 25 - gutter);\n const listDirNameWidth = gutter + edgeDirNameWidth - (isMultiSpace ? 2 : 4);\n for (const m of result.migrations) {\n lines.push(\n ` ${formatOnPathMigrationRow(m.dirName, m.from, m.to, listDirNameWidth, colorize, 'unicode')}`,\n );\n }\n } else {\n lines.push(result.summary);\n }\n return lines.join('\\n');\n}\n\nfunction mapApplyFailure(failure: MigrateFailure): CliStructuredErrorType {\n if (failure.code === 'MIGRATION_PATH_NOT_FOUND') {\n return errorPathUnreachable(failure);\n }\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nasync function executeMigrateCommand(\n options: MigrateCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrateResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(\n options.config,\n config,\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 migrate (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migrate',\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: 'Config.driver is required for migrate',\n }),\n );\n }\n\n if (!targetSupportsMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n const toArg = options.to;\n\n // Construct the family instance up-front so the on-disk contract read\n // crosses the serializer seam (`familyInstance.deserializeContract`) at\n // the read site. The downstream `client.migrate({ contract })`\n // re-validates internally (no harm — validation is idempotent), but\n // closing the gap at the read site is what makes the cast-pattern\n // lint enforceable and matches the other CLI commands. See TML-2536.\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n\n const contractPathAbsolute = resolveContractPath(config);\n let contractRaw: Contract;\n let contractContent: string;\n try {\n contractContent = 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 a valid contract.json, then retry.',\n }),\n );\n }\n return notOk(\n errorContractValidationFailed(\n `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n try {\n contractRaw = familyInstance.deserializeContract(JSON.parse(contractContent) as unknown);\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract at ${contractPathAbsolute} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n const loadedAggregate = await loadContractSpaceAggregateForCli({\n targetId: config.target.targetId,\n migrationsDir,\n appContract: contractRaw,\n extensionPacks: config.extensionPacks ?? [],\n deserializeContract: (json) => familyInstance.deserializeContract(json),\n });\n if (!loadedAggregate.ok) {\n return notOk(loadedAggregate.failure);\n }\n const aggregate = loadedAggregate.value;\n const integrityFailure = refuseContractSpaceIntegrity(aggregate, {\n declaredExtensions: toDeclaredExtensionsFromRaw(\n (config.extensionPacks ?? []) as ReadonlyArray<unknown>,\n ),\n checkContracts: true,\n });\n if (integrityFailure) {\n return notOk(integrityFailure);\n }\n\n let refEntry: RefEntry | undefined;\n let refName: string | undefined;\n if (toArg) {\n const refs = aggregate.app.refs;\n const refResult = parseContractRef(toArg, { graph: aggregate.app.graph(), refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n if (refResult.value.provenance.kind === 'ref') {\n refName = refResult.value.provenance.refName;\n const resolved = refs[refName];\n if (resolved) refEntry = resolved;\n } else {\n refEntry = { hash: refResult.value.hash, invariants: [] };\n }\n }\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 (typeof dbConnection === 'string') {\n details.push({\n label: 'database',\n value: maskConnectionUrl(dbConnection),\n });\n }\n if (toArg) {\n details.push({ label: 'to', value: toArg });\n }\n const header = formatStyledHeader({\n command: 'migrate',\n description: 'Apply planned migrations to advance the database',\n url: 'https://pris.ly/migrate',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n const appGraph = aggregate.app.graph();\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\n try {\n await client.connect(dbConnection);\n\n const allMarkers = await client.readAllMarkers();\n const appMarker = allMarkers.get('app') ?? null;\n\n if (appMarker !== null && !isGraphNode(appMarker.storageHash, appGraph)) {\n return notOk(\n errorMarkerMismatch(\n appMarker.storageHash,\n [...appGraph.nodes].sort(),\n findLatestMigration(appGraph)?.to ?? null,\n ),\n );\n }\n\n if (refEntry && refEntry.invariants.length > 0) {\n const declared = collectDeclaredInvariants(appGraph);\n const known = new Set<string>(declared);\n for (const id of appMarker?.invariants ?? []) known.add(id);\n const unknown = refEntry.invariants.filter((id) => !known.has(id));\n if (unknown.length > 0) {\n return notOk(\n mapMigrationToolsError(\n errorUnknownInvariant({\n ...ifDefined('refName', toArg),\n unknown,\n declared: [...declared].sort(),\n }),\n ),\n );\n }\n }\n\n if (!flags.quiet && !flags.json) {\n ui.step('Loading contract spaces…');\n }\n\n // When `--to` resolves to an on-disk graph node with a matching bundle,\n // verify and apply against THAT bundle's destination contract via\n // `contractAt` — not the emitted `contract.json`. With `--to` omitted,\n // or a target with no matching bundle, the emitted contract stays the\n // apply contract (the only migrate-specific default). The same\n // `contractAt` artifacts feed the optional ref-advancement snapshot.\n let applyContract: Contract = contractRaw;\n let snapshotContractJson: Record<string, unknown> = JSON.parse(contractContent);\n let snapshotContractDts: string | undefined;\n if (toArg && refEntry) {\n const targetHash = refEntry.hash;\n const matchingBundle = aggregate.app.packages.find((p) => p.metadata.to === targetHash);\n if (matchingBundle) {\n try {\n const at = await aggregate.app.contractAt(\n targetHash,\n refName !== undefined ? { refName } : undefined,\n );\n applyContract = at.contract;\n snapshotContractJson = at.contractJson as Record<string, unknown>;\n snapshotContractDts = at.contractDts;\n } catch (error) {\n return mapContractAtError(error, { artifactRole: 'to' });\n }\n }\n }\n\n const applyResult = await client.migrate({\n contract: applyContract,\n migrationsDir,\n ...ifDefined('refHash', refEntry?.hash),\n ...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),\n ...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n let advancedRef: { name: string; hash: string } | null = null;\n if (options.advanceRef !== undefined) {\n try {\n const contractIR =\n snapshotContractDts !== undefined\n ? { contract: snapshotContractJson, contractDts: snapshotContractDts }\n : await readContractIR(snapshotContractJson, contractPathAbsolute);\n advancedRef = await executeRefAdvancement(\n refsDir,\n migrationsDir,\n options.advanceRef,\n value.markerHash,\n contractIR,\n );\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n }\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: value.perSpace.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n perSpace: value.perSpace,\n ...ifDefined('pathDecision', value.pathDecision),\n timings: { total: Date.now() - startTime },\n advancedRef,\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migrate: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrateCommand(): Command {\n const command = new Command('migrate');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to advance the database',\n 'Walks every contract space (app + extensions) and applies pending\\n' +\n 'on-disk migrations in canonical order (extensions alphabetically,\\n' +\n 'then app). Graph-walks the on-disk migration graph for every space.\\n' +\n 'Use --to to target a specific contract (hash, ref name, migration dir).\\n' +\n 'Use --show for a read-only preview of the path that would run.',\n );\n setCommandExamples(command, [\n 'prisma-next migrate --db $DATABASE_URL',\n 'prisma-next migrate --to production --db $DATABASE_URL',\n 'prisma-next migrate --to sha256:abc123 --db $DATABASE_URL',\n 'prisma-next migrate --show --db $DATABASE_URL',\n 'prisma-next migrate --show --from @contract --to production',\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option('--advance-ref <name>', 'Advance the named ref to the post-apply marker after success')\n .option('--show', 'Preview the migration path without applying (read-only)')\n .option(\n '--from <contract>',\n 'From-state for --show preview (@contract, @db, hash, ref name, or migration dir)',\n )\n .action(async (options: MigrateCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const startTime = Date.now();\n\n const ui = createTerminalUI(flags);\n\n if (options.show) {\n // Read-only path: compute the migration plan and print the ordered list.\n // NEVER reaches runMigration() or any write boundary.\n const result = await executeMigrateShowCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (showResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(showResult, null, 2));\n } else {\n // Print directly to stdout — not via ui.log() which injects Clack's │ gutter.\n ui.output(formatMigrateShowOutput(showResult, flags));\n }\n });\n\n process.exit(exitCode);\n return;\n }\n\n const result = await executeMigrateCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (migrateResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(migrateResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationApplyCommandOutput(migrateResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}

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

import { n as enumerateCheckSpaces, r as runMigrationCheck, t as createMigrationCheckCommand } from "../migration-check-CcuayFB2.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,5 +0,5 @@

import { A as formatStyledHeader, _ as createTerminalUI, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-Cpq44aVa.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { A as formatStyledHeader, _ as createTerminalUI, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { n as buildReadAggregate } 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-6z6vuXHa.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";

@@ -6,0 +6,0 @@ import { loadConfig } from "@prisma-next/config-loader";

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

import { a as renderMigrationListHumanOutput, i as migrationSpaceListEntriesFromAggregate, n as executeMigrationListCommand, o as runMigrationList, r as listRefsByContractHash, t as createMigrationListCommand } from "../migration-list-6z6vuXHa.mjs";
import { a as renderMigrationListHumanOutput, i as migrationSpaceListEntriesFromAggregate, n as executeMigrationListCommand, o as runMigrationList, r as listRefsByContractHash, t as createMigrationListCommand } from "../migration-list-cnSKilYH.mjs";
export { createMigrationListCommand, executeMigrationListCommand, listRefsByContractHash, migrationSpaceListEntriesFromAggregate, renderMigrationListHumanOutput, runMigrationList };

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

import { n as executeMigrationLogCommand, t as createMigrationLogCommand } from "../migration-log-C_xam2HZ.mjs";
import { n as executeMigrationLogCommand, t as createMigrationLogCommand } from "../migration-log-CEqFiez9.mjs";
export { createMigrationLogCommand, executeMigrationLogCommand };

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

import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, o as resolveContractPath, ot as errorTargetMigrationNotSupported, r as getTargetMigrations, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-Cpq44aVa.mjs";
import { t as assertFrameworkComponentsCompatible } from "../framework-components-CnbUbiJw.mjs";
import { o as refusePackageCorruptionOnAggregate } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, o as resolveContractPath, ot as errorTargetMigrationNotSupported, r as getTargetMigrations, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { t as assertFrameworkComponentsCompatible } from "../framework-components-VMQJbAwl.mjs";
import { o as refusePackageCorruptionOnAggregate } from "../contract-space-aggregate-loader-hPVymNLw.mjs";
import { Command } from "commander";

@@ -5,0 +5,0 @@ import { loadConfig } from "@prisma-next/config-loader";

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

import { n as formatMigrationPlanOutput, r as resolveBundleByPrefix, t as createMigrationPlanCommand } from "../migration-plan-DuMEUejG.mjs";
import { n as formatMigrationPlanOutput, r as resolveBundleByPrefix, t as createMigrationPlanCommand } from "../migration-plan-BJHulFZC.mjs";
export { createMigrationPlanCommand, formatMigrationPlanOutput, resolveBundleByPrefix };

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

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-Cpq44aVa.mjs";
import { t as createControlClient } from "../client-KuBQftxz.mjs";
import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "../migration-path-target-DpPTsRCg.mjs";
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 { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "../migration-path-target-C0TQuV5n.mjs";
import { Command } from "commander";

@@ -5,0 +5,0 @@ import { loadConfig } from "@prisma-next/config-loader";

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-B61YOaMl.mjs";
import { a as formatStatusHumanOutput, i as executeMigrationStatusCommand, n as buildStatusHeadline, o as formatStatusSummary, r as createMigrationStatusCommand, t as buildNoPathSummary } from "../migration-status-Df4zK5lO.mjs";
export { buildNoPathSummary, buildStatusHeadline, createMigrationStatusCommand, executeMigrationStatusCommand, formatStatusHumanOutput, formatStatusSummary, migrationStatusJsonResultSchema };

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

{"version":3,"file":"ref.d.mts","names":[],"sources":["../../src/commands/ref.ts"],"mappings":";;;;;UA2CU;WACC;WACA;WACA;WACA;;UAGD;WACC;WACA;WACA;;UAGD;WACC;WACA,MAAM,eAAe;;iBAiBV,qBACpB,cACA,uBACA;EAAW;IACV,QAAQ,OAAO,cAAc;iBA2EV,wBACpB,cACA;EAAW;IACV,QAAQ,OAAO,iBAAiB;iBAYb,sBAAsB;EAC1C;IACE,QAAQ,OAAO,eAAe;iBAyGlB,oBAAoB"}
{"version":3,"file":"ref.d.mts","names":[],"sources":["../../src/commands/ref.ts"],"mappings":";;;;;UA0CU;WACC;WACA;WACA;WACA;;UAGD;WACC;WACA;WACA;;UAGD;WACC;WACA,MAAM,eAAe;;iBAiBV,qBACpB,cACA,uBACA;EAAW;IACV,QAAQ,OAAO,cAAc;iBAsEV,wBACpB,cACA;EAAW;IACV,QAAQ,OAAO,iBAAiB;iBAYb,sBAAsB;EAC1C;IACE,QAAQ,OAAO,eAAe;iBAyGlB,oBAAoB"}

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

import { $ as errorRefSetBundleNotFound, F as CliStructuredError, O as formatCommandHelp, W as errorFileNotFound, _ as createTerminalUI, ct as errorUnexpected, et as errorRefSetEmptySentinel, g as parseGlobalFlagsOrExit, h as parseGlobalFlags, l as setCommandDescriptions, lt as mapMigrationToolsError, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, tt as errorRefSetHashNotInGraph, ut as mapRefResolutionError, y as handleResult } from "../command-helpers-Cpq44aVa.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-D_3VeHVb.mjs";
import { i as readContractIR } from "../ref-advancement-BkXlikCA.mjs";
import { $ as errorRefSetBundleNotFound, F as CliStructuredError, O as formatCommandHelp, W as errorFileNotFound, _ as createTerminalUI, ct as errorUnexpected, et as errorRefSetEmptySentinel, g as parseGlobalFlagsOrExit, h as parseGlobalFlags, l as setCommandDescriptions, lt as mapMigrationToolsError, rt as errorRuntime, s as resolveMigrationPaths, t as addGlobalOptions, tt as errorRefSetHashNotInGraph, ut as mapRefResolutionError, y as handleResult } from "../command-helpers-CVn3U9Uz.mjs";
import { n as buildReadAggregate } from "../contract-space-aggregate-loader-hPVymNLw.mjs";
import { Command } from "commander";

@@ -11,4 +10,4 @@ import { loadConfig } from "@prisma-next/config-loader";

import { findLatestMigration, isGraphNode } from "@prisma-next/migration-tools/migration-graph";
import { deleteRefPaired, readRefs, validateRefName, validateRefValue, writeRefPaired } from "@prisma-next/migration-tools/refs";
import { contractSnapshotDir, readContractSnapshotJson } from "@prisma-next/migration-tools/contract-snapshot-store";
import { deleteRef, readRefs, validateRefName, validateRefValue, writeRef } from "@prisma-next/migration-tools/refs";
import { parseContractRef } from "@prisma-next/migration-tools/ref-resolution";

@@ -53,5 +52,4 @@ //#region src/commands/ref.ts

const contractJsonPath = join(contractSnapshotDir(migrationsDir, resolvedHash), "contract.json");
let contractJson;
try {
contractJson = await readContractSnapshotJson(migrationsDir, resolvedHash);
await readContractSnapshotJson(migrationsDir, resolvedHash);
} catch (readError) {

@@ -64,7 +62,6 @@ if (MigrationToolsError.is(readError) && readError.code === "MIGRATION.CONTRACT_SNAPSHOT_MISSING") return notOk(errorFileNotFound(contractJsonPath, {

}
const contractIR = await readContractIR(contractJson, contractJsonPath);
await writeRefPaired(refsDir, name, {
await writeRef(refsDir, name, {
hash: resolvedHash,
invariants: []
}, contractIR);
});
return ok({

@@ -85,3 +82,3 @@ ok: true,

const { refsDir } = resolveMigrationPaths(options.config, config);
await deleteRefPaired(refsDir, name);
await deleteRef(refsDir, name);
return ok({

@@ -88,0 +85,0 @@ ok: true,

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

{"version":3,"file":"ref.mjs","names":[],"sources":["../../src/commands/ref.ts"],"sourcesContent":["import { loadConfig } from '@prisma-next/config-loader';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport type { RefEntry } from '@prisma-next/migration-tools/refs';\nimport {\n deleteRefPaired,\n readRefs,\n validateRefName,\n validateRefValue,\n writeRefPaired,\n} from '@prisma-next/migration-tools/refs';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join } from 'pathe';\nimport {\n CliStructuredError,\n errorFileNotFound,\n errorRefSetBundleNotFound,\n errorRefSetEmptySentinel,\n errorRefSetHashNotInGraph,\n errorRuntime,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveMigrationPaths,\n setCommandDescriptions,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { formatCommandHelp } from '../utils/formatters/help';\nimport { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { readContractIR } from '../utils/ref-advancement';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI } from '../utils/terminal-ui';\n\ninterface RefSetResult {\n readonly ok: true;\n readonly ref: string;\n readonly hash: string;\n readonly invariants: readonly string[];\n}\n\ninterface RefDeleteResult {\n readonly ok: true;\n readonly ref: string;\n readonly deleted: true;\n}\n\ninterface RefListResult {\n readonly ok: true;\n readonly refs: Record<string, RefEntry>;\n}\n\nfunction mapError(error: unknown): CliStructuredError {\n if (MigrationToolsError.is(error)) {\n return mapMigrationToolsError(error);\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error));\n}\n\nfunction cliErrorInvalidRefName(name: string): CliStructuredError {\n return errorRuntime(`Invalid ref name \"${name}\"`, {\n why: `Ref name \"${name}\" does not match the required format`,\n fix: 'Ref names must be lowercase alphanumeric with hyphens or forward slashes, no `.` or `..` segments',\n });\n}\n\nexport async function executeRefSetCommand(\n name: string,\n contractInput: string,\n options: { config?: string },\n): Promise<Result<RefSetResult, CliStructuredError>> {\n if (!validateRefName(name)) {\n return notOk(cliErrorInvalidRefName(name));\n }\n\n try {\n const config = await loadConfig(options.config);\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const graph = loaded.value.aggregate.app.graph();\n const bundles = loaded.value.aggregate.app.packages;\n const refs = loaded.value.aggregate.app.refs;\n\n let resolvedHash: string;\n if (validateRefValue(contractInput)) {\n resolvedHash = contractInput;\n } else {\n const refResult = parseContractRef(contractInput, { graph, refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n resolvedHash = refResult.value.hash;\n }\n\n if (resolvedHash === EMPTY_CONTRACT_HASH) {\n return notOk(errorRefSetEmptySentinel(resolvedHash));\n }\n if (!isGraphNode(resolvedHash, graph)) {\n const graphTip = findLatestMigration(graph)?.to ?? null;\n return notOk(errorRefSetHashNotInGraph(resolvedHash, [...graph.nodes].sort(), graphTip));\n }\n\n const matchingBundle = bundles.find((bundle) => bundle.metadata.to === resolvedHash);\n if (!matchingBundle) {\n return notOk(errorRefSetBundleNotFound(resolvedHash));\n }\n\n const contractJsonPath = join(\n contractSnapshotDir(migrationsDir, resolvedHash),\n 'contract.json',\n );\n let contractJson: Record<string, unknown>;\n try {\n contractJson = (await readContractSnapshotJson(migrationsDir, resolvedHash)) as Record<\n string,\n unknown\n >;\n } catch (readError) {\n if (\n MigrationToolsError.is(readError) &&\n readError.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING'\n ) {\n return notOk(\n errorFileNotFound(contractJsonPath, {\n why: `Migration bundle for hash ${resolvedHash} is missing its contract snapshot at ${contractJsonPath}`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n }),\n );\n }\n throw readError;\n }\n\n const contractIR = await readContractIR(contractJson, contractJsonPath);\n const entry: RefEntry = { hash: resolvedHash, invariants: [] };\n await writeRefPaired(refsDir, name, entry, contractIR);\n return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nexport async function executeRefDeleteCommand(\n name: string,\n options: { config?: string },\n): Promise<Result<RefDeleteResult, CliStructuredError>> {\n try {\n const config = await loadConfig(options.config);\n const { refsDir } = resolveMigrationPaths(options.config, config);\n await deleteRefPaired(refsDir, name);\n return ok({ ok: true as const, ref: name, deleted: true as const });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nexport async function executeRefListCommand(options: {\n config?: string;\n}): Promise<Result<RefListResult, CliStructuredError>> {\n try {\n const config = await loadConfig(options.config);\n const { refsDir } = resolveMigrationPaths(options.config, config);\n const refs = await readRefs(refsDir);\n return ok({ ok: true as const, refs });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nfunction createRefSetCommand(): Command {\n const command = new Command('set');\n setCommandDescriptions(\n command,\n 'Set a ref to a contract reference',\n 'Sets a named ref to point to a resolved contract reference (hash, alias, or path) in migrations/refs/.',\n );\n addGlobalOptions(command)\n .argument('<name>', 'Ref name (e.g., staging, production)')\n .argument(\n '<contract>',\n 'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(\n async (\n name: string,\n hash: string,\n options: { config?: string; json?: string | boolean; quiet?: boolean },\n ) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefSetCommand(name, hash, options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n ui.output(`Set ref \"${value.ref}\" → ${value.hash}`);\n }\n });\n process.exit(exitCode);\n },\n );\n return command;\n}\n\nfunction createRefDeleteCommand(): Command {\n const command = new Command('delete');\n setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');\n addGlobalOptions(command)\n .argument('<name>', 'Ref name to delete')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(\n async (\n name: string,\n options: { config?: string; json?: string | boolean; quiet?: boolean },\n ) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefDeleteCommand(name, options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n ui.output(`Deleted ref \"${value.ref}\"`);\n }\n });\n process.exit(exitCode);\n },\n );\n return command;\n}\n\nfunction createRefListCommand(): Command {\n const command = new Command('list');\n setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefListCommand(options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n const entries = Object.entries(value.refs);\n if (entries.length === 0) {\n ui.output('No refs defined');\n } else {\n for (const [refName, entry] of entries) {\n const invariantsSuffix =\n entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';\n ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);\n }\n }\n }\n });\n process.exit(exitCode);\n });\n return command;\n}\n\nexport function createRefCommand(): Command {\n const command = new Command('ref');\n setCommandDescriptions(\n command,\n 'Manage contract refs',\n 'Manage named refs in migrations/refs/. Refs map logical environment\\n' +\n 'names (e.g., staging, production) to contract hashes.',\n );\n addGlobalOptions(command).configureHelp({\n formatHelp: (cmd) => formatCommandHelp({ command: cmd, flags: parseGlobalFlags({}) }),\n subcommandDescription: () => '',\n });\n command.addCommand(createRefSetCommand());\n command.addCommand(createRefDeleteCommand());\n command.addCommand(createRefListCommand());\n return command;\n}\n"],"mappings":"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"}
{"version":3,"file":"ref.mjs","names":[],"sources":["../../src/commands/ref.ts"],"sourcesContent":["import { loadConfig } from '@prisma-next/config-loader';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n contractSnapshotDir,\n readContractSnapshotJson,\n} from '@prisma-next/migration-tools/contract-snapshot-store';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport type { RefEntry } from '@prisma-next/migration-tools/refs';\nimport {\n deleteRef,\n readRefs,\n validateRefName,\n validateRefValue,\n writeRef,\n} from '@prisma-next/migration-tools/refs';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join } from 'pathe';\nimport {\n CliStructuredError,\n errorFileNotFound,\n errorRefSetBundleNotFound,\n errorRefSetEmptySentinel,\n errorRefSetHashNotInGraph,\n errorRuntime,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveMigrationPaths,\n setCommandDescriptions,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { formatCommandHelp } from '../utils/formatters/help';\nimport { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI } from '../utils/terminal-ui';\n\ninterface RefSetResult {\n readonly ok: true;\n readonly ref: string;\n readonly hash: string;\n readonly invariants: readonly string[];\n}\n\ninterface RefDeleteResult {\n readonly ok: true;\n readonly ref: string;\n readonly deleted: true;\n}\n\ninterface RefListResult {\n readonly ok: true;\n readonly refs: Record<string, RefEntry>;\n}\n\nfunction mapError(error: unknown): CliStructuredError {\n if (MigrationToolsError.is(error)) {\n return mapMigrationToolsError(error);\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error));\n}\n\nfunction cliErrorInvalidRefName(name: string): CliStructuredError {\n return errorRuntime(`Invalid ref name \"${name}\"`, {\n why: `Ref name \"${name}\" does not match the required format`,\n fix: 'Ref names must be lowercase alphanumeric with hyphens or forward slashes, no `.` or `..` segments',\n });\n}\n\nexport async function executeRefSetCommand(\n name: string,\n contractInput: string,\n options: { config?: string },\n): Promise<Result<RefSetResult, CliStructuredError>> {\n if (!validateRefName(name)) {\n return notOk(cliErrorInvalidRefName(name));\n }\n\n try {\n const config = await loadConfig(options.config);\n const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n const graph = loaded.value.aggregate.app.graph();\n const bundles = loaded.value.aggregate.app.packages;\n const refs = loaded.value.aggregate.app.refs;\n\n let resolvedHash: string;\n if (validateRefValue(contractInput)) {\n resolvedHash = contractInput;\n } else {\n const refResult = parseContractRef(contractInput, { graph, refs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n resolvedHash = refResult.value.hash;\n }\n\n if (resolvedHash === EMPTY_CONTRACT_HASH) {\n return notOk(errorRefSetEmptySentinel(resolvedHash));\n }\n if (!isGraphNode(resolvedHash, graph)) {\n const graphTip = findLatestMigration(graph)?.to ?? null;\n return notOk(errorRefSetHashNotInGraph(resolvedHash, [...graph.nodes].sort(), graphTip));\n }\n\n const matchingBundle = bundles.find((bundle) => bundle.metadata.to === resolvedHash);\n if (!matchingBundle) {\n return notOk(errorRefSetBundleNotFound(resolvedHash));\n }\n\n const contractJsonPath = join(\n contractSnapshotDir(migrationsDir, resolvedHash),\n 'contract.json',\n );\n try {\n await readContractSnapshotJson(migrationsDir, resolvedHash);\n } catch (readError) {\n if (\n MigrationToolsError.is(readError) &&\n readError.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING'\n ) {\n return notOk(\n errorFileNotFound(contractJsonPath, {\n why: `Migration bundle for hash ${resolvedHash} is missing its contract snapshot at ${contractJsonPath}`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n }),\n );\n }\n throw readError;\n }\n\n const entry: RefEntry = { hash: resolvedHash, invariants: [] };\n await writeRef(refsDir, name, entry);\n return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nexport async function executeRefDeleteCommand(\n name: string,\n options: { config?: string },\n): Promise<Result<RefDeleteResult, CliStructuredError>> {\n try {\n const config = await loadConfig(options.config);\n const { refsDir } = resolveMigrationPaths(options.config, config);\n await deleteRef(refsDir, name);\n return ok({ ok: true as const, ref: name, deleted: true as const });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nexport async function executeRefListCommand(options: {\n config?: string;\n}): Promise<Result<RefListResult, CliStructuredError>> {\n try {\n const config = await loadConfig(options.config);\n const { refsDir } = resolveMigrationPaths(options.config, config);\n const refs = await readRefs(refsDir);\n return ok({ ok: true as const, refs });\n } catch (error) {\n if (error instanceof CliStructuredError) return notOk(error);\n return notOk(mapError(error));\n }\n}\n\nfunction createRefSetCommand(): Command {\n const command = new Command('set');\n setCommandDescriptions(\n command,\n 'Set a ref to a contract reference',\n 'Sets a named ref to point to a resolved contract reference (hash, alias, or path) in migrations/refs/.',\n );\n addGlobalOptions(command)\n .argument('<name>', 'Ref name (e.g., staging, production)')\n .argument(\n '<contract>',\n 'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(\n async (\n name: string,\n hash: string,\n options: { config?: string; json?: string | boolean; quiet?: boolean },\n ) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefSetCommand(name, hash, options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n ui.output(`Set ref \"${value.ref}\" → ${value.hash}`);\n }\n });\n process.exit(exitCode);\n },\n );\n return command;\n}\n\nfunction createRefDeleteCommand(): Command {\n const command = new Command('delete');\n setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');\n addGlobalOptions(command)\n .argument('<name>', 'Ref name to delete')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(\n async (\n name: string,\n options: { config?: string; json?: string | boolean; quiet?: boolean },\n ) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefDeleteCommand(name, options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n ui.output(`Deleted ref \"${value.ref}\"`);\n }\n });\n process.exit(exitCode);\n },\n );\n return command;\n}\n\nfunction createRefListCommand(): Command {\n const command = new Command('list');\n setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeRefListCommand(options);\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n } else if (!flags.quiet) {\n const entries = Object.entries(value.refs);\n if (entries.length === 0) {\n ui.output('No refs defined');\n } else {\n for (const [refName, entry] of entries) {\n const invariantsSuffix =\n entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';\n ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);\n }\n }\n }\n });\n process.exit(exitCode);\n });\n return command;\n}\n\nexport function createRefCommand(): Command {\n const command = new Command('ref');\n setCommandDescriptions(\n command,\n 'Manage contract refs',\n 'Manage named refs in migrations/refs/. Refs map logical environment\\n' +\n 'names (e.g., staging, production) to contract hashes.',\n );\n addGlobalOptions(command).configureHelp({\n formatHelp: (cmd) => formatCommandHelp({ command: cmd, flags: parseGlobalFlags({}) }),\n subcommandDescription: () => '',\n });\n command.addCommand(createRefSetCommand());\n command.addCommand(createRefDeleteCommand());\n command.addCommand(createRefListCommand());\n return command;\n}\n"],"mappings":"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"}

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

import { t as createTelemetryCommand } from "../../telemetry-DVr6RFnq.mjs";
import { t as createTelemetryCommand } from "../../telemetry-BEyNMfco.mjs";
export { createTelemetryCommand };

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

import { n as executeContractEmit, r as disposeEmitQueue } from "../contract-emit-cJQBlvGb.mjs";
import { n as executeContractEmit, r as disposeEmitQueue } from "../contract-emit-iSMJVs26.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-KuBQftxz.mjs";
import { a as executeDbInit, i as executeDbUpdate, r as executeDbVerify, t as createControlClient } from "../client-2kwLMBSf.mjs";
export { createControlClient, disposeEmitQueue, enrichContract, executeContractEmit, executeDbInit, executeDbUpdate, executeDbVerify };

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

import { t as createContractEmitCommand } from "../contract-emit-CgnXSANN.mjs";
import { t as createFormatCommand } from "../format-DAUAZqPt.mjs";
import { t as createContractEmitCommand } from "../contract-emit-DzOkTs11.mjs";
import { t as createFormatCommand } from "../format-CefCDzkw.mjs";
import { join, resolve } from "pathe";

@@ -4,0 +4,0 @@ import { existsSync, unlinkSync, writeFileSync } from "node:fs";

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

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

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

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

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

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

@@ -179,2 +179,3 @@ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';

refsDir,
migrationsDir,
contractIR,

@@ -181,0 +182,0 @@ mode: result.value.mode,

@@ -203,2 +203,3 @@ import {

refsDir,
migrationsDir,
contractIR,

@@ -205,0 +206,0 @@ mode: result.value.mode,

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

refsDir,
migrationsDir,
options.advanceRef,

@@ -836,0 +837,0 @@ value.markerHash,

@@ -334,3 +334,3 @@ import { readFile } from 'node:fs/promises';

break;
case 'snapshot':
case 'ref':
fromHash = resolutionResult.value.fromHash;

@@ -337,0 +337,0 @@ fromContract = resolutionResult.value.fromContract;

@@ -12,7 +12,7 @@ import { loadConfig } from '@prisma-next/config-loader';

import {
deleteRefPaired,
deleteRef,
readRefs,
validateRefName,
validateRefValue,
writeRefPaired,
writeRef,
} from '@prisma-next/migration-tools/refs';

@@ -41,3 +41,2 @@ import { notOk, ok, type Result } from '@prisma-next/utils/result';

import { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
import { readContractIR } from '../utils/ref-advancement';
import { handleResult } from '../utils/result-handler';

@@ -126,8 +125,4 @@ import { createTerminalUI } from '../utils/terminal-ui';

);
let contractJson: Record<string, unknown>;
try {
contractJson = (await readContractSnapshotJson(migrationsDir, resolvedHash)) as Record<
string,
unknown
>;
await readContractSnapshotJson(migrationsDir, resolvedHash);
} catch (readError) {

@@ -148,5 +143,4 @@ if (

const contractIR = await readContractIR(contractJson, contractJsonPath);
const entry: RefEntry = { hash: resolvedHash, invariants: [] };
await writeRefPaired(refsDir, name, entry, contractIR);
await writeRef(refsDir, name, entry);
return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });

@@ -166,3 +160,3 @@ } catch (error) {

const { refsDir } = resolveMigrationPaths(options.config, config);
await deleteRefPaired(refsDir, name);
await deleteRef(refsDir, name);
return ok({ ok: true as const, ref: name, deleted: true as const });

@@ -169,0 +163,0 @@ } catch (error) {

@@ -218,2 +218,10 @@ /**

/**
* `viaRef: true` (the default) mirrors migration-tools' `errorRefNotResolvable`:
* a ref name with no pointer file, where the fallback hash isn't a graph
* node either — there's nothing to materialize a contract from.
* `viaRef: false` is a distinct, ref-independent case: an explicit `--from
* <hash>` that doesn't name a ref, on an empty migration graph, so there is
* no graph node and no ref to resolve a contract through.
*/
export function errorSnapshotMissing(

@@ -225,12 +233,12 @@ identifier: string,

const fix = viaRef
? `Run "prisma-next db update --advance-ref ${identifier}" to repopulate the snapshot, or "prisma-next ref delete ${identifier}" to clear the orphan pointer.`
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name that has a paired snapshot, or run db update first.`;
? `Create the ref with "prisma-next ref set ${identifier} <hash>" (or advance it via "prisma-next db update --advance-ref ${identifier}"), or pass a hash that is a node in the migration graph.`
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name (its contract resolves through the snapshot store), or run db update first.`;
return errorRuntime(
viaRef
? `Ref "${identifier}" has no paired contract snapshot`
? `Ref "${identifier}" is not resolvable`
: `No contract source for from-hash "${identifier}"`,
{
why: viaRef
? `Ref "${identifier}" exists but its paired snapshot files are missing.`
: `Hash "${identifier}" is not a graph node and no paired ref snapshot supplies a contract.`,
? `Ref "${identifier}" has no pointer file, and the hash being resolved is not a node in the migration graph either.`
: `Hash "${identifier}" is not a node in the migration graph (the graph is empty), and it does not name a ref either.`,
fix,

@@ -237,0 +245,0 @@ meta: {

@@ -18,3 +18,3 @@ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';

switch (error.code) {
case 'MIGRATION.SNAPSHOT_MISSING': {
case 'MIGRATION.REF_NOT_RESOLVABLE': {
const refName =

@@ -30,14 +30,9 @@ typeof error.details?.['refName'] === 'string'

const filePath =
typeof error.details?.['filePath'] === 'string'
? error.details['filePath']
: 'ref-snapshot';
typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';
const message =
typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;
const isRefSnapshot = filePath.endsWith('.contract.json');
return notOk(
errorContractValidationFailed(
isRefSnapshot
? `Ref snapshot contract failed to deserialize: ${message}`
: `Predecessor contract at ${filePath} failed to deserialize: ${message}`,
{ where: { path: isRefSnapshot ? 'ref-snapshot' : filePath } },
`Predecessor contract at ${filePath} failed to deserialize: ${message}`,
{ where: { path: filePath } },
),

@@ -44,0 +39,0 @@ );

@@ -32,3 +32,3 @@ import type { Contract } from '@prisma-next/contract/types';

| {
kind: 'snapshot';
kind: 'ref';
fromHash: string;

@@ -85,3 +85,3 @@ fromContract: Contract;

| {
kind: 'snapshot';
kind: 'ref';
hash: string;

@@ -111,5 +111,5 @@ contract: Contract;

if (at.provenance === 'snapshot') {
if (at.provenance === 'ref') {
return ok({
kind: 'snapshot',
kind: 'ref',
hash: at.hash,

@@ -181,3 +181,3 @@ contract: at.contract,

return ok({
kind: 'snapshot',
kind: 'ref',
fromHash: hash,

@@ -184,0 +184,0 @@ fromContract: contract,

import { readFile } from 'node:fs/promises';
import type { ContractIR } from '@prisma-next/migration-tools/refs';
import { writeRefPaired } from '@prisma-next/migration-tools/refs';
import { writeContractSnapshot } from '@prisma-next/migration-tools/contract-snapshot-store';
import { errorInvalidRefName } from '@prisma-next/migration-tools/errors';
import { validateRefName, writeRef } from '@prisma-next/migration-tools/refs';
import { ifDefined } from '@prisma-next/utils/defined';
export interface ContractIR {
readonly contract: unknown;
readonly contractDts: string;
}
export interface RefAdvancementFields {

@@ -35,2 +41,3 @@ readonly advancedRef: { readonly name: string; readonly hash: string } | null;

refsDir: string,
migrationsDir: string,
name: string,

@@ -40,3 +47,13 @@ hash: string,

): Promise<{ name: string; hash: string }> {
await writeRefPaired(refsDir, name, { hash, invariants: [] }, contractIR);
// Validate the ref name before writing anything: writeRef validates it too,
// but only after the store write below, which would otherwise leave a
// (harmless, but pointless) orphan store entry on an invalid name.
if (!validateRefName(name)) {
throw errorInvalidRefName(name);
}
await writeContractSnapshot(migrationsDir, hash, {
contractJson: contractIR.contract,
contractDts: contractIR.contractDts,
});
await writeRef(refsDir, name, { hash, invariants: [] });
return { name, hash };

@@ -49,2 +66,3 @@ }

readonly refsDir: string;
readonly migrationsDir: string;
readonly contractIR: ContractIR;

@@ -66,2 +84,3 @@ readonly mode: 'plan' | 'apply';

options.refsDir,
options.migrationsDir,
name,

@@ -68,0 +87,0 @@ options.hash,

import { F as CliStructuredError } from "./command-helpers-Cpq44aVa.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-CnbUbiJw.mjs";
import { t as enrichContract } from "./contract-enrichment-gn9sWbPw.mjs";
import { t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-D_3VeHVb.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-KuBQftxz.mjs.map

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

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

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

import { B as errorContractValidationFailed, F as CliStructuredError, W as errorFileNotFound, ct as errorUnexpected, it as errorSnapshotMissing, lt as mapMigrationToolsError } from "./command-helpers-Cpq44aVa.mjs";
import { notOk } from "@prisma-next/utils/result";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/contract-at-errors.ts
function mapContractAtError(error, options) {
if (MigrationToolsError.is(error)) switch (error.code) {
case "MIGRATION.SNAPSHOT_MISSING": return notOk(errorSnapshotMissing(typeof error.details?.["refName"] === "string" ? error.details["refName"] : typeof error.details?.["identifier"] === "string" ? error.details["identifier"] : "unknown"));
case "MIGRATION.CONTRACT_DESERIALIZATION_FAILED": {
const filePath = typeof error.details?.["filePath"] === "string" ? error.details["filePath"] : "ref-snapshot";
const message = typeof error.details?.["message"] === "string" ? error.details["message"] : error.message;
const isRefSnapshot = filePath.endsWith(".contract.json");
return notOk(errorContractValidationFailed(isRefSnapshot ? `Ref snapshot contract failed to deserialize: ${message}` : `Predecessor contract at ${filePath} failed to deserialize: ${message}`, { where: { path: isRefSnapshot ? "ref-snapshot" : filePath } }));
}
case "MIGRATION.INVALID_JSON": {
const filePath = typeof error.details?.["filePath"] === "string" ? error.details["filePath"] : "unknown";
const message = typeof error.details?.["parseError"] === "string" ? error.details["parseError"] : error.message;
return notOk(errorContractValidationFailed((options?.artifactRole ?? "from") === "to" ? `Target contract at ${filePath} failed to deserialize: ${message}` : `Predecessor contract at ${filePath} failed to deserialize: ${message}`, { where: { path: filePath } }));
}
case "MIGRATION.BUNDLE_NOT_FOUND_FOR_GRAPH_NODE": return notOk(errorUnexpected(error.message, {
why: error.why,
fix: error.fix
}));
case "MIGRATION.CONTRACT_SNAPSHOT_MISSING": {
const expectedPath = typeof error.details?.["expectedPath"] === "string" ? error.details["expectedPath"] : "migrations/snapshots/";
return notOk(errorFileNotFound(expectedPath, {
why: (options?.artifactRole ?? "from") === "to" ? `Target migration is missing its contract snapshot at ${expectedPath}` : `Predecessor migration is missing its contract snapshot at ${expectedPath}`,
fix: "Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot."
}));
}
default: return notOk(mapMigrationToolsError(error));
}
if (CliStructuredError.is(error)) return notOk(error);
throw error;
}
//#endregion
export { mapContractAtError as t };
//# sourceMappingURL=contract-at-errors-CTtzKR6q.mjs.map
{"version":3,"file":"contract-at-errors-CTtzKR6q.mjs","names":[],"sources":["../src/utils/contract-at-errors.ts"],"sourcesContent":["import { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { notOk, type Result } from '@prisma-next/utils/result';\nimport {\n CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorSnapshotMissing,\n errorUnexpected,\n mapMigrationToolsError,\n} from './cli-errors';\n\nexport function mapContractAtError(\n error: unknown,\n options?: { readonly artifactRole?: 'from' | 'to' },\n): Result<never, CliStructuredError> {\n if (MigrationToolsError.is(error)) {\n switch (error.code) {\n case 'MIGRATION.SNAPSHOT_MISSING': {\n const refName =\n typeof error.details?.['refName'] === 'string'\n ? error.details['refName']\n : typeof error.details?.['identifier'] === 'string'\n ? error.details['identifier']\n : 'unknown';\n return notOk(errorSnapshotMissing(refName));\n }\n case 'MIGRATION.CONTRACT_DESERIALIZATION_FAILED': {\n const filePath =\n typeof error.details?.['filePath'] === 'string'\n ? error.details['filePath']\n : 'ref-snapshot';\n const message =\n typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;\n const isRefSnapshot = filePath.endsWith('.contract.json');\n return notOk(\n errorContractValidationFailed(\n isRefSnapshot\n ? `Ref snapshot contract failed to deserialize: ${message}`\n : `Predecessor contract at ${filePath} failed to deserialize: ${message}`,\n { where: { path: isRefSnapshot ? 'ref-snapshot' : filePath } },\n ),\n );\n }\n case 'MIGRATION.INVALID_JSON': {\n const filePath =\n typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';\n const message =\n typeof error.details?.['parseError'] === 'string'\n ? error.details['parseError']\n : error.message;\n const role = options?.artifactRole ?? 'from';\n return notOk(\n errorContractValidationFailed(\n role === 'to'\n ? `Target contract at ${filePath} failed to deserialize: ${message}`\n : `Predecessor contract at ${filePath} failed to deserialize: ${message}`,\n { where: { path: filePath } },\n ),\n );\n }\n case 'MIGRATION.BUNDLE_NOT_FOUND_FOR_GRAPH_NODE':\n return notOk(\n errorUnexpected(error.message, {\n why: error.why,\n fix: error.fix,\n }),\n );\n case 'MIGRATION.CONTRACT_SNAPSHOT_MISSING': {\n const expectedPath =\n typeof error.details?.['expectedPath'] === 'string'\n ? error.details['expectedPath']\n : 'migrations/snapshots/';\n const role = options?.artifactRole ?? 'from';\n return notOk(\n errorFileNotFound(expectedPath, {\n why:\n role === 'to'\n ? `Target migration is missing its contract snapshot at ${expectedPath}`\n : `Predecessor migration is missing its contract snapshot at ${expectedPath}`,\n fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',\n }),\n );\n }\n default:\n return notOk(mapMigrationToolsError(error));\n }\n }\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n throw error;\n}\n"],"mappings":";;;;AAWA,SAAgB,mBACd,OACA,SACmC;CACnC,IAAI,oBAAoB,GAAG,KAAK,GAC9B,QAAQ,MAAM,MAAd;EACE,KAAK,8BAOH,OAAO,MAAM,qBALX,OAAO,MAAM,UAAU,eAAe,WAClC,MAAM,QAAQ,aACd,OAAO,MAAM,UAAU,kBAAkB,WACvC,MAAM,QAAQ,gBACd,SACiC,CAAC;EAE5C,KAAK,6CAA6C;GAChD,MAAM,WACJ,OAAO,MAAM,UAAU,gBAAgB,WACnC,MAAM,QAAQ,cACd;GACN,MAAM,UACJ,OAAO,MAAM,UAAU,eAAe,WAAW,MAAM,QAAQ,aAAa,MAAM;GACpF,MAAM,gBAAgB,SAAS,SAAS,gBAAgB;GACxD,OAAO,MACL,8BACE,gBACI,gDAAgD,YAChD,2BAA2B,SAAS,0BAA0B,WAClE,EAAE,OAAO,EAAE,MAAM,gBAAgB,iBAAiB,SAAS,EAAE,CAC/D,CACF;EACF;EACA,KAAK,0BAA0B;GAC7B,MAAM,WACJ,OAAO,MAAM,UAAU,gBAAgB,WAAW,MAAM,QAAQ,cAAc;GAChF,MAAM,UACJ,OAAO,MAAM,UAAU,kBAAkB,WACrC,MAAM,QAAQ,gBACd,MAAM;GAEZ,OAAO,MACL,+BAFW,SAAS,gBAAgB,YAGzB,OACL,sBAAsB,SAAS,0BAA0B,YACzD,2BAA2B,SAAS,0BAA0B,WAClE,EAAE,OAAO,EAAE,MAAM,SAAS,EAAE,CAC9B,CACF;EACF;EACA,KAAK,6CACH,OAAO,MACL,gBAAgB,MAAM,SAAS;GAC7B,KAAK,MAAM;GACX,KAAK,MAAM;EACb,CAAC,CACH;EACF,KAAK,uCAAuC;GAC1C,MAAM,eACJ,OAAO,MAAM,UAAU,oBAAoB,WACvC,MAAM,QAAQ,kBACd;GAEN,OAAO,MACL,kBAAkB,cAAc;IAC9B,MAHS,SAAS,gBAAgB,YAIvB,OACL,wDAAwD,iBACxD,6DAA6D;IACnE,KAAK;GACP,CAAC,CACH;EACF;EACA,SACE,OAAO,MAAM,uBAAuB,KAAK,CAAC;CAC9C;CAEF,IAAI,mBAAmB,GAAG,KAAK,GAC7B,OAAO,MAAM,KAAK;CAEpB,MAAM;AACR"}
import { n as executeContractEmit } from "./contract-emit-cJQBlvGb.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-Cpq44aVa.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-CgnXSANN.mjs.map
{"version":3,"file":"contract-emit-CgnXSANN.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-Cpq44aVa.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-CnbUbiJw.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-cJQBlvGb.mjs.map
{"version":3,"file":"contract-emit-cJQBlvGb.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-Cpq44aVa.mjs";
import { t as inspectLiveSchema } from "./inspect-live-schema-CX7EA5C3.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-Cg8evbeg.mjs.map
{"version":3,"file":"contract-infer-Cg8evbeg.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 { F as CliStructuredError, a as readContractEnvelope, ct as errorUnexpected, lt as mapMigrationToolsError, o as resolveContractPath } from "./command-helpers-Cpq44aVa.mjs";
import { notOk, ok } from "@prisma-next/utils/result";
import { readFile } from "node:fs/promises";
import { createControlStack } from "@prisma-next/framework-components/control";
import { blindCast } from "@prisma-next/utils/casts";
import { loadContractSpaceAggregate } from "@prisma-next/migration-tools/aggregate";
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
//#region src/utils/extension-pack-inputs.ts
/**
* Project the CLI's `Config.extensionPacks` array into the canonical
* {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`
* structural cast in the CLI lives inside this function.
*/
function toExtensionInputs(extensionPacks) {
return extensionPacks.map((raw) => {
const pack = raw;
if (pack.contractSpace === void 0) return {
id: pack.id,
targetId: pack.targetId
};
return {
id: pack.id,
targetId: pack.targetId,
contractSpace: {
contractJson: pack.contractSpace.contractJson,
headRef: pack.contractSpace.headRef,
migrations: pack.contractSpace.migrations ?? []
}
};
});
}
/**
* Minimal aggregate-loader projection that extracts `id` + `targetId`
* from raw extension pack descriptors **without invoking any
* `contractSpace` accessor**. Inspects the own-property descriptor so
* that getter-backed `contractSpace` declarations are detected but
* never called.
*
* Inclusion semantics match {@link toDeclaredExtensions}: a data
* property whose value is explicitly `undefined` is treated as "no
* contract-space declaration" and skipped, mirroring the
* `pack.contractSpace === undefined` check used on canonicalised
* inputs. Prototype-chain `contractSpace` properties (no own
* descriptor) are also skipped.
*
* This variant must be used by `buildContractSpaceAggregate` so that
* the aggregate path (including `db verify`) never reads
* `contractSpace.contractJson` from extension descriptors — the loader
* always reads the contract from on-disk artefacts instead.
*/
function toDeclaredExtensionsFromRaw(extensionPacks) {
const entries = [];
for (const raw of extensionPacks) {
if (typeof raw !== "object" || raw === null) continue;
const descriptor = Object.getOwnPropertyDescriptor(raw, "contractSpace");
if (descriptor === void 0) continue;
if ("value" in descriptor && descriptor.value === void 0) continue;
const pack = raw;
entries.push({
id: pack.id,
targetId: pack.targetId
});
}
return entries;
}
//#endregion
//#region src/utils/contract-space-aggregate-loader.ts
const CONTRACT_SPACES_DOCS_URL = "https://pris.ly/contract-spaces";
function contractSpaceViolationError(summary, options) {
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_VIOLATION", summary, {
why: options.why,
fix: options.fix,
docsUrl: CONTRACT_SPACES_DOCS_URL,
meta: { violations: options.violations }
});
}
/**
* Build the `MIGRATION.CONTRACT_SPACE_VIOLATION` structured-error envelope for a contract-space
* target mismatch. Shared between the declared-extension precheck (the
* descriptor's configured target disagrees with the project target) and
* the on-disk-contract check surfaced by `checkIntegrity`.
*/
function targetMismatchError(spaceId, expected, actual) {
return contractSpaceViolationError(`Contract-space target mismatch for "${spaceId}"`, {
why: `Space "${spaceId}" targets "${actual}" but the project's adapter targets "${expected}".`,
fix: "Update the extension descriptor to target the configured database, or change the project adapter.",
violations: [{
kind: "targetMismatch",
spaceId,
expected,
actual
}]
});
}
/**
* Human-readable detail for an integrity violation, used as the `why`
* of the `integrityFailure` envelope. Mirrors the messages the prior
* throw-on-load loader produced so downstream consumers see the same
* text for the same on-disk state.
*/
function describeIntegrityViolation(violation) {
switch (violation.kind) {
case "hashMismatch": return `Migration "${violation.dirName}" stored hash "${violation.stored}" does not match computed hash "${violation.computed}".`;
case "providedInvariantsMismatch": return `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`;
case "packageUnloadable": return `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`;
case "sameSourceAndTarget": return `Migration "${violation.dirName}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`;
case "headRefMissing": return `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`;
case "headRefNotInGraph": return `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in the migration graph.`;
case "refUnreadable": return `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`;
case "duplicateMigrationHash": return `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(", ")}).`;
default: {
const spaceId = "spaceId" in violation ? violation.spaceId : "*";
return `Integrity violation "${violation.kind}" for contract space "${spaceId}".`;
}
}
}
/**
* Map the integrity violations `checkIntegrity` reports into a single
* CLI structured-error envelope, preserving the error codes the prior
* throw-on-load loader emitted: `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`
* (layout drift, bundled) and `MIGRATION.CONTRACT_SPACE_VIOLATION` (target /
* disjointness / contract-validation / structural integrity). Returns
* `null` when there is nothing to refuse on.
*
* Precedence reproduces the prior loader's first-failure ordering:
* layout drift first (every offence bundled into one envelope), then
* target mismatch, then disjointness, then a contract-validation
* failure, then any remaining structural integrity violation.
*/
function mapIntegrityViolations(violations) {
if (violations.length === 0) return null;
const layout = violations.filter((v) => v.kind === "orphanSpaceDir" || v.kind === "declaredButUnmigrated");
if (layout.length > 0) {
const lines = layout.map((v) => `- [${v.kind}] ${v.spaceId}`);
return new CliStructuredError("MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION", layout.length === 1 ? "Contract-space layout violation detected" : `Contract-space layout violations detected (${layout.length})`, {
why: `The on-disk \`migrations/\` directory and your \`extensionPacks\` declaration are not in agreement.\n${lines.join("\n")}`,
fix: "Declare the extension in `extensionPacks` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.",
docsUrl: CONTRACT_SPACES_DOCS_URL,
meta: { violations: layout }
});
}
const targetMismatch = violations.find((v) => v.kind === "targetMismatch");
if (targetMismatch && targetMismatch.kind === "targetMismatch") return targetMismatchError(targetMismatch.spaceId, targetMismatch.expected, targetMismatch.actual);
const disjointness = violations.find((v) => v.kind === "disjointness");
if (disjointness && disjointness.kind === "disjointness") return contractSpaceViolationError(`Contract-space disjointness violation: storage element "${disjointness.element}" claimed by multiple spaces`, {
why: `Spaces ${disjointness.claimedBy.map((s) => `"${s}"`).join(", ")} all claim the storage element "${disjointness.element}". Each storage element must be owned by exactly one contract space.`,
fix: "Update the conflicting contracts so each storage element is claimed by exactly one space.",
violations: [disjointness]
});
const contractUnreadable = violations.find((v) => v.kind === "contractUnreadable");
if (contractUnreadable && contractUnreadable.kind === "contractUnreadable") return contractSpaceViolationError(`Contract-space contract validation failed for "${contractUnreadable.spaceId}"`, {
why: contractUnreadable.detail,
fix: "Re-emit the extension contract with `prisma-next contract emit`, or fix the extension pack descriptor producing the invalid contract.",
violations: [contractUnreadable]
});
const structural = violations[0];
return contractSpaceViolationError(`Contract-space integrity failure for "${"spaceId" in structural ? structural.spaceId : "*"}"`, {
why: describeIntegrityViolation(structural),
fix: "Re-emit the affected migration package(s) or restore the on-disk `migrations/` directory from version control.",
violations: [structural]
});
}
function declaredExtensionsFromInputs(extensionPacks) {
return toDeclaredExtensionsFromRaw(extensionPacks);
}
/**
* Reject extension descriptors whose configured target disagrees with
* the project target before any on-disk read.
*/
function refuseDeclaredExtensionTargetMismatch(inputs) {
for (const declared of declaredExtensionsFromInputs(inputs.extensionPacks)) if (declared.targetId !== inputs.targetId) return targetMismatchError(declared.id, inputs.targetId, declared.targetId);
return null;
}
/**
* Load the tolerant {@link ContractSpaceAggregate} once at the CLI
* surface. Construction never throws on disk content; callers query
* {@link ContractSpaceAggregate.app} / extension facets instead of
* re-reading `migrations/`.
*/
async function loadContractSpaceAggregateForCli(inputs) {
const targetFailure = refuseDeclaredExtensionTargetMismatch(inputs);
if (targetFailure) return notOk(targetFailure);
return ok(await loadContractSpaceAggregate({
migrationsDir: inputs.migrationsDir,
deserializeContract: inputs.deserializeContract,
appContract: inputs.appContract
}));
}
/**
* Run `checkIntegrity` on a loaded aggregate and map violations into
* the contract-space refusal envelope, or return `null` when the model
* is acceptable for the requested check scope.
*/
function refuseContractSpaceIntegrity(aggregate, options) {
return mapIntegrityViolations(aggregate.checkIntegrity(options));
}
const PACKAGE_CORRUPTION_KINDS = /* @__PURE__ */ new Set([
"hashMismatch",
"providedInvariantsMismatch",
"packageUnloadable"
]);
/**
* Reader-subset integrity refusal for `migration status`: package
* corruptions only (`hashMismatch`, `providedInvariantsMismatch`,
* `packageUnloadable`).
*/
function refusePackageCorruptionOnAggregate(aggregate) {
return mapIntegrityViolations(aggregate.checkIntegrity().filter((v) => PACKAGE_CORRUPTION_KINDS.has(v.kind)));
}
/**
* Construct the tolerant {@link ContractSpaceAggregate} at the CLI
* surface and apply the explicit integrity refusal.
*
* App-space migration packages are read from `migrations/<app>/` by the
* loader itself; callers no longer thread them through.
*/
async function buildContractSpaceAggregate(inputs) {
const declaredExtensions = declaredExtensionsFromInputs(inputs.extensionPacks);
const loaded = await loadContractSpaceAggregateForCli(inputs);
if (!loaded.ok) return loaded;
const failure = refuseContractSpaceIntegrity(loaded.value, {
declaredExtensions,
checkContracts: true
});
if (failure) return notOk(failure);
return ok(loaded.value);
}
/**
* Build a minimal app {@link Contract} carrying only the project's
* contract-identity (`storage.storageHash` + `target` / `targetFamily`)
* when the real `contract.json` is absent or undeserializable.
*
* `loadContractSpaceAggregate` requires an `appContract` to synthesise the
* app space's head ref from its storage hash. Read commands query only that
* hash and the target — never `models` — so an empty-`models` stand-in is
* sufficient for them. It is *not* a valid contract for any consumer that
* reads schema, which is why this is confined to the read-aggregate path.
*/
function appContractStandInFromIdentity(args) {
return blindCast({
storage: { storageHash: args.contractHash },
schemaVersion: "0.0.0",
target: args.targetId,
targetFamily: args.targetFamily,
models: {},
profileHash: EMPTY_CONTRACT_HASH
});
}
async function loadContractRawSafely(config) {
try {
const raw = await readFile(resolveContractPath(config), "utf-8");
return JSON.parse(raw);
} catch {
return null;
}
}
/**
* Tolerant {@link ContractSpaceAggregate} assembly for read-only CLI
* commands. No integrity gate — callers query `aggregate.app` (or other
* facets) without re-reading `migrations/`. When `contract.json` is absent
* or undeserializable, the app contract falls back to an identity-only
* stand-in ({@link appContractStandInFromIdentity}), so these commands
* load without requiring a readable contract.
*/
async function buildReadAggregate(config, options) {
let contractHash = EMPTY_CONTRACT_HASH;
try {
contractHash = (await readContractEnvelope(config)).storageHash;
} catch {}
try {
const contractRawForAggregate = await loadContractRawSafely(config);
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
const deserializeContract = (json) => familyInstance.deserializeContract(json);
let appContractForLoad = appContractStandInFromIdentity({
contractHash,
targetId: config.target.id,
targetFamily: config.target.familyId
});
if (contractRawForAggregate !== null) try {
appContractForLoad = deserializeContract(contractRawForAggregate);
} catch {}
const loaded = await loadContractSpaceAggregateForCli({
targetId: config.target.id,
migrationsDir: options.migrationsDir,
appContract: appContractForLoad,
extensionPacks: config.extensionPacks ?? [],
deserializeContract
});
if (!loaded.ok) return loaded;
return ok({
aggregate: loaded.value,
contractHash
});
} catch (error) {
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}` }));
}
}
//#endregion
export { refuseContractSpaceIntegrity as a, toExtensionInputs as c, loadContractSpaceAggregateForCli as i, buildReadAggregate as n, refusePackageCorruptionOnAggregate as o, loadContractRawSafely as r, toDeclaredExtensionsFromRaw as s, buildContractSpaceAggregate as t };
//# sourceMappingURL=contract-space-aggregate-loader-D_3VeHVb.mjs.map
{"version":3,"file":"contract-space-aggregate-loader-D_3VeHVb.mjs","names":[],"sources":["../src/utils/extension-pack-inputs.ts","../src/utils/contract-space-aggregate-loader.ts"],"sourcesContent":["/**\n * Single descriptor-import boundary for CLI consumers of `Config.extensionPacks`.\n *\n * Every CLI command / utility that reads an extension descriptor's\n * `contractSpace` projection (loader, migrate-pass, extension-migrations\n * pass, migration commands) goes through {@link toExtensionInputs}. The\n * structural cast `pack as { contractSpace?: ... }` lives **only** here —\n * downstream code consumes the canonical shape and maps it to its own\n * narrower shape via the per-consumer adapters below.\n *\n * The CLI receives extension descriptors typed against the SQL family\n * (or any other family in the future); this helper only depends on the\n * structural shape of `contractSpace`. SQL-family callers pass the same\n * `contractJson` / `headRef.hash` value through unchanged.\n */\nimport type { DeclaredExtensionEntry } from '@prisma-next/migration-tools/aggregate';\nimport type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';\nimport type { MigrationOps } from '@prisma-next/migration-tools/package';\n\n/**\n * In-memory authored migration package shipped by an extension descriptor.\n * Mirrors the `MigrationPackage` shape from\n * `@prisma-next/framework-components/control` minus `dirPath`; redeclared\n * structurally here so the helper does not couple to the SQL family's\n * `ExtensionMigrationPackage` type.\n */\nexport interface DescriptorMigrationPackage {\n readonly dirName: string;\n readonly metadata: MigrationMetadata;\n readonly ops: MigrationOps;\n}\n\n/**\n * The most-general projection of a single declared extension pack\n * needed by the CLI's descriptor-import boundary.\n *\n * - `id` / `targetId` are always present.\n * - `contractSpace` is present only when the extension declares one.\n * When present, it carries the canonical inputs every downstream\n * consumer needs — `contractJson`, `headRef`, and the descriptor's\n * pre-built migration packages.\n */\nexport interface ExtensionPackInput {\n readonly id: string;\n readonly targetId: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly headRef: {\n readonly hash: string;\n readonly invariants: readonly string[];\n };\n readonly migrations: readonly DescriptorMigrationPackage[];\n };\n}\n\n/**\n * Structural shape we read off each `Config.extensionPacks` entry.\n *\n * The CLI is the descriptor-import boundary; `extensionPacks` is the only\n * surface where the SQL-family-typed `ControlExtensionDescriptor` flows\n * into framework-neutral helpers. The structural cast lives here, and\n * here alone — every other CLI consumer reads the canonical\n * {@link ExtensionPackInput} shape produced by {@link toExtensionInputs}.\n */\ntype ExtensionPackLike = {\n readonly id: string;\n readonly targetId: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly headRef: {\n readonly hash: string;\n readonly invariants: readonly string[];\n };\n readonly migrations?: readonly DescriptorMigrationPackage[];\n };\n};\n\n/**\n * Project the CLI's `Config.extensionPacks` array into the canonical\n * {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`\n * structural cast in the CLI lives inside this function.\n */\nexport function toExtensionInputs(\n extensionPacks: ReadonlyArray<unknown>,\n): readonly ExtensionPackInput[] {\n return extensionPacks.map((raw) => {\n const pack = raw as ExtensionPackLike;\n if (pack.contractSpace === undefined) {\n return { id: pack.id, targetId: pack.targetId };\n }\n return {\n id: pack.id,\n targetId: pack.targetId,\n contractSpace: {\n contractJson: pack.contractSpace.contractJson,\n headRef: pack.contractSpace.headRef,\n migrations: pack.contractSpace.migrations ?? [],\n },\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-consumer adapters: take the canonical `ExtensionPackInput[]` and\n// project to whatever narrower shape the downstream primitive needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate-loader projection. Surfaces `id` + `targetId` per\n * contract-space-bearing extension to\n * {@link import('./contract-space-aggregate-loader').buildContractSpaceAggregate}.\n *\n * Codec-only extensions (no `contractSpace` declaration) are filtered\n * out: they contribute no contract space, so the aggregate loader\n * has nothing to do with them. Filtering happens at this descriptor-\n * import boundary so the loader stays oblivious to that distinction —\n * every entry it sees expects an on-disk `migrations/<id>/` directory.\n */\nexport function toDeclaredExtensions(\n inputs: ReadonlyArray<ExtensionPackInput>,\n): readonly DeclaredExtensionEntry[] {\n const entries: DeclaredExtensionEntry[] = [];\n for (const pack of inputs) {\n if (pack.contractSpace === undefined) continue;\n entries.push({ id: pack.id, targetId: pack.targetId });\n }\n return entries;\n}\n\n/**\n * Minimal aggregate-loader projection that extracts `id` + `targetId`\n * from raw extension pack descriptors **without invoking any\n * `contractSpace` accessor**. Inspects the own-property descriptor so\n * that getter-backed `contractSpace` declarations are detected but\n * never called.\n *\n * Inclusion semantics match {@link toDeclaredExtensions}: a data\n * property whose value is explicitly `undefined` is treated as \"no\n * contract-space declaration\" and skipped, mirroring the\n * `pack.contractSpace === undefined` check used on canonicalised\n * inputs. Prototype-chain `contractSpace` properties (no own\n * descriptor) are also skipped.\n *\n * This variant must be used by `buildContractSpaceAggregate` so that\n * the aggregate path (including `db verify`) never reads\n * `contractSpace.contractJson` from extension descriptors — the loader\n * always reads the contract from on-disk artefacts instead.\n */\nexport function toDeclaredExtensionsFromRaw(\n extensionPacks: ReadonlyArray<unknown>,\n): readonly DeclaredExtensionEntry[] {\n const entries: DeclaredExtensionEntry[] = [];\n for (const raw of extensionPacks) {\n if (typeof raw !== 'object' || raw === null) continue;\n const descriptor = Object.getOwnPropertyDescriptor(raw, 'contractSpace');\n if (descriptor === undefined) continue;\n if ('value' in descriptor && descriptor.value === undefined) continue;\n const pack = raw as { readonly id: string; readonly targetId: string };\n entries.push({ id: pack.id, targetId: pack.targetId });\n }\n return entries;\n}\n","import { readFile } from 'node:fs/promises';\nimport type { PrismaNextConfig } from '@prisma-next/config/config-types';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ControlExtensionDescriptor } from '@prisma-next/framework-components/control';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport type {\n ContractSpaceAggregate,\n DeclaredExtensionEntry,\n IntegrityQueryOptions,\n IntegrityViolation,\n} from '@prisma-next/migration-tools/aggregate';\nimport { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { CliStructuredError, errorUnexpected, mapMigrationToolsError } from './cli-errors';\nimport { readContractEnvelope, resolveContractPath } from './command-helpers';\nimport { toDeclaredExtensionsFromRaw } from './extension-pack-inputs';\n\nconst CONTRACT_SPACES_DOCS_URL = 'https://pris.ly/contract-spaces';\n\nfunction contractSpaceViolationError(\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly violations: readonly IntegrityViolation[];\n },\n): CliStructuredError {\n return new CliStructuredError('MIGRATION.CONTRACT_SPACE_VIOLATION', summary, {\n why: options.why,\n fix: options.fix,\n docsUrl: CONTRACT_SPACES_DOCS_URL,\n meta: { violations: options.violations },\n });\n}\n\n/**\n * Build the `MIGRATION.CONTRACT_SPACE_VIOLATION` structured-error envelope for a contract-space\n * target mismatch. Shared between the declared-extension precheck (the\n * descriptor's configured target disagrees with the project target) and\n * the on-disk-contract check surfaced by `checkIntegrity`.\n */\nfunction targetMismatchError(\n spaceId: string,\n expected: string,\n actual: string,\n): CliStructuredError {\n return contractSpaceViolationError(`Contract-space target mismatch for \"${spaceId}\"`, {\n why: `Space \"${spaceId}\" targets \"${actual}\" but the project's adapter targets \"${expected}\".`,\n fix: 'Update the extension descriptor to target the configured database, or change the project adapter.',\n violations: [{ kind: 'targetMismatch', spaceId, expected, actual }],\n });\n}\n\n/**\n * Human-readable detail for an integrity violation, used as the `why`\n * of the `integrityFailure` envelope. Mirrors the messages the prior\n * throw-on-load loader produced so downstream consumers see the same\n * text for the same on-disk state.\n */\nfunction describeIntegrityViolation(violation: IntegrityViolation): string {\n switch (violation.kind) {\n case 'hashMismatch':\n return `Migration \"${violation.dirName}\" stored hash \"${violation.stored}\" does not match computed hash \"${violation.computed}\".`;\n case 'providedInvariantsMismatch':\n return `Migration \"${violation.dirName}\" providedInvariants in migration.json disagrees with ops.json.`;\n case 'packageUnloadable':\n return `Migration \"${violation.dirName}\" could not be loaded: ${violation.detail}`;\n case 'sameSourceAndTarget':\n return `Migration \"${violation.dirName}\" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`;\n case 'headRefMissing':\n return `Head ref \\`refs/head.json\\` is missing for contract space \"${violation.spaceId}\".`;\n case 'headRefNotInGraph':\n return `Head ref ${violation.hash} for contract space \"${violation.spaceId}\" is not present in the migration graph.`;\n case 'refUnreadable':\n return `Ref \"${violation.refName}\" for contract space \"${violation.spaceId}\" is unreadable: ${violation.detail}`;\n case 'duplicateMigrationHash':\n return `Multiple migrations in space \"${violation.spaceId}\" share migrationHash \"${violation.migrationHash}\" (${violation.dirNames.join(', ')}).`;\n default: {\n const spaceId = 'spaceId' in violation ? violation.spaceId : '*';\n return `Integrity violation \"${violation.kind}\" for contract space \"${spaceId}\".`;\n }\n }\n}\n\n/**\n * Map the integrity violations `checkIntegrity` reports into a single\n * CLI structured-error envelope, preserving the error codes the prior\n * throw-on-load loader emitted: `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`\n * (layout drift, bundled) and `MIGRATION.CONTRACT_SPACE_VIOLATION` (target /\n * disjointness / contract-validation / structural integrity). Returns\n * `null` when there is nothing to refuse on.\n *\n * Precedence reproduces the prior loader's first-failure ordering:\n * layout drift first (every offence bundled into one envelope), then\n * target mismatch, then disjointness, then a contract-validation\n * failure, then any remaining structural integrity violation.\n */\nexport function mapIntegrityViolations(\n violations: readonly IntegrityViolation[],\n): CliStructuredError | null {\n if (violations.length === 0) return null;\n\n const layout = violations.filter(\n (v): v is Extract<IntegrityViolation, { kind: 'orphanSpaceDir' | 'declaredButUnmigrated' }> =>\n v.kind === 'orphanSpaceDir' || v.kind === 'declaredButUnmigrated',\n );\n if (layout.length > 0) {\n const lines = layout.map((v) => `- [${v.kind}] ${v.spaceId}`);\n const summary =\n layout.length === 1\n ? 'Contract-space layout violation detected'\n : `Contract-space layout violations detected (${layout.length})`;\n return new CliStructuredError('MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION', summary, {\n why: `The on-disk \\`migrations/\\` directory and your \\`extensionPacks\\` declaration are not in agreement.\\n${lines.join('\\n')}`,\n fix: 'Declare the extension in `extensionPacks` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.',\n docsUrl: CONTRACT_SPACES_DOCS_URL,\n meta: { violations: layout },\n });\n }\n\n const targetMismatch = violations.find((v) => v.kind === 'targetMismatch');\n if (targetMismatch && targetMismatch.kind === 'targetMismatch') {\n return targetMismatchError(\n targetMismatch.spaceId,\n targetMismatch.expected,\n targetMismatch.actual,\n );\n }\n\n const disjointness = violations.find((v) => v.kind === 'disjointness');\n if (disjointness && disjointness.kind === 'disjointness') {\n return contractSpaceViolationError(\n `Contract-space disjointness violation: storage element \"${disjointness.element}\" claimed by multiple spaces`,\n {\n why: `Spaces ${disjointness.claimedBy.map((s) => `\"${s}\"`).join(', ')} all claim the storage element \"${disjointness.element}\". Each storage element must be owned by exactly one contract space.`,\n fix: 'Update the conflicting contracts so each storage element is claimed by exactly one space.',\n violations: [disjointness],\n },\n );\n }\n\n const contractUnreadable = violations.find((v) => v.kind === 'contractUnreadable');\n if (contractUnreadable && contractUnreadable.kind === 'contractUnreadable') {\n return contractSpaceViolationError(\n `Contract-space contract validation failed for \"${contractUnreadable.spaceId}\"`,\n {\n why: contractUnreadable.detail,\n fix: 'Re-emit the extension contract with `prisma-next contract emit`, or fix the extension pack descriptor producing the invalid contract.',\n violations: [contractUnreadable],\n },\n );\n }\n\n // Any remaining recoverable structural violation refuses as an\n // integrity failure, surfacing the first one's detail (every violation\n // is still computed; the gate just renders one envelope).\n const structural = violations[0]!;\n const spaceId = 'spaceId' in structural ? structural.spaceId : '*';\n return contractSpaceViolationError(`Contract-space integrity failure for \"${spaceId}\"`, {\n why: describeIntegrityViolation(structural),\n fix: 'Re-emit the affected migration package(s) or restore the on-disk `migrations/` directory from version control.',\n violations: [structural],\n });\n}\n\n/**\n * Inputs needed to compose the aggregate loader at the CLI surface.\n *\n * Keeps the loader framework-neutral (no `Config` import) by accepting\n * already-resolved structural inputs: validated app contract, target\n * id, migrations root directory, and the set of extension descriptors.\n */\nexport interface BuildAggregateInputs<TFamilyId extends string, TTargetId extends string> {\n readonly targetId: TTargetId;\n readonly migrationsDir: string;\n readonly appContract: Contract;\n readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;\n readonly deserializeContract: (contractJson: unknown) => Contract;\n}\n\nfunction declaredExtensionsFromInputs(\n extensionPacks: BuildAggregateInputs<string, string>['extensionPacks'],\n): readonly DeclaredExtensionEntry[] {\n return toDeclaredExtensionsFromRaw(extensionPacks as ReadonlyArray<unknown>);\n}\n\n/**\n * Reject extension descriptors whose configured target disagrees with\n * the project target before any on-disk read.\n */\nexport function refuseDeclaredExtensionTargetMismatch<\n TFamilyId extends string,\n TTargetId extends string,\n>(inputs: BuildAggregateInputs<TFamilyId, TTargetId>): CliStructuredError | null {\n for (const declared of declaredExtensionsFromInputs(inputs.extensionPacks)) {\n if (declared.targetId !== inputs.targetId) {\n return targetMismatchError(declared.id, inputs.targetId, declared.targetId);\n }\n }\n return null;\n}\n\n/**\n * Load the tolerant {@link ContractSpaceAggregate} once at the CLI\n * surface. Construction never throws on disk content; callers query\n * {@link ContractSpaceAggregate.app} / extension facets instead of\n * re-reading `migrations/`.\n */\nexport async function loadContractSpaceAggregateForCli<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n inputs: BuildAggregateInputs<TFamilyId, TTargetId>,\n): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {\n const targetFailure = refuseDeclaredExtensionTargetMismatch(inputs);\n if (targetFailure) {\n return notOk(targetFailure);\n }\n\n const aggregate = await loadContractSpaceAggregate({\n migrationsDir: inputs.migrationsDir,\n deserializeContract: inputs.deserializeContract,\n appContract: inputs.appContract,\n });\n return ok(aggregate);\n}\n\n/**\n * Run `checkIntegrity` on a loaded aggregate and map violations into\n * the contract-space refusal envelope, or return `null` when the model\n * is acceptable for the requested check scope.\n */\nexport function refuseContractSpaceIntegrity(\n aggregate: ContractSpaceAggregate,\n options: IntegrityQueryOptions,\n): CliStructuredError | null {\n return mapIntegrityViolations(aggregate.checkIntegrity(options));\n}\n\nconst PACKAGE_CORRUPTION_KINDS = new Set<IntegrityViolation['kind']>([\n 'hashMismatch',\n 'providedInvariantsMismatch',\n 'packageUnloadable',\n]);\n\n/**\n * Reader-subset integrity refusal for `migration status`: package\n * corruptions only (`hashMismatch`, `providedInvariantsMismatch`,\n * `packageUnloadable`).\n */\nexport function refusePackageCorruptionOnAggregate(\n aggregate: ContractSpaceAggregate,\n): CliStructuredError | null {\n const corruption = aggregate.checkIntegrity().filter((v) => PACKAGE_CORRUPTION_KINDS.has(v.kind));\n return mapIntegrityViolations(corruption);\n}\n\n/**\n * Construct the tolerant {@link ContractSpaceAggregate} at the CLI\n * surface and apply the explicit integrity refusal.\n *\n * App-space migration packages are read from `migrations/<app>/` by the\n * loader itself; callers no longer thread them through.\n */\nexport async function buildContractSpaceAggregate<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n inputs: BuildAggregateInputs<TFamilyId, TTargetId>,\n): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {\n const declaredExtensions = declaredExtensionsFromInputs(inputs.extensionPacks);\n const loaded = await loadContractSpaceAggregateForCli(inputs);\n if (!loaded.ok) {\n return loaded;\n }\n const failure = refuseContractSpaceIntegrity(loaded.value, {\n declaredExtensions,\n checkContracts: true,\n });\n if (failure) {\n return notOk(failure);\n }\n return ok(loaded.value);\n}\n\n/**\n * Build a minimal app {@link Contract} carrying only the project's\n * contract-identity (`storage.storageHash` + `target` / `targetFamily`)\n * when the real `contract.json` is absent or undeserializable.\n *\n * `loadContractSpaceAggregate` requires an `appContract` to synthesise the\n * app space's head ref from its storage hash. Read commands query only that\n * hash and the target — never `models` — so an empty-`models` stand-in is\n * sufficient for them. It is *not* a valid contract for any consumer that\n * reads schema, which is why this is confined to the read-aggregate path.\n */\nexport function appContractStandInFromIdentity(args: {\n readonly contractHash: string;\n readonly targetId: string;\n readonly targetFamily: string;\n}): Contract {\n return blindCast<\n Contract,\n 'read-aggregate consumers query only storage.storageHash and target; empty models stand in for an unreadable contract.json'\n >({\n storage: { storageHash: args.contractHash },\n schemaVersion: '0.0.0',\n target: args.targetId,\n targetFamily: args.targetFamily,\n models: {},\n profileHash: EMPTY_CONTRACT_HASH,\n });\n}\n\nexport async function loadContractRawSafely(config: {\n contract?: { output?: string };\n}): Promise<unknown | null> {\n try {\n const path = resolveContractPath(config);\n const raw = await readFile(path, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Tolerant {@link ContractSpaceAggregate} assembly for read-only CLI\n * commands. No integrity gate — callers query `aggregate.app` (or other\n * facets) without re-reading `migrations/`. When `contract.json` is absent\n * or undeserializable, the app contract falls back to an identity-only\n * stand-in ({@link appContractStandInFromIdentity}), so these commands\n * load without requiring a readable contract.\n */\nexport async function buildReadAggregate(\n config: PrismaNextConfig,\n options: { readonly migrationsDir: string },\n): Promise<\n Result<\n { readonly aggregate: ContractSpaceAggregate; readonly contractHash: string },\n CliStructuredError\n >\n> {\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch {\n // Contract unreadable — marker uses EMPTY_CONTRACT_HASH\n }\n\n try {\n const contractRawForAggregate = await loadContractRawSafely(config);\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const deserializeContract = (json: unknown): Contract =>\n familyInstance.deserializeContract(json);\n let appContractForLoad: Contract = appContractStandInFromIdentity({\n contractHash,\n targetId: config.target.id,\n targetFamily: config.target.familyId,\n });\n if (contractRawForAggregate !== null) {\n try {\n appContractForLoad = deserializeContract(contractRawForAggregate);\n } catch {\n // Deserialization failed — identity-only stand-in fallback\n }\n }\n\n const loaded = await loadContractSpaceAggregateForCli({\n targetId: config.target.id,\n migrationsDir: options.migrationsDir,\n appContract: appContractForLoad,\n extensionPacks: config.extensionPacks ?? [],\n deserializeContract,\n });\n if (!loaded.ok) {\n return loaded;\n }\n return ok({ aggregate: loaded.value, contractHash });\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n}\n"],"mappings":"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"}
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-Cpq44aVa.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { o as ContractValidationError, t as createControlClient } from "./client-KuBQftxz.mjs";
import { c as formatVerifyOutput, i as formatSchemaVerifyOutput, r as formatSchemaVerifyJson, s as formatVerifyJson } from "./verify-CgDVZjhc.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-BEclUzki.mjs.map
{"version":3,"file":"db-verify-BEclUzki.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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"}
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, rt as errorRuntime, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-Cpq44aVa.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, writeFile } from "node:fs/promises";
import { EOL } from "node:os";
import { PslFormatError, format } from "@prisma-next/psl-parser/format";
//#region src/control-api/operations/format.ts
function resolveNewline(formatterNewline, eol) {
if (formatterNewline !== void 0) return formatterNewline;
return eol === "\r\n" ? "CRLF" : "LF";
}
async function executeFormat(options) {
const eol = options.eol ?? EOL;
let config;
try {
config = await loadConfig(options.configPath);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
}
const source = config.contract?.source;
if (source?.sourceFormat !== "psl") return ok({ formatted: false });
const inputPath = source.inputs?.[0];
if (inputPath === void 0) return ok({ formatted: false });
let contents;
try {
contents = await readFile(inputPath, "utf-8");
} catch (error) {
return notOk(errorRuntime("Failed to read contract source file", {
why: error instanceof Error ? error.message : String(error),
fix: `Check that ${inputPath} exists and is readable.`
}));
}
const formatOptions = {
indent: config.formatter?.indent ?? 2,
newline: resolveNewline(config.formatter?.newline, eol)
};
let formatted;
try {
formatted = format(contents, formatOptions);
} catch (error) {
if (error instanceof PslFormatError) return notOk(errorRuntime("Cannot format PSL with parse errors", {
why: error.message,
fix: "Fix the parse errors in your schema and try again.",
meta: { diagnostics: error.diagnostics }
}));
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
}
try {
await writeFile(inputPath, formatted, "utf-8");
} catch (error) {
return notOk(errorRuntime("Failed to write formatted contract source file", {
why: error instanceof Error ? error.message : String(error),
fix: `Check that ${inputPath} is writable.`
}));
}
return ok({
formatted: true,
path: inputPath
});
}
//#endregion
//#region src/commands/format.ts
function createFormatCommand() {
const command = new Command("format");
setCommandDescriptions(command, "Format your PSL contract source", "Formats the Prisma schema (PSL) contract source declared in your config\n(contract.source.inputs[0]) in place. Only runs when contract.source.sourceFormat\nis 'psl'; a TypeScript or unset source is left untouched. Indent and newline are\nread from the optional formatter config section, defaulting to two spaces and the\nsystem newline.");
setCommandExamples(command, ["prisma-next format", "prisma-next format --config ./custom-config.ts"]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
if (!flags.json && !flags.quiet) {
const displayConfigPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
ui.stderr(formatStyledHeader({
command: "format",
description: "Format your PSL contract source",
details: [{
label: "config",
value: displayConfigPath
}],
flags
}));
}
const exitCode = handleResult(await executeFormat({ ...ifDefined("configPath", options.config) }), flags, ui, (value) => {
if (flags.json) {
ui.output(JSON.stringify(value));
return;
}
if (flags.quiet) return;
if (value.formatted) ui.success(`Formatted ${relative(process.cwd(), value.path ?? "")}`);
else ui.info("Nothing to format (contract source is not PSL).");
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { createFormatCommand as t };
//# sourceMappingURL=format-DAUAZqPt.mjs.map
{"version":3,"file":"format-DAUAZqPt.mjs","names":[],"sources":["../src/control-api/operations/format.ts","../src/commands/format.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises';\nimport { EOL } from 'node:os';\nimport { loadConfig } from '@prisma-next/config-loader';\nimport { type FormatOptions, format, PslFormatError } from '@prisma-next/psl-parser/format';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { CliStructuredError, errorRuntime, errorUnexpected } from '../../utils/cli-errors';\n\nexport interface FormatOperationOptions {\n readonly configPath?: string;\n readonly eol?: string;\n}\n\nexport interface FormatOperationResult {\n readonly formatted: boolean;\n readonly path?: string;\n}\n\nexport function resolveNewline(\n formatterNewline: 'LF' | 'CRLF' | undefined,\n eol: string,\n): 'LF' | 'CRLF' {\n if (formatterNewline !== undefined) {\n return formatterNewline;\n }\n return eol === '\\r\\n' ? 'CRLF' : 'LF';\n}\n\nexport async function executeFormat(\n options: FormatOperationOptions,\n): Promise<Result<FormatOperationResult, CliStructuredError>> {\n const eol = options.eol ?? EOL;\n\n let config: Awaited<ReturnType<typeof loadConfig>>;\n try {\n config = await loadConfig(options.configPath);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));\n }\n\n const source = config.contract?.source;\n if (source?.sourceFormat !== 'psl') {\n return ok({ formatted: false });\n }\n\n const inputPath = source.inputs?.[0];\n if (inputPath === undefined) {\n return ok({ formatted: false });\n }\n\n let contents: string;\n try {\n contents = await readFile(inputPath, 'utf-8');\n } catch (error) {\n return notOk(\n errorRuntime('Failed to read contract source file', {\n why: error instanceof Error ? error.message : String(error),\n fix: `Check that ${inputPath} exists and is readable.`,\n }),\n );\n }\n\n const formatOptions: FormatOptions = {\n indent: config.formatter?.indent ?? 2,\n newline: resolveNewline(config.formatter?.newline, eol),\n };\n\n let formatted: string;\n try {\n formatted = format(contents, formatOptions);\n } catch (error) {\n if (error instanceof PslFormatError) {\n return notOk(\n errorRuntime('Cannot format PSL with parse errors', {\n why: error.message,\n fix: 'Fix the parse errors in your schema and try again.',\n meta: { diagnostics: error.diagnostics },\n }),\n );\n }\n return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));\n }\n\n try {\n await writeFile(inputPath, formatted, 'utf-8');\n } catch (error) {\n return notOk(\n errorRuntime('Failed to write formatted contract source file', {\n why: error instanceof Error ? error.message : String(error),\n fix: `Check that ${inputPath} is writable.`,\n }),\n );\n }\n\n return ok({ formatted: true, path: inputPath });\n}\n","import { ifDefined } from '@prisma-next/utils/defined';\nimport { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { executeFormat } from '../control-api/operations/format';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI } from '../utils/terminal-ui';\n\ninterface FormatCommandOptions extends CommonCommandOptions {\n readonly config?: string;\n}\n\nexport function createFormatCommand(): Command {\n const command = new Command('format');\n setCommandDescriptions(\n command,\n 'Format your PSL contract source',\n 'Formats the Prisma schema (PSL) contract source declared in your config\\n' +\n '(contract.source.inputs[0]) in place. Only runs when contract.source.sourceFormat\\n' +\n \"is 'psl'; a TypeScript or unset source is left untouched. Indent and newline are\\n\" +\n 'read from the optional formatter config section, defaulting to two spaces and the\\n' +\n 'system newline.',\n );\n setCommandExamples(command, [\n 'prisma-next format',\n 'prisma-next format --config ./custom-config.ts',\n ]);\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: FormatCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n if (!flags.json && !flags.quiet) {\n const displayConfigPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n ui.stderr(\n formatStyledHeader({\n command: 'format',\n description: 'Format your PSL contract source',\n details: [{ label: 'config', value: displayConfigPath }],\n flags,\n }),\n );\n }\n\n const result = await executeFormat({ ...ifDefined('configPath', options.config) });\n\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value));\n return;\n }\n if (flags.quiet) {\n return;\n }\n if (value.formatted) {\n ui.success(`Formatted ${relative(process.cwd(), value.path ?? '')}`);\n } else {\n ui.info('Nothing to format (contract source is not PSL).');\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
import { R as errorConfigValidation } from "./command-helpers-Cpq44aVa.mjs";
import "@prisma-next/framework-components/components";
//#region src/utils/framework-components.ts
/**
* Asserts that all framework components are compatible with the expected family and target.
*
* This function validates that each component in the framework components array:
* - Has kind 'target', 'adapter', 'extension', or 'driver'
* - Has familyId matching expectedFamilyId
* - Has targetId matching expectedTargetId
*
* This validation happens at the CLI composition boundary, before passing components
* to typed planner/runner instances. It fills the gap between runtime validation
* (via `validateConfig()`) and compile-time type enforcement.
*
* @param expectedFamilyId - The expected family ID (e.g., 'sql')
* @param expectedTargetId - The expected target ID (e.g., 'postgres')
* @param frameworkComponents - Array of framework components to validate
* @returns The same array typed as TargetBoundComponentDescriptor
* @throws CliStructuredError if any component is incompatible
*
* @example
* ```ts
* const config = await loadConfig();
* const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
*
* // Validate and type-narrow components before passing to planner
* const typedComponents = assertFrameworkComponentsCompatible(
* config.family.familyId,
* config.target.targetId,
* frameworkComponents
* );
*
* const planner = target.migrations.createPlanner(familyInstance);
* planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });
* ```
*/
function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
for (let i = 0; i < frameworkComponents.length; i++) {
const component = frameworkComponents[i];
if (typeof component !== "object" || component === null) throw errorConfigValidation("frameworkComponents[]", { why: `Framework component at index ${i} must be an object` });
const record = component;
if (!Object.hasOwn(record, "kind")) throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} must have 'kind' property` });
const kind = record["kind"];
if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')` });
if (!Object.hasOwn(record, "familyId")) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property` });
const familyId = record["familyId"];
if (familyId !== expectedFamilyId) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'` });
if (!Object.hasOwn(record, "targetId")) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property` });
const targetId = record["targetId"];
if (targetId !== expectedTargetId) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'` });
}
return frameworkComponents;
}
//#endregion
export { assertFrameworkComponentsCompatible as t };
//# sourceMappingURL=framework-components-CnbUbiJw.mjs.map
{"version":3,"file":"framework-components-CnbUbiJw.mjs","names":[],"sources":["../src/utils/framework-components.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport {\n checkContractComponentRequirements,\n type TargetBoundComponentDescriptor,\n} from '@prisma-next/framework-components/components';\nimport type { ControlStack } from '@prisma-next/framework-components/control';\nimport { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';\n\n/**\n * Asserts that all framework components are compatible with the expected family and target.\n *\n * This function validates that each component in the framework components array:\n * - Has kind 'target', 'adapter', 'extension', or 'driver'\n * - Has familyId matching expectedFamilyId\n * - Has targetId matching expectedTargetId\n *\n * This validation happens at the CLI composition boundary, before passing components\n * to typed planner/runner instances. It fills the gap between runtime validation\n * (via `validateConfig()`) and compile-time type enforcement.\n *\n * @param expectedFamilyId - The expected family ID (e.g., 'sql')\n * @param expectedTargetId - The expected target ID (e.g., 'postgres')\n * @param frameworkComponents - Array of framework components to validate\n * @returns The same array typed as TargetBoundComponentDescriptor\n * @throws CliStructuredError if any component is incompatible\n *\n * @example\n * ```ts\n * const config = await loadConfig();\n * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n *\n * // Validate and type-narrow components before passing to planner\n * const typedComponents = assertFrameworkComponentsCompatible(\n * config.family.familyId,\n * config.target.targetId,\n * frameworkComponents\n * );\n *\n * const planner = target.migrations.createPlanner(familyInstance);\n * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });\n * ```\n */\nexport function assertFrameworkComponentsCompatible<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n expectedFamilyId: TFamilyId,\n expectedTargetId: TTargetId,\n frameworkComponents: ReadonlyArray<unknown>,\n): ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>> {\n for (let i = 0; i < frameworkComponents.length; i++) {\n const component = frameworkComponents[i];\n\n // Check that component is an object\n if (typeof component !== 'object' || component === null) {\n throw errorConfigValidation('frameworkComponents[]', {\n why: `Framework component at index ${i} must be an object`,\n });\n }\n\n const record = component as Record<string, unknown>;\n\n // Check kind\n if (!Object.hasOwn(record, 'kind')) {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} must have 'kind' property`,\n });\n }\n\n const kind = record['kind'];\n if (kind !== 'target' && kind !== 'adapter' && kind !== 'extension' && kind !== 'driver') {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`,\n });\n }\n\n // Check familyId\n if (!Object.hasOwn(record, 'familyId')) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`,\n });\n }\n\n const familyId = record['familyId'];\n if (familyId !== expectedFamilyId) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`,\n });\n }\n\n // Check targetId\n if (!Object.hasOwn(record, 'targetId')) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`,\n });\n }\n\n const targetId = record['targetId'];\n if (targetId !== expectedTargetId) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`,\n });\n }\n }\n\n // Type assertion is safe because we've validated all components above\n return frameworkComponents as ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Validates that a contract is compatible with the configured target, adapter,\n * and extension packs. Throws on family/target mismatches or missing extension packs.\n *\n * This check ensures the emitted contract matches the CLI config before running\n * commands that depend on the contract (e.g., db verify, db sign).\n *\n * @param contract - The contract to validate (must include targetFamily, target, extensionPacks).\n * @param stack - The control plane stack (target, adapter, driver, extensionPacks).\n *\n * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target\n * doesn't match the configured family/target.\n * @throws {CliStructuredError} errorContractMissingExtensionPacks when the contract requires\n * extension packs that are not provided in the config (includes all missing packs in error.meta).\n *\n * @example\n * ```ts\n * import { assertContractRequirementsSatisfied } from './framework-components';\n *\n * const config = await loadConfig();\n * const contract = await loadContractJson(config.contract.output);\n * const stack = createControlStack({ family: config.family, target: config.target, adapter: config.adapter, ... });\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({ contract, stack });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n stack,\n}: {\n readonly contract: Pick<Contract, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly stack: ControlStack<TFamilyId, TTargetId>;\n}): void {\n const providedComponentIds = new Set<string>([\n stack.target.id,\n ...(stack.adapter ? [stack.adapter.id] : []),\n ]);\n for (const extension of stack.extensionPacks) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: stack.target.familyId,\n expectedTargetId: stack.target.targetId,\n providedComponentIds,\n });\n\n if (result.familyMismatch) {\n throw errorConfigValidation('contract.targetFamily', {\n why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`,\n });\n }\n\n if (result.targetMismatch) {\n throw errorConfigValidation('contract.target', {\n why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`,\n });\n }\n\n if (result.missingExtensionPackIds.length > 0) {\n throw errorContractMissingExtensionPacks({\n missingExtensionPacks: result.missingExtensionPackIds,\n providedComponentIds: [...providedComponentIds],\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,oCAId,kBACA,kBACA,qBACqE;CACrE,KAAK,IAAI,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;EACnD,MAAM,YAAY,oBAAoB;EAGtC,IAAI,OAAO,cAAc,YAAY,cAAc,MACjD,MAAM,sBAAsB,yBAAyB,EACnD,KAAK,gCAAgC,EAAE,oBACzC,CAAC;EAGH,MAAM,SAAS;EAGf,IAAI,CAAC,OAAO,OAAO,QAAQ,MAAM,GAC/B,MAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,4BACzC,CAAC;EAGH,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,YAAY,SAAS,aAAa,SAAS,eAAe,SAAS,UAC9E,MAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,qBAAqB,OAAO,IAAI,EAAE,2DAC3E,CAAC;EAIH,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GACnC,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,iCAChE,CAAC;EAGH,MAAM,WAAW,OAAO;EACxB,IAAI,aAAa,kBACf,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,kBAAkB,OAAO,QAAQ,EAAE,kBAAkB,iBAAiB,GACtI,CAAC;EAIH,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GACnC,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,iCAChE,CAAC;EAGH,MAAM,WAAW,OAAO;EACxB,IAAI,aAAa,kBACf,MAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,IAAI,EAAE,kBAAkB,OAAO,QAAQ,EAAE,kBAAkB,iBAAiB,GACtI,CAAC;CAEL;CAGA,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-Cpq44aVa.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-KuBQftxz.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-CX7EA5C3.mjs.map
{"version":3,"file":"inspect-live-schema-CX7EA5C3.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-Cpq44aVa.mjs";
import { n as buildReadAggregate, s as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-D_3VeHVb.mjs";
import { i as resolveTargetPathAcrossSpaces, n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-DpPTsRCg.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-CcuayFB2.mjs.map
{"version":3,"file":"migration-check-CcuayFB2.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-Cpq44aVa.mjs";
import { t as createProgressAdapter } from "./progress-adapter-CjAeTxY_.mjs";
import { t as createControlClient } from "./client-KuBQftxz.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-CRyYrHZ9.mjs.map
{"version":3,"file":"migration-command-scaffold-CRyYrHZ9.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, K as errorInvalidSpaceId, _ as createTerminalUI, at as errorSpaceNotFound, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, q as errorLegendHumanOnly, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-Cpq44aVa.mjs";
import { n as buildReadAggregate } from "./contract-space-aggregate-loader-D_3VeHVb.mjs";
import { a as renderMigrationGraphLegend, c as renderMigrationListWithStyle, o as createAnsiMigrationListStyler } 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 { APP_SPACE_ID, isValidSpaceId, listContractSpaceDirectories } from "@prisma-next/migration-tools/spaces";
import { HEAD_REF_NAME, refsByContractHash } from "@prisma-next/migration-tools/refs";
//#region src/utils/legend.ts
/**
* The legend is decoration printed alongside the command header on stderr, so
* it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
* `--quiet`) exactly as the header is.
*/
function shouldShowLegend(options, flags) {
return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
}
function validateLegendOptions(options, flags) {
if (options.legend !== true) return ok(void 0);
if (flags.json === true) return notOk(errorLegendHumanOnly("--json"));
if (flags.quiet === true) return notOk(errorLegendHumanOnly("--quiet"));
if (options.dot === true) return notOk(errorLegendHumanOnly("--dot"));
return ok(void 0);
}
//#endregion
//#region src/commands/migration-list.ts
function compareSpaceIds(a, b) {
if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;
if (b === APP_SPACE_ID) return 1;
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
function compareDirNamesDescending(a, b) {
if (a.name < b.name) return 1;
if (a.name > b.name) return -1;
return 0;
}
/**
* Ref names decorating a space's destination contract hashes. The
* tolerant `space.refs` deliberately omits the structural `head.json`;
* for extension spaces the old enumerator surfaced it as a `head`
* decoration on the tip migration, so fold `space.headRef` back in to
* keep that output. The app space synthesises its head, so it carries
* no on-disk `head` ref to restore.
*/
function listRefsByContractHash(space) {
const byHash = new Map(refsByContractHash(space.refs));
if (space.spaceId !== APP_SPACE_ID && space.headRef !== null) {
const hash = space.headRef.hash;
const bucket = byHash.get(hash) ?? [];
if (!bucket.includes(HEAD_REF_NAME)) byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());
}
return byHash;
}
async function orderedOnDiskSpaceIds(projectMigrationsDir) {
return (await listContractSpaceDirectories(projectMigrationsDir)).filter(isValidSpaceId).sort(compareSpaceIds);
}
/**
* Project the loaded {@link ContractSpaceAggregate} into the render-ready
* {@link MigrationSpaceListEntry} rows `migration list` displays.
*
* Space membership matches the on-disk contract-space directories (not the
* aggregate's always-present synthesized app space when `migrations/app/`
* is absent); package and ref data come from `aggregate.space(id)`.
*/
async function migrationSpaceListEntriesFromAggregate(aggregate, projectMigrationsDir) {
const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);
const spaces = [];
for (const spaceId of spaceIds) {
const space = aggregate.space(spaceId);
if (space === void 0) continue;
const refsByHash = listRefsByContractHash(space);
const migrations = space.packages.map((pkg) => ({
name: pkg.dirName,
hash: pkg.metadata.migrationHash,
fromContract: pkg.metadata.from,
toContract: pkg.metadata.to,
operationCount: pkg.ops.length,
createdAt: pkg.metadata.createdAt,
refs: [...refsByHash.get(pkg.metadata.to) ?? []],
providedInvariants: [...pkg.metadata.providedInvariants]
})).sort(compareDirNamesDescending);
spaces.push({
space: spaceId,
migrations
});
}
return spaces;
}
function renderMigrationListHumanOutput(result, options) {
return renderMigrationListWithStyle(result, createAnsiMigrationListStyler({ useColor: options.useColor }), options.glyphMode, {
colorize: options.useColor,
liveContractHash: options.liveContractHash,
graphForSpace: options.graphForSpace,
...options.appSpaceId !== void 0 ? { appSpaceId: options.appSpaceId } : {}
});
}
function computeSummary(spaces) {
const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);
if (spaces.length <= 1) return `${totalMigrations} migration(s) on disk`;
return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;
}
/**
* Policy core of `migration list`: validates `--space`, narrows the
* pre-enumerated spaces, and assembles a {@link MigrationListResult}.
*
* - `migrations/` missing or contains no valid space directories →
* caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.
* - `--space <id>` on an existing-but-empty space → `{ spaceId, migrations: [] }` in the input.
* - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.
*/
function runMigrationList(inputs) {
const { spaces, spaceFilter } = inputs;
if (spaceFilter !== void 0 && !isValidSpaceId(spaceFilter)) return notOk(errorInvalidSpaceId(spaceFilter));
if (spaceFilter !== void 0 && !spaces.some((s) => s.space === spaceFilter)) return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.space).sort()));
const scopedSpaces = spaceFilter !== void 0 ? spaces.filter((s) => s.space === spaceFilter) : spaces;
const resultSpaces = scopedSpaces.length === 0 ? [{
space: APP_SPACE_ID,
migrations: []
}] : scopedSpaces;
return ok({
ok: true,
spaces: [...resultSpaces],
summary: computeSummary(resultSpaces)
});
}
/**
* CLI shell: loads config, resolves paths, prints the styled header on
* stderr (interactive mode only), and delegates to {@link runMigrationList}.
* Kept intentionally thin so the unit-testable surface lives in the core.
*/
async function executeMigrationListCommand(options, flags, ui) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);
if (!flags.json && !flags.quiet) {
const header = formatStyledHeader({
command: "migration list",
description: "List on-disk migrations per contract space",
details: [
{
label: "config",
value: configPath
},
{
label: "migrations",
value: migrationsRelative
},
...options.space !== void 0 ? [{
label: "space",
value: options.space
}] : []
],
flags
});
ui.stderr(header);
if (shouldShowLegend(options, flags)) {
ui.stderr(renderMigrationGraphLegend({
colorize: flags.color !== false,
glyphMode: ui.resolveGlyphMode(options.ascii === true)
}));
ui.stderr("");
}
}
const loaded = await buildReadAggregate(config, { migrationsDir });
if (!loaded.ok) return notOk(loaded.failure);
const { aggregate, contractHash: liveContractHash } = loaded.value;
const listResult = runMigrationList({
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
...ifDefined("spaceFilter", options.space)
});
if (!listResult.ok) return listResult;
return ok({
list: listResult.value,
liveContractHash,
aggregate
});
}
function createMigrationListCommand() {
const command = new Command("list");
setCommandDescriptions(command, "List on-disk migrations per contract space", "Enumerates every on-disk migration under migrations/<space>/ for every\ncontract space found on disk. Offline — does not consult the database.\nHuman output draws the shared migration graph tree with operation counts,\ninvariants on each migration row, and refs on destination contract nodes.\nPass --space <id> to narrow to one contract space. --ascii forces ASCII\ntree glyphs (orthogonal to --no-color).");
setCommandExamples(command, [
"prisma-next migration list",
"prisma-next migration list --space app",
"prisma-next migration list --ascii",
"prisma-next migration list --legend",
"prisma-next migration list --json"
]);
setCommandSeeAlso(command, [
{
verb: "migration status",
oneLiner: "Show migration path and pending status"
},
{
verb: "migration log",
oneLiner: "Show executed migration history"
},
{
verb: "migration graph",
oneLiner: "Show the migration graph topology"
},
{
verb: "migration show",
oneLiner: "Display migration package contents"
}
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--space <id>", "Narrow output to a single contract space").option("--ascii", "Use ASCII kind glyphs (pipe-friendly)").option("--legend", "Print a key for the tree glyphs and lane colors").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 executeMigrationListCommand(options, flags, ui), flags, ui, ({ list, liveContractHash, aggregate }) => {
if (flags.json) ui.output(JSON.stringify(list, null, 2));
else if (!flags.quiet) ui.output(renderMigrationListHumanOutput(list, {
glyphMode: ui.resolveGlyphMode(options.ascii === true),
useColor: ui.useColor,
liveContractHash,
graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),
appSpaceId: aggregate.app.spaceId
}));
});
process.exit(exitCode);
});
return command;
}
//#endregion
export { renderMigrationListHumanOutput as a, validateLegendOptions as c, migrationSpaceListEntriesFromAggregate as i, executeMigrationListCommand as n, runMigrationList as o, listRefsByContractHash as r, shouldShowLegend as s, createMigrationListCommand as t };
//# sourceMappingURL=migration-list-6z6vuXHa.mjs.map
{"version":3,"file":"migration-list-6z6vuXHa.mjs","names":[],"sources":["../src/utils/legend.ts","../src/commands/migration-list.ts"],"sourcesContent":["import { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { type CliStructuredError, errorLegendHumanOnly } from './cli-errors';\nimport type { GlobalFlags } from './global-flags';\n\nexport interface LegendCliOptions {\n readonly legend?: boolean;\n readonly dot?: boolean;\n}\n\n/**\n * The legend is decoration printed alongside the command header on stderr, so\n * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,\n * `--quiet`) exactly as the header is.\n */\nexport function shouldShowLegend(options: LegendCliOptions, flags: GlobalFlags): boolean {\n return (\n options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true\n );\n}\n\nexport function validateLegendOptions(\n options: LegendCliOptions,\n flags: GlobalFlags,\n): Result<void, CliStructuredError> {\n if (options.legend !== true) {\n return ok(undefined);\n }\n if (flags.json === true) {\n return notOk(errorLegendHumanOnly('--json'));\n }\n if (flags.quiet === true) {\n return notOk(errorLegendHumanOnly('--quiet'));\n }\n if (options.dot === true) {\n return notOk(errorLegendHumanOnly('--dot'));\n }\n return ok(undefined);\n}\n","import { loadConfig } from '@prisma-next/config-loader';\nimport type {\n AggregateContractSpace,\n ContractSpaceAggregate,\n} from '@prisma-next/migration-tools/aggregate';\nimport type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';\nimport {\n APP_SPACE_ID,\n isValidSpaceId,\n listContractSpaceDirectories,\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 {\n type CliStructuredError,\n errorInvalidSpaceId,\n errorSpaceNotFound,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-labels';\nimport { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';\nimport { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';\nimport type {\n MigrationListEntry,\n MigrationListResult,\n MigrationSpaceListEntry,\n} 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 type { GlyphMode } from '../utils/glyph-mode';\nimport { shouldShowLegend, validateLegendOptions } from '../utils/legend';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\nfunction compareSpaceIds(a: string, b: string): number {\n if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;\n if (b === APP_SPACE_ID) return 1;\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n\nfunction compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry): number {\n if (a.name < b.name) return 1;\n if (a.name > b.name) return -1;\n return 0;\n}\n\n/**\n * Ref names decorating a space's destination contract hashes. The\n * tolerant `space.refs` deliberately omits the structural `head.json`;\n * for extension spaces the old enumerator surfaced it as a `head`\n * decoration on the tip migration, so fold `space.headRef` back in to\n * keep that output. The app space synthesises its head, so it carries\n * no on-disk `head` ref to restore.\n */\nexport function listRefsByContractHash(\n space: AggregateContractSpace,\n): ReadonlyMap<string, readonly string[]> {\n const byHash = new Map(refsByContractHash(space.refs));\n if (space.spaceId !== APP_SPACE_ID && space.headRef !== null) {\n const hash = space.headRef.hash;\n const bucket = byHash.get(hash) ?? [];\n if (!bucket.includes(HEAD_REF_NAME)) {\n byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());\n }\n }\n return byHash;\n}\n\nasync function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<readonly string[]> {\n const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);\n return candidateDirs.filter(isValidSpaceId).sort(compareSpaceIds);\n}\n\n/**\n * Project the loaded {@link ContractSpaceAggregate} into the render-ready\n * {@link MigrationSpaceListEntry} rows `migration list` displays.\n *\n * Space membership matches the on-disk contract-space directories (not the\n * aggregate's always-present synthesized app space when `migrations/app/`\n * is absent); package and ref data come from `aggregate.space(id)`.\n */\nexport async function migrationSpaceListEntriesFromAggregate(\n aggregate: ContractSpaceAggregate,\n projectMigrationsDir: string,\n): Promise<readonly MigrationSpaceListEntry[]> {\n const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);\n const spaces: MigrationSpaceListEntry[] = [];\n\n for (const spaceId of spaceIds) {\n const space = aggregate.space(spaceId);\n if (space === undefined) {\n continue;\n }\n const refsByHash = listRefsByContractHash(space);\n const migrations: MigrationListEntry[] = space.packages\n .map((pkg) => ({\n name: pkg.dirName,\n hash: pkg.metadata.migrationHash,\n fromContract: pkg.metadata.from,\n toContract: pkg.metadata.to,\n operationCount: pkg.ops.length,\n createdAt: pkg.metadata.createdAt,\n refs: [...(refsByHash.get(pkg.metadata.to) ?? [])],\n providedInvariants: [...pkg.metadata.providedInvariants],\n }))\n .sort(compareDirNamesDescending);\n\n spaces.push({ space: spaceId, migrations });\n }\n\n return spaces;\n}\n\ninterface MigrationListOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly space?: string;\n readonly ascii?: boolean;\n readonly legend?: boolean;\n}\n\nexport interface MigrationListExecuteResult {\n readonly list: MigrationListResult;\n readonly liveContractHash: string;\n readonly aggregate: ContractSpaceAggregate;\n}\n\nexport interface MigrationListHumanRenderOptions {\n readonly glyphMode: GlyphMode;\n readonly useColor: boolean;\n readonly liveContractHash: string;\n readonly graphForSpace: (spaceId: string) => MigrationGraph | undefined;\n readonly appSpaceId?: string;\n}\n\nexport function renderMigrationListHumanOutput(\n result: MigrationListResult,\n options: MigrationListHumanRenderOptions,\n): string {\n const styler = createAnsiMigrationListStyler({ useColor: options.useColor });\n return renderMigrationListWithStyle(result, styler, options.glyphMode, {\n colorize: options.useColor,\n liveContractHash: options.liveContractHash,\n graphForSpace: options.graphForSpace,\n ...(options.appSpaceId !== undefined ? { appSpaceId: options.appSpaceId } : {}),\n });\n}\n\n/**\n * Inputs for {@link runMigrationList} — the policy core of `migration list`\n * that tests exercise directly.\n *\n * The core does not call `loadConfig`, parse CLI flags, render a styled\n * header, or write to any stream. Enumeration is supplied by the caller\n * (the CLI shell builds it from {@link migrationSpaceListEntriesFromAggregate}).\n */\nexport interface RunMigrationListInputs {\n readonly spaces: readonly MigrationSpaceListEntry[];\n readonly spaceFilter?: string;\n}\n\nfunction computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {\n const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);\n if (spaces.length <= 1) {\n return `${totalMigrations} migration(s) on disk`;\n }\n return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;\n}\n\n/**\n * Policy core of `migration list`: validates `--space`, narrows the\n * pre-enumerated spaces, and assembles a {@link MigrationListResult}.\n *\n * - `migrations/` missing or contains no valid space directories →\n * caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.\n * - `--space <id>` on an existing-but-empty space → `{ spaceId, migrations: [] }` in the input.\n * - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.\n */\nexport function runMigrationList(\n inputs: RunMigrationListInputs,\n): Result<MigrationListResult, CliStructuredError> {\n const { spaces, spaceFilter } = inputs;\n\n if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {\n return notOk(errorInvalidSpaceId(spaceFilter));\n }\n\n if (spaceFilter !== undefined && !spaces.some((s) => s.space === spaceFilter)) {\n return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.space).sort()));\n }\n\n const scopedSpaces =\n spaceFilter !== undefined ? spaces.filter((s) => s.space === spaceFilter) : spaces;\n\n const resultSpaces: readonly MigrationSpaceListEntry[] =\n scopedSpaces.length === 0 ? [{ space: APP_SPACE_ID, migrations: [] }] : scopedSpaces;\n\n return ok({\n ok: true,\n spaces: [...resultSpaces],\n summary: computeSummary(resultSpaces),\n });\n}\n\n/**\n * CLI shell: loads config, resolves paths, prints the styled header on\n * stderr (interactive mode only), and delegates to {@link runMigrationList}.\n * Kept intentionally thin so the unit-testable surface lives in the core.\n */\nexport async function executeMigrationListCommand(\n options: MigrationListOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationListExecuteResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration list',\n description: 'List on-disk migrations per contract space',\n details: [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),\n ],\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 loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n\n const { aggregate, contractHash: liveContractHash } = loaded.value;\n\n const spaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);\n\n const listResult = runMigrationList({\n spaces,\n ...ifDefined('spaceFilter', options.space),\n });\n if (!listResult.ok) {\n return listResult;\n }\n return ok({ list: listResult.value, liveContractHash, aggregate });\n}\n\nexport function createMigrationListCommand(): Command {\n const command = new Command('list');\n setCommandDescriptions(\n command,\n 'List on-disk migrations per contract space',\n 'Enumerates every on-disk migration under migrations/<space>/ for every\\n' +\n 'contract space found on disk. Offline — does not consult the database.\\n' +\n 'Human output draws the shared migration graph tree with operation counts,\\n' +\n 'invariants on each migration row, and refs on destination contract nodes.\\n' +\n 'Pass --space <id> to narrow to one contract space. --ascii forces ASCII\\n' +\n 'tree glyphs (orthogonal to --no-color).',\n );\n setCommandExamples(command, [\n 'prisma-next migration list',\n 'prisma-next migration list --space app',\n 'prisma-next migration list --ascii',\n 'prisma-next migration list --legend',\n 'prisma-next migration list --json',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration log', oneLiner: 'Show executed migration history' },\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('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .option('--ascii', 'Use ASCII kind glyphs (pipe-friendly)')\n .option('--legend', 'Print a key for the tree glyphs and lane colors')\n .action(async (options: MigrationListOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const legendValidation = validateLegendOptions(options, flags);\n if (!legendValidation.ok) {\n process.exit(handleResult(legendValidation, flags, ui));\n }\n const result = await executeMigrationListCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, ({ list, liveContractHash, aggregate }) => {\n if (flags.json) {\n ui.output(JSON.stringify(list, null, 2));\n } else if (!flags.quiet) {\n ui.output(\n renderMigrationListHumanOutput(list, {\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n useColor: ui.useColor,\n liveContractHash,\n graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),\n appSpaceId: aggregate.app.spaceId,\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, 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-Cpq44aVa.mjs";
import { t as createControlClient } from "./client-KuBQftxz.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-C_xam2HZ.mjs.map
{"version":3,"file":"migration-log-C_xam2HZ.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 { rt as errorRuntime } from "./command-helpers-Cpq44aVa.mjs";
import { notOk, ok } from "@prisma-next/utils/result";
import { isAbsolute, relative, resolve } from "pathe";
//#region src/utils/migration-path-target.ts
function looksLikePath(target) {
return target.includes("/") || target.includes("\\");
}
function resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative) {
const targetPath = resolve(target);
const relativeToApp = relative(appMigrationsDir, targetPath);
if (relativeToApp === "" || relativeToApp === "." || relativeToApp.startsWith("..") || isAbsolute(relativeToApp)) return notOk(errorRuntime("Target must point to an app-space migration", {
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
fix: "Pass an app-space migration directory or use a hash prefix."
}));
return ok(targetPath);
}
/**
* Resolve a filesystem-path target to the migration dir that contains it,
* searching each in-scope space's `migrationsDir`. A path is explicit, so
* it can belong to at most one space — returns the first match, or `null`
* when the path falls outside every space dir.
*/
function resolveTargetPathAcrossSpaces(target, spaces) {
const targetPath = resolve(target);
for (const space of spaces) {
const rel = relative(space.migrationsDir, targetPath);
if (!(rel === "" || rel === "." || rel.startsWith("..") || isAbsolute(rel))) return targetPath;
}
return null;
}
function findPackageByDirPath(packages, resolvedDirPath) {
const normalized = resolve(resolvedDirPath);
return packages.find((p) => resolve(p.dirPath) === normalized);
}
//#endregion
export { resolveTargetPathAcrossSpaces as i, looksLikePath as n, resolveAppTargetPath as r, findPackageByDirPath as t };
//# sourceMappingURL=migration-path-target-DpPTsRCg.mjs.map
{"version":3,"file":"migration-path-target-DpPTsRCg.mjs","names":[],"sources":["../src/utils/migration-path-target.ts"],"sourcesContent":["import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { isAbsolute, relative, resolve } from 'pathe';\nimport { type CliStructuredError, errorRuntime } from './cli-errors';\n\nexport function looksLikePath(target: string): boolean {\n return target.includes('/') || target.includes('\\\\');\n}\n\nexport function resolveAppTargetPath(\n target: string,\n appMigrationsDir: string,\n appMigrationsRelative: string,\n): Result<string, CliStructuredError> {\n const targetPath = resolve(target);\n const relativeToApp = relative(appMigrationsDir, targetPath);\n const isOutsideAppDir =\n relativeToApp === '' ||\n relativeToApp === '.' ||\n relativeToApp.startsWith('..') ||\n isAbsolute(relativeToApp);\n if (isOutsideAppDir) {\n return notOk(\n errorRuntime('Target must point to an app-space migration', {\n why: `Expected a path under ${appMigrationsRelative}, got ${target}`,\n fix: 'Pass an app-space migration directory or use a hash prefix.',\n }),\n );\n }\n return ok(targetPath);\n}\n\n/**\n * Resolve a filesystem-path target to the migration dir that contains it,\n * searching each in-scope space's `migrationsDir`. A path is explicit, so\n * it can belong to at most one space — returns the first match, or `null`\n * when the path falls outside every space dir.\n */\nexport function resolveTargetPathAcrossSpaces(\n target: string,\n spaces: ReadonlyArray<{ readonly migrationsDir: string }>,\n): string | null {\n const targetPath = resolve(target);\n for (const space of spaces) {\n const rel = relative(space.migrationsDir, targetPath);\n const isOutside = rel === '' || rel === '.' || rel.startsWith('..') || isAbsolute(rel);\n if (!isOutside) {\n return targetPath;\n }\n }\n return null;\n}\n\nexport function findPackageByDirPath(\n packages: readonly OnDiskMigrationPackage[],\n resolvedDirPath: string,\n): OnDiskMigrationPackage | undefined {\n const normalized = resolve(resolvedDirPath);\n return packages.find((p) => resolve(p.dirPath) === normalized);\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,QAAyB;CACrD,OAAO,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,IAAI;AACrD;AAEA,SAAgB,qBACd,QACA,kBACA,uBACoC;CACpC,MAAM,aAAa,QAAQ,MAAM;CACjC,MAAM,gBAAgB,SAAS,kBAAkB,UAAU;CAM3D,IAJE,kBAAkB,MAClB,kBAAkB,OAClB,cAAc,WAAW,IAAI,KAC7B,WAAW,aAAa,GAExB,OAAO,MACL,aAAa,+CAA+C;EAC1D,KAAK,yBAAyB,sBAAsB,QAAQ;EAC5D,KAAK;CACP,CAAC,CACH;CAEF,OAAO,GAAG,UAAU;AACtB;;;;;;;AAQA,SAAgB,8BACd,QACA,QACe;CACf,MAAM,aAAa,QAAQ,MAAM;CACjC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,SAAS,MAAM,eAAe,UAAU;EAEpD,IAAI,EADc,QAAQ,MAAM,QAAQ,OAAO,IAAI,WAAW,IAAI,KAAK,WAAW,GAAG,IAEnF,OAAO;CAEX;CACA,OAAO;AACT;AAEA,SAAgB,qBACd,UACA,iBACoC;CACpC,MAAM,aAAa,QAAQ,eAAe;CAC1C,OAAO,SAAS,MAAM,MAAM,QAAQ,EAAE,OAAO,MAAM,UAAU;AAC/D"}
import { A as formatStyledHeader, B as errorContractValidationFailed, F as CliStructuredError, Q as errorPlanForgotTheFlag, W as errorFileNotFound, X as errorMigrationPlanningFailed, _ as createTerminalUI, ct as errorUnexpected, g as parseGlobalFlagsOrExit, it as errorSnapshotMissing, l as setCommandDescriptions, lt as mapMigrationToolsError, o as resolveContractPath, ot as errorTargetMigrationNotSupported, r as getTargetMigrations, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "./command-helpers-Cpq44aVa.mjs";
import { t as assertFrameworkComponentsCompatible } from "./framework-components-CnbUbiJw.mjs";
import { c as toExtensionInputs, i as loadContractSpaceAggregateForCli, t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-D_3VeHVb.mjs";
import { t as mapContractAtError } from "./contract-at-errors-CTtzKR6q.mjs";
import { Command } from "commander";
import { loadConfig } from "@prisma-next/config-loader";
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
import { notOk, ok } from "@prisma-next/utils/result";
import { join, relative } from "pathe";
import { readFile } from "node:fs/promises";
import { createControlStack, hasOperationPreview } from "@prisma-next/framework-components/control";
import { emitContractSpaceArtifacts, planAllSpaces, readContractSpaceHeadRef, spaceMigrationDirectory } from "@prisma-next/migration-tools/spaces";
import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
import { assertHashIsGraphNode, findLatestMigration, isGraphNode } from "@prisma-next/migration-tools/migration-graph";
import { snapshotsImportPathFrom, writeContractSnapshot } from "@prisma-next/migration-tools/contract-snapshot-store";
import { parseContractRef } from "@prisma-next/migration-tools/ref-resolution";
import { computeMigrationHash } from "@prisma-next/migration-tools/hash";
import { formatMigrationDirName, materialiseExtensionMigrationPackageIfMissing, writeMigrationPackage } from "@prisma-next/migration-tools/io";
import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
import { deriveProvidedInvariants } from "@prisma-next/migration-tools/invariants";
//#region src/utils/contract-space-seed-phase.ts
/**
* Phase-1 of the two-phase `migration plan` pipeline (sub-spec § 4).
*
* For every extension that exposes a `contractSpace`:
*
* 1. Read the on-disk head ref (returns `null` on first emit).
* 2. Write the head contract into the migrations-root snapshot store
* (write-if-absent, keyed by hash) and unconditionally re-emit
* `refs/head.json`, via {@link emitContractSpaceArtifacts}. The
* framework owns `refs/head.json`; re-emit is the contract for that
* file. The snapshot itself is only written once per distinct hash.
* 3. Materialise any descriptor-shipped migration packages not yet on
* disk via {@link materialiseExtensionMigrationPackageIfMissing}.
* Existing packages are left untouched (by-existence skip).
*
* The return value lets the caller render a per-space status line and
* lets the phase-2 aggregate loader run on a now-consistent disk state
* (every loaded extension is guaranteed to have its head ref pinned
* to the descriptor's hash and to ship every package the descriptor
* declares).
*
* Output ordering is deterministic and alphabetical by spaceId (via
* {@link planAllSpaces}, which also detects duplicate spaceIds). This
* matches the canonical sort order used by every other aggregate
* surface (`migrate`, `migration status`, the runner).
*/
async function runContractSpaceSeedPhase(inputs) {
const planned = planAllSpaces(inputs.extensionPacks.filter((pack) => pack.contractSpace !== void 0).map((pack) => ({
spaceId: pack.id,
priorContract: null,
newContract: pack.contractSpace.contractJson,
__pack: pack.contractSpace
})), (input) => input.__pack.migrations);
const descriptorBySpace = /* @__PURE__ */ new Map();
for (const pack of inputs.extensionPacks) if (pack.contractSpace !== void 0) descriptorBySpace.set(pack.id, pack.contractSpace);
const seeded = [];
for (const space of planned) {
const descriptor = descriptorBySpace.get(space.spaceId);
if (descriptor === void 0) continue;
const priorHash = (await readContractSpaceHeadRef(inputs.migrationsDir, space.spaceId))?.hash ?? null;
await emitContractSpaceArtifacts(inputs.migrationsDir, space.spaceId, {
contract: descriptor.contractJson,
contractDts: buildPlaceholderContractDts(space.spaceId),
headRef: {
hash: descriptor.headRef.hash,
invariants: descriptor.headRef.invariants
}
});
const spaceDir = spaceMigrationDirectory(inputs.migrationsDir, space.spaceId);
const newMigrationDirs = [];
for (const pkg of space.migrationPackages) {
const { written } = await materialiseExtensionMigrationPackageIfMissing(spaceDir, pkg);
if (written) newMigrationDirs.push(pkg.dirName);
}
const action = priorHash !== descriptor.headRef.hash || newMigrationDirs.length > 0 ? "updated" : "unchanged";
seeded.push({
spaceId: space.spaceId,
action,
priorHash,
newHash: descriptor.headRef.hash,
newMigrationDirs
});
}
return { seeded };
}
/**
* Placeholder `.d.ts` content for an extension space's on-disk mirror.
*
* Rendering a fully-typed `.d.ts` for an extension contract requires
* the SQL-family renderer with the codec / typemap registry threaded
* through; until that integration ships, the on-disk `.d.ts` is a
* stub `export {};` module that documents how consumers should
* validate the sibling `contract.json`. The stub typechecks on its
* own and does not need any TypeScript suppressions.
*/
function buildPlaceholderContractDts(spaceId) {
return [
"/**",
` * Placeholder \`.d.ts\` for extension space "${spaceId}".`,
" *",
" * The framework re-emits this file on every `migration plan` run",
" * alongside `contract.json` and `refs/head.json`. A typed `.d.ts`",
" * rendering pass for extension contracts is tracked separately;",
" * until that ships, consumers should import `contract.json`",
" * and pass it through the target descriptor’s `contractSerializer`.",
" */",
"export {};",
""
].join("\n");
}
//#endregion
//#region src/utils/plan-resolution.ts
const FULL_HASH_PATTERN = /^sha256:([0-9a-f]{64}|empty)$/;
function looksLikeFullHash(input) {
return FULL_HASH_PATTERN.test(input);
}
function graphIsEmpty(space) {
return space.packages.length === 0;
}
function getReachableRefs(refs, graph) {
return Object.entries(refs).flatMap(([name, entry]) => entry && isGraphNode(entry.hash, graph) ? [{
name,
hash: entry.hash
}] : []).sort((a, b) => a.name.localeCompare(b.name));
}
function assertFromIsGraphNode(fromHash, graph, refs, graphTipHash) {
try {
assertHashIsGraphNode(fromHash, graph);
} catch (error) {
if (MigrationToolsError.is(error) && error.code === "MIGRATION.HASH_NOT_IN_GRAPH") throw errorPlanForgotTheFlag(fromHash, getReachableRefs(refs, graph), graphTipHash);
throw error;
}
}
async function resolveContractRef(parsed, space, options) {
const { hash, provenance } = parsed;
const refName = provenance.kind === "ref" ? provenance.refName : void 0;
try {
const at = await space.contractAt(hash, refName !== void 0 ? { refName } : void 0);
if (at.provenance === "snapshot") return ok({
kind: "snapshot",
hash: at.hash,
contract: at.contract,
contractJson: at.contractJson,
contractDts: at.contractDts
});
return ok({
kind: "graph-node",
hash: at.hash,
contract: at.contract,
contractJson: at.contractJson,
contractDts: at.contractDts
});
} catch (error) {
return mapContractAtError(error, options?.artifactRole !== void 0 ? { artifactRole: options.artifactRole } : void 0);
}
}
async function resolveFromPolicy(parsed, input, refs, explicitFromLabel) {
const resolution = await resolveContractRef(parsed, input.space, {
...explicitFromLabel !== void 0 ? { explicitLabel: explicitFromLabel } : {},
artifactRole: "from"
});
if (!resolution.ok) return resolution;
if (resolution.value.kind === "graph-node") return ok({
kind: "graph-node",
fromHash: resolution.value.hash,
fromContract: resolution.value.contract
});
const { hash, contract, contractJson, contractDts } = resolution.value;
if (graphIsEmpty(input.space)) return ok({
kind: "auto-baseline",
fromHash: hash,
fromContract: contract,
contractDts,
contractJson
});
const graph = input.space.graph();
const graphTip = findLatestMigration(graph)?.to ?? null;
try {
assertFromIsGraphNode(hash, graph, refs, graphTip);
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
throw error;
}
return ok({
kind: "snapshot",
fromHash: hash,
fromContract: contract,
contractDts,
contractJson
});
}
async function resolveFromForPlan(input) {
const { optionsFrom, space } = input;
const graph = space.graph();
const refs = space.refs;
if (optionsFrom === void 0) {
const dbRef = refs["db"];
if (!dbRef) return ok({
kind: "greenfield",
fromHash: null,
fromContract: null
});
return resolveFromPolicy({
hash: dbRef.hash,
provenance: {
kind: "ref",
refName: "db"
}
}, input, refs);
}
const refResult = parseContractRef(optionsFrom, {
graph,
refs
});
if (!refResult.ok) {
if (looksLikeFullHash(optionsFrom)) {
const empty = graphIsEmpty(space);
const graphTip = findLatestMigration(graph)?.to ?? null;
if (empty) return notOk(errorSnapshotMissing(optionsFrom, { viaRef: false }));
return notOk(errorPlanForgotTheFlag(optionsFrom, getReachableRefs(refs, graph), graphTip));
}
return notOk(mapRefResolutionError(refResult.failure));
}
return resolveFromPolicy(refResult.value, input, refs, optionsFrom);
}
async function resolveToForPlan(optionsTo, input) {
const { space } = input;
const graph = space.graph();
const refs = space.refs;
const refResult = parseContractRef(optionsTo, {
graph,
refs
});
if (!refResult.ok) return notOk(mapRefResolutionError(refResult.failure));
const resolution = await resolveContractRef(refResult.value, space, {
explicitLabel: optionsTo,
artifactRole: "to"
});
if (!resolution.ok) return resolution;
const { hash, contract, contractJson, contractDts } = resolution.value;
return ok({
hash,
contract,
contractJson,
contractDts
});
}
//#endregion
//#region src/commands/migration-plan.ts
async function runPlannerLeg(planner, migrations, frameworkComponents, contract, fromContract, spaceId, ownership, snapshotsImportPath) {
const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
const plannerResult = planner.plan({
contract,
schema: fromSchema,
policy: { allowedOperationClasses: [
"additive",
"widening",
"destructive",
"data"
] },
fromContract,
frameworkComponents,
spaceId,
ownership,
snapshotsImportPath
});
if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
let plannedOps = [];
let hasPlaceholders = false;
try {
plannedOps = await Promise.all(plannerResult.plan.operations);
if (plannedOps.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
kind: "unsupportedChange",
summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
}] }));
} catch (e) {
if (CliStructuredError.is(e) && e.code === "MIGRATION.UNFILLED_PLACEHOLDER") hasPlaceholders = true;
else throw e;
}
return ok({
plannedOps,
migrationTsContent: plannerResult.plan.renderTypeScript(),
hasPlaceholders
});
}
async function writePlannedMigrationPackage(packageDir, fromHash, toHash, createdAt, leg) {
const opsForWrite = leg.hasPlaceholders ? [] : leg.plannedOps;
const metadataWithInvariants = {
from: fromHash,
to: toHash,
providedInvariants: deriveProvidedInvariants(opsForWrite),
createdAt: createdAt.toISOString()
};
await writeMigrationPackage(packageDir, {
...metadataWithInvariants,
migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite)
}, opsForWrite);
await writeMigrationTs(packageDir, leg.migrationTsContent);
}
async function executeMigrationPlanCommand(options, flags, ui, startTime) {
const config = await loadConfig(options.config);
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
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
},
{
label: "migrations",
value: appMigrationsRelative
}
];
if (options.from) details.push({
label: "from",
value: options.from
});
if (options.to) details.push({
label: "to",
value: options.to
});
if (options.name) details.push({
label: "name",
value: options.name
});
const header = formatStyledHeader({
command: "migration plan",
description: "Plan a migration from contract changes",
url: "https://pris.ly/migration-plan",
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)}` }));
}
const stack = createControlStack(config);
const familyInstance = config.family.create(stack);
const controlAdapter = config.adapter.create(stack);
let toContract;
try {
toContract = familyInstance.deserializeContract(JSON.parse(contractJsonContent));
} catch (error) {
return notOk(errorContractValidationFailed(`Contract at ${contractPathAbsolute} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
}
const rawStorageHash = toContract.storage?.storageHash;
if (typeof rawStorageHash !== "string") return notOk(errorContractValidationFailed("Contract is missing storageHash", { where: { path: contractPathAbsolute } }));
let toStorageHash = rawStorageHash;
let toArtifacts = null;
let fromContract = null;
let fromHash = null;
let snapshotStartContract = null;
let isAutoBaseline = false;
const tolerantAggregateResult = await loadContractSpaceAggregateForCli({
targetId: config.target.targetId,
migrationsDir,
appContract: toContract,
extensionPacks: config.extensionPacks ?? [],
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!tolerantAggregateResult.ok) return notOk(tolerantAggregateResult.failure);
const resolutionSpace = tolerantAggregateResult.value.app;
const resolutionResult = await resolveFromForPlan({
optionsFrom: options.from,
space: resolutionSpace
});
if (!resolutionResult.ok) return notOk(resolutionResult.failure);
switch (resolutionResult.value.kind) {
case "greenfield": break;
case "graph-node":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
break;
case "snapshot":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
snapshotStartContract = {
fromHash: resolutionResult.value.fromHash,
contractJson: resolutionResult.value.contractJson,
contractDts: resolutionResult.value.contractDts
};
break;
case "auto-baseline":
fromHash = resolutionResult.value.fromHash;
fromContract = resolutionResult.value.fromContract;
snapshotStartContract = {
fromHash: resolutionResult.value.fromHash,
contractJson: resolutionResult.value.contractJson,
contractDts: resolutionResult.value.contractDts
};
isAutoBaseline = true;
break;
}
if (options.to !== void 0) {
const toResolution = await resolveToForPlan(options.to, { space: resolutionSpace });
if (!toResolution.ok) return notOk(toResolution.failure);
toContract = toResolution.value.contract;
toStorageHash = toResolution.value.hash;
toArtifacts = {
contractJson: toResolution.value.contractJson,
contractDts: toResolution.value.contractDts
};
}
const seedResult = await runContractSpaceSeedPhase({
migrationsDir,
extensionPacks: toExtensionInputs(config.extensionPacks ?? [])
});
if (!flags.json && !flags.quiet) {
for (const record of seedResult.seeded) if (record.action === "updated") {
const pkgSuffix = record.newMigrationDirs.length > 0 ? `; ${record.newMigrationDirs.length} new migration package(s) materialised` : "";
ui.step(`Updated ${record.spaceId} to ${record.newHash}${pkgSuffix}`);
}
}
const emittedExtensionDirs = seedResult.seeded.flatMap((r) => r.newMigrationDirs.map((dirName) => ({
spaceId: r.spaceId,
dirName
})));
if (fromHash === toStorageHash && !isAutoBaseline) return ok({
ok: true,
noOp: true,
from: fromHash,
to: toStorageHash,
operations: [],
emittedExtensionDirs,
summary: "No changes detected between contracts",
timings: { total: Date.now() - startTime }
});
const migrations = getTargetMigrations(config.target);
if (!migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
const aggregateResult = await buildContractSpaceAggregate({
targetId: config.target.targetId,
migrationsDir,
appContract: toContract,
extensionPacks: config.extensionPacks ?? [],
deserializeContract: (json) => familyInstance.deserializeContract(json)
});
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
const aggregate = aggregateResult.value;
const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
config.target,
config.adapter,
...config.extensionPacks ?? []
]);
async function writeDestinationSnapshot(destHash) {
if (toArtifacts !== null) {
await writeContractSnapshot(migrationsDir, destHash, {
contractJson: toArtifacts.contractJson,
contractDts: toArtifacts.contractDts
});
return;
}
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
const [contractJsonRaw, contractDts] = await Promise.all([readFile(destinationArtifacts.jsonPath, "utf-8"), readFile(destinationArtifacts.dtsPath, "utf-8")]);
await writeContractSnapshot(migrationsDir, destHash, {
contractJson: JSON.parse(contractJsonRaw),
contractDts
});
}
try {
const planner = migrations.createPlanner(controlAdapter);
if (isAutoBaseline && fromHash !== null && fromContract !== null && snapshotStartContract !== null) {
const baselineTimestamp = /* @__PURE__ */ new Date();
const deltaTimestamp = new Date(baselineTimestamp.getTime() + 6e4);
const baselineDirName = formatMigrationDirName(baselineTimestamp, "baseline");
const deltaDirName = formatMigrationDirName(deltaTimestamp, options.name ?? "migration");
const baselinePackageDir = join(appMigrationsDir, baselineDirName);
const deltaPackageDir = join(appMigrationsDir, deltaDirName);
const baselineLeg = await runPlannerLeg(planner, migrations, frameworkComponents, fromContract, null, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(baselinePackageDir, migrationsDir));
if (!baselineLeg.ok) return notOk(baselineLeg.failure);
await writePlannedMigrationPackage(baselinePackageDir, null, fromHash, baselineTimestamp, baselineLeg.value);
await writeContractSnapshot(migrationsDir, fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
if (fromHash === toStorageHash) {
const baselineOps = baselineLeg.value.hasPlaceholders ? [] : baselineLeg.value.plannedOps;
if (baselineLeg.value.hasPlaceholders) {
const baselineDir = relative(process.cwd(), baselinePackageDir);
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: baselineDir,
baselineDir,
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned baseline with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
}
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(baselineOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: baselineOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildAutoBaselinePlanSummary(0, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
}
const deltaLeg = await runPlannerLeg(planner, migrations, frameworkComponents, aggregate.app.contract(), fromContract, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(deltaPackageDir, migrationsDir));
if (!deltaLeg.ok) return notOk(deltaLeg.failure);
await writePlannedMigrationPackage(deltaPackageDir, fromHash, toStorageHash, deltaTimestamp, deltaLeg.value);
await writeDestinationSnapshot(toStorageHash);
await writeContractSnapshot(migrationsDir, fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
const deltaOps = deltaLeg.value.hasPlaceholders ? [] : deltaLeg.value.plannedOps;
if (deltaLeg.value.hasPlaceholders) return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), deltaPackageDir),
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned baseline + migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(deltaOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), deltaPackageDir),
baselineDir: relative(process.cwd(), baselinePackageDir),
operations: deltaOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildAutoBaselinePlanSummary(deltaOps.length, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
}
const timestamp = /* @__PURE__ */ new Date();
const packageDir = join(appMigrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
const deltaLeg = await runPlannerLeg(planner, migrations, frameworkComponents, aggregate.app.contract(), fromContract, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(packageDir, migrationsDir));
if (!deltaLeg.ok) return notOk(deltaLeg.failure);
await writePlannedMigrationPackage(packageDir, fromHash, toStorageHash, timestamp, deltaLeg.value);
await writeDestinationSnapshot(toStorageHash);
if (snapshotStartContract !== null) await writeContractSnapshot(migrationsDir, snapshotStartContract.fromHash, {
contractJson: snapshotStartContract.contractJson,
contractDts: snapshotStartContract.contractDts
});
if (deltaLeg.value.hasPlaceholders) return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), packageDir),
operations: [],
emittedExtensionDirs,
pendingPlaceholders: true,
summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
timings: { total: Date.now() - startTime }
});
const plannedOps = deltaLeg.value.plannedOps;
const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(plannedOps) : void 0;
return ok({
ok: true,
noOp: false,
from: fromHash,
to: toStorageHash,
dir: relative(process.cwd(), packageDir),
operations: plannedOps.map((op) => ({
id: op.id,
label: op.label,
operationClass: op.operationClass
})),
emittedExtensionDirs,
...preview !== void 0 ? { preview } : {},
summary: buildPlanSummary(plannedOps.length, emittedExtensionDirs.length),
timings: { total: Date.now() - startTime }
});
} catch (error) {
if (CliStructuredError.is(error)) return notOk(error);
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
const message = error instanceof Error ? error.message : String(error);
return notOk(errorUnexpected(message, { why: `Unexpected error during migration plan: ${message}` }));
}
}
function createMigrationPlanCommand() {
const command = new Command("plan");
setCommandDescriptions(command, "Plan a migration from contract changes", "Compares the emitted contract against the latest on-disk migration state and\nproduces a new migration package with the required operations. No database\nconnection is needed — this is a fully offline operation.");
setCommandExamples(command, [
"prisma-next migration plan",
"prisma-next migration plan --name add-users-table",
"prisma-next migration plan --to <migration-dir>^ --name rollback"
]);
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--name <slug>", "Name slug for the migration directory", "migration").option("--from <contract>", "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)").option("--to <contract>", "Destination contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path); defaults to the emitted contract").action(async (options) => {
const flags = parseGlobalFlagsOrExit(options);
const startTime = Date.now();
const ui = createTerminalUI(flags);
const exitCode = handleResult(await executeMigrationPlanCommand(options, flags, ui, startTime), flags, ui, (planResult) => {
if (flags.json) ui.output(JSON.stringify(planResult, null, 2));
else if (!flags.quiet) ui.log(formatMigrationPlanOutput(planResult, flags));
});
process.exit(exitCode);
});
return command;
}
/**
* Compose the success-line summary so the cross-space side effect
* (extension-space migration packages materialised on disk during
* this `plan` run) is visible in the top line — not just in the
* step log above it.
*
* Example outputs:
* - `Planned 3 operation(s)` (app-space-only project)
* - `Planned 3 operation(s); materialised 1 extension-space migration` (one extension)
* - `Planned 3 operation(s); materialised 2 extension-space migrations` (two extensions)
*
* Locks AC3 at the summary-line level: a reader of the success line
* can tell that something happened beyond the app space.
*/
function buildPlanSummary(plannedOpsCount, emittedExtensionDirsCount) {
const base = `Planned ${plannedOpsCount} operation(s)`;
if (emittedExtensionDirsCount === 0) return base;
return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
}
function buildAutoBaselinePlanSummary(deltaOpsCount, emittedExtensionDirsCount) {
const base = `Planned baseline + ${deltaOpsCount} operation(s)`;
if (emittedExtensionDirsCount === 0) return base;
return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
}
function formatMigrationPlanOutput(result, flags) {
const lines = [];
const useColor = flags.color !== false;
const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
function appendEmittedExtensions() {
if (result.emittedExtensionDirs.length === 0) return;
lines.push("");
lines.push(dim_("Emitted extension migrations:"));
for (const entry of result.emittedExtensionDirs) lines.push(dim_(` ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
lines.push("");
lines.push(`Next: review the extension migrations above, then run ${green_("prisma-next migrate")}.`);
}
if (result.noOp) {
lines.push(`${green_("✔")} No changes detected`);
lines.push(dim_(` from: ${result.from}`));
lines.push(dim_(` to: ${result.to}`));
appendEmittedExtensions();
return lines.join("\n");
}
if (result.pendingPlaceholders) {
lines.push(`${yellow_("⚠")} ${result.summary}`);
lines.push("");
lines.push(dim_(`from: ${result.from}`));
lines.push(dim_(`to: ${result.to}`));
if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
lines.push("");
lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
appendEmittedExtensions();
return lines.join("\n");
}
lines.push(`${green_("✔")} ${result.summary}`);
lines.push("");
if (result.operations.length > 0) {
lines.push(dim_("│"));
for (let i = 0; i < result.operations.length; i++) {
const op = result.operations[i];
const treeChar = i === result.operations.length - 1 ? "└" : "├";
const destructiveMarker = op.operationClass === "destructive" ? ` ${yellow_("(destructive)")}` : "";
lines.push(`${dim_(treeChar)}─ ${op.label}${destructiveMarker}`);
}
if (result.operations.some((op) => op.operationClass === "destructive")) {
lines.push("");
lines.push(`${yellow_("⚠")} This migration contains destructive operations that may cause data loss.`);
}
lines.push("");
}
lines.push(dim_(`from: ${result.from}`));
lines.push(dim_(`to: ${result.to}`));
if (result.baselineDir) lines.push(dim_(`Baseline → ${result.baselineDir}`));
if (result.dir) lines.push(dim_(`App space → ${result.dir}`));
for (const entry of result.emittedExtensionDirs) lines.push(dim_(`Extension space ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
lines.push("");
const reviewTarget = result.baselineDir !== void 0 && result.dir !== void 0 ? `${result.baselineDir} and ${result.dir}` : result.baselineDir ?? result.dir ?? "<dir>";
lines.push(`Next: review ${green_(reviewTarget)} if needed, then run ${green_("prisma-next migrate")}.`);
if (result.preview && result.preview.statements.length > 0) {
const allSql = result.preview.statements.every((s) => s.language === "sql");
lines.push("");
lines.push(dim_(allSql ? "DDL preview" : "Operation preview"));
lines.push("");
for (const statement of result.preview.statements) {
const trimmed = statement.text.trim();
if (!trimmed) continue;
const line = statement.language === "sql" && !trimmed.endsWith(";") ? `${trimmed};` : trimmed;
lines.push(line);
}
}
if (flags.verbose && result.timings) {
lines.push("");
lines.push(dim_(`Total time: ${result.timings.total}ms`));
}
return lines.join("\n");
}
/**
* Resolve a migration package by **target contract hash** (`metadata.to`)
* using exact match or prefix match.
*
* Note: matches `metadata.to` (the contract hash this migration produces),
* not `metadata.migrationHash` (the package's content-addressed identity).
* Tries exact match first, then prefix match (auto-prepending `sha256:` when
* the needle omits the scheme). Returns the matched package on success, or a
* discriminated failure indicating whether the prefix was ambiguous or simply
* not found.
*
* @internal Exported for testing only.
*/
function resolveBundleByPrefix(bundles, needle) {
const exact = bundles.find((p) => p.metadata.to === needle);
if (exact) return ok(exact);
const prefixWithScheme = needle.startsWith("sha256:") ? needle : `sha256:${needle}`;
const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
if (candidates.length === 1) return ok(candidates[0]);
if (candidates.length > 1) return notOk({
reason: "ambiguous",
count: candidates.length
});
return notOk({ reason: "not-found" });
}
//#endregion
export { formatMigrationPlanOutput as n, resolveBundleByPrefix as r, createMigrationPlanCommand as t };
//# sourceMappingURL=migration-plan-DuMEUejG.mjs.map

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

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-Cpq44aVa.mjs";
import { t as createControlClient } from "./client-KuBQftxz.mjs";
import { n as buildReadAggregate, o as refusePackageCorruptionOnAggregate, r as loadContractRawSafely } from "./contract-space-aggregate-loader-D_3VeHVb.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-6z6vuXHa.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-B61YOaMl.mjs.map
{"version":3,"file":"migration-status-B61YOaMl.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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"}
import { ifDefined } from "@prisma-next/utils/defined";
import { readFile } from "node:fs/promises";
import { writeRefPaired } from "@prisma-next/migration-tools/refs";
//#region src/utils/ref-advancement.ts
function computeRefAdvancementName(options) {
if (options.advanceRef !== void 0) return options.advanceRef;
if (options.db === void 0) return "db";
return null;
}
async function readContractIR(contractJson, contractJsonPath) {
return {
contract: contractJson,
contractDts: await readFile(contractJsonPath.replace(/\.json$/i, ".d.ts"), "utf-8")
};
}
async function executeRefAdvancement(refsDir, name, hash, contractIR) {
await writeRefPaired(refsDir, name, {
hash,
invariants: []
}, contractIR);
return {
name,
hash
};
}
async function buildRefAdvancementFields(options) {
const name = computeRefAdvancementName({
...ifDefined("advanceRef", options.advanceRef),
...ifDefined("db", options.db)
});
if (name === null) return {
advancedRef: null,
plannedAdvanceRef: null
};
if (options.mode === "plan") return {
advancedRef: null,
plannedAdvanceRef: {
name,
hash: options.hash
}
};
return {
advancedRef: await executeRefAdvancement(options.refsDir, name, options.hash, options.contractIR),
plannedAdvanceRef: null
};
}
//#endregion
export { readContractIR as i, computeRefAdvancementName as n, executeRefAdvancement as r, buildRefAdvancementFields as t };
//# sourceMappingURL=ref-advancement-BkXlikCA.mjs.map
{"version":3,"file":"ref-advancement-BkXlikCA.mjs","names":[],"sources":["../src/utils/ref-advancement.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { ContractIR } from '@prisma-next/migration-tools/refs';\nimport { writeRefPaired } from '@prisma-next/migration-tools/refs';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport interface RefAdvancementFields {\n readonly advancedRef: { readonly name: string; readonly hash: string } | null;\n readonly plannedAdvanceRef: { readonly name: string; readonly hash: string } | null;\n}\n\nexport function computeRefAdvancementName(options: {\n readonly advanceRef?: string;\n readonly db?: string;\n}): string | null {\n if (options.advanceRef !== undefined) {\n return options.advanceRef;\n }\n if (options.db === undefined) {\n return 'db';\n }\n return null;\n}\n\nexport async function readContractIR(\n contractJson: Record<string, unknown>,\n contractJsonPath: string,\n): Promise<ContractIR> {\n const contractDtsPath = contractJsonPath.replace(/\\.json$/i, '.d.ts');\n const contractDts = await readFile(contractDtsPath, 'utf-8');\n return { contract: contractJson, contractDts };\n}\n\nexport async function executeRefAdvancement(\n refsDir: string,\n name: string,\n hash: string,\n contractIR: ContractIR,\n): Promise<{ name: string; hash: string }> {\n await writeRefPaired(refsDir, name, { hash, invariants: [] }, contractIR);\n return { name, hash };\n}\n\nexport async function buildRefAdvancementFields(options: {\n readonly advanceRef?: string;\n readonly db?: string;\n readonly refsDir: string;\n readonly contractIR: ContractIR;\n readonly mode: 'plan' | 'apply';\n readonly hash: string;\n}): Promise<RefAdvancementFields> {\n const name = computeRefAdvancementName({\n ...ifDefined('advanceRef', options.advanceRef),\n ...ifDefined('db', options.db),\n });\n if (name === null) {\n return { advancedRef: null, plannedAdvanceRef: null };\n }\n if (options.mode === 'plan') {\n return { advancedRef: null, plannedAdvanceRef: { name, hash: options.hash } };\n }\n const advancedRef = await executeRefAdvancement(\n options.refsDir,\n name,\n options.hash,\n options.contractIR,\n );\n return { advancedRef, plannedAdvanceRef: null };\n}\n"],"mappings":";;;;AAUA,SAAgB,0BAA0B,SAGxB;CAChB,IAAI,QAAQ,eAAe,KAAA,GACzB,OAAO,QAAQ;CAEjB,IAAI,QAAQ,OAAO,KAAA,GACjB,OAAO;CAET,OAAO;AACT;AAEA,eAAsB,eACpB,cACA,kBACqB;CAGrB,OAAO;EAAE,UAAU;EAAc,aAAA,MADP,SADF,iBAAiB,QAAQ,YAAY,OACZ,GAAG,OAAO;CACd;AAC/C;AAEA,eAAsB,sBACpB,SACA,MACA,MACA,YACyC;CACzC,MAAM,eAAe,SAAS,MAAM;EAAE;EAAM,YAAY,CAAC;CAAE,GAAG,UAAU;CACxE,OAAO;EAAE;EAAM;CAAK;AACtB;AAEA,eAAsB,0BAA0B,SAOd;CAChC,MAAM,OAAO,0BAA0B;EACrC,GAAG,UAAU,cAAc,QAAQ,UAAU;EAC7C,GAAG,UAAU,MAAM,QAAQ,EAAE;CAC/B,CAAC;CACD,IAAI,SAAS,MACX,OAAO;EAAE,aAAa;EAAM,mBAAmB;CAAK;CAEtD,IAAI,QAAQ,SAAS,QACnB,OAAO;EAAE,aAAa;EAAM,mBAAmB;GAAE;GAAM,MAAM,QAAQ;EAAK;CAAE;CAQ9E,OAAO;EAAE,aAAA,MANiB,sBACxB,QAAQ,SACR,MACA,QAAQ,MACR,QAAQ,UACV;EACsB,mBAAmB;CAAK;AAChD"}
import { D as isCI, O as formatCommandHelp, _ as createTerminalUI, g as parseGlobalFlagsOrExit, h as parseGlobalFlags, l as setCommandDescriptions, t as addGlobalOptions, u as setCommandExamples } from "./command-helpers-Cpq44aVa.mjs";
import { Command } from "commander";
import { readUserConfig, resolveGating, userConfigPath, writeUserConfig } from "@prisma-next/cli-telemetry";
//#region src/commands/telemetry/status.ts
/**
* Resolves the same gate the runtime uses (CI check + `resolveGating`) and
* projects it into a user-facing status. Pure read: never mints, never
* writes. The `installationId` value itself is never surfaced — only its
* presence — so `status` discloses nothing identifying.
*/
function resolveTelemetryStatus(inputs) {
const config = readUserConfig();
const configPath = userConfigPath();
const installationIdStored = typeof config.installationId === "string" && config.installationId.length > 0;
if (inputs.inCI) return {
enabled: false,
reason: "ci",
configPath,
installationIdStored
};
const gating = resolveGating({
env: inputs.env,
config
});
if (!gating.enabled) return {
enabled: false,
reason: gating.reason === "env-override" ? "env-opt-out" : "stored-opt-out",
configPath,
installationIdStored
};
return {
enabled: true,
reason: config.enableTelemetry === true ? "stored-opt-in" : "default-on",
configPath,
installationIdStored
};
}
const REASON_EXPLANATION = {
ci: "CI environment detected — telemetry is hard-disabled.",
"env-opt-out": "an environment opt-out is set (DO_NOT_TRACK / PRISMA_NEXT_DISABLE_TELEMETRY).",
"stored-opt-out": "\"enableTelemetry\": false is stored in your config.",
"stored-opt-in": "\"enableTelemetry\": true is stored in your config.",
"default-on": "no explicit choice is stored, so the opt-out default applies."
};
function formatTelemetryStatusLines(status) {
return [
`Telemetry is ${status.enabled ? "enabled" : "disabled"}: ${REASON_EXPLANATION[status.reason]}`,
`Config file: ${status.configPath}`,
`Installation ID: ${status.installationIdStored ? "stored" : "not stored"}`
];
}
//#endregion
//#region src/commands/telemetry/index.ts
function createTelemetryStatusCommand() {
const command = new Command("status");
setCommandDescriptions(command, "Show whether anonymous CLI telemetry is enabled and why", "Reports whether telemetry is currently enabled or disabled and the reason\n(default-on, stored opt-out, environment opt-out, or CI), the path to your\nuser-level config file, and whether an installation ID has been stored.\nRead-only: never sends an event, never mints an ID, never writes anything.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
const ui = createTerminalUI(flags);
const status = resolveTelemetryStatus({
env: process.env,
inCI: isCI()
});
if (flags.json) ui.output(JSON.stringify(status));
else for (const line of formatTelemetryStatusLines(status)) ui.output(line);
process.exit(0);
});
}
function createTelemetryEnableCommand() {
const command = new Command("enable");
setCommandDescriptions(command, "Enable anonymous CLI telemetry", "Stores \"enableTelemetry\": true in your user-level config and mints an\ninstallation ID if one is not already stored.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
writeUserConfig({ enableTelemetry: true });
const ui = createTerminalUI(flags);
if (flags.json) ui.output(JSON.stringify({
enableTelemetry: true,
configPath: userConfigPath()
}));
else ui.output(`Telemetry enabled. Preference stored in ${userConfigPath()}.`);
process.exit(0);
});
}
function createTelemetryDisableCommand() {
const command = new Command("disable");
setCommandDescriptions(command, "Disable anonymous CLI telemetry", "Stores \"enableTelemetry\": false in your user-level config. No installation\nID is minted and no event is sent.");
return addGlobalOptions(command).action((options) => {
const flags = parseGlobalFlagsOrExit(options);
writeUserConfig({ enableTelemetry: false });
const ui = createTerminalUI(flags);
if (flags.json) ui.output(JSON.stringify({
enableTelemetry: false,
configPath: userConfigPath()
}));
else ui.output(`Telemetry disabled. Preference stored in ${userConfigPath()}.`);
process.exit(0);
});
}
function createTelemetryCommand() {
const command = new Command("telemetry");
setCommandDescriptions(command, "Inspect and change anonymous CLI telemetry", "Show telemetry status, or enable / disable anonymous CLI usage data.\nTelemetry is on by default (opt-out); see https://prisma-next.dev/docs/cli/telemetry\nfor what is collected and why.");
setCommandExamples(command, [
"prisma-next telemetry status",
"prisma-next telemetry disable",
"prisma-next telemetry enable"
]);
command.configureHelp({
formatHelp: (cmd) => formatCommandHelp({
command: cmd,
flags: parseGlobalFlags({})
}),
subcommandDescription: () => ""
});
command.addCommand(createTelemetryStatusCommand());
command.addCommand(createTelemetryEnableCommand());
command.addCommand(createTelemetryDisableCommand());
return command;
}
//#endregion
export { createTelemetryCommand as t };
//# sourceMappingURL=telemetry-DVr6RFnq.mjs.map
{"version":3,"file":"telemetry-DVr6RFnq.mjs","names":[],"sources":["../src/commands/telemetry/status.ts","../src/commands/telemetry/index.ts"],"sourcesContent":["import { readUserConfig, resolveGating, userConfigPath } from '@prisma-next/cli-telemetry';\n\n/**\n * Why telemetry resolves the way it does, in the order the CLI's\n * `resolveTelemetryGate` evaluates: CI hard-disables first, then the env\n * opt-outs, then the stored `enableTelemetry`, then the opt-out default.\n */\nexport type TelemetryStatusReason =\n | 'ci'\n | 'env-opt-out'\n | 'stored-opt-out'\n | 'stored-opt-in'\n | 'default-on';\n\nexport interface TelemetryStatus {\n readonly enabled: boolean;\n readonly reason: TelemetryStatusReason;\n readonly configPath: string;\n readonly installationIdStored: boolean;\n}\n\n/**\n * Resolves the same gate the runtime uses (CI check + `resolveGating`) and\n * projects it into a user-facing status. Pure read: never mints, never\n * writes. The `installationId` value itself is never surfaced — only its\n * presence — so `status` discloses nothing identifying.\n */\nexport function resolveTelemetryStatus(inputs: {\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly inCI: boolean;\n}): TelemetryStatus {\n const config = readUserConfig();\n const configPath = userConfigPath();\n const installationIdStored =\n typeof config.installationId === 'string' && config.installationId.length > 0;\n\n if (inputs.inCI) {\n return { enabled: false, reason: 'ci', configPath, installationIdStored };\n }\n\n const gating = resolveGating({ env: inputs.env, config });\n if (!gating.enabled) {\n const reason: TelemetryStatusReason =\n gating.reason === 'env-override' ? 'env-opt-out' : 'stored-opt-out';\n return { enabled: false, reason, configPath, installationIdStored };\n }\n\n const reason: TelemetryStatusReason =\n config.enableTelemetry === true ? 'stored-opt-in' : 'default-on';\n return { enabled: true, reason, configPath, installationIdStored };\n}\n\nconst REASON_EXPLANATION: Record<TelemetryStatusReason, string> = {\n ci: 'CI environment detected — telemetry is hard-disabled.',\n 'env-opt-out': 'an environment opt-out is set (DO_NOT_TRACK / PRISMA_NEXT_DISABLE_TELEMETRY).',\n 'stored-opt-out': '\"enableTelemetry\": false is stored in your config.',\n 'stored-opt-in': '\"enableTelemetry\": true is stored in your config.',\n 'default-on': 'no explicit choice is stored, so the opt-out default applies.',\n};\n\nexport function formatTelemetryStatusLines(status: TelemetryStatus): string[] {\n return [\n `Telemetry is ${status.enabled ? 'enabled' : 'disabled'}: ${REASON_EXPLANATION[status.reason]}`,\n `Config file: ${status.configPath}`,\n `Installation ID: ${status.installationIdStored ? 'stored' : 'not stored'}`,\n ];\n}\n","import { userConfigPath, writeUserConfig } from '@prisma-next/cli-telemetry';\nimport { Command } from 'commander';\nimport {\n addGlobalOptions,\n setCommandDescriptions,\n setCommandExamples,\n} from '../../utils/command-helpers';\nimport { formatCommandHelp } from '../../utils/formatters/help';\nimport {\n type CommonCommandOptions,\n parseGlobalFlags,\n parseGlobalFlagsOrExit,\n} from '../../utils/global-flags';\nimport { isCI } from '../../utils/is-ci';\nimport { createTerminalUI } from '../../utils/terminal-ui';\nimport { formatTelemetryStatusLines, resolveTelemetryStatus } from './status';\n\nfunction createTelemetryStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show whether anonymous CLI telemetry is enabled and why',\n 'Reports whether telemetry is currently enabled or disabled and the reason\\n' +\n '(default-on, stored opt-out, environment opt-out, or CI), the path to your\\n' +\n 'user-level config file, and whether an installation ID has been stored.\\n' +\n 'Read-only: never sends an event, never mints an ID, never writes anything.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const status = resolveTelemetryStatus({ env: process.env, inCI: isCI() });\n if (flags.json) {\n ui.output(JSON.stringify(status));\n } else {\n for (const line of formatTelemetryStatusLines(status)) {\n ui.output(line);\n }\n }\n process.exit(0);\n });\n}\n\nfunction createTelemetryEnableCommand(): Command {\n const command = new Command('enable');\n setCommandDescriptions(\n command,\n 'Enable anonymous CLI telemetry',\n 'Stores \"enableTelemetry\": true in your user-level config and mints an\\n' +\n 'installation ID if one is not already stored.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n writeUserConfig({ enableTelemetry: true });\n const ui = createTerminalUI(flags);\n if (flags.json) {\n ui.output(JSON.stringify({ enableTelemetry: true, configPath: userConfigPath() }));\n } else {\n ui.output(`Telemetry enabled. Preference stored in ${userConfigPath()}.`);\n }\n process.exit(0);\n });\n}\n\nfunction createTelemetryDisableCommand(): Command {\n const command = new Command('disable');\n setCommandDescriptions(\n command,\n 'Disable anonymous CLI telemetry',\n 'Stores \"enableTelemetry\": false in your user-level config. No installation\\n' +\n 'ID is minted and no event is sent.',\n );\n return addGlobalOptions(command).action((options: CommonCommandOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n writeUserConfig({ enableTelemetry: false });\n const ui = createTerminalUI(flags);\n if (flags.json) {\n ui.output(JSON.stringify({ enableTelemetry: false, configPath: userConfigPath() }));\n } else {\n ui.output(`Telemetry disabled. Preference stored in ${userConfigPath()}.`);\n }\n process.exit(0);\n });\n}\n\nexport function createTelemetryCommand(): Command {\n const command = new Command('telemetry');\n setCommandDescriptions(\n command,\n 'Inspect and change anonymous CLI telemetry',\n 'Show telemetry status, or enable / disable anonymous CLI usage data.\\n' +\n 'Telemetry is on by default (opt-out); see https://prisma-next.dev/docs/cli/telemetry\\n' +\n 'for what is collected and why.',\n );\n setCommandExamples(command, [\n 'prisma-next telemetry status',\n 'prisma-next telemetry disable',\n 'prisma-next telemetry enable',\n ]);\n command.configureHelp({\n formatHelp: (cmd) => formatCommandHelp({ command: cmd, flags: parseGlobalFlags({}) }),\n subcommandDescription: () => '',\n });\n command.addCommand(createTelemetryStatusCommand());\n command.addCommand(createTelemetryEnableCommand());\n command.addCommand(createTelemetryDisableCommand());\n return command;\n}\n"],"mappings":"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"}
import { M as createColorFormatter, N as formatDim, P as isVerbose } from "./command-helpers-Cpq44aVa.mjs";
import { ifDefined } from "@prisma-next/utils/defined";
import { issueOutcome } from "@prisma-next/framework-components/control";
import { bold, cyan, dim, green, magenta, red, yellow } from "colorette";
//#region src/utils/formatters/verify.ts
/** Human-readable label for each outcome, prefixed onto an issue's message for display. */
const OUTCOME_LABEL = {
"not-found": "missing",
"not-expected": "extra",
"not-equal": "mismatch"
};
/**
* The issue's display text: its own path, prefixed with a human label for
* why it's flagged. Turning the presence-derived outcome (and the path) into
* prose is this formatter's job, not the differ's — the differ's issue is
* data (`path` + nodes), not prose.
*/
function formatIssueMessage(issue) {
return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join("/")}`;
}
/**
* Formats human-readable output for database verify.
*/
function formatVerifyOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatYellow = createColorFormatter(useColor, yellow);
const formatDimText = (text) => formatDim(useColor, text);
const verificationMode = result.mode === "full" ? `marker + schema${result.schema?.strict ? " (strict)" : " (tolerant)"}` : "marker only (--marker-only)";
lines.push(`${formatGreen("✔")} ${result.summary}`);
lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);
lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);
if (result.contract.profileHash) lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
if (result.schema?.warnings && result.schema.warnings.length > 0) {
lines.push("");
lines.push(formatYellow("Schema warnings:"));
for (const message of result.schema.warnings) lines.push(` ${formatYellow("⚠")} ${message}`);
}
if (result.unclaimed && result.unclaimed.length > 0) {
lines.push("");
lines.push(formatYellow("Unclaimed elements (declared by no contract):"));
for (const name of result.unclaimed) lines.push(` ${formatYellow("⚠")} ${name}`);
}
if (result.warning) {
lines.push("");
lines.push(`${formatYellow("⚠")} ${result.warning}`);
}
if (isVerbose(flags, 1)) {
if (result.codecCoverageSkipped) lines.push(`${formatDimText(" Codec coverage check skipped (helper returned no supported types)")}`);
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
}
return lines.join("\n");
}
/**
* Formats JSON output for database verify.
*/
function formatVerifyJson(result) {
const output = {
ok: result.ok,
summary: result.summary,
mode: result.mode,
contract: result.contract,
...ifDefined("marker", result.marker),
target: result.target,
...ifDefined("missingCodecs", result.missingCodecs),
...ifDefined("codecCoverageSkipped", result.codecCoverageSkipped),
...ifDefined("schema", result.schema),
unclaimed: result.unclaimed ?? [],
...ifDefined("warning", result.warning),
...ifDefined("meta", result.meta),
timings: result.timings
};
return JSON.stringify(output, null, 2);
}
/**
* Formats JSON output for database introspection.
*/
function formatIntrospectJson(result) {
return JSON.stringify(result, null, 2);
}
/**
* Renders a schema tree structure from CoreSchemaView.
* Status-glyph tree styling shared with the retired verification-tree renderer.
*/
function renderSchemaTree(node, flags, options) {
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
const lines = [];
let formattedLabel = node.label;
if (useColor) switch (node.kind) {
case "root":
formattedLabel = bold(node.label);
break;
case "entity": {
const tableMatch = node.label.match(/^table\s+(.+)$/);
if (tableMatch?.[1]) {
const tableName = tableMatch[1];
formattedLabel = `${dim("table")} ${cyan(tableName)}`;
} else formattedLabel = cyan(node.label);
break;
}
case "collection":
formattedLabel = dim(node.label);
break;
case "field": {
const columnMatch = node.label.match(/^([^:]+):\s*(.+)$/);
if (columnMatch?.[1] && columnMatch[2]) {
const columnName = columnMatch[1];
const rest = columnMatch[2];
const typeMatch = rest.match(/^([^\s(]+)\s*(\([^)]+\))$/);
if (typeMatch?.[1] && typeMatch[2]) {
const typeDisplay = typeMatch[1];
const nullability = typeMatch[2];
formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;
} else formattedLabel = `${cyan(columnName)}: ${rest}`;
} else formattedLabel = node.label;
break;
}
case "index": {
const pkMatch = node.label.match(/^primary key:\s*(.+)$/);
if (pkMatch?.[1]) {
const columnNames = pkMatch[1];
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
} else {
const uniqueMatch = node.label.match(/^unique\s+(.+)$/);
if (uniqueMatch?.[1]) {
const name = uniqueMatch[1];
formattedLabel = `${dim("unique")} ${cyan(name)}`;
} else {
const indexMatch = node.label.match(/^(unique\s+)?index\s+(.+)$/);
if (indexMatch?.[2]) {
const indexPrefix = indexMatch[1] ? `${dim("unique")} ` : "";
const name = indexMatch[2];
formattedLabel = `${indexPrefix}${dim("index")} ${cyan(name)}`;
} else formattedLabel = dim(node.label);
}
}
break;
}
case "dependency": {
const extMatch = node.label.match(/^([^\s]+)\s+(extension is enabled)$/);
if (extMatch?.[1] && extMatch[2]) {
const extName = extMatch[1];
const rest = extMatch[2];
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
} else formattedLabel = magenta(node.label);
break;
}
default:
formattedLabel = node.label;
break;
}
if (isRoot) lines.push(formattedLabel);
else {
const treePrefix = `${formatDimText(isLast ? "└" : "├")}─ `;
lines.push(`${prefix}${treePrefix}${formattedLabel}`);
}
if (node.children && node.children.length > 0) {
const childPrefix = isRoot ? "" : `${prefix}${isLast ? " " : `${formatDimText("│")} `}`;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (!child) continue;
const childLines = renderSchemaTree(child, flags, {
isLast: i === node.children.length - 1,
prefix: childPrefix,
useColor,
formatDimText,
isRoot: false
});
lines.push(...childLines);
}
}
return lines;
}
/**
* Formats human-readable output for database introspection.
*/
function formatIntrospectOutput(result, schemaView, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatDimText = (text) => formatDim(useColor, text);
if (schemaView) {
const treeLines = renderSchemaTree(schemaView.root, flags, {
isLast: true,
prefix: "",
useColor,
formatDimText,
isRoot: true
});
lines.push(...treeLines);
} else {
lines.push(`✔ ${result.summary}`);
if (isVerbose(flags, 1)) {
lines.push(` Target: ${result.target.familyId}/${result.target.id}`);
if (result.meta?.dbUrl) lines.push(` Database: ${result.meta.dbUrl}`);
}
}
if (isVerbose(flags, 1)) lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
return lines.join("\n");
}
/**
* Formats human-readable output for database schema verification.
*/
function formatSchemaVerifyOutput(result, flags, unclaimed = []) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatRed = createColorFormatter(useColor, red);
const formatYellow = createColorFormatter(useColor, yellow);
const formatDimText = (text) => formatDim(useColor, text);
const issueMessages = result.schema.issues.map(formatIssueMessage);
if (issueMessages.length > 0) {
lines.push(formatRed("Schema issues:"));
for (const message of issueMessages) lines.push(` ${formatRed("✖")} ${message}`);
}
const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);
if (warningMessages.length > 0) {
if (lines.length > 0) lines.push("");
lines.push(formatYellow("Schema warnings:"));
for (const message of warningMessages) lines.push(` ${formatYellow("⚠")} ${message}`);
}
if (unclaimed.length > 0) {
const strict = result.meta?.strict ?? false;
if (lines.length > 0) lines.push("");
lines.push((strict ? formatRed : formatYellow)("Unclaimed elements (declared by no contract):"));
for (const name of unclaimed) lines.push(` ${(strict ? formatRed : formatYellow)(strict ? "✖" : "⚠")} ${name}`);
}
if (isVerbose(flags, 1)) lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
if (lines.length > 0) lines.push("");
if (result.ok) lines.push(`${formatGreen("✔")} ${result.summary}`);
else {
const codeText = result.code ? ` (${result.code})` : "";
lines.push(`${formatRed("✖")} ${result.summary}${codeText}`);
}
return lines.join("\n");
}
/**
* Formats JSON output for database schema verification. The unclaimed-elements
* list is a top-level field alongside the combined result, reported once for
* the whole database.
*/
function formatSchemaVerifyJson(result, unclaimed = []) {
return JSON.stringify({
...result,
unclaimed
}, null, 2);
}
/**
* Formats human-readable output for database sign.
*/
function formatSignOutput(result, flags) {
if (flags.quiet) return "";
const lines = [];
const useColor = flags.color !== false;
const formatGreen = createColorFormatter(useColor, green);
const formatDimText = (text) => formatDim(useColor, text);
if (result.ok) {
lines.push(`${formatGreen("✔")} Database signed`);
const previousHash = result.marker.previous?.storageHash ?? "none";
const currentHash = result.contract.storageHash;
lines.push(`${formatDimText(` from: ${previousHash}`)}`);
lines.push(`${formatDimText(` to: ${currentHash}`)}`);
if (isVerbose(flags, 1)) {
if (result.contract.profileHash) lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
if (result.marker.previous?.profileHash) lines.push(`${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`);
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
}
}
return lines.join("\n");
}
/**
* Formats JSON output for database sign.
*/
function formatSignJson(result) {
return JSON.stringify(result, null, 2);
}
//#endregion
export { formatSignJson as a, formatVerifyOutput as c, formatSchemaVerifyOutput as i, formatIntrospectOutput as n, formatSignOutput as o, formatSchemaVerifyJson as r, formatVerifyJson as s, formatIntrospectJson as t };
//# sourceMappingURL=verify-CgDVZjhc.mjs.map
{"version":3,"file":"verify-CgDVZjhc.mjs","names":[],"sources":["../src/utils/formatters/verify.ts"],"sourcesContent":["import type {\n CoreSchemaView,\n ExpectationFailureReason,\n IntrospectSchemaResult,\n SchemaDiffIssue,\n SchemaTreeNode,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { issueOutcome } from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n/** Human-readable label for each outcome, prefixed onto an issue's message for display. */\nconst OUTCOME_LABEL: Record<ExpectationFailureReason, string> = {\n 'not-found': 'missing',\n 'not-expected': 'extra',\n 'not-equal': 'mismatch',\n};\n\n/**\n * The issue's display text: its own path, prefixed with a human label for\n * why it's flagged. Turning the presence-derived outcome (and the path) into\n * prose is this formatter's job, not the differ's — the differ's issue is\n * data (`path` + nodes), not prose.\n */\nfunction formatIssueMessage(issue: SchemaDiffIssue): string {\n return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join('/')}`;\n}\n\n// ============================================================================\n// Verify Output Formatters\n// ============================================================================\n\nexport interface DbVerifyCommandSuccessResult {\n readonly ok: true;\n readonly mode: 'full' | 'marker-only';\n readonly summary: string;\n readonly contract: VerifyDatabaseResult['contract'];\n readonly marker?: VerifyDatabaseResult['marker'];\n readonly target: VerifyDatabaseResult['target'];\n readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];\n readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];\n readonly schema?: {\n readonly summary: string;\n readonly strict: boolean;\n /**\n * Warn-graded finding messages (observed-policy drift). Informational —\n * present on a passing verify; the full-mode result summarizes them as a\n * flat message list.\n */\n readonly warnings?: readonly string[];\n };\n /**\n * Live element names no contract space declares. In full success this is\n * only ever non-empty in lenient mode — strict mode fails on it — and is\n * rendered informationally.\n */\n readonly unclaimed?: readonly string[];\n readonly warning?: string;\n readonly meta?:\n | (NonNullable<VerifyDatabaseResult['meta']> & {\n readonly schemaVerification: 'performed' | 'skipped';\n })\n | {\n readonly schemaVerification: 'performed' | 'skipped';\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for database verify.\n */\nexport function formatVerifyOutput(\n result: DbVerifyCommandSuccessResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n const verificationMode =\n result.mode === 'full'\n ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`\n : 'marker only (--marker-only)';\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);\n lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.schema?.warnings && result.schema.warnings.length > 0) {\n lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of result.schema.warnings) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (result.unclaimed && result.unclaimed.length > 0) {\n lines.push('');\n lines.push(formatYellow('Unclaimed elements (declared by no contract):'));\n for (const name of result.unclaimed) {\n lines.push(` ${formatYellow('⚠')} ${name}`);\n }\n }\n\n if (result.warning) {\n lines.push('');\n lines.push(`${formatYellow('⚠')} ${result.warning}`);\n }\n\n if (isVerbose(flags, 1)) {\n if (result.codecCoverageSkipped) {\n lines.push(\n `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database verify.\n */\nexport function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {\n const output = {\n ok: result.ok,\n summary: result.summary,\n mode: result.mode,\n contract: result.contract,\n ...ifDefined('marker', result.marker),\n target: result.target,\n ...ifDefined('missingCodecs', result.missingCodecs),\n ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),\n ...ifDefined('schema', result.schema),\n unclaimed: result.unclaimed ?? [],\n ...ifDefined('warning', result.warning),\n ...ifDefined('meta', result.meta),\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Formats JSON output for database introspection.\n */\nexport function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Renders a schema tree structure from CoreSchemaView.\n * Status-glyph tree styling shared with the retired verification-tree renderer.\n */\nfunction renderSchemaTree(\n node: SchemaTreeNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format node label with color based on kind (matching schema-verify style)\n let formattedLabel: string = node.label;\n\n if (useColor) {\n switch (node.kind) {\n case 'root':\n formattedLabel = bold(node.label);\n break;\n case 'entity': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.label.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n // Fallback: color entire label with cyan\n formattedLabel = cyan(node.label);\n }\n break;\n }\n case 'collection': {\n // \"columns\" grouping node - dim the label\n formattedLabel = dim(node.label);\n break;\n }\n case 'field': {\n // Parse column name format: \"columnName: typeDisplay (nullability)\"\n // Color code: column name (cyan), type (default), nullability (dim)\n const columnMatch = node.label.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"typeDisplay (nullability)\"\n const typeMatch = rest.match(/^([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2]) {\n const typeDisplay = typeMatch[1];\n const nullability = typeMatch[2];\n formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.label;\n }\n break;\n }\n case 'index': {\n // Parse index/unique constraint/primary key formats\n // \"primary key: columnName\" -> dim \"primary key\", cyan columnName\n const pkMatch = node.label.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n // \"unique name\" -> dim \"unique\", cyan \"name\"\n const uniqueMatch = node.label.match(/^unique\\s+(.+)$/);\n if (uniqueMatch?.[1]) {\n const name = uniqueMatch[1];\n formattedLabel = `${dim('unique')} ${cyan(name)}`;\n } else {\n // \"index name\" or \"unique index name\" -> dim label prefix, cyan name\n const indexMatch = node.label.match(/^(unique\\s+)?index\\s+(.+)$/);\n if (indexMatch?.[2]) {\n const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';\n const name = indexMatch[2];\n formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;\n } else {\n formattedLabel = dim(node.label);\n }\n }\n }\n break;\n }\n case 'dependency': {\n // Parse extension message formats similar to schema-verify\n // \"extensionName extension is enabled\" -> cyan extensionName, dim rest\n const extMatch = node.label.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire label with magenta\n formattedLabel = magenta(node.label);\n }\n break;\n }\n default:\n formattedLabel = node.label;\n break;\n }\n }\n\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(formattedLabel);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${formattedLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database introspection.\n */\nexport function formatIntrospectOutput(\n result: IntrospectSchemaResult<unknown>,\n schemaView: CoreSchemaView | undefined,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (schemaView) {\n // Render tree structure - root node is special (no tree characters)\n const treeLines = renderSchemaTree(schemaView.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n } else {\n // Fallback: print summary when toSchemaView is not available\n lines.push(`✔ ${result.summary}`);\n if (isVerbose(flags, 1)) {\n lines.push(` Target: ${result.target.familyId}/${result.target.id}`);\n if (result.meta?.dbUrl) {\n lines.push(` Database: ${result.meta.dbUrl}`);\n }\n }\n }\n\n // Add timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats human-readable output for database schema verification.\n */\nexport function formatSchemaVerifyOutput(\n result: VerifyDatabaseSchemaResult,\n flags: GlobalFlags,\n unclaimed: readonly string[] = [],\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatRed = createColorFormatter(useColor, red);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n const issueMessages = result.schema.issues.map(formatIssueMessage);\n if (issueMessages.length > 0) {\n lines.push(formatRed('Schema issues:'));\n for (const message of issueMessages) {\n lines.push(` ${formatRed('✖')} ${message}`);\n }\n }\n\n const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);\n if (warningMessages.length > 0) {\n if (lines.length > 0) lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of warningMessages) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (unclaimed.length > 0) {\n const strict = result.meta?.strict ?? false;\n if (lines.length > 0) lines.push('');\n lines.push(\n (strict ? formatRed : formatYellow)('Unclaimed elements (declared by no contract):'),\n );\n for (const name of unclaimed) {\n lines.push(` ${(strict ? formatRed : formatYellow)(strict ? '✖' : '⚠')} ${name}`);\n }\n }\n\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n // Blank line before summary\n if (lines.length > 0) lines.push('');\n\n // Summary line at the end: verdict with status glyph\n if (result.ok) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n } else {\n const codeText = result.code ? ` (${result.code})` : '';\n lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database schema verification. The unclaimed-elements\n * list is a top-level field alongside the combined result, reported once for\n * the whole database.\n */\nexport function formatSchemaVerifyJson(\n result: VerifyDatabaseSchemaResult,\n unclaimed: readonly string[] = [],\n): string {\n return JSON.stringify({ ...result, unclaimed }, null, 2);\n}\n\n// ============================================================================\n// Sign Output Formatters\n// ============================================================================\n\n/**\n * Formats human-readable output for database sign.\n */\nexport function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Main success message in white (not dimmed)\n lines.push(`${formatGreen('✔')} Database signed`);\n\n // Show from -> to hashes with clear labels\n const previousHash = result.marker.previous?.storageHash ?? 'none';\n const currentHash = result.contract.storageHash;\n\n lines.push(`${formatDimText(` from: ${previousHash}`)}`);\n lines.push(`${formatDimText(` to: ${currentHash}`)}`);\n\n if (isVerbose(flags, 1)) {\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.marker.previous?.profileHash) {\n lines.push(\n `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database sign.\n */\nexport function formatSignJson(result: SignDatabaseResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}