everything-dev 1.49.0 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/app.cjs +66 -86
  2. package/dist/app.cjs.map +1 -1
  3. package/dist/app.mjs +66 -87
  4. package/dist/app.mjs.map +1 -1
  5. package/dist/cli/db-doctor.cjs +6 -23
  6. package/dist/cli/db-doctor.cjs.map +1 -1
  7. package/dist/cli/db-doctor.mjs +7 -24
  8. package/dist/cli/db-doctor.mjs.map +1 -1
  9. package/dist/cli/db-repair.cjs +25 -20
  10. package/dist/cli/db-repair.cjs.map +1 -1
  11. package/dist/cli/db-repair.mjs +25 -20
  12. package/dist/cli/db-repair.mjs.map +1 -1
  13. package/dist/cli/db-studio.cjs +4 -2
  14. package/dist/cli/db-studio.cjs.map +1 -1
  15. package/dist/cli/db-studio.mjs +5 -3
  16. package/dist/cli/db-studio.mjs.map +1 -1
  17. package/dist/cli/framework-version.cjs +45 -9
  18. package/dist/cli/framework-version.cjs.map +1 -1
  19. package/dist/cli/framework-version.mjs +45 -10
  20. package/dist/cli/framework-version.mjs.map +1 -1
  21. package/dist/cli/infra.cjs +89 -3
  22. package/dist/cli/infra.cjs.map +1 -1
  23. package/dist/cli/infra.mjs +83 -4
  24. package/dist/cli/infra.mjs.map +1 -1
  25. package/dist/cli/init.cjs +39 -15
  26. package/dist/cli/init.cjs.map +1 -1
  27. package/dist/cli/init.d.cts +8 -3
  28. package/dist/cli/init.d.cts.map +1 -1
  29. package/dist/cli/init.d.mts +8 -3
  30. package/dist/cli/init.d.mts.map +1 -1
  31. package/dist/cli/init.mjs +39 -16
  32. package/dist/cli/init.mjs.map +1 -1
  33. package/dist/cli/status.cjs +6 -2
  34. package/dist/cli/status.cjs.map +1 -1
  35. package/dist/cli/status.mjs +7 -3
  36. package/dist/cli/status.mjs.map +1 -1
  37. package/dist/cli/sync.cjs +1 -3
  38. package/dist/cli/sync.cjs.map +1 -1
  39. package/dist/cli/sync.mjs +1 -3
  40. package/dist/cli/sync.mjs.map +1 -1
  41. package/dist/cli/upgrade.cjs +8 -4
  42. package/dist/cli/upgrade.cjs.map +1 -1
  43. package/dist/cli/upgrade.mjs +8 -4
  44. package/dist/cli/upgrade.mjs.map +1 -1
  45. package/dist/cli.cjs +73 -9
  46. package/dist/cli.cjs.map +1 -1
  47. package/dist/cli.mjs +74 -10
  48. package/dist/cli.mjs.map +1 -1
  49. package/dist/config.cjs +7 -3
  50. package/dist/config.cjs.map +1 -1
  51. package/dist/config.d.cts.map +1 -1
  52. package/dist/config.d.mts.map +1 -1
  53. package/dist/config.mjs +7 -3
  54. package/dist/config.mjs.map +1 -1
  55. package/dist/contract.cjs +76 -4
  56. package/dist/contract.cjs.map +1 -1
  57. package/dist/contract.d.cts +164 -19
  58. package/dist/contract.d.cts.map +1 -1
  59. package/dist/contract.d.mts +164 -19
  60. package/dist/contract.d.mts.map +1 -1
  61. package/dist/contract.meta.cjs +17 -2
  62. package/dist/contract.meta.cjs.map +1 -1
  63. package/dist/contract.meta.d.cts +23 -2
  64. package/dist/contract.meta.d.mts +23 -2
  65. package/dist/contract.meta.mjs +17 -2
  66. package/dist/contract.meta.mjs.map +1 -1
  67. package/dist/contract.mjs +72 -5
  68. package/dist/contract.mjs.map +1 -1
  69. package/dist/db.cjs +32 -24
  70. package/dist/db.cjs.map +1 -1
  71. package/dist/db.d.cts +16 -9
  72. package/dist/db.d.cts.map +1 -1
  73. package/dist/db.d.mts +16 -9
  74. package/dist/db.d.mts.map +1 -1
  75. package/dist/db.mjs +32 -23
  76. package/dist/db.mjs.map +1 -1
  77. package/dist/dev-session.cjs +28 -0
  78. package/dist/dev-session.cjs.map +1 -1
  79. package/dist/dev-session.mjs +29 -1
  80. package/dist/dev-session.mjs.map +1 -1
  81. package/dist/index.cjs +5 -0
  82. package/dist/index.d.cts +2 -2
  83. package/dist/index.d.mts +2 -2
  84. package/dist/index.mjs +2 -2
  85. package/dist/infra/planner.cjs +337 -0
  86. package/dist/infra/planner.cjs.map +1 -0
  87. package/dist/infra/planner.mjs +336 -0
  88. package/dist/infra/planner.mjs.map +1 -0
  89. package/dist/infra/preflight.cjs +131 -0
  90. package/dist/infra/preflight.cjs.map +1 -0
  91. package/dist/infra/preflight.mjs +130 -0
  92. package/dist/infra/preflight.mjs.map +1 -0
  93. package/dist/infra/types.cjs +9 -0
  94. package/dist/infra/types.cjs.map +1 -0
  95. package/dist/infra/types.mjs +8 -0
  96. package/dist/infra/types.mjs.map +1 -0
  97. package/dist/near-cli.cjs +37 -0
  98. package/dist/near-cli.cjs.map +1 -1
  99. package/dist/near-cli.mjs +36 -1
  100. package/dist/near-cli.mjs.map +1 -1
  101. package/dist/orchestrator.cjs +10 -2
  102. package/dist/orchestrator.cjs.map +1 -1
  103. package/dist/orchestrator.mjs +10 -2
  104. package/dist/orchestrator.mjs.map +1 -1
  105. package/dist/plugin.cjs +160 -43
  106. package/dist/plugin.cjs.map +1 -1
  107. package/dist/plugin.d.cts +64 -8
  108. package/dist/plugin.d.cts.map +1 -1
  109. package/dist/plugin.d.mts +64 -8
  110. package/dist/plugin.d.mts.map +1 -1
  111. package/dist/plugin.mjs +164 -47
  112. package/dist/plugin.mjs.map +1 -1
  113. package/dist/process-registry.cjs +88 -0
  114. package/dist/process-registry.cjs.map +1 -0
  115. package/dist/process-registry.mjs +83 -0
  116. package/dist/process-registry.mjs.map +1 -0
  117. package/dist/publish.cjs +4 -0
  118. package/dist/publish.cjs.map +1 -1
  119. package/dist/publish.mjs +4 -0
  120. package/dist/publish.mjs.map +1 -1
  121. package/dist/service-descriptor.cjs +4 -0
  122. package/dist/service-descriptor.cjs.map +1 -1
  123. package/dist/service-descriptor.mjs +4 -1
  124. package/dist/service-descriptor.mjs.map +1 -1
  125. package/dist/types.d.cts +2 -2
  126. package/dist/types.d.mts +2 -2
  127. package/dist/ui/index.cjs +2 -1
  128. package/dist/ui/index.d.cts +2 -2
  129. package/dist/ui/index.d.mts +2 -2
  130. package/dist/ui/index.mjs +2 -2
  131. package/dist/ui/router.cjs +40 -0
  132. package/dist/ui/router.cjs.map +1 -1
  133. package/dist/ui/router.d.cts +6 -1
  134. package/dist/ui/router.d.cts.map +1 -1
  135. package/dist/ui/router.d.mts +6 -1
  136. package/dist/ui/router.d.mts.map +1 -1
  137. package/dist/ui/router.mjs +40 -1
  138. package/dist/ui/router.mjs.map +1 -1
  139. package/package.json +4 -1
  140. package/skills/cli-reference/SKILL.md +5 -4
  141. package/skills/dev-workflow/SKILL.md +40 -11
  142. package/skills/plugin-development/SKILL.md +9 -2
