@bungres/kit 0.3.0 → 0.5.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.
- package/README.md +22 -13
- package/dist/cli.js +1513 -638
- package/dist/commands/drop.d.ts.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/studio.d.ts.map +1 -1
- package/dist/config.d.ts +32 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +609 -191
- package/package.json +20 -4
- package/src/cli.ts +0 -162
- package/src/commands/drop.ts +0 -92
- package/src/commands/fresh.ts +0 -17
- package/src/commands/generate.ts +0 -151
- package/src/commands/migrate.ts +0 -84
- package/src/commands/pull.ts +0 -339
- package/src/commands/push.ts +0 -105
- package/src/commands/refresh.ts +0 -37
- package/src/commands/seed.ts +0 -41
- package/src/commands/status.ts +0 -64
- package/src/commands/studio.ts +0 -471
- package/src/commands/tusky.ts +0 -83
- package/src/config.ts +0 -102
- package/src/differ.ts +0 -236
- package/src/ensure-db.ts +0 -32
- package/src/index.ts +0 -21
- package/src/schema-loader.ts +0 -50
- package/src/utils/colors.ts +0 -4
package/src/commands/pull.ts
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import { resolve, join } from "node:path";
|
|
2
|
-
import type { ResolvedConfig } from "../config.js";
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// pull — introspect a live Postgres database and generate schema TypeScript
|
|
6
|
-
// Reads information_schema + pg_indexes to reconstruct table definitions
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
interface PgColumn {
|
|
10
|
-
table_name: string;
|
|
11
|
-
column_name: string;
|
|
12
|
-
data_type: string;
|
|
13
|
-
udt_name: string;
|
|
14
|
-
is_nullable: string;
|
|
15
|
-
column_default: string | null;
|
|
16
|
-
character_maximum_length: number | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface PgConstraint {
|
|
20
|
-
table_name: string;
|
|
21
|
-
column_name: string;
|
|
22
|
-
constraint_type: string;
|
|
23
|
-
foreign_table: string | null;
|
|
24
|
-
foreign_column: string | null;
|
|
25
|
-
delete_rule: string | null;
|
|
26
|
-
update_rule: string | null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface PgIndex {
|
|
30
|
-
tablename: string;
|
|
31
|
-
indexname: string;
|
|
32
|
-
indexdef: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function introspectDb(
|
|
36
|
-
sql: any, // Bun.SQL
|
|
37
|
-
dbSchema: string
|
|
38
|
-
): Promise<Map<string, TableInfo>> {
|
|
39
|
-
// Fetch columns
|
|
40
|
-
const columns = await sql.unsafe(
|
|
41
|
-
`SELECT
|
|
42
|
-
c.table_name,
|
|
43
|
-
c.column_name,
|
|
44
|
-
c.data_type,
|
|
45
|
-
c.udt_name,
|
|
46
|
-
c.is_nullable,
|
|
47
|
-
c.column_default,
|
|
48
|
-
c.character_maximum_length
|
|
49
|
-
FROM information_schema.columns c
|
|
50
|
-
WHERE c.table_schema = $1
|
|
51
|
-
AND c.table_name NOT IN ('__bungres_migrations', '__bungres_push')
|
|
52
|
-
ORDER BY c.table_name, c.ordinal_position`,
|
|
53
|
-
[dbSchema]
|
|
54
|
-
) as unknown as PgColumn[];
|
|
55
|
-
|
|
56
|
-
// Fetch constraints (PK, FK, UNIQUE)
|
|
57
|
-
const constraints = await sql.unsafe(
|
|
58
|
-
`SELECT
|
|
59
|
-
tc.table_name,
|
|
60
|
-
kcu.column_name,
|
|
61
|
-
tc.constraint_type,
|
|
62
|
-
ccu.table_name AS foreign_table,
|
|
63
|
-
ccu.column_name AS foreign_column,
|
|
64
|
-
rc.delete_rule,
|
|
65
|
-
rc.update_rule
|
|
66
|
-
FROM information_schema.table_constraints tc
|
|
67
|
-
JOIN information_schema.key_column_usage kcu
|
|
68
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
69
|
-
AND tc.table_schema = kcu.table_schema
|
|
70
|
-
LEFT JOIN information_schema.constraint_column_usage ccu
|
|
71
|
-
ON tc.constraint_name = ccu.constraint_name
|
|
72
|
-
AND tc.table_schema = ccu.table_schema
|
|
73
|
-
LEFT JOIN information_schema.referential_constraints rc
|
|
74
|
-
ON tc.constraint_name = rc.constraint_name
|
|
75
|
-
AND tc.table_schema = rc.constraint_schema
|
|
76
|
-
WHERE tc.table_schema = $1`,
|
|
77
|
-
[dbSchema]
|
|
78
|
-
) as unknown as PgConstraint[];
|
|
79
|
-
|
|
80
|
-
// Fetch indexes
|
|
81
|
-
const indexes = await sql.unsafe(
|
|
82
|
-
`SELECT tablename, indexname, indexdef
|
|
83
|
-
FROM pg_indexes
|
|
84
|
-
WHERE schemaname = $1`,
|
|
85
|
-
[dbSchema]
|
|
86
|
-
) as unknown as PgIndex[];
|
|
87
|
-
|
|
88
|
-
// Group by table
|
|
89
|
-
return groupByTable(columns, constraints, indexes);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function runPull(config: ResolvedConfig): Promise<void> {
|
|
93
|
-
console.log("@bungres/kit pull: introspecting database...");
|
|
94
|
-
|
|
95
|
-
const sql = new Bun.SQL(config.dbUrl);
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const dbSchema = config.dbSchema;
|
|
99
|
-
const tableMap = await introspectDb(sql, dbSchema);
|
|
100
|
-
|
|
101
|
-
if (tableMap.size === 0) {
|
|
102
|
-
console.log("No tables found in schema:", dbSchema);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Generate TypeScript
|
|
107
|
-
const outDir = resolve(config.outDir);
|
|
108
|
-
await Bun.$`mkdir -p ${outDir}`.quiet();
|
|
109
|
-
|
|
110
|
-
const outPath = join(outDir, "schema.ts");
|
|
111
|
-
const code = generateSchemaTS(tableMap, dbSchema);
|
|
112
|
-
|
|
113
|
-
await Bun.write(outPath, code);
|
|
114
|
-
console.log(`Generated schema: ${outPath}`);
|
|
115
|
-
console.log(` Tables: ${[...tableMap.keys()].join(", ")}`);
|
|
116
|
-
} finally {
|
|
117
|
-
await sql.end();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
// Internals
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
|
|
125
|
-
export interface TableInfo {
|
|
126
|
-
tableName: string;
|
|
127
|
-
columns: Array<{
|
|
128
|
-
name: string;
|
|
129
|
-
dataType: string;
|
|
130
|
-
udtName: string;
|
|
131
|
-
isNullable: boolean;
|
|
132
|
-
columnDefault: string | null;
|
|
133
|
-
maxLength: number | null;
|
|
134
|
-
isPrimary: boolean;
|
|
135
|
-
isUnique: boolean;
|
|
136
|
-
foreignTable: string | undefined;
|
|
137
|
-
foreignColumn: string | undefined;
|
|
138
|
-
deleteRule: string | undefined;
|
|
139
|
-
updateRule: string | undefined;
|
|
140
|
-
}>;
|
|
141
|
-
indexes: PgIndex[];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function groupByTable(
|
|
145
|
-
columns: PgColumn[],
|
|
146
|
-
constraints: PgConstraint[],
|
|
147
|
-
indexes: PgIndex[]
|
|
148
|
-
): Map<string, TableInfo> {
|
|
149
|
-
const map = new Map<string, TableInfo>();
|
|
150
|
-
|
|
151
|
-
for (const col of columns) {
|
|
152
|
-
if (!map.has(col.table_name)) {
|
|
153
|
-
map.set(col.table_name, {
|
|
154
|
-
tableName: col.table_name,
|
|
155
|
-
columns: [],
|
|
156
|
-
indexes: indexes.filter((i) => i.tablename === col.table_name),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const tableConstraints = constraints.filter(
|
|
161
|
-
(c) => c.table_name === col.table_name && c.column_name === col.column_name
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
const pkConstraint = tableConstraints.find((c) => c.constraint_type === "PRIMARY KEY");
|
|
165
|
-
const uniqueConstraint = tableConstraints.find((c) => c.constraint_type === "UNIQUE");
|
|
166
|
-
const fkConstraint = tableConstraints.find((c) => c.constraint_type === "FOREIGN KEY");
|
|
167
|
-
|
|
168
|
-
map.get(col.table_name)!.columns.push({
|
|
169
|
-
name: col.column_name,
|
|
170
|
-
dataType: col.data_type,
|
|
171
|
-
udtName: col.udt_name,
|
|
172
|
-
isNullable: col.is_nullable === "YES",
|
|
173
|
-
columnDefault: col.column_default,
|
|
174
|
-
maxLength: col.character_maximum_length,
|
|
175
|
-
isPrimary: !!pkConstraint,
|
|
176
|
-
isUnique: !!uniqueConstraint,
|
|
177
|
-
foreignTable: fkConstraint?.foreign_table ?? undefined,
|
|
178
|
-
foreignColumn: fkConstraint?.foreign_column ?? undefined,
|
|
179
|
-
deleteRule: fkConstraint?.delete_rule ?? undefined,
|
|
180
|
-
updateRule: fkConstraint?.update_rule ?? undefined,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return map;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function generateSchemaTS(
|
|
188
|
-
tableMap: Map<string, TableInfo>,
|
|
189
|
-
dbSchema: string
|
|
190
|
-
): string {
|
|
191
|
-
const lines: string[] = [
|
|
192
|
-
`// Generated by @bungres/kit pull`,
|
|
193
|
-
`// Do not edit manually — re-run \`bungres pull\` to regenerate`,
|
|
194
|
-
`// Generated at: ${new Date().toISOString()}`,
|
|
195
|
-
``,
|
|
196
|
-
`import {`,
|
|
197
|
-
` snakeCase,`,
|
|
198
|
-
` text, varchar, char, integer, bigint, smallint,`,
|
|
199
|
-
` serial, bigserial, boolean, real, doublePrecision,`,
|
|
200
|
-
` numeric, decimal, json, jsonb,`,
|
|
201
|
-
` timestamp, timestamptz, date, time, uuid,`,
|
|
202
|
-
` bytea, inet,`,
|
|
203
|
-
` textArray, integerArray, varcharArray, uuidArray,`,
|
|
204
|
-
`} from "@bungres/orm";`,
|
|
205
|
-
``,
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
for (const [, table] of tableMap) {
|
|
209
|
-
const varName = toCamelCase(table.tableName);
|
|
210
|
-
lines.push(`export const ${varName} = snakeCase.table("${table.tableName}", {`);
|
|
211
|
-
|
|
212
|
-
for (const col of table.columns) {
|
|
213
|
-
const colExpr = buildColumnExpression(col);
|
|
214
|
-
lines.push(` ${toCamelCase(col.name)}: ${colExpr},`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Third argument: Table options (schema, indexes)
|
|
218
|
-
const options: string[] = [];
|
|
219
|
-
if (dbSchema !== "public") {
|
|
220
|
-
options.push(`schema: "${dbSchema}"`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (table.indexes.length > 0) {
|
|
224
|
-
const idxLines: string[] = [];
|
|
225
|
-
for (const idx of table.indexes) {
|
|
226
|
-
// Simple regex to parse `indexdef`
|
|
227
|
-
// e.g. CREATE UNIQUE INDEX my_idx ON users USING btree (col1, col2) WHERE col3 = 1
|
|
228
|
-
const m = idx.indexdef.match(/CREATE (UNIQUE )?INDEX (.+) ON (.+) USING (\w+) \((.+)\)(?: WHERE (.+))?/i);
|
|
229
|
-
if (m) {
|
|
230
|
-
const isUnique = !!m[1];
|
|
231
|
-
const name = m[2]!.trim().replace(/^"|"$/g, "");
|
|
232
|
-
// Skip auto-generated PK and UNIQUE constraint indexes which are already attached to columns
|
|
233
|
-
if (name.endsWith("_pkey") || name.endsWith("_key")) continue;
|
|
234
|
-
|
|
235
|
-
const using = m[4]!.toLowerCase();
|
|
236
|
-
const cols = m[5]!.split(",").map(c => `"${c.trim().replace(/^"|"$/g, "")}"`);
|
|
237
|
-
let idxStr = `{ name: "${name}", columns: [${cols.join(", ")}], using: "${using}"`;
|
|
238
|
-
if (isUnique) idxStr += `, unique: true`;
|
|
239
|
-
if (m[6]) idxStr += `, where: \`${m[6].trim()}\``;
|
|
240
|
-
idxStr += ` }`;
|
|
241
|
-
idxLines.push(idxStr);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
if (idxLines.length > 0) {
|
|
245
|
-
options.push(`indexes: [\n ${idxLines.join(",\n ")}\n ]`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (options.length > 0) {
|
|
250
|
-
lines.push(`}, {`);
|
|
251
|
-
lines.push(` ${options.join(",\n ")}`);
|
|
252
|
-
lines.push(`});`);
|
|
253
|
-
} else {
|
|
254
|
-
lines.push(`});`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
lines.push(``);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return lines.join("\n");
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function buildColumnExpression(col: TableInfo["columns"][number]): string {
|
|
264
|
-
const opts: string[] = [];
|
|
265
|
-
|
|
266
|
-
if (col.dataType === "character varying" || col.dataType === "character") {
|
|
267
|
-
if (col.maxLength) opts.push(`length: ${col.maxLength}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (col.isPrimary) opts.push(`primaryKey: true`);
|
|
271
|
-
else if (!col.isNullable) opts.push(`notNull: true`);
|
|
272
|
-
|
|
273
|
-
if (col.isUnique && !col.isPrimary) opts.push(`unique: true`);
|
|
274
|
-
|
|
275
|
-
if (col.columnDefault !== null && !col.isPrimary) {
|
|
276
|
-
if (col.columnDefault.includes("(")) {
|
|
277
|
-
opts.push(`defaultRaw: "${col.columnDefault}"`);
|
|
278
|
-
} else if (col.dataType === "boolean" || col.dataType.includes("int") || col.dataType.includes("numeric") || col.dataType.includes("real") || col.dataType.includes("double")) {
|
|
279
|
-
opts.push(`default: ${col.columnDefault}`);
|
|
280
|
-
} else {
|
|
281
|
-
const cleaned = col.columnDefault.replace(/^'(.*)'::.*$/, "$1");
|
|
282
|
-
opts.push(`default: "${cleaned}"`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (col.foreignTable && col.foreignColumn) {
|
|
287
|
-
const deleteRule = col.deleteRule?.toLowerCase().replace(" ", " ");
|
|
288
|
-
const updateRule = col.updateRule?.toLowerCase().replace(" ", " ");
|
|
289
|
-
let refOpts = `table: "${col.foreignTable}", column: "${col.foreignColumn}"`;
|
|
290
|
-
if (deleteRule && deleteRule !== "no action") refOpts += `, onDelete: "${deleteRule}"`;
|
|
291
|
-
if (updateRule && updateRule !== "no action") refOpts += `, onUpdate: "${updateRule}"`;
|
|
292
|
-
opts.push(`references: { ${refOpts} }`);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
let builderName = pgTypeToBungresBuilderName(col);
|
|
296
|
-
|
|
297
|
-
if (opts.length > 0) {
|
|
298
|
-
return `${builderName}({ ${opts.join(", ")} })`;
|
|
299
|
-
}
|
|
300
|
-
return `${builderName}()`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function pgTypeToBungresBuilderName(col: TableInfo["columns"][number]): string {
|
|
304
|
-
const dt = col.dataType;
|
|
305
|
-
|
|
306
|
-
if (dt === "uuid") return "uuid";
|
|
307
|
-
if (dt === "text") return "text";
|
|
308
|
-
if (dt === "character varying") return "varchar";
|
|
309
|
-
if (dt === "character") return "char";
|
|
310
|
-
if (dt === "integer") return "integer";
|
|
311
|
-
if (dt === "bigint") return "bigint";
|
|
312
|
-
if (dt === "smallint") return "smallint";
|
|
313
|
-
if (dt === "boolean") return "boolean";
|
|
314
|
-
if (dt === "real") return "real";
|
|
315
|
-
if (dt === "double precision") return "doublePrecision";
|
|
316
|
-
if (dt === "numeric" || dt === "decimal") return "numeric";
|
|
317
|
-
if (dt === "json") return "json";
|
|
318
|
-
if (dt === "jsonb") return "jsonb";
|
|
319
|
-
if (dt === "timestamp without time zone") return "timestamp";
|
|
320
|
-
if (dt === "timestamp with time zone") return "timestamptz";
|
|
321
|
-
if (dt === "date") return "date";
|
|
322
|
-
if (dt === "time without time zone") return "time";
|
|
323
|
-
if (dt === "bytea") return "bytea";
|
|
324
|
-
if (dt === "inet") return "inet";
|
|
325
|
-
if (dt === "USER-DEFINED" && col.udtName === "citext") return "text";
|
|
326
|
-
if (dt === "ARRAY") {
|
|
327
|
-
if (col.udtName === "_text") return "textArray";
|
|
328
|
-
if (col.udtName === "_int4" || col.udtName === "_int8") return "integerArray";
|
|
329
|
-
if (col.udtName === "_varchar") return "varcharArray";
|
|
330
|
-
if (col.udtName === "_uuid") return "uuidArray";
|
|
331
|
-
return "textArray";
|
|
332
|
-
}
|
|
333
|
-
// Fallback
|
|
334
|
-
return "text";
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function toCamelCase(str: string): string {
|
|
338
|
-
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
339
|
-
}
|
package/src/commands/push.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import type { ResolvedConfig } from "../config.js";
|
|
3
|
-
import { diffSchemas, type SchemaSnapshot } from "../differ.js";
|
|
4
|
-
import { loadSchemas } from "../schema-loader.js";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// push — apply schema directly to the database (no migration files)
|
|
8
|
-
// Uses a hidden table __bungres_push to track the previous schema state
|
|
9
|
-
// and perform an exact diff.
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
export async function runPush(
|
|
13
|
-
config: ResolvedConfig,
|
|
14
|
-
opts: { force?: boolean } = {}
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
console.log("@bungres/kit push: loading schemas...");
|
|
17
|
-
|
|
18
|
-
const schemas = await loadSchemas(config.schema);
|
|
19
|
-
|
|
20
|
-
if (schemas.length === 0) {
|
|
21
|
-
console.warn("No table definitions found. Check your schema glob pattern.");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const sql = new Bun.SQL(config.dbUrl);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Ensure our tracking table exists
|
|
29
|
-
await sql.unsafe(`
|
|
30
|
-
CREATE TABLE IF NOT EXISTS public.__bungres_push (
|
|
31
|
-
id SERIAL PRIMARY KEY,
|
|
32
|
-
snapshot JSONB NOT NULL
|
|
33
|
-
);
|
|
34
|
-
`);
|
|
35
|
-
|
|
36
|
-
// Load previous snapshot from DB
|
|
37
|
-
const rows = await sql.unsafe(`SELECT snapshot FROM public.__bungres_push ORDER BY id DESC LIMIT 1;`) as any[];
|
|
38
|
-
let prevSnapshot: SchemaSnapshot = {};
|
|
39
|
-
if (rows.length > 0) {
|
|
40
|
-
prevSnapshot = typeof rows[0].snapshot === "string"
|
|
41
|
-
? JSON.parse(rows[0].snapshot)
|
|
42
|
-
: rows[0].snapshot;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Current snapshot from TypeScript
|
|
46
|
-
const currentSnapshot: SchemaSnapshot = Object.fromEntries(
|
|
47
|
-
schemas.map((s) => [s.config.name, s.config])
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Diff
|
|
51
|
-
const diff = diffSchemas(prevSnapshot, currentSnapshot);
|
|
52
|
-
|
|
53
|
-
if (diff.statements.length === 0) {
|
|
54
|
-
console.log("\nNo schema changes detected. Database is up to date.");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(`\nChanges to apply:`);
|
|
59
|
-
for (const s of diff.summary) console.log(` + ${s}`);
|
|
60
|
-
|
|
61
|
-
if (diff.warnings && diff.warnings.length > 0) {
|
|
62
|
-
console.warn(`\n ⚠️ WARNING: Data Loss Detected!`);
|
|
63
|
-
for (const w of diff.warnings) console.warn(` ! ${w}`);
|
|
64
|
-
console.warn(`\n These changes will be immediately executed against the database!`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!opts.force) {
|
|
68
|
-
process.stdout.write("\nAre you sure you want to push these changes? Type YES to continue: ");
|
|
69
|
-
const answer = await readLine();
|
|
70
|
-
if (answer.trim().toLowerCase() !== "yes") {
|
|
71
|
-
console.log("Aborted.");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log(`\nPushing changes...`);
|
|
77
|
-
|
|
78
|
-
// We must run extension first if needed
|
|
79
|
-
await sql.unsafe(`CREATE EXTENSION IF NOT EXISTS "pgcrypto";`);
|
|
80
|
-
|
|
81
|
-
// Execute diff statements
|
|
82
|
-
for (const stmt of diff.statements) {
|
|
83
|
-
if (config.verbose) {
|
|
84
|
-
console.log(`-- ${stmt}`);
|
|
85
|
-
}
|
|
86
|
-
await sql.unsafe(stmt);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Save new snapshot
|
|
90
|
-
await sql.unsafe(
|
|
91
|
-
`INSERT INTO public.__bungres_push (snapshot) VALUES ($1);`,
|
|
92
|
-
[JSON.stringify(currentSnapshot)]
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
console.log("\nPush complete.");
|
|
96
|
-
} finally {
|
|
97
|
-
await sql.end();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function readLine(): Promise<string> {
|
|
102
|
-
const buf = Buffer.alloc(256);
|
|
103
|
-
const n = fs.readSync(0, buf, 0, 256, null);
|
|
104
|
-
return buf.subarray(0, n).toString().trim();
|
|
105
|
-
}
|
package/src/commands/refresh.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { ResolvedConfig } from "../config.js";
|
|
2
|
-
import { loadSchemas } from "../schema-loader.js";
|
|
3
|
-
import { colorize } from "../utils/colors.js";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// refresh — truncate all tables to reset data without dropping schema
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export async function runRefresh(config: ResolvedConfig): Promise<void> {
|
|
10
|
-
const schemas = await loadSchemas(config.schema);
|
|
11
|
-
|
|
12
|
-
if (schemas.length === 0) {
|
|
13
|
-
console.warn("No table definitions found in schema files.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const sql = new Bun.SQL(config.dbUrl);
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const tableNames = schemas.map(
|
|
21
|
-
(s) => `"${s.config.schema ? s.config.schema + '"."' : ""}${s.config.name}"`
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
console.log(`Truncating ${tableNames.length} tables...`);
|
|
25
|
-
|
|
26
|
-
// We use CASCADE to handle foreign key constraints automatically
|
|
27
|
-
for (const entry of schemas) {
|
|
28
|
-
const ddl = `TRUNCATE TABLE "${entry.config.name}" CASCADE;`;
|
|
29
|
-
await sql.unsafe(ddl);
|
|
30
|
-
console.log(colorize(` ✓ truncated ${entry.config.name}`, "green"));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log(colorize("\nRefresh complete. All tables are now empty.", "green"));
|
|
34
|
-
} finally {
|
|
35
|
-
await sql.end();
|
|
36
|
-
}
|
|
37
|
-
}
|
package/src/commands/seed.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import type { ResolvedConfig } from "../config.js";
|
|
3
|
-
import { colorize } from "../utils/colors.js";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// seed — execute a seed script to populate the database
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export async function runSeed(config: ResolvedConfig): Promise<void> {
|
|
10
|
-
if (!config.seed) {
|
|
11
|
-
console.log(colorize(`No seed file configured in bungres.config.ts`, "yellow"));
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const seedPath = resolve(process.cwd(), config.seed);
|
|
16
|
-
|
|
17
|
-
const file = Bun.file(seedPath);
|
|
18
|
-
if (!(await file.exists())) {
|
|
19
|
-
console.error(colorize(`Seed file not found at ${seedPath}`, "red"));
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
console.log(colorize(`\nRunning seeder: ${config.seed}...`, "cyan"));
|
|
24
|
-
|
|
25
|
-
// Run the seed script in a new process to ensure a clean state
|
|
26
|
-
const proc = Bun.spawn(["bun", "run", seedPath], {
|
|
27
|
-
cwd: process.cwd(),
|
|
28
|
-
stdout: "inherit",
|
|
29
|
-
stderr: "inherit",
|
|
30
|
-
env: { ...process.env, DATABASE_URL: config.dbUrl } // Pass dbUrl implicitly just in case
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const exitCode = await proc.exited;
|
|
34
|
-
|
|
35
|
-
if (exitCode === 0) {
|
|
36
|
-
console.log("\nSeed complete.");
|
|
37
|
-
} else {
|
|
38
|
-
console.error(`\nSeed failed with exit code ${exitCode}.`);
|
|
39
|
-
process.exit(exitCode);
|
|
40
|
-
}
|
|
41
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { resolve, join } from "node:path";
|
|
2
|
-
import type { ResolvedConfig } from "../config.js";
|
|
3
|
-
import { colorize } from "../utils/colors.js";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// status — show which migrations have been applied vs. pending
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
const MIGRATIONS_TABLE = "__bungres_migrations";
|
|
10
|
-
|
|
11
|
-
export async function runStatus(config: ResolvedConfig): Promise<void> {
|
|
12
|
-
const migrationsDir = resolve(config.migrationsDir);
|
|
13
|
-
const sql = new Bun.SQL(config.dbUrl);
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// Check if migrations table exists
|
|
17
|
-
const tableCheck = await sql.unsafe(
|
|
18
|
-
`SELECT EXISTS (
|
|
19
|
-
SELECT 1 FROM information_schema.tables
|
|
20
|
-
WHERE table_name = $1
|
|
21
|
-
) AS exists`,
|
|
22
|
-
[MIGRATIONS_TABLE]
|
|
23
|
-
) as Array<{ exists: boolean }>;
|
|
24
|
-
|
|
25
|
-
const trackingExists = tableCheck[0]?.exists ?? false;
|
|
26
|
-
|
|
27
|
-
// Discover migration files
|
|
28
|
-
const glob = new Bun.Glob("*.sql");
|
|
29
|
-
const files: string[] = [];
|
|
30
|
-
for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
|
|
31
|
-
files.push(file);
|
|
32
|
-
}
|
|
33
|
-
files.sort();
|
|
34
|
-
|
|
35
|
-
if (files.length === 0) {
|
|
36
|
-
console.log(colorize("No migration files found. Run `bungres generate` first.", "yellow"));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let appliedSet = new Set<string>();
|
|
41
|
-
if (trackingExists) {
|
|
42
|
-
const applied = await sql.unsafe(
|
|
43
|
-
`SELECT name FROM "${MIGRATIONS_TABLE}" ORDER BY applied_at`
|
|
44
|
-
) as Array<{ name: string }>;
|
|
45
|
-
appliedSet = new Set(applied.map((r) => r.name));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(colorize("\nMigration status:\n", "cyan"));
|
|
49
|
-
let pendingCount = 0;
|
|
50
|
-
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const isApplied = appliedSet.has(file);
|
|
53
|
-
const status = isApplied ? colorize("✓ applied ", "green") : colorize("✗ pending ", "yellow");
|
|
54
|
-
if (!isApplied) pendingCount++;
|
|
55
|
-
console.log(` ${status} ${file}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(
|
|
59
|
-
`\n${colorize(appliedSet.size.toString(), "green")} applied, ${colorize(pendingCount.toString(), "yellow")} pending.\n`
|
|
60
|
-
);
|
|
61
|
-
} finally {
|
|
62
|
-
await sql.end();
|
|
63
|
-
}
|
|
64
|
-
}
|