| @@ -1,5 +1,5 @@ |
| 1 | 1 | import { existsSync, readFileSync } from "node:fs"; |
| 2 | | −import { mkdir, unlink } from "node:fs/promises"; |
| 2 | +import { mkdir, rename, unlink } from "node:fs/promises"; |
| 3 | 3 | import { join, resolve } from "node:path"; |
| 4 | 4 | |
| 5 | 5 | import { FileSystem } from "@effect/platform"; |
| @@ -243,6 +243,21 @@ type LegacyWorkspaceSnapshot = { · 237 unmodified lines |
| 243 | 243 | toolArtifacts: LegacyToolArtifactRow[]; |
| 244 | 244 | }; |
| 245 | 245 | |
| 246 | +type LegacySnapshotSource = |
| 247 | + | { |
| 248 | + kind: "pglite"; |
| 249 | + localDataDir: string; |
| 250 | + } |
| 251 | + | { |
| 252 | + kind: "postgres"; |
| 253 | + databaseUrl: string; |
| 254 | + }; |
| 255 | + |
| 256 | +type LoadedLegacyWorkspaceSnapshot = { |
| 257 | + snapshot: LegacyWorkspaceSnapshot; |
| 258 | + source: LegacySnapshotSource; |
| 259 | +}; |
| 260 | + |
| 246 | 261 | type LegacyQueryClient = { |
| 247 | 262 | label: string; |
| 248 | 263 | query: (sql: string) => Promise<LegacyRow[]>; |
| @@ -726,31 +741,71 @@ const loadLegacyWorkspaceSnapshot = async ( · 477 unmodified lines |
| 726 | 741 | }; |
| 727 | 742 | }; |
| 728 | 743 | |
| 744 | +const backupLegacyPGliteDataDir = async ( |
| 745 | + localDataDir: string, |
| 746 | +): Promise<string | null> => { |
| 747 | + if (localDataDir.trim() === ":memory:") { |
| 748 | + return null; |
| 749 | + } |
| 750 | + |
| 751 | + const resolvedDataDir = resolve(localDataDir); |
| 752 | + if (!existsSync(resolvedDataDir)) { |
| 753 | + return null; |
| 754 | + } |
| 755 | + |
| 756 | + const backupPath = `${resolvedDataDir}.migrated-backup-${Date.now()}`; |
| 757 | + await rename(resolvedDataDir, backupPath); |
| 758 | + return backupPath; |
| 759 | +}; |
| 760 | + |
| 729 | 761 | const tryLoadLegacyWorkspaceSnapshot = async (input: { |
| 730 | 762 | localDataDir?: string; |
| 731 | 763 | databaseUrl?: string; |
| 732 | | −}): Promise<LegacyWorkspaceSnapshot | null> => { |
| 733 | | − const clients: LegacyQueryClient[] = []; |
| 764 | +}): Promise<LoadedLegacyWorkspaceSnapshot | null> => { |
| 765 | + const clients: Array<{ |
| 766 | + client: LegacyQueryClient; |
| 767 | + source: LegacySnapshotSource; |
| 768 | + }> = []; |
| 734 | 769 | |
| 735 | 770 | try { |
| 736 | 771 | if (trimOrNull(input.localDataDir) !== null) { |
| 737 | 772 | const localClient = await openLegacyPGliteClient(input.localDataDir!); |
| 738 | 773 | if (localClient !== null) { |
| 739 | | − clients.push(localClient); |
| 774 | + clients.push({ |
| 775 | + client: localClient, |
| 776 | + source: { |
| 777 | + kind: "pglite", |
| 778 | + localDataDir: input.localDataDir!, |
| 779 | + }, |
| 780 | + }); |
| 740 | 781 | } |
| 741 | 782 | } |
| 742 | 783 | |
| 743 | 784 | if (clients.length === 0 && isPostgresUrl(input.databaseUrl)) { |
| 744 | | − clients.push(openLegacyPostgresClient(input.databaseUrl!)); |
| 785 | + clients.push({ |
| 786 | + client: openLegacyPostgresClient(input.databaseUrl!), |
| 787 | + source: { |
| 788 | + kind: "postgres", |
| 789 | + databaseUrl: input.databaseUrl!, |
| 790 | + }, |
| 791 | + }); |
| 745 | 792 | } |
| 746 | 793 | |
| 747 | | − for (const client of clients) { |
| 794 | + for (const { client, source } of clients) { |
| 748 | 795 | try { |
| 749 | 796 | if (!(await hasLegacySchema(client))) { |
| 750 | 797 | continue; |
| 751 | 798 | } |
| 752 | 799 | |
| 753 | | − return await loadLegacyWorkspaceSnapshot(client); |
| 800 | + const snapshot = await loadLegacyWorkspaceSnapshot(client); |
| 801 | + if (snapshot === null) { |
| 802 | + continue; |
| 803 | + } |
| 804 | + |
| 805 | + return { |
| 806 | + snapshot, |
| 807 | + source, |
| 808 | + }; |
| 754 | 809 | } catch { |
| 755 | 810 | continue; |
| 756 | 811 | } |
| @@ -759,7 +814,7 @@ const tryLoadLegacyWorkspaceSnapshot = async (input: { · 2 unmodified lines |
| 759 | 814 | return null; |
| 760 | 815 | } finally { |
| 761 | 816 | await Promise.all( |
| 762 | | − clients.map((client) => |
| 817 | + clients.map(({ client }) => |
| 763 | 818 | client.close().catch(() => undefined)), |
| 764 | 819 | ); |
| 765 | 820 | } |
| @@ -1526,7 +1581,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 760 unmodified lines |
| 1526 | 1581 | return; |
| 1527 | 1582 | } |
| 1528 | 1583 | |
| 1529 | | − const snapshot = yield* Effect.tryPromise({ |
| 1584 | + const loadedSnapshot = yield* Effect.tryPromise({ |
| 1530 | 1585 | try: () => |
| 1531 | 1586 | tryLoadLegacyWorkspaceSnapshot({ |
| 1532 | 1587 | localDataDir: input.legacyLocalDataDir, |
| @@ -1534,7 +1589,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line |
| 1534 | 1589 | }), |
| 1535 | 1590 | catch: toError, |
| 1536 | 1591 | }); |
| 1537 | | − if (snapshot === null) { |
| 1592 | + if (loadedSnapshot === null) { |
| 1538 | 1593 | return; |
| 1539 | 1594 | } |
| 1540 | 1595 | |
| @@ -1542,7 +1597,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line |
| 1542 | 1597 | try: () => |
| 1543 | 1598 | migrateLegacyWorkspace({ |
| 1544 | 1599 | context: input.context, |
| 1545 | | − snapshot, |
| 1600 | + snapshot: loadedSnapshot.snapshot, |
| 1546 | 1601 | }), |
| 1547 | 1602 | catch: toError, |
| 1548 | 1603 | }); |
| @@ -1570,6 +1625,31 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 21 unmodified lines |
| 1570 | 1625 | { discard: true }, |
| 1571 | 1626 | ); |
| 1572 | 1627 | |
| 1628 | + if (loadedSnapshot.source.kind === "pglite") { |
| 1629 | + const legacyLocalDataDir = loadedSnapshot.source.localDataDir; |
| 1630 | + const backupExit = yield* Effect.exit( |
| 1631 | + Effect.tryPromise({ |
| 1632 | + try: () => backupLegacyPGliteDataDir(legacyLocalDataDir), |
| 1633 | + catch: toError, |
| 1634 | + }), |
| 1635 | + ); |
| 1636 | + |
| 1637 | + yield* Effect.sync(() => { |
| 1638 | + if (backupExit._tag === "Success" && backupExit.value !== null) { |
| 1639 | + console.warn( |
| 1640 | + `[executor] Backed up legacy local data dir to: ${backupExit.value}`, |
| 1641 | + ); |
| 1642 | + return; |
| 1643 | + } |
| 1644 | + |
| 1645 | + if (backupExit._tag === "Failure") { |
| 1646 | + console.warn( |
| 1647 | + `[executor] Migrated workspace files but failed to back up legacy local data dir: ${String(backupExit.cause)}`, |
| 1648 | + ); |
| 1649 | + } |
| 1650 | + }); |
| 1651 | + } |
| 1652 | + |
| 1573 | 1653 | yield* Effect.sync(() => { |
| 1574 | 1654 | console.warn( |
| 1575 | 1655 | `[executor] Migrated ${String(migrated.sourceCount)} legacy source(s) and ${String(migrated.policyCount)} policy/policies into ${input.context.projectConfigPath}`, |