@@ -1,4 +1,4 @@
1
- import { extractExpectedTables, pluginMigrationSlug } from "../db.mjs";
1
+ import { extractExpectedTables, getMigrationStorage, pluginMigrationSlug } from "../db.mjs";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { createHash } from "node:crypto";
@@ -30,8 +30,9 @@ function readLocalMigrations(workspaceDir) {
30
30
  async function diagnosePlugin(info) {
31
31
  const { Pool } = await import("../node_modules/pg/esm/index.mjs");
32
32
  const slug = pluginMigrationSlug(info.key);
33
- const journalTable = `__drizzle_migrations_${slug}`;
34
- const journalSchema = "drizzle";
33
+ const storage = getMigrationStorage(slug);
34
+ const journalTable = storage.table;
35
+ const journalSchema = storage.schema;
35
36
  const journalRef = `"${journalSchema}"."${journalTable}"`;
36
37
  const localMigrations = info.workspaceDir ? readLocalMigrations(info.workspaceDir) : [];
37
38
  const expectedTables = extractExpectedTables(localMigrations);
@@ -62,10 +63,10 @@ async function diagnosePlugin(info) {
62
63
  const existing = new Set(tableResult.rows.map((r) => r.table_name));
63
64
  missingTables = expectedTables.filter((t) => !existing.has(t));
64
65
  }
65
- const legacyCount = await countLegacyHashes(pool, localHashes);
66
66
  let diagnosis;
67
67
  if (localMigrations.length === 0) diagnosis = "no-local-migrations";
68
- else if (appliedHashCount === 0 && localHashes.length > 0) diagnosis = legacyCount > 0 ? "legacy-importable" : "unapplied";
68
+ else if (appliedHashCount === 0 && missingTables.length === 0 && localHashes.length > 0) diagnosis = "untracked-existing-schema";
69
+ else if (appliedHashCount === 0 && localHashes.length > 0) diagnosis = "unapplied";
69
70
  else if (missingTables.length === 0) diagnosis = "healthy";
70
71
  else if (missingTables.length === expectedTables.length) diagnosis = "drift-safe-repair";
71
72
  else diagnosis = "drift-manual";
@@ -83,8 +84,7 @@ async function diagnosePlugin(info) {
83
84
  expectedTables,
84
85
  missingTables,
85
86
  migrationHashes,
86
- diagnosis,
87
- legacyCount
87
+ diagnosis
88
88
  };
89
89
  } catch (error) {
90
90
  return {
@@ -101,29 +101,12 @@ async function diagnosePlugin(info) {
101
101
  missingTables: [],
102
102
  migrationHashes: [],
103
103
  diagnosis: "error",
104
- legacyCount: 0,
105
104
  error: error instanceof Error ? error.message : String(error)
106
105
  };
107
106
  } finally {
108
107
  await pool.end().catch(() => {});
109
108
  }
110
109
  }
111
- async function countLegacyHashes(pool, localHashes) {
112
- if (localHashes.length === 0) return 0;
113
- const candidates = [{
114
- schema: "drizzle",
115
- table: "__drizzle_migrations"
116
- }, {
117
- schema: "public",
118
- table: "drizzle_migrations"
119
- }];
120
- let total = 0;
121
- for (const c of candidates) try {
122
- const result = await pool.query(`SELECT count(*)::int AS cnt FROM "${c.schema}"."${c.table}" WHERE hash = ANY($1)`, [localHashes]);
123
- total += result.rows[0]?.cnt ?? 0;
124
- } catch {}
125
- return total;
126
- }
127
110
 
128
111
  //#endregion
129
112
  export { diagnosePlugin };
@@ -1 +1 @@
1
- {"version":3,"file":"db-doctor.mjs","names":[],"sources":["../../src/cli/db-doctor.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { extractExpectedTables, pluginMigrationSlug } from \"../db\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface DoctorReport {\n plugin: string;\n slug: string;\n journalTable: string;\n journalSchema: string;\n dbSecret: string;\n dbUrl: string;\n workspaceDir: string | undefined;\n localMigrationCount: number;\n appliedHashCount: number;\n expectedTables: string[];\n missingTables: string[];\n migrationHashes: string[];\n diagnosis: string;\n legacyCount: number;\n error?: string;\n}\n\nfunction hashFile(raw: string): string {\n return createHash(\"sha256\").update(raw).digest(\"hex\");\n}\n\ninterface LocalMigration {\n tag: string;\n hash: string;\n sql: string[];\n}\n\nfunction readLocalMigrations(workspaceDir: string): LocalMigration[] {\n const migrationsDir = resolve(workspaceDir, \"src/db/migrations\");\n const metaDir = join(migrationsDir, \"meta\");\n const journalPath = join(metaDir, \"_journal.json\");\n if (!existsSync(journalPath)) return [];\n\n const journal = JSON.parse(readFileSync(journalPath, \"utf8\")) as {\n entries: { idx: number; tag: string }[];\n };\n\n return journal.entries.map((entry) => {\n const sqlPath = join(migrationsDir, `${entry.tag}.sql`);\n if (!existsSync(sqlPath)) {\n return { tag: entry.tag, hash: \"\", sql: [] };\n }\n const raw = readFileSync(sqlPath, \"utf8\");\n const sql = raw.split(\"--> statement-breakpoint\").map((s) => s.trim());\n return { tag: entry.tag, hash: hashFile(raw), sql };\n });\n}\n\nexport async function diagnosePlugin(info: PluginDbInfo): Promise<DoctorReport> {\n const { Pool } = await import(\"pg\");\n\n const slug = pluginMigrationSlug(info.key);\n const journalTable = `__drizzle_migrations_${slug}`;\n const journalSchema = \"drizzle\";\n const journalRef = `\"${journalSchema}\".\"${journalTable}\"`;\n\n const localMigrations = info.workspaceDir ? readLocalMigrations(info.workspaceDir) : [];\n\n const expectedTables = extractExpectedTables(localMigrations);\n const localHashes = localMigrations.map((m) => m.hash).filter(Boolean);\n const migrationHashes = localHashes;\n\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n const journalExists = await pool.query(`\n SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = '${journalSchema}' AND table_name = '${journalTable}'\n ) AS exists\n `);\n\n let appliedHashCount = 0;\n if (journalExists.rows[0]?.exists) {\n const result = await pool.query(`SELECT hash FROM ${journalRef}`);\n appliedHashCount = result.rows.length;\n }\n\n let missingTables: string[] = [];\n if (expectedTables.length > 0) {\n const tableResult = await pool.query(\n `\n SELECT table_name FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_name = ANY($1)\n `,\n [expectedTables],\n );\n const existing = new Set(tableResult.rows.map((r: any) => r.table_name));\n missingTables = expectedTables.filter((t) => !existing.has(t));\n }\n\n const legacyCount = await countLegacyHashes(pool, localHashes);\n\n let diagnosis: string;\n if (localMigrations.length === 0) {\n diagnosis = \"no-local-migrations\";\n } else if (appliedHashCount === 0 && localHashes.length > 0) {\n diagnosis = legacyCount > 0 ? \"legacy-importable\" : \"unapplied\";\n } else if (missingTables.length === 0) {\n diagnosis = \"healthy\";\n } else if (missingTables.length === expectedTables.length) {\n diagnosis = \"drift-safe-repair\";\n } else {\n diagnosis = \"drift-manual\";\n }\n\n const masked = info.databaseUrl.replace(/\\/\\/[^:]+:[^@]+@/, \"//***:***@\");\n\n return {\n plugin: info.key,\n slug,\n journalTable,\n journalSchema,\n dbSecret: info.databaseSecret,\n dbUrl: masked,\n workspaceDir: info.workspaceDir,\n localMigrationCount: localMigrations.length,\n appliedHashCount,\n expectedTables,\n missingTables,\n migrationHashes,\n diagnosis,\n legacyCount,\n };\n } catch (error) {\n return {\n plugin: info.key,\n slug,\n journalTable,\n journalSchema,\n dbSecret: info.databaseSecret,\n dbUrl: info.databaseUrl.replace(/\\/\\/[^:]+:[^@]+@/, \"//***:***@\"),\n workspaceDir: info.workspaceDir,\n localMigrationCount: localMigrations.length,\n appliedHashCount: 0,\n expectedTables,\n missingTables: [],\n migrationHashes: [],\n diagnosis: \"error\",\n legacyCount: 0,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n await pool.end().catch(() => {});\n }\n}\n\nasync function countLegacyHashes(pool: any, localHashes: string[]): Promise<number> {\n if (localHashes.length === 0) return 0;\n const candidates = [\n { schema: \"drizzle\", table: \"__drizzle_migrations\" },\n { schema: \"public\", table: \"drizzle_migrations\" },\n ];\n let total = 0;\n for (const c of candidates) {\n try {\n const result = await pool.query(\n `SELECT count(*)::int AS cnt FROM \"${c.schema}\".\"${c.table}\" WHERE hash = ANY($1)`,\n [localHashes],\n );\n total += result.rows[0]?.cnt ?? 0;\n } catch {\n // table might not exist\n }\n }\n return total;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"db-doctor.mjs","names":[],"sources":["../../src/cli/db-doctor.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { extractExpectedTables, getMigrationStorage, pluginMigrationSlug } from \"../db\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface DoctorReport {\n plugin: string;\n slug: string;\n journalTable: string;\n journalSchema: string;\n dbSecret: string;\n dbUrl: string;\n workspaceDir: string | undefined;\n localMigrationCount: number;\n appliedHashCount: number;\n expectedTables: string[];\n missingTables: string[];\n migrationHashes: string[];\n diagnosis: string;\n error?: string;\n}\n\nfunction hashFile(raw: string): string {\n return createHash(\"sha256\").update(raw).digest(\"hex\");\n}\n\ninterface LocalMigration {\n tag: string;\n hash: string;\n sql: string[];\n}\n\nfunction readLocalMigrations(workspaceDir: string): LocalMigration[] {\n const migrationsDir = resolve(workspaceDir, \"src/db/migrations\");\n const metaDir = join(migrationsDir, \"meta\");\n const journalPath = join(metaDir, \"_journal.json\");\n if (!existsSync(journalPath)) return [];\n\n const journal = JSON.parse(readFileSync(journalPath, \"utf8\")) as {\n entries: { idx: number; tag: string }[];\n };\n\n return journal.entries.map((entry) => {\n const sqlPath = join(migrationsDir, `${entry.tag}.sql`);\n if (!existsSync(sqlPath)) {\n return { tag: entry.tag, hash: \"\", sql: [] };\n }\n const raw = readFileSync(sqlPath, \"utf8\");\n const sql = raw.split(\"--> statement-breakpoint\").map((s) => s.trim());\n return { tag: entry.tag, hash: hashFile(raw), sql };\n });\n}\n\nexport async function diagnosePlugin(info: PluginDbInfo): Promise<DoctorReport> {\n const { Pool } = await import(\"pg\");\n\n const slug = pluginMigrationSlug(info.key);\n const storage = getMigrationStorage(slug);\n const journalTable = storage.table;\n const journalSchema = storage.schema;\n const journalRef = `\"${journalSchema}\".\"${journalTable}\"`;\n\n const localMigrations = info.workspaceDir ? readLocalMigrations(info.workspaceDir) : [];\n\n const expectedTables = extractExpectedTables(localMigrations);\n const localHashes = localMigrations.map((m) => m.hash).filter(Boolean);\n const migrationHashes = localHashes;\n\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n const journalExists = await pool.query(`\n SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = '${journalSchema}' AND table_name = '${journalTable}'\n ) AS exists\n `);\n\n let appliedHashCount = 0;\n if (journalExists.rows[0]?.exists) {\n const result = await pool.query(`SELECT hash FROM ${journalRef}`);\n appliedHashCount = result.rows.length;\n }\n\n let missingTables: string[] = [];\n if (expectedTables.length > 0) {\n const tableResult = await pool.query(\n `\n SELECT table_name FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_name = ANY($1)\n `,\n [expectedTables],\n );\n const existing = new Set(tableResult.rows.map((r: any) => r.table_name));\n missingTables = expectedTables.filter((t) => !existing.has(t));\n }\n\n let diagnosis: string;\n if (localMigrations.length === 0) {\n diagnosis = \"no-local-migrations\";\n } else if (appliedHashCount === 0 && missingTables.length === 0 && localHashes.length > 0) {\n // Journal is empty but all expected public tables exist.\n diagnosis = \"untracked-existing-schema\";\n } else if (appliedHashCount === 0 && localHashes.length > 0) {\n diagnosis = \"unapplied\";\n } else if (missingTables.length === 0) {\n diagnosis = \"healthy\";\n } else if (missingTables.length === expectedTables.length) {\n diagnosis = \"drift-safe-repair\";\n } else {\n diagnosis = \"drift-manual\";\n }\n\n const masked = info.databaseUrl.replace(/\\/\\/[^:]+:[^@]+@/, \"//***:***@\");\n\n return {\n plugin: info.key,\n slug,\n journalTable,\n journalSchema,\n dbSecret: info.databaseSecret,\n dbUrl: masked,\n workspaceDir: info.workspaceDir,\n localMigrationCount: localMigrations.length,\n appliedHashCount,\n expectedTables,\n missingTables,\n migrationHashes,\n diagnosis,\n };\n } catch (error) {\n return {\n plugin: info.key,\n slug,\n journalTable,\n journalSchema,\n dbSecret: info.databaseSecret,\n dbUrl: info.databaseUrl.replace(/\\/\\/[^:]+:[^@]+@/, \"//***:***@\"),\n workspaceDir: info.workspaceDir,\n localMigrationCount: localMigrations.length,\n appliedHashCount: 0,\n expectedTables,\n missingTables: [],\n migrationHashes: [],\n diagnosis: \"error\",\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n await pool.end().catch(() => {});\n }\n}\n"],"mappings":"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"}
@@ -15,9 +15,9 @@ async function repairPlugin(info, mode) {
15
15
  if (mode === "recreate") return {
16
16
  status: "refused",
17
17
  diagnosis,
18
- message: "Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead."
18
+ message: "Recreate mode is not supported without per-plugin database schemas. Use --mode history-reset instead."
19
19
  };
20
- if (diagnosis.diagnosis !== "drift-safe-repair" && diagnosis.diagnosis !== "legacy-importable") {
20
+ if (diagnosis.diagnosis !== "drift-safe-repair") {
21
21
  if (diagnosis.diagnosis === "healthy") return {
22
22
  status: "refused",
23
23
  diagnosis,
@@ -38,6 +38,11 @@ async function repairPlugin(info, mode) {
38
38
  diagnosis,
39
39
  message: "Migrations have not been applied yet. Start the dev server to apply them."
40
40
  };
41
+ if (diagnosis.diagnosis === "untracked-existing-schema") return {
42
+ status: "refused",
43
+ diagnosis,
44
+ message: "Tables exist but no matching migration history was found. Run `drizzle-kit pull --init` in the plugin workspace to adopt the existing schema, then run migrations from that baseline."
45
+ };
41
46
  return {
42
47
  status: "refused",
43
48
  diagnosis,
@@ -54,32 +59,32 @@ async function repairPlugin(info, mode) {
54
59
  });
55
60
  try {
56
61
  await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);
62
+ if (info.workspaceDir) {
63
+ const configPath = (0, node_path.join)(info.workspaceDir, "drizzle.config.ts");
64
+ if ((0, node_fs.existsSync)(configPath)) try {
65
+ await spawnDrizzleMigrate(info.workspaceDir, configPath);
66
+ return {
67
+ status: "repaired",
68
+ diagnosis,
69
+ message: `Migration history reset for ${diagnosis.plugin}. Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`
70
+ };
71
+ } catch (error) {
72
+ return {
73
+ status: "repaired",
74
+ diagnosis,
75
+ message: `Migration history reset for ${diagnosis.plugin}. Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. Run \`bun run --cwd ${info.workspaceDir} db:migrate\` manually.`
76
+ };
77
+ }
78
+ }
57
79
  } catch (error) {
58
80
  return {
59
81
  status: "error",
60
82
  diagnosis,
61
- message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`
83
+ message: `Repair failed: ${error instanceof Error ? error.message : String(error)}`
62
84
  };
63
85
  } finally {
64
86
  await pool.end().catch(() => {});
65
87
  }
66
- if (info.workspaceDir) {
67
- const configPath = (0, node_path.join)(info.workspaceDir, "drizzle.config.ts");
68
- if ((0, node_fs.existsSync)(configPath)) try {
69
- await spawnDrizzleMigrate(info.workspaceDir, configPath);
70
- return {
71
- status: "repaired",
72
- diagnosis,
73
- message: `Migration history reset for ${diagnosis.plugin}. Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`
74
- };
75
- } catch (error) {
76
- return {
77
- status: "repaired",
78
- diagnosis,
79
- message: `Migration history reset for ${diagnosis.plugin}. Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. Run \`bun run --cwd ${info.workspaceDir} db:migrate\` manually.`
80
- };
81
- }
82
- }
83
88
  return {
84
89
  status: "repaired",
85
90
  diagnosis,
@@ -1 +1 @@
1
- {"version":3,"file":"db-repair.cjs","names":["diagnosePlugin"],"sources":["../../src/cli/db-repair.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type DoctorReport, diagnosePlugin } from \"./db-doctor\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface RepairResult {\n status: \"repaired\" | \"refused\" | \"error\";\n diagnosis: DoctorReport;\n message: string;\n}\n\nexport async function repairPlugin(\n info: PluginDbInfo,\n mode: \"history-reset\" | \"recreate\",\n): Promise<RepairResult> {\n const diagnosis = await diagnosePlugin(info);\n\n if (diagnosis.diagnosis === \"error\") {\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis failed: ${diagnosis.error}`,\n };\n }\n\n if (mode === \"recreate\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead.\",\n };\n }\n\n if (diagnosis.diagnosis !== \"drift-safe-repair\" && diagnosis.diagnosis !== \"legacy-importable\") {\n if (diagnosis.diagnosis === \"healthy\") {\n return { status: \"refused\", diagnosis, message: \"Database is healthy — no repair needed.\" };\n }\n if (diagnosis.diagnosis === \"no-local-migrations\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"No local migrations found for this plugin.\",\n };\n }\n if (diagnosis.diagnosis === \"drift-manual\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). ` +\n \"Manual intervention required — some tables exist but schema is incomplete.\",\n };\n }\n if (diagnosis.diagnosis === \"unapplied\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"Migrations have not been applied yet. Start the dev server to apply them.\",\n };\n }\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`,\n };\n }\n\n const { Pool } = await import(\"pg\");\n const journalRef = `\"${diagnosis.journalSchema}\".\"${diagnosis.journalTable}\"`;\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);\n } catch (error) {\n return {\n status: \"error\",\n diagnosis,\n message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await pool.end().catch(() => {});\n }\n\n if (info.workspaceDir) {\n const configPath = join(info.workspaceDir, \"drizzle.config.ts\");\n if (existsSync(configPath)) {\n try {\n await spawnDrizzleMigrate(info.workspaceDir, configPath);\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`,\n };\n } catch (error) {\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. ` +\n `Run \\`bun run --cwd ${info.workspaceDir} db:migrate\\` manually.`,\n };\n }\n }\n }\n\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n \"No local drizzle.config.ts found — start the dev server to reapply migrations.\",\n };\n}\n\nfunction spawnDrizzleMigrate(cwd: string, configPath: string): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(\"npx\", [\"drizzle-kit\", \"migrate\", \"--config\", configPath], {\n cwd,\n stdio: \"pipe\",\n shell: true,\n });\n\n let stderr = \"\";\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => reject(err));\n\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));\n }\n });\n });\n}\n"],"mappings":";;;;;;;AAYA,eAAsB,aACpB,MACA,MACuB;CACvB,MAAM,YAAY,MAAMA,iCAAe,KAAK;AAE5C,KAAI,UAAU,cAAc,QAC1B,QAAO;EACL,QAAQ;EACR;EACA,SAAS,qCAAqC,UAAU;EACzD;AAGH,KAAI,SAAS,WACX,QAAO;EACL,QAAQ;EACR;EACA,SACE;EACH;AAGH,KAAI,UAAU,cAAc,uBAAuB,UAAU,cAAc,qBAAqB;AAC9F,MAAI,UAAU,cAAc,UAC1B,QAAO;GAAE,QAAQ;GAAW;GAAW,SAAS;GAA2C;AAE7F,MAAI,UAAU,cAAc,sBAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,eAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE,2BAA2B,UAAU,cAAc,OAAO,GAAG,UAAU,eAAe,OAAO;GAEhG;AAEH,MAAI,UAAU,cAAc,YAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,SAAO;GACL,QAAQ;GACR;GACA,SAAS,8BAA8B,UAAU;GAClD;;CAGH,MAAM,EAAE,SAAS,2CAAM;CACvB,MAAM,aAAa,IAAI,UAAU,cAAc,KAAK,UAAU,aAAa;CAC3E,MAAM,OAAO,IAAI,KAAK;EACpB,kBAAkB,KAAK;EACvB,KACE,KAAK,YAAY,SAAS,YAAY,IAAI,KAAK,YAAY,SAAS,YAAY,GAC5E,QACA,EAAE,oBAAoB,OAAO;EACnC,KAAK;EACL,yBAAyB;EAC1B,CAAC;AAEF,KAAI;AACF,QAAM,KAAK,MAAM,wBAAwB,aAAa;UAC/C,OAAO;AACd,SAAO;GACL,QAAQ;GACR;GACA,SAAS,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC5F;WACO;AACR,QAAM,KAAK,KAAK,CAAC,YAAY,GAAG;;AAGlC,KAAI,KAAK,cAAc;EACrB,MAAM,iCAAkB,KAAK,cAAc,oBAAoB;AAC/D,8BAAe,WAAW,CACxB,KAAI;AACF,SAAM,oBAAoB,KAAK,cAAc,WAAW;AACxD,UAAO;IACL,QAAQ;IACR;IACA,SACE,+BAA+B,UAAU,OAAO;IAEnD;WACM,OAAO;AACd,UAAO;IACL,QAAQ;IACR;IACA,SACE,+BAA+B,UAAU,OAAO,8BACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,wBAC7D,KAAK,aAAa;IAC5C;;;AAKP,QAAO;EACL,QAAQ;EACR;EACA,SACE,+BAA+B,UAAU,OAAO;EAEnD;;AAGH,SAAS,oBAAoB,KAAa,YAAmC;AAC3E,QAAO,IAAI,SAAS,gBAAgB,WAAW;EAC7C,MAAM,sCAAc,OAAO;GAAC;GAAe;GAAW;GAAY;GAAW,EAAE;GAC7E;GACA,OAAO;GACP,OAAO;GACR,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,GAAG,SAAS,SAAiB;AACzC,aAAU,KAAK,UAAU;IACzB;AAEF,QAAM,GAAG,UAAU,QAAQ,OAAO,IAAI,CAAC;AAEvC,QAAM,GAAG,SAAS,SAAS;AACzB,OAAI,SAAS,EACX,iBAAgB;OAEhB,wBAAO,IAAI,MAAM,wCAAwC,KAAK,IAAI,SAAS,CAAC;IAE9E;GACF"}
1
+ {"version":3,"file":"db-repair.cjs","names":["diagnosePlugin"],"sources":["../../src/cli/db-repair.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type DoctorReport, diagnosePlugin } from \"./db-doctor\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface RepairResult {\n status: \"repaired\" | \"refused\" | \"error\";\n diagnosis: DoctorReport;\n message: string;\n}\n\nexport async function repairPlugin(\n info: PluginDbInfo,\n mode: \"history-reset\" | \"recreate\",\n): Promise<RepairResult> {\n const diagnosis = await diagnosePlugin(info);\n\n if (diagnosis.diagnosis === \"error\") {\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis failed: ${diagnosis.error}`,\n };\n }\n\n if (mode === \"recreate\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Recreate mode is not supported without per-plugin database schemas. Use --mode history-reset instead.\",\n };\n }\n\n if (diagnosis.diagnosis !== \"drift-safe-repair\") {\n if (diagnosis.diagnosis === \"healthy\") {\n return { status: \"refused\", diagnosis, message: \"Database is healthy — no repair needed.\" };\n }\n if (diagnosis.diagnosis === \"no-local-migrations\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"No local migrations found for this plugin.\",\n };\n }\n if (diagnosis.diagnosis === \"drift-manual\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). ` +\n \"Manual intervention required — some tables exist but schema is incomplete.\",\n };\n }\n if (diagnosis.diagnosis === \"unapplied\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"Migrations have not been applied yet. Start the dev server to apply them.\",\n };\n }\n if (diagnosis.diagnosis === \"untracked-existing-schema\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Tables exist but no matching migration history was found. \" +\n \"Run `drizzle-kit pull --init` in the plugin workspace to adopt the existing schema, \" +\n \"then run migrations from that baseline.\",\n };\n }\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`,\n };\n }\n\n // drift-safe-repair: drop the journal and replay\n const { Pool } = await import(\"pg\");\n const journalRef = `\"${diagnosis.journalSchema}\".\"${diagnosis.journalTable}\"`;\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);\n\n if (info.workspaceDir) {\n const configPath = join(info.workspaceDir, \"drizzle.config.ts\");\n if (existsSync(configPath)) {\n try {\n await spawnDrizzleMigrate(info.workspaceDir, configPath);\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`,\n };\n } catch (error) {\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. ` +\n `Run \\`bun run --cwd ${info.workspaceDir} db:migrate\\` manually.`,\n };\n }\n }\n }\n } catch (error) {\n return {\n status: \"error\",\n diagnosis,\n message: `Repair failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await pool.end().catch(() => {});\n }\n\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n \"No local drizzle.config.ts found — start the dev server to reapply migrations.\",\n };\n}\n\nfunction spawnDrizzleMigrate(cwd: string, configPath: string): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(\"npx\", [\"drizzle-kit\", \"migrate\", \"--config\", configPath], {\n cwd,\n stdio: \"pipe\",\n shell: true,\n });\n\n let stderr = \"\";\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => reject(err));\n\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));\n }\n });\n });\n}\n"],"mappings":";;;;;;;AAYA,eAAsB,aACpB,MACA,MACuB;CACvB,MAAM,YAAY,MAAMA,iCAAe,KAAK;AAE5C,KAAI,UAAU,cAAc,QAC1B,QAAO;EACL,QAAQ;EACR;EACA,SAAS,qCAAqC,UAAU;EACzD;AAGH,KAAI,SAAS,WACX,QAAO;EACL,QAAQ;EACR;EACA,SACE;EACH;AAGH,KAAI,UAAU,cAAc,qBAAqB;AAC/C,MAAI,UAAU,cAAc,UAC1B,QAAO;GAAE,QAAQ;GAAW;GAAW,SAAS;GAA2C;AAE7F,MAAI,UAAU,cAAc,sBAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,eAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE,2BAA2B,UAAU,cAAc,OAAO,GAAG,UAAU,eAAe,OAAO;GAEhG;AAEH,MAAI,UAAU,cAAc,YAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,4BAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE;GAGH;AAEH,SAAO;GACL,QAAQ;GACR;GACA,SAAS,8BAA8B,UAAU;GAClD;;CAIH,MAAM,EAAE,SAAS,2CAAM;CACvB,MAAM,aAAa,IAAI,UAAU,cAAc,KAAK,UAAU,aAAa;CAC3E,MAAM,OAAO,IAAI,KAAK;EACpB,kBAAkB,KAAK;EACvB,KACE,KAAK,YAAY,SAAS,YAAY,IAAI,KAAK,YAAY,SAAS,YAAY,GAC5E,QACA,EAAE,oBAAoB,OAAO;EACnC,KAAK;EACL,yBAAyB;EAC1B,CAAC;AAEF,KAAI;AACF,QAAM,KAAK,MAAM,wBAAwB,aAAa;AAEtD,MAAI,KAAK,cAAc;GACrB,MAAM,iCAAkB,KAAK,cAAc,oBAAoB;AAC/D,+BAAe,WAAW,CACxB,KAAI;AACF,UAAM,oBAAoB,KAAK,cAAc,WAAW;AACxD,WAAO;KACL,QAAQ;KACR;KACA,SACE,+BAA+B,UAAU,OAAO;KAEnD;YACM,OAAO;AACd,WAAO;KACL,QAAQ;KACR;KACA,SACE,+BAA+B,UAAU,OAAO,8BACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,wBAC7D,KAAK,aAAa;KAC5C;;;UAIA,OAAO;AACd,SAAO;GACL,QAAQ;GACR;GACA,SAAS,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAClF;WACO;AACR,QAAM,KAAK,KAAK,CAAC,YAAY,GAAG;;AAGlC,QAAO;EACL,QAAQ;EACR;EACA,SACE,+BAA+B,UAAU,OAAO;EAEnD;;AAGH,SAAS,oBAAoB,KAAa,YAAmC;AAC3E,QAAO,IAAI,SAAS,gBAAgB,WAAW;EAC7C,MAAM,sCAAc,OAAO;GAAC;GAAe;GAAW;GAAY;GAAW,EAAE;GAC7E;GACA,OAAO;GACP,OAAO;GACR,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,GAAG,SAAS,SAAiB;AACzC,aAAU,KAAK,UAAU;IACzB;AAEF,QAAM,GAAG,UAAU,QAAQ,OAAO,IAAI,CAAC;AAEvC,QAAM,GAAG,SAAS,SAAS;AACzB,OAAI,SAAS,EACX,iBAAgB;OAEhB,wBAAO,IAAI,MAAM,wCAAwC,KAAK,IAAI,SAAS,CAAC;IAE9E;GACF"}
@@ -14,9 +14,9 @@ async function repairPlugin(info, mode) {
14
14
  if (mode === "recreate") return {
15
15
  status: "refused",
16
16
  diagnosis,
17
- message: "Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead."
17
+ message: "Recreate mode is not supported without per-plugin database schemas. Use --mode history-reset instead."
18
18
  };
19
- if (diagnosis.diagnosis !== "drift-safe-repair" && diagnosis.diagnosis !== "legacy-importable") {
19
+ if (diagnosis.diagnosis !== "drift-safe-repair") {
20
20
  if (diagnosis.diagnosis === "healthy") return {
21
21
  status: "refused",
22
22
  diagnosis,
@@ -37,6 +37,11 @@ async function repairPlugin(info, mode) {
37
37
  diagnosis,
38
38
  message: "Migrations have not been applied yet. Start the dev server to apply them."
39
39
  };
40
+ if (diagnosis.diagnosis === "untracked-existing-schema") return {
41
+ status: "refused",
42
+ diagnosis,
43
+ message: "Tables exist but no matching migration history was found. Run `drizzle-kit pull --init` in the plugin workspace to adopt the existing schema, then run migrations from that baseline."
44
+ };
40
45
  return {
41
46
  status: "refused",
42
47
  diagnosis,
@@ -53,32 +58,32 @@ async function repairPlugin(info, mode) {
53
58
  });
54
59
  try {
55
60
  await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);
61
+ if (info.workspaceDir) {
62
+ const configPath = join(info.workspaceDir, "drizzle.config.ts");
63
+ if (existsSync(configPath)) try {
64
+ await spawnDrizzleMigrate(info.workspaceDir, configPath);
65
+ return {
66
+ status: "repaired",
67
+ diagnosis,
68
+ message: `Migration history reset for ${diagnosis.plugin}. Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`
69
+ };
70
+ } catch (error) {
71
+ return {
72
+ status: "repaired",
73
+ diagnosis,
74
+ message: `Migration history reset for ${diagnosis.plugin}. Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. Run \`bun run --cwd ${info.workspaceDir} db:migrate\` manually.`
75
+ };
76
+ }
77
+ }
56
78
  } catch (error) {
57
79
  return {
58
80
  status: "error",
59
81
  diagnosis,
60
- message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`
82
+ message: `Repair failed: ${error instanceof Error ? error.message : String(error)}`
61
83
  };
62
84
  } finally {
63
85
  await pool.end().catch(() => {});
64
86
  }
65
- if (info.workspaceDir) {
66
- const configPath = join(info.workspaceDir, "drizzle.config.ts");
67
- if (existsSync(configPath)) try {
68
- await spawnDrizzleMigrate(info.workspaceDir, configPath);
69
- return {
70
- status: "repaired",
71
- diagnosis,
72
- message: `Migration history reset for ${diagnosis.plugin}. Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`
73
- };
74
- } catch (error) {
75
- return {
76
- status: "repaired",
77
- diagnosis,
78
- message: `Migration history reset for ${diagnosis.plugin}. Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. Run \`bun run --cwd ${info.workspaceDir} db:migrate\` manually.`
79
- };
80
- }
81
- }
82
87
  return {
83
88
  status: "repaired",
84
89
  diagnosis,
@@ -1 +1 @@
1
- {"version":3,"file":"db-repair.mjs","names":[],"sources":["../../src/cli/db-repair.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type DoctorReport, diagnosePlugin } from \"./db-doctor\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface RepairResult {\n status: \"repaired\" | \"refused\" | \"error\";\n diagnosis: DoctorReport;\n message: string;\n}\n\nexport async function repairPlugin(\n info: PluginDbInfo,\n mode: \"history-reset\" | \"recreate\",\n): Promise<RepairResult> {\n const diagnosis = await diagnosePlugin(info);\n\n if (diagnosis.diagnosis === \"error\") {\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis failed: ${diagnosis.error}`,\n };\n }\n\n if (mode === \"recreate\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead.\",\n };\n }\n\n if (diagnosis.diagnosis !== \"drift-safe-repair\" && diagnosis.diagnosis !== \"legacy-importable\") {\n if (diagnosis.diagnosis === \"healthy\") {\n return { status: \"refused\", diagnosis, message: \"Database is healthy — no repair needed.\" };\n }\n if (diagnosis.diagnosis === \"no-local-migrations\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"No local migrations found for this plugin.\",\n };\n }\n if (diagnosis.diagnosis === \"drift-manual\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). ` +\n \"Manual intervention required — some tables exist but schema is incomplete.\",\n };\n }\n if (diagnosis.diagnosis === \"unapplied\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"Migrations have not been applied yet. Start the dev server to apply them.\",\n };\n }\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`,\n };\n }\n\n const { Pool } = await import(\"pg\");\n const journalRef = `\"${diagnosis.journalSchema}\".\"${diagnosis.journalTable}\"`;\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);\n } catch (error) {\n return {\n status: \"error\",\n diagnosis,\n message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await pool.end().catch(() => {});\n }\n\n if (info.workspaceDir) {\n const configPath = join(info.workspaceDir, \"drizzle.config.ts\");\n if (existsSync(configPath)) {\n try {\n await spawnDrizzleMigrate(info.workspaceDir, configPath);\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`,\n };\n } catch (error) {\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. ` +\n `Run \\`bun run --cwd ${info.workspaceDir} db:migrate\\` manually.`,\n };\n }\n }\n }\n\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n \"No local drizzle.config.ts found — start the dev server to reapply migrations.\",\n };\n}\n\nfunction spawnDrizzleMigrate(cwd: string, configPath: string): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(\"npx\", [\"drizzle-kit\", \"migrate\", \"--config\", configPath], {\n cwd,\n stdio: \"pipe\",\n shell: true,\n });\n\n let stderr = \"\";\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => reject(err));\n\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));\n }\n });\n });\n}\n"],"mappings":";;;;;;AAYA,eAAsB,aACpB,MACA,MACuB;CACvB,MAAM,YAAY,MAAM,eAAe,KAAK;AAE5C,KAAI,UAAU,cAAc,QAC1B,QAAO;EACL,QAAQ;EACR;EACA,SAAS,qCAAqC,UAAU;EACzD;AAGH,KAAI,SAAS,WACX,QAAO;EACL,QAAQ;EACR;EACA,SACE;EACH;AAGH,KAAI,UAAU,cAAc,uBAAuB,UAAU,cAAc,qBAAqB;AAC9F,MAAI,UAAU,cAAc,UAC1B,QAAO;GAAE,QAAQ;GAAW;GAAW,SAAS;GAA2C;AAE7F,MAAI,UAAU,cAAc,sBAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,eAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE,2BAA2B,UAAU,cAAc,OAAO,GAAG,UAAU,eAAe,OAAO;GAEhG;AAEH,MAAI,UAAU,cAAc,YAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,SAAO;GACL,QAAQ;GACR;GACA,SAAS,8BAA8B,UAAU;GAClD;;CAGH,MAAM,EAAE,SAAS,MAAM,OAAO;CAC9B,MAAM,aAAa,IAAI,UAAU,cAAc,KAAK,UAAU,aAAa;CAC3E,MAAM,OAAO,IAAI,KAAK;EACpB,kBAAkB,KAAK;EACvB,KACE,KAAK,YAAY,SAAS,YAAY,IAAI,KAAK,YAAY,SAAS,YAAY,GAC5E,QACA,EAAE,oBAAoB,OAAO;EACnC,KAAK;EACL,yBAAyB;EAC1B,CAAC;AAEF,KAAI;AACF,QAAM,KAAK,MAAM,wBAAwB,aAAa;UAC/C,OAAO;AACd,SAAO;GACL,QAAQ;GACR;GACA,SAAS,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC5F;WACO;AACR,QAAM,KAAK,KAAK,CAAC,YAAY,GAAG;;AAGlC,KAAI,KAAK,cAAc;EACrB,MAAM,aAAa,KAAK,KAAK,cAAc,oBAAoB;AAC/D,MAAI,WAAW,WAAW,CACxB,KAAI;AACF,SAAM,oBAAoB,KAAK,cAAc,WAAW;AACxD,UAAO;IACL,QAAQ;IACR;IACA,SACE,+BAA+B,UAAU,OAAO;IAEnD;WACM,OAAO;AACd,UAAO;IACL,QAAQ;IACR;IACA,SACE,+BAA+B,UAAU,OAAO,8BACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,wBAC7D,KAAK,aAAa;IAC5C;;;AAKP,QAAO;EACL,QAAQ;EACR;EACA,SACE,+BAA+B,UAAU,OAAO;EAEnD;;AAGH,SAAS,oBAAoB,KAAa,YAAmC;AAC3E,QAAO,IAAI,SAAS,gBAAgB,WAAW;EAC7C,MAAM,QAAQ,MAAM,OAAO;GAAC;GAAe;GAAW;GAAY;GAAW,EAAE;GAC7E;GACA,OAAO;GACP,OAAO;GACR,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,GAAG,SAAS,SAAiB;AACzC,aAAU,KAAK,UAAU;IACzB;AAEF,QAAM,GAAG,UAAU,QAAQ,OAAO,IAAI,CAAC;AAEvC,QAAM,GAAG,SAAS,SAAS;AACzB,OAAI,SAAS,EACX,iBAAgB;OAEhB,wBAAO,IAAI,MAAM,wCAAwC,KAAK,IAAI,SAAS,CAAC;IAE9E;GACF"}
1
+ {"version":3,"file":"db-repair.mjs","names":[],"sources":["../../src/cli/db-repair.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type DoctorReport, diagnosePlugin } from \"./db-doctor\";\nimport type { PluginDbInfo } from \"./db-studio\";\n\nexport interface RepairResult {\n status: \"repaired\" | \"refused\" | \"error\";\n diagnosis: DoctorReport;\n message: string;\n}\n\nexport async function repairPlugin(\n info: PluginDbInfo,\n mode: \"history-reset\" | \"recreate\",\n): Promise<RepairResult> {\n const diagnosis = await diagnosePlugin(info);\n\n if (diagnosis.diagnosis === \"error\") {\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis failed: ${diagnosis.error}`,\n };\n }\n\n if (mode === \"recreate\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Recreate mode is not supported without per-plugin database schemas. Use --mode history-reset instead.\",\n };\n }\n\n if (diagnosis.diagnosis !== \"drift-safe-repair\") {\n if (diagnosis.diagnosis === \"healthy\") {\n return { status: \"refused\", diagnosis, message: \"Database is healthy — no repair needed.\" };\n }\n if (diagnosis.diagnosis === \"no-local-migrations\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"No local migrations found for this plugin.\",\n };\n }\n if (diagnosis.diagnosis === \"drift-manual\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). ` +\n \"Manual intervention required — some tables exist but schema is incomplete.\",\n };\n }\n if (diagnosis.diagnosis === \"unapplied\") {\n return {\n status: \"refused\",\n diagnosis,\n message: \"Migrations have not been applied yet. Start the dev server to apply them.\",\n };\n }\n if (diagnosis.diagnosis === \"untracked-existing-schema\") {\n return {\n status: \"refused\",\n diagnosis,\n message:\n \"Tables exist but no matching migration history was found. \" +\n \"Run `drizzle-kit pull --init` in the plugin workspace to adopt the existing schema, \" +\n \"then run migrations from that baseline.\",\n };\n }\n return {\n status: \"refused\",\n diagnosis,\n message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`,\n };\n }\n\n // drift-safe-repair: drop the journal and replay\n const { Pool } = await import(\"pg\");\n const journalRef = `\"${diagnosis.journalSchema}\".\"${diagnosis.journalTable}\"`;\n const pool = new Pool({\n connectionString: info.databaseUrl,\n ssl:\n info.databaseUrl.includes(\"localhost\") || info.databaseUrl.includes(\"127.0.0.1\")\n ? false\n : { rejectUnauthorized: false },\n max: 1,\n connectionTimeoutMillis: 10_000,\n });\n\n try {\n await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);\n\n if (info.workspaceDir) {\n const configPath = join(info.workspaceDir, \"drizzle.config.ts\");\n if (existsSync(configPath)) {\n try {\n await spawnDrizzleMigrate(info.workspaceDir, configPath);\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Migrations reapplied via drizzle-kit. Restart the dev server to confirm.`,\n };\n } catch (error) {\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n `Automatic reapply failed: ${error instanceof Error ? error.message : String(error)}. ` +\n `Run \\`bun run --cwd ${info.workspaceDir} db:migrate\\` manually.`,\n };\n }\n }\n }\n } catch (error) {\n return {\n status: \"error\",\n diagnosis,\n message: `Repair failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await pool.end().catch(() => {});\n }\n\n return {\n status: \"repaired\",\n diagnosis,\n message:\n `Migration history reset for ${diagnosis.plugin}. ` +\n \"No local drizzle.config.ts found — start the dev server to reapply migrations.\",\n };\n}\n\nfunction spawnDrizzleMigrate(cwd: string, configPath: string): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(\"npx\", [\"drizzle-kit\", \"migrate\", \"--config\", configPath], {\n cwd,\n stdio: \"pipe\",\n shell: true,\n });\n\n let stderr = \"\";\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (err) => reject(err));\n\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));\n }\n });\n });\n}\n"],"mappings":";;;;;;AAYA,eAAsB,aACpB,MACA,MACuB;CACvB,MAAM,YAAY,MAAM,eAAe,KAAK;AAE5C,KAAI,UAAU,cAAc,QAC1B,QAAO;EACL,QAAQ;EACR;EACA,SAAS,qCAAqC,UAAU;EACzD;AAGH,KAAI,SAAS,WACX,QAAO;EACL,QAAQ;EACR;EACA,SACE;EACH;AAGH,KAAI,UAAU,cAAc,qBAAqB;AAC/C,MAAI,UAAU,cAAc,UAC1B,QAAO;GAAE,QAAQ;GAAW;GAAW,SAAS;GAA2C;AAE7F,MAAI,UAAU,cAAc,sBAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,eAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE,2BAA2B,UAAU,cAAc,OAAO,GAAG,UAAU,eAAe,OAAO;GAEhG;AAEH,MAAI,UAAU,cAAc,YAC1B,QAAO;GACL,QAAQ;GACR;GACA,SAAS;GACV;AAEH,MAAI,UAAU,cAAc,4BAC1B,QAAO;GACL,QAAQ;GACR;GACA,SACE;GAGH;AAEH,SAAO;GACL,QAAQ;GACR;GACA,SAAS,8BAA8B,UAAU;GAClD;;CAIH,MAAM,EAAE,SAAS,MAAM,OAAO;CAC9B,MAAM,aAAa,IAAI,UAAU,cAAc,KAAK,UAAU,aAAa;CAC3E,MAAM,OAAO,IAAI,KAAK;EACpB,kBAAkB,KAAK;EACvB,KACE,KAAK,YAAY,SAAS,YAAY,IAAI,KAAK,YAAY,SAAS,YAAY,GAC5E,QACA,EAAE,oBAAoB,OAAO;EACnC,KAAK;EACL,yBAAyB;EAC1B,CAAC;AAEF,KAAI;AACF,QAAM,KAAK,MAAM,wBAAwB,aAAa;AAEtD,MAAI,KAAK,cAAc;GACrB,MAAM,aAAa,KAAK,KAAK,cAAc,oBAAoB;AAC/D,OAAI,WAAW,WAAW,CACxB,KAAI;AACF,UAAM,oBAAoB,KAAK,cAAc,WAAW;AACxD,WAAO;KACL,QAAQ;KACR;KACA,SACE,+BAA+B,UAAU,OAAO;KAEnD;YACM,OAAO;AACd,WAAO;KACL,QAAQ;KACR;KACA,SACE,+BAA+B,UAAU,OAAO,8BACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,wBAC7D,KAAK,aAAa;KAC5C;;;UAIA,OAAO;AACd,SAAO;GACL,QAAQ;GACR;GACA,SAAS,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAClF;WACO;AACR,QAAM,KAAK,KAAK,CAAC,YAAY,GAAG;;AAGlC,QAAO;EACL,QAAQ;EACR;EACA,SACE,+BAA+B,UAAU,OAAO;EAEnD;;AAGH,SAAS,oBAAoB,KAAa,YAAmC;AAC3E,QAAO,IAAI,SAAS,gBAAgB,WAAW;EAC7C,MAAM,QAAQ,MAAM,OAAO;GAAC;GAAe;GAAW;GAAY;GAAW,EAAE;GAC7E;GACA,OAAO;GACP,OAAO;GACR,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,GAAG,SAAS,SAAiB;AACzC,aAAU,KAAK,UAAU;IACzB;AAEF,QAAM,GAAG,UAAU,QAAQ,OAAO,IAAI,CAAC;AAEvC,QAAM,GAAG,SAAS,SAAS;AACzB,OAAI,SAAS,EACX,iBAAgB;OAEhB,wBAAO,IAAI,MAAM,wCAAwC,KAAK,IAAI,SAAS,CAAC;IAE9E;GACF"}
@@ -66,7 +66,9 @@ async function runStudioLocal(info) {
66
66
  async function runStudioRemote(info) {
67
67
  const dbDir = (0, node_path.resolve)(info.projectDir, ".bos", "db", info.key);
68
68
  (0, node_fs.mkdirSync)(dbDir, { recursive: true });
69
- const journalTable = `__drizzle_migrations_${require_db.pluginMigrationSlug(info.key)}`;
69
+ const storage = require_db.getMigrationStorage(require_db.pluginMigrationSlug(info.key));
70
+ const journalSchema = storage.schema;
71
+ const journalTable = storage.table;
70
72
  const configPath = (0, node_path.join)(dbDir, "drizzle.config.ts");
71
73
  (0, node_fs.writeFileSync)(configPath, `import { defineConfig } from "drizzle-kit";
72
74
 
@@ -78,7 +80,7 @@ export default defineConfig({
78
80
  url: "${info.databaseUrl}",
79
81
  },
80
82
  migrations: {
81
- schema: "drizzle",
83
+ schema: "${journalSchema}",
82
84
  table: "${journalTable}",
83
85
  },
84
86
  verbose: true,
@@ -1 +1 @@
1
- {"version":3,"file":"db-studio.cjs","names":["pluginMigrationSlug"],"sources":["../../src/cli/db-studio.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport type { RuntimeConfig } from \"../types\";\n\nexport interface PluginDbInfo {\n key: string;\n source: \"local\" | \"remote\";\n section: \"app.api\" | \"app.auth\" | \"plugins\";\n databaseSecret: string;\n databaseUrl: string;\n workspaceDir?: string;\n projectDir: string;\n}\n\nexport function resolvePluginDbInfo(\n pluginKey: string,\n runtimeConfig: RuntimeConfig,\n projectDir: string,\n): PluginDbInfo {\n let source: \"local\" | \"remote\" | undefined;\n let section: PluginDbInfo[\"section\"];\n let secrets: string[] | undefined;\n let localPath: string | undefined;\n let key: string;\n\n if (pluginKey === \"api\" && runtimeConfig.api) {\n source = runtimeConfig.api.source;\n section = \"app.api\";\n secrets = runtimeConfig.api.secrets;\n localPath = runtimeConfig.api.localPath;\n key = \"api\";\n } else if (pluginKey === \"auth\" && runtimeConfig.auth) {\n source = runtimeConfig.auth.source;\n section = \"app.auth\";\n secrets = runtimeConfig.auth.secrets;\n localPath = runtimeConfig.auth.localPath;\n key = \"auth\";\n } else if (runtimeConfig.plugins?.[pluginKey]) {\n const plugin = runtimeConfig.plugins[pluginKey];\n source = plugin.source;\n section = \"plugins\";\n secrets = plugin.secrets;\n localPath = plugin.localPath;\n key = pluginKey;\n } else {\n throw new Error(\n `Plugin \"${pluginKey}\" not found in app.api, app.auth, or plugins. ` +\n `Available: ${[\n \"api\",\n ...(runtimeConfig.auth ? [\"auth\"] : []),\n ...Object.keys(runtimeConfig.plugins ?? {}),\n ].join(\", \")}`,\n );\n }\n\n const dbSecret = secrets?.find((s) => s.endsWith(\"_DATABASE_URL\"));\n if (!dbSecret) {\n throw new Error(\n `Plugin \"${pluginKey}\" has no database secret (no secret ending in _DATABASE_URL). ` +\n `Secrets: ${(secrets ?? []).join(\", \") || \"none\"}`,\n );\n }\n\n const dbUrl = process.env[dbSecret];\n if (!dbUrl) {\n throw new Error(\n `.env missing ${dbSecret} for plugin \"${pluginKey}\". ` +\n `Add it to your .env file (see .env.example).`,\n );\n }\n\n return {\n key,\n source: source ?? \"remote\",\n section,\n databaseSecret: dbSecret,\n databaseUrl: dbUrl,\n workspaceDir: localPath,\n projectDir,\n };\n}\n\nexport async function runStudioLocal(info: PluginDbInfo): Promise<void> {\n const workspaceRoot = info.workspaceDir\n ? resolve(info.projectDir, info.workspaceDir)\n : resolve(info.projectDir, info.key);\n const configPath = join(workspaceRoot, \"drizzle.config.ts\");\n\n if (!existsSync(configPath)) {\n throw new Error(\n `No drizzle.config.ts found in ${workspaceRoot}. ` +\n `Run 'drizzle-kit init' first in the plugin workspace.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key} (local)...`);\n\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: workspaceRoot,\n });\n}\n\nimport { pluginMigrationSlug } from \"../db\";\n\nexport async function runStudioRemote(info: PluginDbInfo): Promise<void> {\n const dbDir = resolve(info.projectDir, \".bos\", \"db\", info.key);\n\n mkdirSync(dbDir, { recursive: true });\n\n const slug = pluginMigrationSlug(info.key);\n const journalTable = `__drizzle_migrations_${slug}`;\n\n const configPath = join(dbDir, \"drizzle.config.ts\");\n const configContent = `import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n schema: \"./drizzle/schema.ts\",\n out: \"./drizzle\",\n dialect: \"postgresql\",\n dbCredentials: {\n url: \"${info.databaseUrl}\",\n },\n migrations: {\n schema: \"drizzle\",\n table: \"${journalTable}\",\n },\n verbose: true,\n strict: true,\n});\n`;\n\n writeFileSync(configPath, configContent);\n\n p.log.info(`Introspecting database schema for ${info.key}...`);\n try {\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"pull\", \"--config\", configPath], {\n cwd: dbDir,\n });\n } catch {\n throw new Error(\n `Failed to introspect database for \"${info.key}\". ` +\n `Check that ${info.databaseSecret} is correct and the database is reachable.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key}...`);\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: dbDir,\n });\n}\n\nfunction spawnAsync(cmd: string, args: string[], options: { cwd: string }): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(cmd, args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n shell: true,\n });\n\n child.on(\"error\", (err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new Error(\n `\"${cmd}\" not found. Ensure it is installed (e.g. 'npm install -g drizzle-kit').`,\n ),\n );\n } else {\n reject(new Error(`Failed to start ${cmd}: ${err.message}`));\n }\n });\n\n child.on(\"exit\", (code) => {\n if (code === 0 || code === null) {\n resolvePromise();\n } else {\n reject(new Error(`\"${cmd}\" exited with code ${code}`));\n }\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"db-studio.cjs","names":["getMigrationStorage","pluginMigrationSlug"],"sources":["../../src/cli/db-studio.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { getMigrationStorage, pluginMigrationSlug } from \"../db\";\nimport type { RuntimeConfig } from \"../types\";\n\nexport interface PluginDbInfo {\n key: string;\n source: \"local\" | \"remote\";\n section: \"app.api\" | \"app.auth\" | \"plugins\";\n databaseSecret: string;\n databaseUrl: string;\n workspaceDir?: string;\n projectDir: string;\n}\n\nexport function resolvePluginDbInfo(\n pluginKey: string,\n runtimeConfig: RuntimeConfig,\n projectDir: string,\n): PluginDbInfo {\n let source: \"local\" | \"remote\" | undefined;\n let section: PluginDbInfo[\"section\"];\n let secrets: string[] | undefined;\n let localPath: string | undefined;\n let key: string;\n\n if (pluginKey === \"api\" && runtimeConfig.api) {\n source = runtimeConfig.api.source;\n section = \"app.api\";\n secrets = runtimeConfig.api.secrets;\n localPath = runtimeConfig.api.localPath;\n key = \"api\";\n } else if (pluginKey === \"auth\" && runtimeConfig.auth) {\n source = runtimeConfig.auth.source;\n section = \"app.auth\";\n secrets = runtimeConfig.auth.secrets;\n localPath = runtimeConfig.auth.localPath;\n key = \"auth\";\n } else if (runtimeConfig.plugins?.[pluginKey]) {\n const plugin = runtimeConfig.plugins[pluginKey];\n source = plugin.source;\n section = \"plugins\";\n secrets = plugin.secrets;\n localPath = plugin.localPath;\n key = pluginKey;\n } else {\n throw new Error(\n `Plugin \"${pluginKey}\" not found in app.api, app.auth, or plugins. ` +\n `Available: ${[\n \"api\",\n ...(runtimeConfig.auth ? [\"auth\"] : []),\n ...Object.keys(runtimeConfig.plugins ?? {}),\n ].join(\", \")}`,\n );\n }\n\n const dbSecret = secrets?.find((s) => s.endsWith(\"_DATABASE_URL\"));\n if (!dbSecret) {\n throw new Error(\n `Plugin \"${pluginKey}\" has no database secret (no secret ending in _DATABASE_URL). ` +\n `Secrets: ${(secrets ?? []).join(\", \") || \"none\"}`,\n );\n }\n\n const dbUrl = process.env[dbSecret];\n if (!dbUrl) {\n throw new Error(\n `.env missing ${dbSecret} for plugin \"${pluginKey}\". ` +\n `Add it to your .env file (see .env.example).`,\n );\n }\n\n return {\n key,\n source: source ?? \"remote\",\n section,\n databaseSecret: dbSecret,\n databaseUrl: dbUrl,\n workspaceDir: localPath,\n projectDir,\n };\n}\n\nexport async function runStudioLocal(info: PluginDbInfo): Promise<void> {\n const workspaceRoot = info.workspaceDir\n ? resolve(info.projectDir, info.workspaceDir)\n : resolve(info.projectDir, info.key);\n const configPath = join(workspaceRoot, \"drizzle.config.ts\");\n\n if (!existsSync(configPath)) {\n throw new Error(\n `No drizzle.config.ts found in ${workspaceRoot}. ` +\n `Run 'drizzle-kit init' first in the plugin workspace.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key} (local)...`);\n\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: workspaceRoot,\n });\n}\n\nexport async function runStudioRemote(info: PluginDbInfo): Promise<void> {\n const dbDir = resolve(info.projectDir, \".bos\", \"db\", info.key);\n\n mkdirSync(dbDir, { recursive: true });\n\n const storage = getMigrationStorage(pluginMigrationSlug(info.key));\n const journalSchema = storage.schema;\n const journalTable = storage.table;\n\n const configPath = join(dbDir, \"drizzle.config.ts\");\n const configContent = `import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n schema: \"./drizzle/schema.ts\",\n out: \"./drizzle\",\n dialect: \"postgresql\",\n dbCredentials: {\n url: \"${info.databaseUrl}\",\n },\n migrations: {\n schema: \"${journalSchema}\",\n table: \"${journalTable}\",\n },\n verbose: true,\n strict: true,\n});\n`;\n\n writeFileSync(configPath, configContent);\n\n p.log.info(`Introspecting database schema for ${info.key}...`);\n try {\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"pull\", \"--config\", configPath], {\n cwd: dbDir,\n });\n } catch {\n throw new Error(\n `Failed to introspect database for \"${info.key}\". ` +\n `Check that ${info.databaseSecret} is correct and the database is reachable.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key}...`);\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: dbDir,\n });\n}\n\nfunction spawnAsync(cmd: string, args: string[], options: { cwd: string }): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(cmd, args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n shell: true,\n });\n\n child.on(\"error\", (err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new Error(\n `\"${cmd}\" not found. Ensure it is installed (e.g. 'npm install -g drizzle-kit').`,\n ),\n );\n } else {\n reject(new Error(`Failed to start ${cmd}: ${err.message}`));\n }\n });\n\n child.on(\"exit\", (code) => {\n if (code === 0 || code === null) {\n resolvePromise();\n } else {\n reject(new Error(`\"${cmd}\" exited with code ${code}`));\n }\n });\n });\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { pluginMigrationSlug } from "../db.mjs";
1
+ import { getMigrationStorage, pluginMigrationSlug } from "../db.mjs";
2
2
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import * as p from "@clack/prompts";
@@ -64,7 +64,9 @@ async function runStudioLocal(info) {
64
64
  async function runStudioRemote(info) {
65
65
  const dbDir = resolve(info.projectDir, ".bos", "db", info.key);
66
66
  mkdirSync(dbDir, { recursive: true });
67
- const journalTable = `__drizzle_migrations_${pluginMigrationSlug(info.key)}`;
67
+ const storage = getMigrationStorage(pluginMigrationSlug(info.key));
68
+ const journalSchema = storage.schema;
69
+ const journalTable = storage.table;
68
70
  const configPath = join(dbDir, "drizzle.config.ts");
69
71
  writeFileSync(configPath, `import { defineConfig } from "drizzle-kit";
70
72
 
@@ -76,7 +78,7 @@ export default defineConfig({
76
78
  url: "${info.databaseUrl}",
77
79
  },
78
80
  migrations: {
79
- schema: "drizzle",
81
+ schema: "${journalSchema}",
80
82
  table: "${journalTable}",
81
83
  },
82
84
  verbose: true,
@@ -1 +1 @@
1
- {"version":3,"file":"db-studio.mjs","names":[],"sources":["../../src/cli/db-studio.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport type { RuntimeConfig } from \"../types\";\n\nexport interface PluginDbInfo {\n key: string;\n source: \"local\" | \"remote\";\n section: \"app.api\" | \"app.auth\" | \"plugins\";\n databaseSecret: string;\n databaseUrl: string;\n workspaceDir?: string;\n projectDir: string;\n}\n\nexport function resolvePluginDbInfo(\n pluginKey: string,\n runtimeConfig: RuntimeConfig,\n projectDir: string,\n): PluginDbInfo {\n let source: \"local\" | \"remote\" | undefined;\n let section: PluginDbInfo[\"section\"];\n let secrets: string[] | undefined;\n let localPath: string | undefined;\n let key: string;\n\n if (pluginKey === \"api\" && runtimeConfig.api) {\n source = runtimeConfig.api.source;\n section = \"app.api\";\n secrets = runtimeConfig.api.secrets;\n localPath = runtimeConfig.api.localPath;\n key = \"api\";\n } else if (pluginKey === \"auth\" && runtimeConfig.auth) {\n source = runtimeConfig.auth.source;\n section = \"app.auth\";\n secrets = runtimeConfig.auth.secrets;\n localPath = runtimeConfig.auth.localPath;\n key = \"auth\";\n } else if (runtimeConfig.plugins?.[pluginKey]) {\n const plugin = runtimeConfig.plugins[pluginKey];\n source = plugin.source;\n section = \"plugins\";\n secrets = plugin.secrets;\n localPath = plugin.localPath;\n key = pluginKey;\n } else {\n throw new Error(\n `Plugin \"${pluginKey}\" not found in app.api, app.auth, or plugins. ` +\n `Available: ${[\n \"api\",\n ...(runtimeConfig.auth ? [\"auth\"] : []),\n ...Object.keys(runtimeConfig.plugins ?? {}),\n ].join(\", \")}`,\n );\n }\n\n const dbSecret = secrets?.find((s) => s.endsWith(\"_DATABASE_URL\"));\n if (!dbSecret) {\n throw new Error(\n `Plugin \"${pluginKey}\" has no database secret (no secret ending in _DATABASE_URL). ` +\n `Secrets: ${(secrets ?? []).join(\", \") || \"none\"}`,\n );\n }\n\n const dbUrl = process.env[dbSecret];\n if (!dbUrl) {\n throw new Error(\n `.env missing ${dbSecret} for plugin \"${pluginKey}\". ` +\n `Add it to your .env file (see .env.example).`,\n );\n }\n\n return {\n key,\n source: source ?? \"remote\",\n section,\n databaseSecret: dbSecret,\n databaseUrl: dbUrl,\n workspaceDir: localPath,\n projectDir,\n };\n}\n\nexport async function runStudioLocal(info: PluginDbInfo): Promise<void> {\n const workspaceRoot = info.workspaceDir\n ? resolve(info.projectDir, info.workspaceDir)\n : resolve(info.projectDir, info.key);\n const configPath = join(workspaceRoot, \"drizzle.config.ts\");\n\n if (!existsSync(configPath)) {\n throw new Error(\n `No drizzle.config.ts found in ${workspaceRoot}. ` +\n `Run 'drizzle-kit init' first in the plugin workspace.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key} (local)...`);\n\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: workspaceRoot,\n });\n}\n\nimport { pluginMigrationSlug } from \"../db\";\n\nexport async function runStudioRemote(info: PluginDbInfo): Promise<void> {\n const dbDir = resolve(info.projectDir, \".bos\", \"db\", info.key);\n\n mkdirSync(dbDir, { recursive: true });\n\n const slug = pluginMigrationSlug(info.key);\n const journalTable = `__drizzle_migrations_${slug}`;\n\n const configPath = join(dbDir, \"drizzle.config.ts\");\n const configContent = `import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n schema: \"./drizzle/schema.ts\",\n out: \"./drizzle\",\n dialect: \"postgresql\",\n dbCredentials: {\n url: \"${info.databaseUrl}\",\n },\n migrations: {\n schema: \"drizzle\",\n table: \"${journalTable}\",\n },\n verbose: true,\n strict: true,\n});\n`;\n\n writeFileSync(configPath, configContent);\n\n p.log.info(`Introspecting database schema for ${info.key}...`);\n try {\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"pull\", \"--config\", configPath], {\n cwd: dbDir,\n });\n } catch {\n throw new Error(\n `Failed to introspect database for \"${info.key}\". ` +\n `Check that ${info.databaseSecret} is correct and the database is reachable.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key}...`);\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: dbDir,\n });\n}\n\nfunction spawnAsync(cmd: string, args: string[], options: { cwd: string }): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(cmd, args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n shell: true,\n });\n\n child.on(\"error\", (err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new Error(\n `\"${cmd}\" not found. Ensure it is installed (e.g. 'npm install -g drizzle-kit').`,\n ),\n );\n } else {\n reject(new Error(`Failed to start ${cmd}: ${err.message}`));\n }\n });\n\n child.on(\"exit\", (code) => {\n if (code === 0 || code === null) {\n resolvePromise();\n } else {\n reject(new Error(`\"${cmd}\" exited with code ${code}`));\n }\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"db-studio.mjs","names":[],"sources":["../../src/cli/db-studio.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { getMigrationStorage, pluginMigrationSlug } from \"../db\";\nimport type { RuntimeConfig } from \"../types\";\n\nexport interface PluginDbInfo {\n key: string;\n source: \"local\" | \"remote\";\n section: \"app.api\" | \"app.auth\" | \"plugins\";\n databaseSecret: string;\n databaseUrl: string;\n workspaceDir?: string;\n projectDir: string;\n}\n\nexport function resolvePluginDbInfo(\n pluginKey: string,\n runtimeConfig: RuntimeConfig,\n projectDir: string,\n): PluginDbInfo {\n let source: \"local\" | \"remote\" | undefined;\n let section: PluginDbInfo[\"section\"];\n let secrets: string[] | undefined;\n let localPath: string | undefined;\n let key: string;\n\n if (pluginKey === \"api\" && runtimeConfig.api) {\n source = runtimeConfig.api.source;\n section = \"app.api\";\n secrets = runtimeConfig.api.secrets;\n localPath = runtimeConfig.api.localPath;\n key = \"api\";\n } else if (pluginKey === \"auth\" && runtimeConfig.auth) {\n source = runtimeConfig.auth.source;\n section = \"app.auth\";\n secrets = runtimeConfig.auth.secrets;\n localPath = runtimeConfig.auth.localPath;\n key = \"auth\";\n } else if (runtimeConfig.plugins?.[pluginKey]) {\n const plugin = runtimeConfig.plugins[pluginKey];\n source = plugin.source;\n section = \"plugins\";\n secrets = plugin.secrets;\n localPath = plugin.localPath;\n key = pluginKey;\n } else {\n throw new Error(\n `Plugin \"${pluginKey}\" not found in app.api, app.auth, or plugins. ` +\n `Available: ${[\n \"api\",\n ...(runtimeConfig.auth ? [\"auth\"] : []),\n ...Object.keys(runtimeConfig.plugins ?? {}),\n ].join(\", \")}`,\n );\n }\n\n const dbSecret = secrets?.find((s) => s.endsWith(\"_DATABASE_URL\"));\n if (!dbSecret) {\n throw new Error(\n `Plugin \"${pluginKey}\" has no database secret (no secret ending in _DATABASE_URL). ` +\n `Secrets: ${(secrets ?? []).join(\", \") || \"none\"}`,\n );\n }\n\n const dbUrl = process.env[dbSecret];\n if (!dbUrl) {\n throw new Error(\n `.env missing ${dbSecret} for plugin \"${pluginKey}\". ` +\n `Add it to your .env file (see .env.example).`,\n );\n }\n\n return {\n key,\n source: source ?? \"remote\",\n section,\n databaseSecret: dbSecret,\n databaseUrl: dbUrl,\n workspaceDir: localPath,\n projectDir,\n };\n}\n\nexport async function runStudioLocal(info: PluginDbInfo): Promise<void> {\n const workspaceRoot = info.workspaceDir\n ? resolve(info.projectDir, info.workspaceDir)\n : resolve(info.projectDir, info.key);\n const configPath = join(workspaceRoot, \"drizzle.config.ts\");\n\n if (!existsSync(configPath)) {\n throw new Error(\n `No drizzle.config.ts found in ${workspaceRoot}. ` +\n `Run 'drizzle-kit init' first in the plugin workspace.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key} (local)...`);\n\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: workspaceRoot,\n });\n}\n\nexport async function runStudioRemote(info: PluginDbInfo): Promise<void> {\n const dbDir = resolve(info.projectDir, \".bos\", \"db\", info.key);\n\n mkdirSync(dbDir, { recursive: true });\n\n const storage = getMigrationStorage(pluginMigrationSlug(info.key));\n const journalSchema = storage.schema;\n const journalTable = storage.table;\n\n const configPath = join(dbDir, \"drizzle.config.ts\");\n const configContent = `import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n schema: \"./drizzle/schema.ts\",\n out: \"./drizzle\",\n dialect: \"postgresql\",\n dbCredentials: {\n url: \"${info.databaseUrl}\",\n },\n migrations: {\n schema: \"${journalSchema}\",\n table: \"${journalTable}\",\n },\n verbose: true,\n strict: true,\n});\n`;\n\n writeFileSync(configPath, configContent);\n\n p.log.info(`Introspecting database schema for ${info.key}...`);\n try {\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"pull\", \"--config\", configPath], {\n cwd: dbDir,\n });\n } catch {\n throw new Error(\n `Failed to introspect database for \"${info.key}\". ` +\n `Check that ${info.databaseSecret} is correct and the database is reachable.`,\n );\n }\n\n p.log.info(`Starting Drizzle Studio for ${info.key}...`);\n await spawnAsync(\"npx\", [\"drizzle-kit\", \"studio\", \"--config\", configPath], {\n cwd: dbDir,\n });\n}\n\nfunction spawnAsync(cmd: string, args: string[], options: { cwd: string }): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(cmd, args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n shell: true,\n });\n\n child.on(\"error\", (err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new Error(\n `\"${cmd}\" not found. Ensure it is installed (e.g. 'npm install -g drizzle-kit').`,\n ),\n );\n } else {\n reject(new Error(`Failed to start ${cmd}: ${err.message}`));\n }\n });\n\n child.on(\"exit\", (code) => {\n if (code === 0 || code === null) {\n resolvePromise();\n } else {\n reject(new Error(`\"${cmd}\" exited with code ${code}`));\n }\n });\n });\n}\n"],"mappings":"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"}
@@ -17,19 +17,55 @@ function readNodeModulesVersion(projectDir, packageName) {
17
17
  if (!(0, node_fs.existsSync)(pkgPath)) return void 0;
18
18
  return JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8")).version;
19
19
  }
20
- function readInstalledFrameworkVersion(projectDir, packageName) {
21
- const pkgPath = (0, node_path.join)(projectDir, "package.json");
22
- if (!(0, node_fs.existsSync)(pkgPath)) return void 0;
23
- const pkg = JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8"));
20
+ function readSpecifier(pkg, packageName) {
24
21
  const deps = pkg.dependencies ?? {};
25
22
  const devDeps = pkg.devDependencies ?? {};
26
- const version = deps[packageName] || devDeps[packageName];
27
- if (!version) return readRootCatalogVersion(projectDir, packageName) ?? readNodeModulesVersion(projectDir, packageName);
28
- if (version.startsWith("catalog:")) return readRootCatalogVersion(projectDir, packageName) ?? readNodeModulesVersion(projectDir, packageName);
29
- if (version.startsWith("workspace:") || version.startsWith("file:")) return readNodeModulesVersion(projectDir, packageName);
30
- return stripVersionPrefix(version);
23
+ const peerDeps = pkg.peerDependencies ?? {};
24
+ return deps[packageName] || devDeps[packageName] || peerDeps[packageName];
25
+ }
26
+ function resolveFrameworkPackage(projectDir, packageName) {
27
+ const pkgPath = (0, node_path.join)(projectDir, "package.json");
28
+ if (!(0, node_fs.existsSync)(pkgPath)) return {
29
+ isLinked: false,
30
+ isWorkspaceLike: false
31
+ };
32
+ const specifier = readSpecifier(JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8")), packageName);
33
+ if (!specifier) return {
34
+ specifier,
35
+ installedVersion: readRootCatalogVersion(projectDir, packageName) ?? readNodeModulesVersion(projectDir, packageName),
36
+ isLinked: false,
37
+ isWorkspaceLike: false
38
+ };
39
+ if (specifier.startsWith("link:")) return {
40
+ specifier,
41
+ installedVersion: readNodeModulesVersion(projectDir, packageName),
42
+ isLinked: true,
43
+ isWorkspaceLike: false
44
+ };
45
+ if (specifier.startsWith("workspace:") || specifier.startsWith("file:")) return {
46
+ specifier,
47
+ installedVersion: readNodeModulesVersion(projectDir, packageName),
48
+ isLinked: false,
49
+ isWorkspaceLike: true
50
+ };
51
+ if (specifier === "catalog:") return {
52
+ specifier,
53
+ installedVersion: readRootCatalogVersion(projectDir, packageName) ?? readNodeModulesVersion(projectDir, packageName),
54
+ isLinked: false,
55
+ isWorkspaceLike: false
56
+ };
57
+ return {
58
+ specifier,
59
+ installedVersion: stripVersionPrefix(specifier),
60
+ isLinked: false,
61
+ isWorkspaceLike: false
62
+ };
63
+ }
64
+ function readInstalledFrameworkVersion(projectDir, packageName) {
65
+ return resolveFrameworkPackage(projectDir, packageName).installedVersion;
31
66
  }
32
67
 
33
68
  //#endregion
34
69
  exports.readInstalledFrameworkVersion = readInstalledFrameworkVersion;
70
+ exports.resolveFrameworkPackage = resolveFrameworkPackage;
35
71
  //# sourceMappingURL=framework-version.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"framework-version.cjs","names":[],"sources":["../../src/cli/framework-version.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nfunction stripVersionPrefix(version: string): string {\n return version.replace(/^[\\^~>=]+/, \"\");\n}\n\nexport function readRootCatalogVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: { catalog?: Record<string, string> };\n };\n const version = pkg.workspaces?.catalog?.[packageName];\n return version ? stripVersionPrefix(version) : undefined;\n}\n\nexport function readNodeModulesVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n const pkgPath = join(projectDir, \"node_modules\", packageName, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as { version?: string };\n return pkg.version;\n}\n\nexport function readInstalledFrameworkVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as Record<string, unknown>;\n const deps = (pkg.dependencies ?? {}) as Record<string, string>;\n const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n const version = deps[packageName] || devDeps[packageName];\n\n if (!version) {\n return (\n readRootCatalogVersion(projectDir, packageName) ??\n readNodeModulesVersion(projectDir, packageName)\n );\n }\n\n if (version.startsWith(\"catalog:\")) {\n return (\n readRootCatalogVersion(projectDir, packageName) ??\n readNodeModulesVersion(projectDir, packageName)\n );\n }\n\n if (version.startsWith(\"workspace:\") || version.startsWith(\"file:\")) {\n return readNodeModulesVersion(projectDir, packageName);\n }\n\n return stripVersionPrefix(version);\n}\n"],"mappings":";;;;;AAGA,SAAS,mBAAmB,SAAyB;AACnD,QAAO,QAAQ,QAAQ,aAAa,GAAG;;AAGzC,SAAgB,uBACd,YACA,aACoB;CACpB,MAAM,8BAAe,YAAY,eAAe;AAChD,KAAI,yBAAY,QAAQ,CAAE,QAAO;CAIjC,MAAM,UAHM,KAAK,gCAAmB,SAAS,QAAQ,CAGlC,CAAC,YAAY,UAAU;AAC1C,QAAO,UAAU,mBAAmB,QAAQ,GAAG;;AAGjD,SAAgB,uBACd,YACA,aACoB;CACpB,MAAM,8BAAe,YAAY,gBAAgB,aAAa,eAAe;AAC7E,KAAI,yBAAY,QAAQ,CAAE,QAAO;AAEjC,QADY,KAAK,gCAAmB,SAAS,QAAQ,CAC3C,CAAC;;AAGb,SAAgB,8BACd,YACA,aACoB;CACpB,MAAM,8BAAe,YAAY,eAAe;AAChD,KAAI,yBAAY,QAAQ,CAAE,QAAO;CACjC,MAAM,MAAM,KAAK,gCAAmB,SAAS,QAAQ,CAAC;CACtD,MAAM,OAAQ,IAAI,gBAAgB,EAAE;CACpC,MAAM,UAAW,IAAI,mBAAmB,EAAE;CAC1C,MAAM,UAAU,KAAK,gBAAgB,QAAQ;AAE7C,KAAI,CAAC,QACH,QACE,uBAAuB,YAAY,YAAY,IAC/C,uBAAuB,YAAY,YAAY;AAInD,KAAI,QAAQ,WAAW,WAAW,CAChC,QACE,uBAAuB,YAAY,YAAY,IAC/C,uBAAuB,YAAY,YAAY;AAInD,KAAI,QAAQ,WAAW,aAAa,IAAI,QAAQ,WAAW,QAAQ,CACjE,QAAO,uBAAuB,YAAY,YAAY;AAGxD,QAAO,mBAAmB,QAAQ"}
1
+ {"version":3,"file":"framework-version.cjs","names":[],"sources":["../../src/cli/framework-version.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface FrameworkPackageResolution {\n specifier?: string;\n installedVersion?: string;\n isLinked: boolean;\n isWorkspaceLike: boolean;\n}\n\nfunction stripVersionPrefix(version: string): string {\n return version.replace(/^[\\^~>=]+/, \"\");\n}\n\nexport function readRootCatalogVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: { catalog?: Record<string, string> };\n };\n const version = pkg.workspaces?.catalog?.[packageName];\n return version ? stripVersionPrefix(version) : undefined;\n}\n\nexport function readNodeModulesVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n const pkgPath = join(projectDir, \"node_modules\", packageName, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as { version?: string };\n return pkg.version;\n}\n\nfunction readSpecifier(pkg: Record<string, unknown>, packageName: string): string | undefined {\n const deps = (pkg.dependencies ?? {}) as Record<string, string>;\n const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n const peerDeps = (pkg.peerDependencies ?? {}) as Record<string, string>;\n return deps[packageName] || devDeps[packageName] || peerDeps[packageName];\n}\n\nexport function resolveFrameworkPackage(\n projectDir: string,\n packageName: string,\n): FrameworkPackageResolution {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) {\n return { isLinked: false, isWorkspaceLike: false };\n }\n\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as Record<string, unknown>;\n const specifier = readSpecifier(pkg, packageName);\n\n if (!specifier) {\n const catalog = readRootCatalogVersion(projectDir, packageName);\n const installed = catalog ?? readNodeModulesVersion(projectDir, packageName);\n return {\n specifier,\n installedVersion: installed,\n isLinked: false,\n isWorkspaceLike: false,\n };\n }\n\n if (specifier.startsWith(\"link:\")) {\n return {\n specifier,\n installedVersion: readNodeModulesVersion(projectDir, packageName),\n isLinked: true,\n isWorkspaceLike: false,\n };\n }\n\n if (specifier.startsWith(\"workspace:\") || specifier.startsWith(\"file:\")) {\n return {\n specifier,\n installedVersion: readNodeModulesVersion(projectDir, packageName),\n isLinked: false,\n isWorkspaceLike: true,\n };\n }\n\n if (specifier === \"catalog:\") {\n const catalog = readRootCatalogVersion(projectDir, packageName);\n const installed = catalog ?? readNodeModulesVersion(projectDir, packageName);\n return {\n specifier,\n installedVersion: installed,\n isLinked: false,\n isWorkspaceLike: false,\n };\n }\n\n return {\n specifier,\n installedVersion: stripVersionPrefix(specifier),\n isLinked: false,\n isWorkspaceLike: false,\n };\n}\n\nexport function readInstalledFrameworkVersion(\n projectDir: string,\n packageName: string,\n): string | undefined {\n return resolveFrameworkPackage(projectDir, packageName).installedVersion;\n}\n"],"mappings":";;;;;AAUA,SAAS,mBAAmB,SAAyB;AACnD,QAAO,QAAQ,QAAQ,aAAa,GAAG;;AAGzC,SAAgB,uBACd,YACA,aACoB;CACpB,MAAM,8BAAe,YAAY,eAAe;AAChD,KAAI,yBAAY,QAAQ,CAAE,QAAO;CAIjC,MAAM,UAHM,KAAK,gCAAmB,SAAS,QAAQ,CAGlC,CAAC,YAAY,UAAU;AAC1C,QAAO,UAAU,mBAAmB,QAAQ,GAAG;;AAGjD,SAAgB,uBACd,YACA,aACoB;CACpB,MAAM,8BAAe,YAAY,gBAAgB,aAAa,eAAe;AAC7E,KAAI,yBAAY,QAAQ,CAAE,QAAO;AAEjC,QADY,KAAK,gCAAmB,SAAS,QAAQ,CAC3C,CAAC;;AAGb,SAAS,cAAc,KAA8B,aAAyC;CAC5F,MAAM,OAAQ,IAAI,gBAAgB,EAAE;CACpC,MAAM,UAAW,IAAI,mBAAmB,EAAE;CAC1C,MAAM,WAAY,IAAI,oBAAoB,EAAE;AAC5C,QAAO,KAAK,gBAAgB,QAAQ,gBAAgB,SAAS;;AAG/D,SAAgB,wBACd,YACA,aAC4B;CAC5B,MAAM,8BAAe,YAAY,eAAe;AAChD,KAAI,yBAAY,QAAQ,CACtB,QAAO;EAAE,UAAU;EAAO,iBAAiB;EAAO;CAIpD,MAAM,YAAY,cADN,KAAK,gCAAmB,SAAS,QAAQ,CAClB,EAAE,YAAY;AAEjD,KAAI,CAAC,UAGH,QAAO;EACL;EACA,kBAJc,uBAAuB,YAAY,YAC1B,IAAI,uBAAuB,YAAY,YAAY;EAI1E,UAAU;EACV,iBAAiB;EAClB;AAGH,KAAI,UAAU,WAAW,QAAQ,CAC/B,QAAO;EACL;EACA,kBAAkB,uBAAuB,YAAY,YAAY;EACjE,UAAU;EACV,iBAAiB;EAClB;AAGH,KAAI,UAAU,WAAW,aAAa,IAAI,UAAU,WAAW,QAAQ,CACrE,QAAO;EACL;EACA,kBAAkB,uBAAuB,YAAY,YAAY;EACjE,UAAU;EACV,iBAAiB;EAClB;AAGH,KAAI,cAAc,WAGhB,QAAO;EACL;EACA,kBAJc,uBAAuB,YAAY,YAC1B,IAAI,uBAAuB,YAAY,YAAY;EAI1E,UAAU;EACV,iBAAiB;EAClB;AAGH,QAAO;EACL;EACA,kBAAkB,mBAAmB,UAAU;EAC/C,UAAU;EACV,iBAAiB;EAClB;;AAGH,SAAgB,8BACd,YACA,aACoB;AACpB,QAAO,wBAAwB,YAAY,YAAY,CAAC"}