| @@ -1,5 +1,5 @@ |
| 1 | import { existsSync, readFileSync } from "node:fs"; | 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 | import { join, resolve } from "node:path"; | 3 | import { join, resolve } from "node:path"; |
| 4 | | 4 | |
| 5 | import { FileSystem } from "@effect/platform"; | 5 | import { FileSystem } from "@effect/platform"; |
| @@ -243,6 +243,21 @@ type LegacyWorkspaceSnapshot = { · 237 unmodified lines |
| 243 | toolArtifacts: LegacyToolArtifactRow[]; | 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 | type LegacyQueryClient = { | 261 | type LegacyQueryClient = { |
| 247 | label: string; | 262 | label: string; |
| 248 | query: (sql: string) => Promise<LegacyRow[]>; | 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 | const tryLoadLegacyWorkspaceSnapshot = async (input: { | 761 | const tryLoadLegacyWorkspaceSnapshot = async (input: { |
| 730 | localDataDir?: string; | 762 | localDataDir?: string; |
| 731 | databaseUrl?: string; | 763 | databaseUrl?: string; |
| 732 | }): Promise<LegacyWorkspaceSnapshot | null> => { | 764 | }): Promise<LoadedLegacyWorkspaceSnapshot | null> => { |
| 733 | const clients: LegacyQueryClient[] = []; | 765 | const clients: Array<{ |
| | 766 | client: LegacyQueryClient; |
| | 767 | source: LegacySnapshotSource; |
| | 768 | }> = []; |
| 734 | | 769 | |
| 735 | try { | 770 | try { |
| 736 | if (trimOrNull(input.localDataDir) !== null) { | 771 | if (trimOrNull(input.localDataDir) !== null) { |
| 737 | const localClient = await openLegacyPGliteClient(input.localDataDir!); | 772 | const localClient = await openLegacyPGliteClient(input.localDataDir!); |
| 738 | if (localClient !== null) { | 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 | if (clients.length === 0 && isPostgresUrl(input.databaseUrl)) { | 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 | try { | 795 | try { |
| 749 | if (!(await hasLegacySchema(client))) { | 796 | if (!(await hasLegacySchema(client))) { |
| 750 | continue; | 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 | } catch { | 809 | } catch { |
| 755 | continue; | 810 | continue; |
| 756 | } | 811 | } |
| @@ -759,7 +814,7 @@ const tryLoadLegacyWorkspaceSnapshot = async (input: { · 2 unmodified lines |
| 759 | return null; | 814 | return null; |
| 760 | } finally { | 815 | } finally { |
| 761 | await Promise.all( | 816 | await Promise.all( |
| 762 | clients.map((client) => | 817 | clients.map(({ client }) => |
| 763 | client.close().catch(() => undefined)), | 818 | client.close().catch(() => undefined)), |
| 764 | ); | 819 | ); |
| 765 | } | 820 | } |
| @@ -1526,7 +1581,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 760 unmodified lines |
| 1526 | return; | 1581 | return; |
| 1527 | } | 1582 | } |
| 1528 | | 1583 | |
| 1529 | const snapshot = yield* Effect.tryPromise({ | 1584 | const loadedSnapshot = yield* Effect.tryPromise({ |
| 1530 | try: () => | 1585 | try: () => |
| 1531 | tryLoadLegacyWorkspaceSnapshot({ | 1586 | tryLoadLegacyWorkspaceSnapshot({ |
| 1532 | localDataDir: input.legacyLocalDataDir, | 1587 | localDataDir: input.legacyLocalDataDir, |
| @@ -1534,7 +1589,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line |
| 1534 | }), | 1589 | }), |
| 1535 | catch: toError, | 1590 | catch: toError, |
| 1536 | }); | 1591 | }); |
| 1537 | if (snapshot === null) { | 1592 | if (loadedSnapshot === null) { |
| 1538 | return; | 1593 | return; |
| 1539 | } | 1594 | } |
| 1540 | | 1595 | |
| @@ -1542,7 +1597,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line |
| 1542 | try: () => | 1597 | try: () => |
| 1543 | migrateLegacyWorkspace({ | 1598 | migrateLegacyWorkspace({ |
| 1544 | context: input.context, | 1599 | context: input.context, |
| 1545 | snapshot, | 1600 | snapshot: loadedSnapshot.snapshot, |
| 1546 | }), | 1601 | }), |
| 1547 | catch: toError, | 1602 | catch: toError, |
| 1548 | }); | 1603 | }); |
| @@ -1570,6 +1625,31 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 21 unmodified lines |
| 1570 | { discard: true }, | 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 | yield* Effect.sync(() => { | 1653 | yield* Effect.sync(() => { |
| 1574 | console.warn( | 1654 | console.warn( |
| 1575 | `[executor] Migrated ${String(migrated.sourceCount)} legacy source(s) and ${String(migrated.policyCount)} policy/policies into ${input.context.projectConfigPath}`, | 1655 | `[executor] Migrated ${String(migrated.sourceCount)} legacy source(s) and ${String(migrated.policyCount)} policy/policies into ${input.context.projectConfigPath}`, |