refactor(cli): limit identity assessment to migration state

This commit is contained in:
Gustavo Valverde
2026-08-25 07:23:34 -04:00
parent 4c0ec4231c
commit 74e922ccbf
7 changed files with 3 additions and 132 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ npx auth@latest migrate plan
npx auth@latest migrate plan --json > better-auth-migration-plan.json
```
The JSON plan includes the adapter, dialect, selected and detected account identity strategies, account counts, issuer-resolution coverage, projected collisions, sorted schema changes, blockers, and any release migration actions. A blocked plan exits with status `1`.
The JSON plan includes the adapter, dialect, selected and detected account identity strategies, sorted schema changes, blockers, and any release migration actions. A blocked plan exits with status `1`.
### Apply a migration
@@ -144,7 +144,6 @@ it("plans a default provider-scoped upgrade against Drizzle snake_case columns",
expect(inspection.accountIdentity).toMatchObject({
selectedStrategy: "provider-id",
detectedStrategy: "provider-id",
totalAccounts: 1,
});
expect(inspection.migrationBlockers).not.toContainEqual(
expect.objectContaining({
@@ -583,11 +583,6 @@ export interface AccountIdentityMigrationAssessment {
}
| undefined;
migrationRequired: boolean;
totalAccounts: number;
externalAccounts: number;
automaticIssuerResolution?: { resolved: number; total: number } | undefined;
projectedCollisions: number;
manualReviewProviders: string[];
}
/** Inspects the configured account-identity path without changing the database. */
@@ -606,10 +601,6 @@ export async function inspectAccountIdentityMigration(
detectedStrategy: "empty",
physicalSchema,
migrationRequired: false,
totalAccounts: 0,
externalAccounts: 0,
projectedCollisions: 0,
manualReviewProviders: [],
});
if (!accountSchema) return createEmptyAssessment();
const physicalSchema = {
@@ -656,42 +647,6 @@ export async function inspectAccountIdentityMigration(
: accountsWithIssuer === accounts.length
? "issuer"
: "mixed";
const configuredIssuers = await resolveConfiguredIssuerState(config);
const isExternal = (providerId: string) =>
(configuredIssuers.providerKinds[providerId] ??
(providerId === "credential" || providerId === "siwe"
? "local"
: "external")) === "external";
const externalAccounts = accounts.filter((account) =>
isExternal(account.providerId),
);
const manualReviewProviders =
selectedStrategy === "issuer"
? [
...new Set(
externalAccounts
.filter(
(account) =>
!readStoredIssuer(account.issuer) &&
!configuredIssuers.issuers[account.providerId],
)
.map((account) => account.providerId),
),
].sort()
: [];
const projectedIdentities = new Set<string>();
let projectedCollisions = 0;
for (const account of accounts) {
const namespace =
selectedStrategy === "provider-id"
? account.providerId
: readStoredIssuer(account.issuer) ||
configuredIssuers.issuers[account.providerId];
if (!namespace) continue;
const identity = JSON.stringify([namespace, account.providerAccountId]);
if (projectedIdentities.has(identity)) projectedCollisions += 1;
else projectedIdentities.add(identity);
}
return {
selectedStrategy,
detectedStrategy,
@@ -700,22 +655,6 @@ export async function inspectAccountIdentityMigration(
selectedStrategy === "issuer"
? detectedStrategy !== "issuer"
: detectedStrategy === "issuer" || detectedStrategy === "mixed",
totalAccounts: accounts.length,
externalAccounts: externalAccounts.length,
...(selectedStrategy === "issuer"
? {
automaticIssuerResolution: {
resolved: externalAccounts.filter(
(account) =>
Boolean(readStoredIssuer(account.issuer)) ||
Boolean(configuredIssuers.issuers[account.providerId]),
).length,
total: externalAccounts.length,
},
}
: {}),
projectedCollisions,
manualReviewProviders,
};
}
-14
View File
@@ -198,20 +198,6 @@ export function printHumanMigrationPlan(
console.log(
`Account identity: ${migrationPlan.accountIdentity.selectedStrategy} (database: ${migrationPlan.accountIdentity.detectedStrategy})`,
);
console.log(
`Accounts: ${migrationPlan.accountIdentity.totalAccounts} total, ${migrationPlan.accountIdentity.externalAccounts} external`,
);
if (migrationPlan.accountIdentity.automaticIssuerResolution) {
console.log(
`Automatic issuer resolution: ${migrationPlan.accountIdentity.automaticIssuerResolution.resolved}/${migrationPlan.accountIdentity.automaticIssuerResolution.total}`,
);
}
console.log(
`Projected collisions: ${migrationPlan.accountIdentity.projectedCollisions}`,
);
console.log(
`Providers requiring manual issuer review: ${migrationPlan.accountIdentity.manualReviewProviders.join(", ") || "none"}`,
);
console.log(
`Blockers: ${migrationPlan.blockers.map(({ code }) => code).join(", ") || "none"}`,
);
@@ -183,7 +183,7 @@ export function describeIrreversibleReleaseActions(
if (
accountIdentity.selectedStrategy === "issuer" &&
accountIdentity.migrationRequired &&
accountIdentity.totalAccounts > 0
accountIdentity.detectedStrategy !== "empty"
) {
actions.push(
"write the 1.7 account identity onto every existing account row",
@@ -1,5 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { printHumanMigrationPlan } from "./migrate";
import { describe, expect, it } from "vitest";
import { createMigrationPlan } from "./migration-plan";
describe("createMigrationPlan", () => {
@@ -9,10 +8,6 @@ describe("createMigrationPlan", () => {
selectedStrategy: "provider-id",
detectedStrategy: "issuer",
migrationRequired: true,
totalAccounts: 4,
externalAccounts: 3,
projectedCollisions: 0,
manualReviewProviders: [],
},
hasChanges: false,
migrationBlockers: [
@@ -39,44 +34,4 @@ describe("createMigrationPlan", () => {
},
});
});
it("prints issuer readiness and manual-review providers before applying", () => {
const plan = createMigrationPlan({
accountIdentity: {
selectedStrategy: "issuer",
detectedStrategy: "provider-id",
migrationRequired: true,
totalAccounts: 17,
externalAccounts: 3,
automaticIssuerResolution: { resolved: 2, total: 3 },
projectedCollisions: 1,
manualReviewProviders: ["custom-oidc"],
},
hasChanges: true,
migrationBlockers: [],
migrationTarget: { adapter: "prisma", dialect: "postgres" },
toBeAdded: [],
toBeAddedIndexes: [],
toBeCreated: [],
});
const log = vi.spyOn(console, "log").mockImplementation(() => {});
try {
printHumanMigrationPlan(plan, [], [], []);
const output = log.mock.calls
.map((parts) => parts.map(String).join(" "))
.join("\n");
expect(output).toContain(
"Account identity: issuer (database: provider-id)",
);
expect(output).toContain("Automatic issuer resolution: 2/3");
expect(output).toContain("Projected collisions: 1");
expect(output).toContain(
"Providers requiring manual issuer review: custom-oidc",
);
expect(output).toContain("No database changes were applied.");
} finally {
log.mockRestore();
}
});
});
-8
View File
@@ -321,10 +321,6 @@ describe("migration plan status", () => {
selectedStrategy: "provider-id",
detectedStrategy: "provider-id",
migrationRequired: false,
totalAccounts: 1,
externalAccounts: 0,
projectedCollisions: 0,
manualReviewProviders: [],
},
hasChanges: false,
migrationBlockers: [],
@@ -347,10 +343,6 @@ describe("migration plan status", () => {
selectedStrategy: "issuer",
detectedStrategy: "provider-id",
migrationRequired: true,
totalAccounts: 1,
externalAccounts: 0,
projectedCollisions: 0,
manualReviewProviders: [],
},
hasChanges: true,
migrationBlockers: [