everything-dev 1.47.3 → 1.48.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 (63) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +2 -0
  2. package/dist/_virtual/_rolldown/runtime.mjs +32 -0
  3. package/dist/cli/db-doctor.cjs +131 -0
  4. package/dist/cli/db-doctor.cjs.map +1 -0
  5. package/dist/cli/db-doctor.mjs +130 -0
  6. package/dist/cli/db-doctor.mjs.map +1 -0
  7. package/dist/cli/db-repair.cjs +115 -0
  8. package/dist/cli/db-repair.cjs.map +1 -0
  9. package/dist/cli/db-repair.mjs +114 -0
  10. package/dist/cli/db-repair.mjs.map +1 -0
  11. package/dist/cli/db-studio.cjs +6 -0
  12. package/dist/cli/db-studio.cjs.map +1 -1
  13. package/dist/cli/db-studio.mjs +6 -0
  14. package/dist/cli/db-studio.mjs.map +1 -1
  15. package/dist/cli/init.cjs +4 -0
  16. package/dist/cli/init.cjs.map +1 -1
  17. package/dist/cli/init.d.cts.map +1 -1
  18. package/dist/cli/init.d.mts.map +1 -1
  19. package/dist/cli/init.mjs +4 -0
  20. package/dist/cli/init.mjs.map +1 -1
  21. package/dist/cli/sync.cjs +10 -1
  22. package/dist/cli/sync.cjs.map +1 -1
  23. package/dist/cli/sync.mjs +10 -1
  24. package/dist/cli/sync.mjs.map +1 -1
  25. package/dist/cli.cjs +46 -0
  26. package/dist/cli.cjs.map +1 -1
  27. package/dist/cli.mjs +46 -0
  28. package/dist/cli.mjs.map +1 -1
  29. package/dist/contract.cjs +46 -1
  30. package/dist/contract.cjs.map +1 -1
  31. package/dist/contract.d.cts +92 -8
  32. package/dist/contract.d.cts.map +1 -1
  33. package/dist/contract.d.mts +92 -8
  34. package/dist/contract.d.mts.map +1 -1
  35. package/dist/contract.meta.cjs +20 -0
  36. package/dist/contract.meta.cjs.map +1 -1
  37. package/dist/contract.meta.d.cts +26 -0
  38. package/dist/contract.meta.d.mts +26 -0
  39. package/dist/contract.meta.mjs +20 -0
  40. package/dist/contract.meta.mjs.map +1 -1
  41. package/dist/contract.mjs +43 -2
  42. package/dist/contract.mjs.map +1 -1
  43. package/dist/db.cjs +74 -0
  44. package/dist/db.cjs.map +1 -0
  45. package/dist/db.d.cts +23 -0
  46. package/dist/db.d.cts.map +1 -0
  47. package/dist/db.d.mts +23 -0
  48. package/dist/db.d.mts.map +1 -0
  49. package/dist/db.mjs +66 -0
  50. package/dist/db.mjs.map +1 -0
  51. package/dist/index.cjs +4 -0
  52. package/dist/index.d.cts +2 -2
  53. package/dist/index.d.mts +2 -2
  54. package/dist/index.mjs +2 -2
  55. package/dist/plugin.cjs +93 -0
  56. package/dist/plugin.cjs.map +1 -1
  57. package/dist/plugin.d.cts +43 -4
  58. package/dist/plugin.d.mts +43 -4
  59. package/dist/plugin.mjs +93 -0
  60. package/dist/plugin.mjs.map +1 -1
  61. package/dist/types.d.cts +2 -2
  62. package/dist/types.d.mts +2 -2
  63. package/package.json +7 -2
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
8
9
  var __copyProps = (to, from, except, desc) => {
9
10
  if (from && typeof from === "object" || typeof from === "function") {
10
11
  for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
@@ -26,4 +27,5 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
27
 
27
28
  //#endregion
28
29
 
30
+ exports.__commonJSMin = __commonJSMin;
29
31
  exports.__toESM = __toESM;
@@ -0,0 +1,32 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ //#region \0rolldown/runtime.js
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) {
16
+ __defProp(to, key, {
17
+ get: ((k) => from[k]).bind(null, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ }
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
26
+ value: mod,
27
+ enumerable: true
28
+ }) : target, mod));
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+
31
+ //#endregion
32
+ export { __commonJSMin, __require, __toESM };
@@ -0,0 +1,131 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_db = require('../db.cjs');
3
+ let node_fs = require("node:fs");
4
+ let node_path = require("node:path");
5
+ let node_crypto = require("node:crypto");
6
+
7
+ //#region src/cli/db-doctor.ts
8
+ function hashFile(raw) {
9
+ return (0, node_crypto.createHash)("sha256").update(raw).digest("hex");
10
+ }
11
+ function readLocalMigrations(workspaceDir) {
12
+ const migrationsDir = (0, node_path.resolve)(workspaceDir, "src/db/migrations");
13
+ const journalPath = (0, node_path.join)((0, node_path.join)(migrationsDir, "meta"), "_journal.json");
14
+ if (!(0, node_fs.existsSync)(journalPath)) return [];
15
+ return JSON.parse((0, node_fs.readFileSync)(journalPath, "utf8")).entries.map((entry) => {
16
+ const sqlPath = (0, node_path.join)(migrationsDir, `${entry.tag}.sql`);
17
+ if (!(0, node_fs.existsSync)(sqlPath)) return {
18
+ tag: entry.tag,
19
+ hash: "",
20
+ sql: []
21
+ };
22
+ const raw = (0, node_fs.readFileSync)(sqlPath, "utf8");
23
+ const sql = raw.split("--> statement-breakpoint").map((s) => s.trim());
24
+ return {
25
+ tag: entry.tag,
26
+ hash: hashFile(raw),
27
+ sql
28
+ };
29
+ });
30
+ }
31
+ async function diagnosePlugin(info) {
32
+ const { Pool } = await Promise.resolve().then(() => require("../node_modules/pg/esm/index.cjs"));
33
+ const slug = require_db.pluginMigrationSlug(info.key);
34
+ const journalTable = `__drizzle_migrations_${slug}`;
35
+ const journalSchema = "drizzle";
36
+ const journalRef = `"${journalSchema}"."${journalTable}"`;
37
+ const localMigrations = info.workspaceDir ? readLocalMigrations(info.workspaceDir) : [];
38
+ const expectedTables = require_db.extractExpectedTables(localMigrations);
39
+ const localHashes = localMigrations.map((m) => m.hash).filter(Boolean);
40
+ const migrationHashes = localHashes;
41
+ const pool = new Pool({
42
+ connectionString: info.databaseUrl,
43
+ ssl: info.databaseUrl.includes("localhost") || info.databaseUrl.includes("127.0.0.1") ? false : { rejectUnauthorized: false },
44
+ max: 1,
45
+ connectionTimeoutMillis: 1e4
46
+ });
47
+ try {
48
+ const journalExists = await pool.query(`
49
+ SELECT EXISTS (
50
+ SELECT 1 FROM information_schema.tables
51
+ WHERE table_schema = '${journalSchema}' AND table_name = '${journalTable}'
52
+ ) AS exists
53
+ `);
54
+ let appliedHashCount = 0;
55
+ if (journalExists.rows[0]?.exists) appliedHashCount = (await pool.query(`SELECT hash FROM ${journalRef}`)).rows.length;
56
+ let missingTables = [];
57
+ if (expectedTables.length > 0) {
58
+ const tableResult = await pool.query(`
59
+ SELECT table_name FROM information_schema.tables
60
+ WHERE table_schema = 'public'
61
+ AND table_name = ANY($1)
62
+ `, [expectedTables]);
63
+ const existing = new Set(tableResult.rows.map((r) => r.table_name));
64
+ missingTables = expectedTables.filter((t) => !existing.has(t));
65
+ }
66
+ const legacyCount = await countLegacyHashes(pool, localHashes);
67
+ let diagnosis;
68
+ if (localMigrations.length === 0) diagnosis = "no-local-migrations";
69
+ else if (appliedHashCount === 0 && localHashes.length > 0) diagnosis = legacyCount > 0 ? "legacy-importable" : "unapplied";
70
+ else if (missingTables.length === 0) diagnosis = "healthy";
71
+ else if (missingTables.length === expectedTables.length) diagnosis = "drift-safe-repair";
72
+ else diagnosis = "drift-manual";
73
+ const masked = info.databaseUrl.replace(/\/\/[^:]+:[^@]+@/, "//***:***@");
74
+ return {
75
+ plugin: info.key,
76
+ slug,
77
+ journalTable,
78
+ journalSchema,
79
+ dbSecret: info.databaseSecret,
80
+ dbUrl: masked,
81
+ workspaceDir: info.workspaceDir,
82
+ localMigrationCount: localMigrations.length,
83
+ appliedHashCount,
84
+ expectedTables,
85
+ missingTables,
86
+ migrationHashes,
87
+ diagnosis,
88
+ legacyCount
89
+ };
90
+ } catch (error) {
91
+ return {
92
+ plugin: info.key,
93
+ slug,
94
+ journalTable,
95
+ journalSchema,
96
+ dbSecret: info.databaseSecret,
97
+ dbUrl: info.databaseUrl.replace(/\/\/[^:]+:[^@]+@/, "//***:***@"),
98
+ workspaceDir: info.workspaceDir,
99
+ localMigrationCount: localMigrations.length,
100
+ appliedHashCount: 0,
101
+ expectedTables,
102
+ missingTables: [],
103
+ migrationHashes: [],
104
+ diagnosis: "error",
105
+ legacyCount: 0,
106
+ error: error instanceof Error ? error.message : String(error)
107
+ };
108
+ } finally {
109
+ await pool.end().catch(() => {});
110
+ }
111
+ }
112
+ async function countLegacyHashes(pool, localHashes) {
113
+ if (localHashes.length === 0) return 0;
114
+ const candidates = [{
115
+ schema: "drizzle",
116
+ table: "__drizzle_migrations"
117
+ }, {
118
+ schema: "public",
119
+ table: "drizzle_migrations"
120
+ }];
121
+ let total = 0;
122
+ for (const c of candidates) try {
123
+ const result = await pool.query(`SELECT count(*)::int AS cnt FROM "${c.schema}"."${c.table}" WHERE hash = ANY($1)`, [localHashes]);
124
+ total += result.rows[0]?.cnt ?? 0;
125
+ } catch {}
126
+ return total;
127
+ }
128
+
129
+ //#endregion
130
+ exports.diagnosePlugin = diagnosePlugin;
131
+ //# sourceMappingURL=db-doctor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-doctor.cjs","names":["pluginMigrationSlug","extractExpectedTables"],"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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"}
@@ -0,0 +1,130 @@
1
+ import { extractExpectedTables, pluginMigrationSlug } from "../db.mjs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { createHash } from "node:crypto";
5
+
6
+ //#region src/cli/db-doctor.ts
7
+ function hashFile(raw) {
8
+ return createHash("sha256").update(raw).digest("hex");
9
+ }
10
+ function readLocalMigrations(workspaceDir) {
11
+ const migrationsDir = resolve(workspaceDir, "src/db/migrations");
12
+ const journalPath = join(join(migrationsDir, "meta"), "_journal.json");
13
+ if (!existsSync(journalPath)) return [];
14
+ return JSON.parse(readFileSync(journalPath, "utf8")).entries.map((entry) => {
15
+ const sqlPath = join(migrationsDir, `${entry.tag}.sql`);
16
+ if (!existsSync(sqlPath)) return {
17
+ tag: entry.tag,
18
+ hash: "",
19
+ sql: []
20
+ };
21
+ const raw = readFileSync(sqlPath, "utf8");
22
+ const sql = raw.split("--> statement-breakpoint").map((s) => s.trim());
23
+ return {
24
+ tag: entry.tag,
25
+ hash: hashFile(raw),
26
+ sql
27
+ };
28
+ });
29
+ }
30
+ async function diagnosePlugin(info) {
31
+ const { Pool } = await import("../node_modules/pg/esm/index.mjs");
32
+ const slug = pluginMigrationSlug(info.key);
33
+ const journalTable = `__drizzle_migrations_${slug}`;
34
+ const journalSchema = "drizzle";
35
+ const journalRef = `"${journalSchema}"."${journalTable}"`;
36
+ const localMigrations = info.workspaceDir ? readLocalMigrations(info.workspaceDir) : [];
37
+ const expectedTables = extractExpectedTables(localMigrations);
38
+ const localHashes = localMigrations.map((m) => m.hash).filter(Boolean);
39
+ const migrationHashes = localHashes;
40
+ const pool = new Pool({
41
+ connectionString: info.databaseUrl,
42
+ ssl: info.databaseUrl.includes("localhost") || info.databaseUrl.includes("127.0.0.1") ? false : { rejectUnauthorized: false },
43
+ max: 1,
44
+ connectionTimeoutMillis: 1e4
45
+ });
46
+ try {
47
+ const journalExists = await pool.query(`
48
+ SELECT EXISTS (
49
+ SELECT 1 FROM information_schema.tables
50
+ WHERE table_schema = '${journalSchema}' AND table_name = '${journalTable}'
51
+ ) AS exists
52
+ `);
53
+ let appliedHashCount = 0;
54
+ if (journalExists.rows[0]?.exists) appliedHashCount = (await pool.query(`SELECT hash FROM ${journalRef}`)).rows.length;
55
+ let missingTables = [];
56
+ if (expectedTables.length > 0) {
57
+ const tableResult = await pool.query(`
58
+ SELECT table_name FROM information_schema.tables
59
+ WHERE table_schema = 'public'
60
+ AND table_name = ANY($1)
61
+ `, [expectedTables]);
62
+ const existing = new Set(tableResult.rows.map((r) => r.table_name));
63
+ missingTables = expectedTables.filter((t) => !existing.has(t));
64
+ }
65
+ const legacyCount = await countLegacyHashes(pool, localHashes);
66
+ let diagnosis;
67
+ if (localMigrations.length === 0) diagnosis = "no-local-migrations";
68
+ else if (appliedHashCount === 0 && localHashes.length > 0) diagnosis = legacyCount > 0 ? "legacy-importable" : "unapplied";
69
+ else if (missingTables.length === 0) diagnosis = "healthy";
70
+ else if (missingTables.length === expectedTables.length) diagnosis = "drift-safe-repair";
71
+ else diagnosis = "drift-manual";
72
+ const masked = info.databaseUrl.replace(/\/\/[^:]+:[^@]+@/, "//***:***@");
73
+ return {
74
+ plugin: info.key,
75
+ slug,
76
+ journalTable,
77
+ journalSchema,
78
+ dbSecret: info.databaseSecret,
79
+ dbUrl: masked,
80
+ workspaceDir: info.workspaceDir,
81
+ localMigrationCount: localMigrations.length,
82
+ appliedHashCount,
83
+ expectedTables,
84
+ missingTables,
85
+ migrationHashes,
86
+ diagnosis,
87
+ legacyCount
88
+ };
89
+ } catch (error) {
90
+ return {
91
+ plugin: info.key,
92
+ slug,
93
+ journalTable,
94
+ journalSchema,
95
+ dbSecret: info.databaseSecret,
96
+ dbUrl: info.databaseUrl.replace(/\/\/[^:]+:[^@]+@/, "//***:***@"),
97
+ workspaceDir: info.workspaceDir,
98
+ localMigrationCount: localMigrations.length,
99
+ appliedHashCount: 0,
100
+ expectedTables,
101
+ missingTables: [],
102
+ migrationHashes: [],
103
+ diagnosis: "error",
104
+ legacyCount: 0,
105
+ error: error instanceof Error ? error.message : String(error)
106
+ };
107
+ } finally {
108
+ await pool.end().catch(() => {});
109
+ }
110
+ }
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
+
128
+ //#endregion
129
+ export { diagnosePlugin };
130
+ //# sourceMappingURL=db-doctor.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,115 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_db_doctor = require('./db-doctor.cjs');
3
+ let node_fs = require("node:fs");
4
+ let node_path = require("node:path");
5
+ let node_child_process = require("node:child_process");
6
+
7
+ //#region src/cli/db-repair.ts
8
+ async function repairPlugin(info, mode) {
9
+ const diagnosis = await require_db_doctor.diagnosePlugin(info);
10
+ if (diagnosis.diagnosis === "error") return {
11
+ status: "refused",
12
+ diagnosis,
13
+ message: `Cannot repair — diagnosis failed: ${diagnosis.error}`
14
+ };
15
+ if (mode === "recreate") return {
16
+ status: "refused",
17
+ diagnosis,
18
+ message: "Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead."
19
+ };
20
+ if (diagnosis.diagnosis !== "drift-safe-repair" && diagnosis.diagnosis !== "legacy-importable") {
21
+ if (diagnosis.diagnosis === "healthy") return {
22
+ status: "refused",
23
+ diagnosis,
24
+ message: "Database is healthy — no repair needed."
25
+ };
26
+ if (diagnosis.diagnosis === "no-local-migrations") return {
27
+ status: "refused",
28
+ diagnosis,
29
+ message: "No local migrations found for this plugin."
30
+ };
31
+ if (diagnosis.diagnosis === "drift-manual") return {
32
+ status: "refused",
33
+ diagnosis,
34
+ message: `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). Manual intervention required — some tables exist but schema is incomplete.`
35
+ };
36
+ if (diagnosis.diagnosis === "unapplied") return {
37
+ status: "refused",
38
+ diagnosis,
39
+ message: "Migrations have not been applied yet. Start the dev server to apply them."
40
+ };
41
+ return {
42
+ status: "refused",
43
+ diagnosis,
44
+ message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`
45
+ };
46
+ }
47
+ const { Pool } = await Promise.resolve().then(() => require("../node_modules/pg/esm/index.cjs"));
48
+ const journalRef = `"${diagnosis.journalSchema}"."${diagnosis.journalTable}"`;
49
+ const pool = new Pool({
50
+ connectionString: info.databaseUrl,
51
+ ssl: info.databaseUrl.includes("localhost") || info.databaseUrl.includes("127.0.0.1") ? false : { rejectUnauthorized: false },
52
+ max: 1,
53
+ connectionTimeoutMillis: 1e4
54
+ });
55
+ try {
56
+ await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);
57
+ } catch (error) {
58
+ return {
59
+ status: "error",
60
+ diagnosis,
61
+ message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`
62
+ };
63
+ } finally {
64
+ await pool.end().catch(() => {});
65
+ }
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
+ return {
84
+ status: "repaired",
85
+ diagnosis,
86
+ message: `Migration history reset for ${diagnosis.plugin}. No local drizzle.config.ts found — start the dev server to reapply migrations.`
87
+ };
88
+ }
89
+ function spawnDrizzleMigrate(cwd, configPath) {
90
+ return new Promise((resolvePromise, reject) => {
91
+ const child = (0, node_child_process.spawn)("npx", [
92
+ "drizzle-kit",
93
+ "migrate",
94
+ "--config",
95
+ configPath
96
+ ], {
97
+ cwd,
98
+ stdio: "pipe",
99
+ shell: true
100
+ });
101
+ let stderr = "";
102
+ child.stderr?.on("data", (data) => {
103
+ stderr += data.toString();
104
+ });
105
+ child.on("error", (err) => reject(err));
106
+ child.on("exit", (code) => {
107
+ if (code === 0) resolvePromise();
108
+ else reject(/* @__PURE__ */ new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));
109
+ });
110
+ });
111
+ }
112
+
113
+ //#endregion
114
+ exports.repairPlugin = repairPlugin;
115
+ //# sourceMappingURL=db-repair.cjs.map
@@ -0,0 +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"}
@@ -0,0 +1,114 @@
1
+ import { diagnosePlugin } from "./db-doctor.mjs";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { spawn } from "node:child_process";
5
+
6
+ //#region src/cli/db-repair.ts
7
+ async function repairPlugin(info, mode) {
8
+ const diagnosis = await diagnosePlugin(info);
9
+ if (diagnosis.diagnosis === "error") return {
10
+ status: "refused",
11
+ diagnosis,
12
+ message: `Cannot repair — diagnosis failed: ${diagnosis.error}`
13
+ };
14
+ if (mode === "recreate") return {
15
+ status: "refused",
16
+ diagnosis,
17
+ message: "Recreate mode is not supported without per-plugin schemas. Use --mode history-reset instead."
18
+ };
19
+ if (diagnosis.diagnosis !== "drift-safe-repair" && diagnosis.diagnosis !== "legacy-importable") {
20
+ if (diagnosis.diagnosis === "healthy") return {
21
+ status: "refused",
22
+ diagnosis,
23
+ message: "Database is healthy — no repair needed."
24
+ };
25
+ if (diagnosis.diagnosis === "no-local-migrations") return {
26
+ status: "refused",
27
+ diagnosis,
28
+ message: "No local migrations found for this plugin."
29
+ };
30
+ if (diagnosis.diagnosis === "drift-manual") return {
31
+ status: "refused",
32
+ diagnosis,
33
+ message: `Partial drift detected (${diagnosis.missingTables.length}/${diagnosis.expectedTables.length} tables missing). Manual intervention required — some tables exist but schema is incomplete.`
34
+ };
35
+ if (diagnosis.diagnosis === "unapplied") return {
36
+ status: "refused",
37
+ diagnosis,
38
+ message: "Migrations have not been applied yet. Start the dev server to apply them."
39
+ };
40
+ return {
41
+ status: "refused",
42
+ diagnosis,
43
+ message: `Cannot repair — diagnosis: ${diagnosis.diagnosis}`
44
+ };
45
+ }
46
+ const { Pool } = await import("../node_modules/pg/esm/index.mjs");
47
+ const journalRef = `"${diagnosis.journalSchema}"."${diagnosis.journalTable}"`;
48
+ const pool = new Pool({
49
+ connectionString: info.databaseUrl,
50
+ ssl: info.databaseUrl.includes("localhost") || info.databaseUrl.includes("127.0.0.1") ? false : { rejectUnauthorized: false },
51
+ max: 1,
52
+ connectionTimeoutMillis: 1e4
53
+ });
54
+ try {
55
+ await pool.query(`DROP TABLE IF EXISTS ${journalRef}`);
56
+ } catch (error) {
57
+ return {
58
+ status: "error",
59
+ diagnosis,
60
+ message: `Failed to reset journal: ${error instanceof Error ? error.message : String(error)}`
61
+ };
62
+ } finally {
63
+ await pool.end().catch(() => {});
64
+ }
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
+ return {
83
+ status: "repaired",
84
+ diagnosis,
85
+ message: `Migration history reset for ${diagnosis.plugin}. No local drizzle.config.ts found — start the dev server to reapply migrations.`
86
+ };
87
+ }
88
+ function spawnDrizzleMigrate(cwd, configPath) {
89
+ return new Promise((resolvePromise, reject) => {
90
+ const child = spawn("npx", [
91
+ "drizzle-kit",
92
+ "migrate",
93
+ "--config",
94
+ configPath
95
+ ], {
96
+ cwd,
97
+ stdio: "pipe",
98
+ shell: true
99
+ });
100
+ let stderr = "";
101
+ child.stderr?.on("data", (data) => {
102
+ stderr += data.toString();
103
+ });
104
+ child.on("error", (err) => reject(err));
105
+ child.on("exit", (code) => {
106
+ if (code === 0) resolvePromise();
107
+ else reject(/* @__PURE__ */ new Error(`drizzle-kit migrate exited with code ${code}: ${stderr}`));
108
+ });
109
+ });
110
+ }
111
+
112
+ //#endregion
113
+ export { repairPlugin };
114
+ //# sourceMappingURL=db-repair.mjs.map
@@ -0,0 +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,4 +1,5 @@
1
1
  const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_db = require('../db.cjs');
2
3
  let node_fs = require("node:fs");
3
4
  let node_path = require("node:path");
4
5
  let _clack_prompts = require("@clack/prompts");
@@ -65,6 +66,7 @@ async function runStudioLocal(info) {
65
66
  async function runStudioRemote(info) {
66
67
  const dbDir = (0, node_path.resolve)(info.projectDir, ".bos", "db", info.key);
67
68
  (0, node_fs.mkdirSync)(dbDir, { recursive: true });
69
+ const journalTable = `__drizzle_migrations_${require_db.pluginMigrationSlug(info.key)}`;
68
70
  const configPath = (0, node_path.join)(dbDir, "drizzle.config.ts");
69
71
  (0, node_fs.writeFileSync)(configPath, `import { defineConfig } from "drizzle-kit";
70
72
 
@@ -75,6 +77,10 @@ export default defineConfig({
75
77
  dbCredentials: {
76
78
  url: "${info.databaseUrl}",
77
79
  },
80
+ migrations: {
81
+ schema: "drizzle",
82
+ table: "${journalTable}",
83
+ },
78
84
  verbose: true,
79
85
  strict: true,
80
86
  });