@xubylele/schema-forge 1.7.0 → 1.8.1
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 +51 -25
- package/dist/api.d.ts +101 -0
- package/dist/api.js +3632 -0
- package/dist/cli.js +24 -5
- package/package.json +14 -4
package/dist/api.js
ADDED
|
@@ -0,0 +1,3632 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/parser.js
|
|
34
|
+
function parseSchema(source) {
|
|
35
|
+
const lines = source.split("\n");
|
|
36
|
+
const tables = {};
|
|
37
|
+
let currentLine = 0;
|
|
38
|
+
const validBaseColumnTypes = /* @__PURE__ */ new Set([
|
|
39
|
+
"uuid",
|
|
40
|
+
"varchar",
|
|
41
|
+
"text",
|
|
42
|
+
"int",
|
|
43
|
+
"bigint",
|
|
44
|
+
"boolean",
|
|
45
|
+
"timestamptz",
|
|
46
|
+
"date"
|
|
47
|
+
]);
|
|
48
|
+
const validIdentifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
49
|
+
function normalizeColumnType6(type) {
|
|
50
|
+
return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
51
|
+
}
|
|
52
|
+
function isValidColumnType2(type) {
|
|
53
|
+
const normalizedType = normalizeColumnType6(type);
|
|
54
|
+
if (validBaseColumnTypes.has(normalizedType)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const varcharMatch = normalizedType.match(/^varchar\((\d+)\)$/);
|
|
58
|
+
if (varcharMatch) {
|
|
59
|
+
const length = Number(varcharMatch[1]);
|
|
60
|
+
return Number.isInteger(length) && length > 0;
|
|
61
|
+
}
|
|
62
|
+
const numericMatch = normalizedType.match(/^numeric\((\d+),(\d+)\)$/);
|
|
63
|
+
if (numericMatch) {
|
|
64
|
+
const precision = Number(numericMatch[1]);
|
|
65
|
+
const scale = Number(numericMatch[2]);
|
|
66
|
+
return Number.isInteger(precision) && Number.isInteger(scale) && precision > 0 && scale >= 0 && scale <= precision;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function validateIdentifier(identifier, lineNum, context) {
|
|
71
|
+
if (!validIdentifierPattern.test(identifier)) {
|
|
72
|
+
throw new Error(`Line ${lineNum}: Invalid ${context} name '${identifier}'. Use letters, numbers, and underscores, and do not start with a number.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function validateDefaultValue(value, lineNum) {
|
|
76
|
+
let parenBalance = 0;
|
|
77
|
+
let inSingleQuote = false;
|
|
78
|
+
let inDoubleQuote = false;
|
|
79
|
+
for (let index = 0; index < value.length; index++) {
|
|
80
|
+
const char = value[index];
|
|
81
|
+
if (char === "'" && !inDoubleQuote) {
|
|
82
|
+
if (inSingleQuote && value[index + 1] === "'") {
|
|
83
|
+
index++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
inSingleQuote = !inSingleQuote;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (char === '"' && !inSingleQuote) {
|
|
90
|
+
if (inDoubleQuote && value[index + 1] === '"') {
|
|
91
|
+
index++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
inDoubleQuote = !inDoubleQuote;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (char === "(") {
|
|
101
|
+
parenBalance++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (char === ")") {
|
|
105
|
+
parenBalance--;
|
|
106
|
+
if (parenBalance < 0) {
|
|
107
|
+
throw new Error(`Line ${lineNum}: Invalid default value '${value}'. Unmatched parentheses in function call.`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
112
|
+
throw new Error(`Line ${lineNum}: Invalid default value '${value}'. Unterminated quoted string.`);
|
|
113
|
+
}
|
|
114
|
+
if (parenBalance > 0) {
|
|
115
|
+
if (parenBalance === 1) {
|
|
116
|
+
throw new Error(`Line ${lineNum}: Invalid default value '${value}'. Function call is missing closing parenthesis.`);
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Line ${lineNum}: Invalid default value '${value}'. Unmatched parentheses in function call.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function cleanLine(line) {
|
|
122
|
+
let inSingleQuote = false;
|
|
123
|
+
let inDoubleQuote = false;
|
|
124
|
+
for (let index = 0; index < line.length; index++) {
|
|
125
|
+
const char = line[index];
|
|
126
|
+
const nextChar = line[index + 1];
|
|
127
|
+
if (char === "'" && !inDoubleQuote) {
|
|
128
|
+
if (inSingleQuote && nextChar === "'") {
|
|
129
|
+
index++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
inSingleQuote = !inSingleQuote;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (char === '"' && !inSingleQuote) {
|
|
136
|
+
if (inDoubleQuote && nextChar === '"') {
|
|
137
|
+
index++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
inDoubleQuote = !inDoubleQuote;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (char === "#") {
|
|
147
|
+
line = line.substring(0, index);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (char === "/" && nextChar === "/") {
|
|
151
|
+
line = line.substring(0, index);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return line.trim();
|
|
156
|
+
}
|
|
157
|
+
function parseForeignKey(fkRef, lineNum) {
|
|
158
|
+
const parts = fkRef.split(".");
|
|
159
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
160
|
+
throw new Error(`Line ${lineNum}: Invalid foreign key format '${fkRef}'. Expected format: table.column`);
|
|
161
|
+
}
|
|
162
|
+
validateIdentifier(parts[0], lineNum, "foreign key table");
|
|
163
|
+
validateIdentifier(parts[1], lineNum, "foreign key column");
|
|
164
|
+
return {
|
|
165
|
+
table: parts[0],
|
|
166
|
+
column: parts[1]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parseColumn(line, lineNum) {
|
|
170
|
+
const tokens = line.split(/\s+/).filter((t) => t.length > 0);
|
|
171
|
+
const modifiers = /* @__PURE__ */ new Set(["pk", "unique", "nullable", "default", "fk"]);
|
|
172
|
+
const appliedModifiers = /* @__PURE__ */ new Set();
|
|
173
|
+
if (tokens.length < 2) {
|
|
174
|
+
throw new Error(`Line ${lineNum}: Invalid column definition. Expected: <name> <type> [modifiers...]`);
|
|
175
|
+
}
|
|
176
|
+
const colName = tokens[0];
|
|
177
|
+
const colType = normalizeColumnType6(tokens[1]);
|
|
178
|
+
validateIdentifier(colName, lineNum, "column");
|
|
179
|
+
if (!isValidColumnType2(colType)) {
|
|
180
|
+
throw new Error(`Line ${lineNum}: Invalid column type '${tokens[1]}'. Valid types: ${Array.from(validBaseColumnTypes).join(", ")}, varchar(n), numeric(p,s)`);
|
|
181
|
+
}
|
|
182
|
+
const column = {
|
|
183
|
+
name: colName,
|
|
184
|
+
type: colType,
|
|
185
|
+
nullable: true
|
|
186
|
+
};
|
|
187
|
+
let i = 2;
|
|
188
|
+
while (i < tokens.length) {
|
|
189
|
+
const modifier = tokens[i];
|
|
190
|
+
const markModifierApplied = (name) => {
|
|
191
|
+
if (appliedModifiers.has(name)) {
|
|
192
|
+
throw new Error(`Line ${lineNum}: Duplicate modifier '${name}'`);
|
|
193
|
+
}
|
|
194
|
+
appliedModifiers.add(name);
|
|
195
|
+
};
|
|
196
|
+
switch (modifier) {
|
|
197
|
+
case "pk":
|
|
198
|
+
markModifierApplied("pk");
|
|
199
|
+
column.primaryKey = true;
|
|
200
|
+
i++;
|
|
201
|
+
break;
|
|
202
|
+
case "unique":
|
|
203
|
+
markModifierApplied("unique");
|
|
204
|
+
column.unique = true;
|
|
205
|
+
i++;
|
|
206
|
+
break;
|
|
207
|
+
case "nullable":
|
|
208
|
+
if (appliedModifiers.has("not null")) {
|
|
209
|
+
throw new Error(`Line ${lineNum}: Cannot combine 'nullable' with 'not null'`);
|
|
210
|
+
}
|
|
211
|
+
markModifierApplied("nullable");
|
|
212
|
+
column.nullable = true;
|
|
213
|
+
i++;
|
|
214
|
+
break;
|
|
215
|
+
case "not":
|
|
216
|
+
if (tokens[i + 1] !== "null") {
|
|
217
|
+
throw new Error(`Line ${lineNum}: Unknown modifier 'not'`);
|
|
218
|
+
}
|
|
219
|
+
if (appliedModifiers.has("nullable")) {
|
|
220
|
+
throw new Error(`Line ${lineNum}: Cannot combine 'not null' with 'nullable'`);
|
|
221
|
+
}
|
|
222
|
+
markModifierApplied("not null");
|
|
223
|
+
column.nullable = false;
|
|
224
|
+
i += 2;
|
|
225
|
+
break;
|
|
226
|
+
case "default":
|
|
227
|
+
markModifierApplied("default");
|
|
228
|
+
i++;
|
|
229
|
+
if (i >= tokens.length) {
|
|
230
|
+
throw new Error(`Line ${lineNum}: 'default' modifier requires a value`);
|
|
231
|
+
}
|
|
232
|
+
{
|
|
233
|
+
const defaultTokens = [];
|
|
234
|
+
while (i < tokens.length && !modifiers.has(tokens[i]) && !(tokens[i] === "not" && tokens[i + 1] === "null")) {
|
|
235
|
+
defaultTokens.push(tokens[i]);
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
if (defaultTokens.length === 0) {
|
|
239
|
+
throw new Error(`Line ${lineNum}: 'default' modifier requires a value`);
|
|
240
|
+
}
|
|
241
|
+
const defaultValue = defaultTokens.join(" ");
|
|
242
|
+
validateDefaultValue(defaultValue, lineNum);
|
|
243
|
+
column.default = defaultValue;
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
case "fk":
|
|
247
|
+
markModifierApplied("fk");
|
|
248
|
+
i++;
|
|
249
|
+
if (i >= tokens.length) {
|
|
250
|
+
throw new Error(`Line ${lineNum}: 'fk' modifier requires a table.column reference`);
|
|
251
|
+
}
|
|
252
|
+
column.foreignKey = parseForeignKey(tokens[i], lineNum);
|
|
253
|
+
i++;
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
throw new Error(`Line ${lineNum}: Unknown modifier '${modifier}'`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return column;
|
|
260
|
+
}
|
|
261
|
+
function parseTableBlock(startLine) {
|
|
262
|
+
const firstLine = cleanLine(lines[startLine]);
|
|
263
|
+
const match = firstLine.match(/^table\s+(\w+)\s*\{\s*$/);
|
|
264
|
+
if (!match) {
|
|
265
|
+
throw new Error(`Line ${startLine + 1}: Invalid table definition. Expected: table <name> {`);
|
|
266
|
+
}
|
|
267
|
+
const tableName = match[1];
|
|
268
|
+
validateIdentifier(tableName, startLine + 1, "table");
|
|
269
|
+
if (tables[tableName]) {
|
|
270
|
+
throw new Error(`Line ${startLine + 1}: Duplicate table definition '${tableName}'`);
|
|
271
|
+
}
|
|
272
|
+
const columns = [];
|
|
273
|
+
let lineIdx = startLine + 1;
|
|
274
|
+
let foundClosingBrace = false;
|
|
275
|
+
while (lineIdx < lines.length) {
|
|
276
|
+
const cleaned = cleanLine(lines[lineIdx]);
|
|
277
|
+
if (!cleaned) {
|
|
278
|
+
lineIdx++;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (cleaned === "}") {
|
|
282
|
+
foundClosingBrace = true;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const column = parseColumn(cleaned, lineIdx + 1);
|
|
287
|
+
columns.push(column);
|
|
288
|
+
} catch (error2) {
|
|
289
|
+
throw error2;
|
|
290
|
+
}
|
|
291
|
+
lineIdx++;
|
|
292
|
+
}
|
|
293
|
+
if (!foundClosingBrace) {
|
|
294
|
+
throw new Error(`Line ${startLine + 1}: Table '${tableName}' block not closed (missing '}')`);
|
|
295
|
+
}
|
|
296
|
+
const primaryKeyColumn = columns.find((column) => column.primaryKey)?.name ?? null;
|
|
297
|
+
tables[tableName] = {
|
|
298
|
+
name: tableName,
|
|
299
|
+
columns,
|
|
300
|
+
...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }
|
|
301
|
+
};
|
|
302
|
+
return lineIdx;
|
|
303
|
+
}
|
|
304
|
+
while (currentLine < lines.length) {
|
|
305
|
+
const cleaned = cleanLine(lines[currentLine]);
|
|
306
|
+
if (!cleaned) {
|
|
307
|
+
currentLine++;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (cleaned.startsWith("table ")) {
|
|
311
|
+
currentLine = parseTableBlock(currentLine);
|
|
312
|
+
} else {
|
|
313
|
+
throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table definition.`);
|
|
314
|
+
}
|
|
315
|
+
currentLine++;
|
|
316
|
+
}
|
|
317
|
+
return { tables };
|
|
318
|
+
}
|
|
319
|
+
var init_parser = __esm({
|
|
320
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/parser.js"() {
|
|
321
|
+
"use strict";
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/normalize.js
|
|
326
|
+
function normalizeIdent(input) {
|
|
327
|
+
return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
328
|
+
}
|
|
329
|
+
function pkName(table) {
|
|
330
|
+
return `pk_${normalizeIdent(table)}`;
|
|
331
|
+
}
|
|
332
|
+
function uqName(table, column) {
|
|
333
|
+
return `uq_${normalizeIdent(table)}_${normalizeIdent(column)}`;
|
|
334
|
+
}
|
|
335
|
+
function legacyPkName(table) {
|
|
336
|
+
return `${normalizeIdent(table)}_pkey`;
|
|
337
|
+
}
|
|
338
|
+
function legacyUqName(table, column) {
|
|
339
|
+
return `${normalizeIdent(table)}_${normalizeIdent(column)}_key`;
|
|
340
|
+
}
|
|
341
|
+
function normalizeSpacesOutsideQuotes(value) {
|
|
342
|
+
let result = "";
|
|
343
|
+
let inSingleQuote = false;
|
|
344
|
+
let inDoubleQuote = false;
|
|
345
|
+
let pendingSpace = false;
|
|
346
|
+
for (const char of value) {
|
|
347
|
+
if (char === "'" && !inDoubleQuote) {
|
|
348
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
349
|
+
result += " ";
|
|
350
|
+
}
|
|
351
|
+
pendingSpace = false;
|
|
352
|
+
inSingleQuote = !inSingleQuote;
|
|
353
|
+
result += char;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (char === '"' && !inSingleQuote) {
|
|
357
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
358
|
+
result += " ";
|
|
359
|
+
}
|
|
360
|
+
pendingSpace = false;
|
|
361
|
+
inDoubleQuote = !inDoubleQuote;
|
|
362
|
+
result += char;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (!inSingleQuote && !inDoubleQuote && /\s/.test(char)) {
|
|
366
|
+
pendingSpace = true;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
370
|
+
result += " ";
|
|
371
|
+
}
|
|
372
|
+
pendingSpace = false;
|
|
373
|
+
result += char;
|
|
374
|
+
}
|
|
375
|
+
return result.trim();
|
|
376
|
+
}
|
|
377
|
+
function normalizeKnownFunctionsOutsideQuotes(value) {
|
|
378
|
+
let result = "";
|
|
379
|
+
let inSingleQuote = false;
|
|
380
|
+
let inDoubleQuote = false;
|
|
381
|
+
let buffer = "";
|
|
382
|
+
function flushBuffer() {
|
|
383
|
+
if (!buffer) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
result += buffer.replace(/\bnow\s*\(\s*\)/gi, "now()").replace(/\bgen_random_uuid\s*\(\s*\)/gi, "gen_random_uuid()");
|
|
387
|
+
buffer = "";
|
|
388
|
+
}
|
|
389
|
+
for (const char of value) {
|
|
390
|
+
if (char === "'" && !inDoubleQuote) {
|
|
391
|
+
flushBuffer();
|
|
392
|
+
inSingleQuote = !inSingleQuote;
|
|
393
|
+
result += char;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (char === '"' && !inSingleQuote) {
|
|
397
|
+
flushBuffer();
|
|
398
|
+
inDoubleQuote = !inDoubleQuote;
|
|
399
|
+
result += char;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
403
|
+
result += char;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
buffer += char;
|
|
407
|
+
}
|
|
408
|
+
flushBuffer();
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
function normalizePunctuationOutsideQuotes(value) {
|
|
412
|
+
let result = "";
|
|
413
|
+
let inSingleQuote = false;
|
|
414
|
+
let inDoubleQuote = false;
|
|
415
|
+
for (let index = 0; index < value.length; index++) {
|
|
416
|
+
const char = value[index];
|
|
417
|
+
if (char === "'" && !inDoubleQuote) {
|
|
418
|
+
inSingleQuote = !inSingleQuote;
|
|
419
|
+
result += char;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (char === '"' && !inSingleQuote) {
|
|
423
|
+
inDoubleQuote = !inDoubleQuote;
|
|
424
|
+
result += char;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!inSingleQuote && !inDoubleQuote && (char === "(" || char === ")")) {
|
|
428
|
+
while (result.endsWith(" ")) {
|
|
429
|
+
result = result.slice(0, -1);
|
|
430
|
+
}
|
|
431
|
+
result += char;
|
|
432
|
+
let lookahead = index + 1;
|
|
433
|
+
while (lookahead < value.length && value[lookahead] === " ") {
|
|
434
|
+
lookahead++;
|
|
435
|
+
}
|
|
436
|
+
index = lookahead - 1;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (!inSingleQuote && !inDoubleQuote && char === ",") {
|
|
440
|
+
while (result.endsWith(" ")) {
|
|
441
|
+
result = result.slice(0, -1);
|
|
442
|
+
}
|
|
443
|
+
result += ", ";
|
|
444
|
+
let lookahead = index + 1;
|
|
445
|
+
while (lookahead < value.length && value[lookahead] === " ") {
|
|
446
|
+
lookahead++;
|
|
447
|
+
}
|
|
448
|
+
index = lookahead - 1;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
result += char;
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
function normalizeDefault(expr) {
|
|
456
|
+
if (expr === void 0 || expr === null) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const trimmed = expr.trim();
|
|
460
|
+
if (trimmed.length === 0) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
const normalizedSpacing = normalizeSpacesOutsideQuotes(trimmed);
|
|
464
|
+
const normalizedPunctuation = normalizePunctuationOutsideQuotes(normalizedSpacing);
|
|
465
|
+
return normalizeKnownFunctionsOutsideQuotes(normalizedPunctuation);
|
|
466
|
+
}
|
|
467
|
+
var init_normalize = __esm({
|
|
468
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/normalize.js"() {
|
|
469
|
+
"use strict";
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/diff.js
|
|
474
|
+
function getTableNamesFromState(state) {
|
|
475
|
+
return new Set(Object.keys(state.tables));
|
|
476
|
+
}
|
|
477
|
+
function getTableNamesFromSchema(schema) {
|
|
478
|
+
return new Set(Object.keys(schema.tables));
|
|
479
|
+
}
|
|
480
|
+
function getColumnNamesFromState(stateColumns) {
|
|
481
|
+
return new Set(Object.keys(stateColumns));
|
|
482
|
+
}
|
|
483
|
+
function getColumnNamesFromSchema(dbColumns) {
|
|
484
|
+
return new Set(dbColumns.map((column) => column.name));
|
|
485
|
+
}
|
|
486
|
+
function getSortedNames(names) {
|
|
487
|
+
return Array.from(names).sort((a, b) => a.localeCompare(b));
|
|
488
|
+
}
|
|
489
|
+
function normalizeColumnType(type) {
|
|
490
|
+
return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
491
|
+
}
|
|
492
|
+
function resolveStatePrimaryKey(table) {
|
|
493
|
+
return table.primaryKey ?? Object.entries(table.columns).find(([, column]) => column.primaryKey)?.[0] ?? null;
|
|
494
|
+
}
|
|
495
|
+
function resolveSchemaPrimaryKey(table) {
|
|
496
|
+
return table.primaryKey ?? table.columns.find((column) => column.primaryKey)?.name ?? null;
|
|
497
|
+
}
|
|
498
|
+
function normalizeNullable(nullable) {
|
|
499
|
+
return nullable ?? true;
|
|
500
|
+
}
|
|
501
|
+
function diffSchemas(oldState, newSchema) {
|
|
502
|
+
const operations = [];
|
|
503
|
+
const oldTableNames = getTableNamesFromState(oldState);
|
|
504
|
+
const newTableNames = getTableNamesFromSchema(newSchema);
|
|
505
|
+
const sortedNewTableNames = getSortedNames(newTableNames);
|
|
506
|
+
const sortedOldTableNames = getSortedNames(oldTableNames);
|
|
507
|
+
for (const tableName of sortedNewTableNames) {
|
|
508
|
+
if (!oldTableNames.has(tableName)) {
|
|
509
|
+
operations.push({
|
|
510
|
+
kind: "create_table",
|
|
511
|
+
table: newSchema.tables[tableName]
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const commonTableNames = sortedNewTableNames.filter((tableName) => oldTableNames.has(tableName));
|
|
516
|
+
for (const tableName of commonTableNames) {
|
|
517
|
+
const newTable = newSchema.tables[tableName];
|
|
518
|
+
const oldTable = oldState.tables[tableName];
|
|
519
|
+
if (!newTable || !oldTable) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
for (const column of newTable.columns) {
|
|
523
|
+
const previousColumn = oldTable.columns[column.name];
|
|
524
|
+
if (!previousColumn) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const previousType = normalizeColumnType(previousColumn.type);
|
|
528
|
+
const currentType = normalizeColumnType(column.type);
|
|
529
|
+
if (previousType !== currentType) {
|
|
530
|
+
operations.push({
|
|
531
|
+
kind: "column_type_changed",
|
|
532
|
+
tableName,
|
|
533
|
+
columnName: column.name,
|
|
534
|
+
fromType: previousColumn.type,
|
|
535
|
+
toType: column.type
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
for (const tableName of commonTableNames) {
|
|
541
|
+
const newTable = newSchema.tables[tableName];
|
|
542
|
+
const oldTable = oldState.tables[tableName];
|
|
543
|
+
if (!newTable || !oldTable) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const previousPrimaryKey = resolveStatePrimaryKey(oldTable);
|
|
547
|
+
const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
|
|
548
|
+
if (previousPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
|
|
549
|
+
operations.push({
|
|
550
|
+
kind: "drop_primary_key_constraint",
|
|
551
|
+
tableName
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
for (const tableName of commonTableNames) {
|
|
556
|
+
const newTable = newSchema.tables[tableName];
|
|
557
|
+
const oldTable = oldState.tables[tableName];
|
|
558
|
+
if (!newTable || !oldTable) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
for (const column of newTable.columns) {
|
|
562
|
+
const previousColumn = oldTable.columns[column.name];
|
|
563
|
+
if (!previousColumn) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const previousUnique = previousColumn.unique ?? false;
|
|
567
|
+
const currentUnique = column.unique ?? false;
|
|
568
|
+
if (previousUnique !== currentUnique) {
|
|
569
|
+
operations.push({
|
|
570
|
+
kind: "column_unique_changed",
|
|
571
|
+
tableName,
|
|
572
|
+
columnName: column.name,
|
|
573
|
+
from: previousUnique,
|
|
574
|
+
to: currentUnique
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (const tableName of commonTableNames) {
|
|
580
|
+
const newTable = newSchema.tables[tableName];
|
|
581
|
+
const oldTable = oldState.tables[tableName];
|
|
582
|
+
if (!newTable || !oldTable) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
for (const column of newTable.columns) {
|
|
586
|
+
const previousColumn = oldTable.columns[column.name];
|
|
587
|
+
if (!previousColumn) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const previousNullable = normalizeNullable(previousColumn.nullable);
|
|
591
|
+
const currentNullable = normalizeNullable(column.nullable);
|
|
592
|
+
if (previousNullable !== currentNullable) {
|
|
593
|
+
operations.push({
|
|
594
|
+
kind: "column_nullability_changed",
|
|
595
|
+
tableName,
|
|
596
|
+
columnName: column.name,
|
|
597
|
+
from: previousNullable,
|
|
598
|
+
to: currentNullable
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
for (const tableName of commonTableNames) {
|
|
604
|
+
const newTable = newSchema.tables[tableName];
|
|
605
|
+
const oldTable = oldState.tables[tableName];
|
|
606
|
+
if (!newTable || !oldTable) {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
for (const column of newTable.columns) {
|
|
610
|
+
const previousColumn = oldTable.columns[column.name];
|
|
611
|
+
if (!previousColumn) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const previousDefault = normalizeDefault(previousColumn.default);
|
|
615
|
+
const currentDefault = normalizeDefault(column.default);
|
|
616
|
+
if (previousDefault !== currentDefault) {
|
|
617
|
+
operations.push({
|
|
618
|
+
kind: "column_default_changed",
|
|
619
|
+
tableName,
|
|
620
|
+
columnName: column.name,
|
|
621
|
+
fromDefault: previousDefault,
|
|
622
|
+
toDefault: currentDefault
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (const tableName of commonTableNames) {
|
|
628
|
+
const newTable = newSchema.tables[tableName];
|
|
629
|
+
const oldTable = oldState.tables[tableName];
|
|
630
|
+
if (!newTable || !oldTable) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
const oldColumnNames = getColumnNamesFromState(oldTable.columns);
|
|
634
|
+
for (const column of newTable.columns) {
|
|
635
|
+
if (!oldColumnNames.has(column.name)) {
|
|
636
|
+
operations.push({
|
|
637
|
+
kind: "add_column",
|
|
638
|
+
tableName,
|
|
639
|
+
column
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
for (const tableName of commonTableNames) {
|
|
645
|
+
const newTable = newSchema.tables[tableName];
|
|
646
|
+
const oldTable = oldState.tables[tableName];
|
|
647
|
+
if (!newTable || !oldTable) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const previousPrimaryKey = resolveStatePrimaryKey(oldTable);
|
|
651
|
+
const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
|
|
652
|
+
if (currentPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
|
|
653
|
+
operations.push({
|
|
654
|
+
kind: "add_primary_key_constraint",
|
|
655
|
+
tableName,
|
|
656
|
+
columnName: currentPrimaryKey
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const tableName of commonTableNames) {
|
|
661
|
+
const newTable = newSchema.tables[tableName];
|
|
662
|
+
const oldTable = oldState.tables[tableName];
|
|
663
|
+
if (!newTable || !oldTable) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const newColumnNames = getColumnNamesFromSchema(newTable.columns);
|
|
667
|
+
for (const columnName of Object.keys(oldTable.columns)) {
|
|
668
|
+
if (!newColumnNames.has(columnName)) {
|
|
669
|
+
operations.push({
|
|
670
|
+
kind: "drop_column",
|
|
671
|
+
tableName,
|
|
672
|
+
columnName
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
for (const tableName of sortedOldTableNames) {
|
|
678
|
+
if (!newTableNames.has(tableName)) {
|
|
679
|
+
operations.push({
|
|
680
|
+
kind: "drop_table",
|
|
681
|
+
tableName
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return { operations };
|
|
686
|
+
}
|
|
687
|
+
var init_diff = __esm({
|
|
688
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/diff.js"() {
|
|
689
|
+
"use strict";
|
|
690
|
+
init_normalize();
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/drift-analyzer.js
|
|
695
|
+
function normalizeColumnType2(type) {
|
|
696
|
+
return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
697
|
+
}
|
|
698
|
+
function getSortedNames2(values) {
|
|
699
|
+
return Array.from(values).sort((left, right) => left.localeCompare(right));
|
|
700
|
+
}
|
|
701
|
+
function analyzeSchemaDrift(state, liveSchema) {
|
|
702
|
+
const stateTableNames = getSortedNames2(Object.keys(state.tables));
|
|
703
|
+
const liveTableNames = getSortedNames2(Object.keys(liveSchema.tables));
|
|
704
|
+
const liveTableNameSet = new Set(liveTableNames);
|
|
705
|
+
const stateTableNameSet = new Set(stateTableNames);
|
|
706
|
+
const missingTables = stateTableNames.filter((tableName) => !liveTableNameSet.has(tableName));
|
|
707
|
+
const extraTables = liveTableNames.filter((tableName) => !stateTableNameSet.has(tableName));
|
|
708
|
+
const commonTableNames = liveTableNames.filter((tableName) => stateTableNameSet.has(tableName));
|
|
709
|
+
const columnDifferences = [];
|
|
710
|
+
const typeMismatches = [];
|
|
711
|
+
for (const tableName of commonTableNames) {
|
|
712
|
+
const stateTable = state.tables[tableName];
|
|
713
|
+
const liveTable = liveSchema.tables[tableName];
|
|
714
|
+
if (!stateTable || !liveTable) {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const stateColumnNames = getSortedNames2(Object.keys(stateTable.columns));
|
|
718
|
+
const liveColumnsByName = new Map(liveTable.columns.map((column) => [column.name, column]));
|
|
719
|
+
const liveColumnNames = getSortedNames2(liveColumnsByName.keys());
|
|
720
|
+
const stateColumnNameSet = new Set(stateColumnNames);
|
|
721
|
+
const liveColumnNameSet = new Set(liveColumnNames);
|
|
722
|
+
const missingInLive = stateColumnNames.filter((columnName) => !liveColumnNameSet.has(columnName));
|
|
723
|
+
const extraInLive = liveColumnNames.filter((columnName) => !stateColumnNameSet.has(columnName));
|
|
724
|
+
if (missingInLive.length > 0 || extraInLive.length > 0) {
|
|
725
|
+
columnDifferences.push({
|
|
726
|
+
tableName,
|
|
727
|
+
missingInLive,
|
|
728
|
+
extraInLive
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
const commonColumns = stateColumnNames.filter((columnName) => liveColumnNameSet.has(columnName));
|
|
732
|
+
for (const columnName of commonColumns) {
|
|
733
|
+
const stateColumn = stateTable.columns[columnName];
|
|
734
|
+
const liveColumn = liveColumnsByName.get(columnName);
|
|
735
|
+
if (!stateColumn || !liveColumn) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const expectedType = stateColumn.type;
|
|
739
|
+
const actualType = liveColumn.type;
|
|
740
|
+
if (normalizeColumnType2(expectedType) !== normalizeColumnType2(actualType)) {
|
|
741
|
+
typeMismatches.push({
|
|
742
|
+
tableName,
|
|
743
|
+
columnName,
|
|
744
|
+
expectedType,
|
|
745
|
+
actualType
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
missingTables,
|
|
752
|
+
extraTables,
|
|
753
|
+
columnDifferences,
|
|
754
|
+
typeMismatches
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
var init_drift_analyzer = __esm({
|
|
758
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/drift-analyzer.js"() {
|
|
759
|
+
"use strict";
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/validator.js
|
|
764
|
+
function isValidColumnType(type) {
|
|
765
|
+
const normalizedType = type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
766
|
+
if (VALID_BASE_COLUMN_TYPES.includes(normalizedType)) {
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
return /^varchar\(\d+\)$/.test(normalizedType) || /^numeric\(\d+,\d+\)$/.test(normalizedType);
|
|
770
|
+
}
|
|
771
|
+
function validateSchema(schema) {
|
|
772
|
+
validateDuplicateTables(schema);
|
|
773
|
+
for (const tableName in schema.tables) {
|
|
774
|
+
const table = schema.tables[tableName];
|
|
775
|
+
validateTableColumns(tableName, table, schema.tables);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function validateDuplicateTables(schema) {
|
|
779
|
+
const tableNames = Object.keys(schema.tables);
|
|
780
|
+
const seen = /* @__PURE__ */ new Set();
|
|
781
|
+
for (const tableName of tableNames) {
|
|
782
|
+
if (seen.has(tableName)) {
|
|
783
|
+
throw new Error(`Duplicate table: '${tableName}'`);
|
|
784
|
+
}
|
|
785
|
+
seen.add(tableName);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function validateTableColumns(tableName, table, allTables) {
|
|
789
|
+
const columnNames = /* @__PURE__ */ new Set();
|
|
790
|
+
const primaryKeyColumns = [];
|
|
791
|
+
for (const column of table.columns) {
|
|
792
|
+
if (columnNames.has(column.name)) {
|
|
793
|
+
throw new Error(`Table '${tableName}': duplicate column '${column.name}'`);
|
|
794
|
+
}
|
|
795
|
+
columnNames.add(column.name);
|
|
796
|
+
if (column.primaryKey) {
|
|
797
|
+
primaryKeyColumns.push(column.name);
|
|
798
|
+
}
|
|
799
|
+
if (!isValidColumnType(column.type)) {
|
|
800
|
+
throw new Error(`Table '${tableName}', column '${column.name}': type '${column.type}' is not valid. Supported types: ${VALID_BASE_COLUMN_TYPES.join(", ")}, varchar(n), numeric(p,s)`);
|
|
801
|
+
}
|
|
802
|
+
if (column.foreignKey) {
|
|
803
|
+
const fkTable = column.foreignKey.table;
|
|
804
|
+
const fkColumn = column.foreignKey.column;
|
|
805
|
+
if (!allTables[fkTable]) {
|
|
806
|
+
throw new Error(`Table '${tableName}', column '${column.name}': referenced table '${fkTable}' does not exist`);
|
|
807
|
+
}
|
|
808
|
+
const referencedTable = allTables[fkTable];
|
|
809
|
+
const columnExists = referencedTable.columns.some((col) => col.name === fkColumn);
|
|
810
|
+
if (!columnExists) {
|
|
811
|
+
throw new Error(`Table '${tableName}', column '${column.name}': table '${fkTable}' does not have column '${fkColumn}'`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (primaryKeyColumns.length > 1) {
|
|
816
|
+
throw new Error(`Table '${tableName}': can only have one primary key (found ${primaryKeyColumns.length})`);
|
|
817
|
+
}
|
|
818
|
+
const normalizedPrimaryKey = table.primaryKey ?? primaryKeyColumns[0] ?? null;
|
|
819
|
+
if (table.primaryKey && !columnNames.has(table.primaryKey)) {
|
|
820
|
+
throw new Error(`Table '${tableName}': primary key column '${table.primaryKey}' does not exist`);
|
|
821
|
+
}
|
|
822
|
+
if (table.primaryKey && primaryKeyColumns.length === 1 && primaryKeyColumns[0] !== table.primaryKey) {
|
|
823
|
+
throw new Error(`Table '${tableName}': column-level primary key '${primaryKeyColumns[0]}' does not match table primary key '${table.primaryKey}'`);
|
|
824
|
+
}
|
|
825
|
+
if (normalizedPrimaryKey) {
|
|
826
|
+
const pkMatches = table.columns.filter((column) => column.name === normalizedPrimaryKey);
|
|
827
|
+
if (pkMatches.length !== 1) {
|
|
828
|
+
throw new Error(`Table '${tableName}': primary key column '${normalizedPrimaryKey}' is invalid`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
var VALID_BASE_COLUMN_TYPES;
|
|
833
|
+
var init_validator = __esm({
|
|
834
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/validator.js"() {
|
|
835
|
+
"use strict";
|
|
836
|
+
VALID_BASE_COLUMN_TYPES = [
|
|
837
|
+
"uuid",
|
|
838
|
+
"varchar",
|
|
839
|
+
"text",
|
|
840
|
+
"int",
|
|
841
|
+
"bigint",
|
|
842
|
+
"boolean",
|
|
843
|
+
"timestamptz",
|
|
844
|
+
"date"
|
|
845
|
+
];
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/safety/operation-classifier.js
|
|
850
|
+
function normalizeColumnType3(type) {
|
|
851
|
+
return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
852
|
+
}
|
|
853
|
+
function parseVarcharLength(type) {
|
|
854
|
+
const match = normalizeColumnType3(type).match(/^varchar\((\d+)\)$/);
|
|
855
|
+
return match ? Number(match[1]) : null;
|
|
856
|
+
}
|
|
857
|
+
function parseNumericType(type) {
|
|
858
|
+
const match = normalizeColumnType3(type).match(/^numeric\((\d+),(\d+)\)$/);
|
|
859
|
+
if (!match) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
precision: Number(match[1]),
|
|
864
|
+
scale: Number(match[2])
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function classifyTypeChange(from, to) {
|
|
868
|
+
const fromType = normalizeColumnType3(from);
|
|
869
|
+
const toType = normalizeColumnType3(to);
|
|
870
|
+
const uuidInvolved = fromType === "uuid" || toType === "uuid";
|
|
871
|
+
if (uuidInvolved && fromType !== toType) {
|
|
872
|
+
return "DESTRUCTIVE";
|
|
873
|
+
}
|
|
874
|
+
if (fromType === "int" && toType === "bigint") {
|
|
875
|
+
return "WARNING";
|
|
876
|
+
}
|
|
877
|
+
if (fromType === "bigint" && toType === "int") {
|
|
878
|
+
return "DESTRUCTIVE";
|
|
879
|
+
}
|
|
880
|
+
if (fromType === "text" && parseVarcharLength(toType) !== null) {
|
|
881
|
+
return "DESTRUCTIVE";
|
|
882
|
+
}
|
|
883
|
+
if (parseVarcharLength(fromType) !== null && toType === "text") {
|
|
884
|
+
return "WARNING";
|
|
885
|
+
}
|
|
886
|
+
const fromVarcharLength = parseVarcharLength(fromType);
|
|
887
|
+
const toVarcharLength = parseVarcharLength(toType);
|
|
888
|
+
if (fromVarcharLength !== null && toVarcharLength !== null) {
|
|
889
|
+
if (toVarcharLength >= fromVarcharLength) {
|
|
890
|
+
return "WARNING";
|
|
891
|
+
}
|
|
892
|
+
return "DESTRUCTIVE";
|
|
893
|
+
}
|
|
894
|
+
const fromNumeric = parseNumericType(fromType);
|
|
895
|
+
const toNumeric = parseNumericType(toType);
|
|
896
|
+
if (fromNumeric && toNumeric && fromNumeric.scale === toNumeric.scale) {
|
|
897
|
+
if (toNumeric.precision >= fromNumeric.precision) {
|
|
898
|
+
return "WARNING";
|
|
899
|
+
}
|
|
900
|
+
return "DESTRUCTIVE";
|
|
901
|
+
}
|
|
902
|
+
return "WARNING";
|
|
903
|
+
}
|
|
904
|
+
function classifyOperation(operation) {
|
|
905
|
+
switch (operation.kind) {
|
|
906
|
+
case "create_table":
|
|
907
|
+
return "SAFE";
|
|
908
|
+
case "add_column":
|
|
909
|
+
return "SAFE";
|
|
910
|
+
case "drop_table":
|
|
911
|
+
return "DESTRUCTIVE";
|
|
912
|
+
case "drop_column":
|
|
913
|
+
return "DESTRUCTIVE";
|
|
914
|
+
case "column_type_changed":
|
|
915
|
+
return classifyTypeChange(operation.fromType, operation.toType);
|
|
916
|
+
case "column_nullability_changed":
|
|
917
|
+
if (operation.from && !operation.to) {
|
|
918
|
+
return "WARNING";
|
|
919
|
+
}
|
|
920
|
+
return "SAFE";
|
|
921
|
+
case "column_default_changed":
|
|
922
|
+
return "SAFE";
|
|
923
|
+
case "column_unique_changed":
|
|
924
|
+
return "SAFE";
|
|
925
|
+
case "drop_primary_key_constraint":
|
|
926
|
+
return "DESTRUCTIVE";
|
|
927
|
+
case "add_primary_key_constraint":
|
|
928
|
+
return "SAFE";
|
|
929
|
+
default:
|
|
930
|
+
const _exhaustive = operation;
|
|
931
|
+
return _exhaustive;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
var init_operation_classifier = __esm({
|
|
935
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/safety/operation-classifier.js"() {
|
|
936
|
+
"use strict";
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/safety/safety-checker.js
|
|
941
|
+
function normalizeColumnType4(type) {
|
|
942
|
+
return type.toLowerCase().trim().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
943
|
+
}
|
|
944
|
+
function parseVarcharLength2(type) {
|
|
945
|
+
const match = normalizeColumnType4(type).match(/^varchar\((\d+)\)$/);
|
|
946
|
+
return match ? Number(match[1]) : null;
|
|
947
|
+
}
|
|
948
|
+
function parseNumericType2(type) {
|
|
949
|
+
const match = normalizeColumnType4(type).match(/^numeric\((\d+),(\d+)\)$/);
|
|
950
|
+
if (!match) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
precision: Number(match[1]),
|
|
955
|
+
scale: Number(match[2])
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function generateTypeChangeMessage(from, to) {
|
|
959
|
+
const fromType = normalizeColumnType4(from);
|
|
960
|
+
const toType = normalizeColumnType4(to);
|
|
961
|
+
const uuidInvolved = fromType === "uuid" || toType === "uuid";
|
|
962
|
+
if (uuidInvolved && fromType !== toType) {
|
|
963
|
+
return `Type changed from ${fromType} to ${toType} (likely incompatible cast)`;
|
|
964
|
+
}
|
|
965
|
+
if (fromType === "int" && toType === "bigint") {
|
|
966
|
+
return "Type widened from int to bigint";
|
|
967
|
+
}
|
|
968
|
+
if (fromType === "bigint" && toType === "int") {
|
|
969
|
+
return "Type narrowed from bigint to int (likely incompatible cast)";
|
|
970
|
+
}
|
|
971
|
+
if (fromType === "text" && parseVarcharLength2(toType) !== null) {
|
|
972
|
+
return `Type changed from text to ${toType} (may truncate existing values)`;
|
|
973
|
+
}
|
|
974
|
+
if (parseVarcharLength2(fromType) !== null && toType === "text") {
|
|
975
|
+
const length = parseVarcharLength2(fromType);
|
|
976
|
+
return `Type widened from varchar(${length}) to text`;
|
|
977
|
+
}
|
|
978
|
+
const fromVarcharLength = parseVarcharLength2(fromType);
|
|
979
|
+
const toVarcharLength = parseVarcharLength2(toType);
|
|
980
|
+
if (fromVarcharLength !== null && toVarcharLength !== null) {
|
|
981
|
+
if (toVarcharLength >= fromVarcharLength) {
|
|
982
|
+
return `Type widened from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`;
|
|
983
|
+
}
|
|
984
|
+
return `Type narrowed from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`;
|
|
985
|
+
}
|
|
986
|
+
const fromNumeric = parseNumericType2(fromType);
|
|
987
|
+
const toNumeric = parseNumericType2(toType);
|
|
988
|
+
if (fromNumeric && toNumeric && fromNumeric.scale === toNumeric.scale) {
|
|
989
|
+
if (toNumeric.precision >= fromNumeric.precision) {
|
|
990
|
+
return `Type widened from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`;
|
|
991
|
+
}
|
|
992
|
+
return `Type narrowed from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`;
|
|
993
|
+
}
|
|
994
|
+
return `Type changed from ${fromType} to ${toType} (compatibility unknown)`;
|
|
995
|
+
}
|
|
996
|
+
function checkOperationSafety(operation) {
|
|
997
|
+
const safetyLevel = classifyOperation(operation);
|
|
998
|
+
if (safetyLevel === "SAFE") {
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
switch (operation.kind) {
|
|
1002
|
+
case "drop_table":
|
|
1003
|
+
return {
|
|
1004
|
+
safetyLevel,
|
|
1005
|
+
code: "DROP_TABLE",
|
|
1006
|
+
table: operation.tableName,
|
|
1007
|
+
message: "Table removed",
|
|
1008
|
+
operationKind: operation.kind
|
|
1009
|
+
};
|
|
1010
|
+
case "drop_column":
|
|
1011
|
+
return {
|
|
1012
|
+
safetyLevel,
|
|
1013
|
+
code: "DROP_COLUMN",
|
|
1014
|
+
table: operation.tableName,
|
|
1015
|
+
column: operation.columnName,
|
|
1016
|
+
message: "Column removed",
|
|
1017
|
+
operationKind: operation.kind
|
|
1018
|
+
};
|
|
1019
|
+
case "column_type_changed":
|
|
1020
|
+
return {
|
|
1021
|
+
safetyLevel,
|
|
1022
|
+
code: "ALTER_COLUMN_TYPE",
|
|
1023
|
+
table: operation.tableName,
|
|
1024
|
+
column: operation.columnName,
|
|
1025
|
+
from: normalizeColumnType4(operation.fromType),
|
|
1026
|
+
to: normalizeColumnType4(operation.toType),
|
|
1027
|
+
message: generateTypeChangeMessage(operation.fromType, operation.toType),
|
|
1028
|
+
operationKind: operation.kind
|
|
1029
|
+
};
|
|
1030
|
+
case "column_nullability_changed":
|
|
1031
|
+
if (operation.from && !operation.to) {
|
|
1032
|
+
return {
|
|
1033
|
+
safetyLevel,
|
|
1034
|
+
code: "SET_NOT_NULL",
|
|
1035
|
+
table: operation.tableName,
|
|
1036
|
+
column: operation.columnName,
|
|
1037
|
+
message: "Column changed to NOT NULL (may fail if data contains NULLs)",
|
|
1038
|
+
operationKind: operation.kind
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
return null;
|
|
1042
|
+
case "drop_primary_key_constraint":
|
|
1043
|
+
return {
|
|
1044
|
+
safetyLevel,
|
|
1045
|
+
code: "DROP_TABLE",
|
|
1046
|
+
// Reuse code for primary key drops
|
|
1047
|
+
table: operation.tableName,
|
|
1048
|
+
message: "Primary key constraint removed",
|
|
1049
|
+
operationKind: operation.kind
|
|
1050
|
+
};
|
|
1051
|
+
default:
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function checkSchemaSafety(previousState, currentSchema) {
|
|
1056
|
+
const findings = [];
|
|
1057
|
+
const diff2 = diffSchemas(previousState, currentSchema);
|
|
1058
|
+
for (const operation of diff2.operations) {
|
|
1059
|
+
const finding = checkOperationSafety(operation);
|
|
1060
|
+
if (finding) {
|
|
1061
|
+
findings.push(finding);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const hasWarnings = findings.some((f) => f.safetyLevel === "WARNING");
|
|
1065
|
+
const hasDestructiveOps = findings.some((f) => f.safetyLevel === "DESTRUCTIVE");
|
|
1066
|
+
return {
|
|
1067
|
+
findings,
|
|
1068
|
+
hasSafeIssues: false,
|
|
1069
|
+
// All findings are non-safe by definition
|
|
1070
|
+
hasWarnings,
|
|
1071
|
+
hasDestructiveOps
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
var init_safety_checker = __esm({
|
|
1075
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/safety/safety-checker.js"() {
|
|
1076
|
+
"use strict";
|
|
1077
|
+
init_diff();
|
|
1078
|
+
init_operation_classifier();
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/safety/index.js
|
|
1083
|
+
var init_safety = __esm({
|
|
1084
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/safety/index.js"() {
|
|
1085
|
+
"use strict";
|
|
1086
|
+
init_operation_classifier();
|
|
1087
|
+
init_safety_checker();
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/validate.js
|
|
1092
|
+
function safetyLevelToSeverity(level) {
|
|
1093
|
+
if (level === "DESTRUCTIVE") {
|
|
1094
|
+
return "error";
|
|
1095
|
+
}
|
|
1096
|
+
return "warning";
|
|
1097
|
+
}
|
|
1098
|
+
function adaptSafetyFinding(finding) {
|
|
1099
|
+
return {
|
|
1100
|
+
severity: safetyLevelToSeverity(finding.safetyLevel),
|
|
1101
|
+
code: finding.code,
|
|
1102
|
+
table: finding.table,
|
|
1103
|
+
column: finding.column,
|
|
1104
|
+
from: finding.from,
|
|
1105
|
+
to: finding.to,
|
|
1106
|
+
message: finding.message
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function validateSchemaChanges(previousState, currentSchema) {
|
|
1110
|
+
const safetyReport = checkSchemaSafety(previousState, currentSchema);
|
|
1111
|
+
return safetyReport.findings.map(adaptSafetyFinding);
|
|
1112
|
+
}
|
|
1113
|
+
function toValidationReport(findings) {
|
|
1114
|
+
const errors = findings.filter((finding) => finding.severity === "error");
|
|
1115
|
+
const warnings = findings.filter((finding) => finding.severity === "warning");
|
|
1116
|
+
return {
|
|
1117
|
+
hasErrors: errors.length > 0,
|
|
1118
|
+
hasWarnings: warnings.length > 0,
|
|
1119
|
+
errors: errors.map(({ severity, ...finding }) => finding),
|
|
1120
|
+
warnings: warnings.map(({ severity, ...finding }) => finding)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
var init_validate = __esm({
|
|
1124
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/validate.js"() {
|
|
1125
|
+
"use strict";
|
|
1126
|
+
init_safety();
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/fs.js
|
|
1131
|
+
async function ensureDir2(dirPath) {
|
|
1132
|
+
try {
|
|
1133
|
+
await import_fs2.promises.mkdir(dirPath, { recursive: true });
|
|
1134
|
+
} catch (error2) {
|
|
1135
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error2}`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
async function fileExists2(filePath) {
|
|
1139
|
+
try {
|
|
1140
|
+
await import_fs2.promises.access(filePath);
|
|
1141
|
+
return true;
|
|
1142
|
+
} catch {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async function readTextFile2(filePath) {
|
|
1147
|
+
try {
|
|
1148
|
+
return await import_fs2.promises.readFile(filePath, "utf-8");
|
|
1149
|
+
} catch (error2) {
|
|
1150
|
+
throw new Error(`Failed to read file ${filePath}: ${error2}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
async function writeTextFile2(filePath, content) {
|
|
1154
|
+
try {
|
|
1155
|
+
const dir = import_path3.default.dirname(filePath);
|
|
1156
|
+
await ensureDir2(dir);
|
|
1157
|
+
await import_fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1158
|
+
} catch (error2) {
|
|
1159
|
+
throw new Error(`Failed to write file ${filePath}: ${error2}`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async function readJsonFile2(filePath, fallback) {
|
|
1163
|
+
try {
|
|
1164
|
+
const exists = await fileExists2(filePath);
|
|
1165
|
+
if (!exists) {
|
|
1166
|
+
return fallback;
|
|
1167
|
+
}
|
|
1168
|
+
const content = await readTextFile2(filePath);
|
|
1169
|
+
return JSON.parse(content);
|
|
1170
|
+
} catch (error2) {
|
|
1171
|
+
throw new Error(`Failed to read JSON file ${filePath}: ${error2}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async function writeJsonFile2(filePath, data) {
|
|
1175
|
+
try {
|
|
1176
|
+
const content = JSON.stringify(data, null, 2);
|
|
1177
|
+
await writeTextFile2(filePath, content);
|
|
1178
|
+
} catch (error2) {
|
|
1179
|
+
throw new Error(`Failed to write JSON file ${filePath}: ${error2}`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async function findFiles(dirPath, pattern) {
|
|
1183
|
+
const results = [];
|
|
1184
|
+
try {
|
|
1185
|
+
const items = await import_fs2.promises.readdir(dirPath, { withFileTypes: true });
|
|
1186
|
+
for (const item of items) {
|
|
1187
|
+
const fullPath = import_path3.default.join(dirPath, item.name);
|
|
1188
|
+
if (item.isDirectory()) {
|
|
1189
|
+
const subResults = await findFiles(fullPath, pattern);
|
|
1190
|
+
results.push(...subResults);
|
|
1191
|
+
} else if (item.isFile() && pattern.test(item.name)) {
|
|
1192
|
+
results.push(fullPath);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
} catch (error2) {
|
|
1196
|
+
throw new Error(`Failed to find files in ${dirPath}: ${error2}`);
|
|
1197
|
+
}
|
|
1198
|
+
return results;
|
|
1199
|
+
}
|
|
1200
|
+
var import_fs2, import_path3;
|
|
1201
|
+
var init_fs = __esm({
|
|
1202
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/fs.js"() {
|
|
1203
|
+
"use strict";
|
|
1204
|
+
import_fs2 = require("fs");
|
|
1205
|
+
import_path3 = __toESM(require("path"), 1);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/state-transform.js
|
|
1210
|
+
async function schemaToState(schema) {
|
|
1211
|
+
const tables = {};
|
|
1212
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
1213
|
+
const columns = {};
|
|
1214
|
+
const primaryKeyColumn = table.primaryKey ?? table.columns.find((column) => column.primaryKey)?.name ?? null;
|
|
1215
|
+
for (const column of table.columns) {
|
|
1216
|
+
columns[column.name] = {
|
|
1217
|
+
type: column.type,
|
|
1218
|
+
...column.primaryKey !== void 0 && { primaryKey: column.primaryKey },
|
|
1219
|
+
...column.unique !== void 0 && { unique: column.unique },
|
|
1220
|
+
nullable: column.nullable ?? true,
|
|
1221
|
+
...column.default !== void 0 && { default: column.default },
|
|
1222
|
+
...column.foreignKey !== void 0 && { foreignKey: column.foreignKey }
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
tables[tableName] = {
|
|
1226
|
+
columns,
|
|
1227
|
+
...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
version: 1,
|
|
1232
|
+
tables
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
var init_state_transform = __esm({
|
|
1236
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/state-transform.js"() {
|
|
1237
|
+
"use strict";
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/state-manager.js
|
|
1242
|
+
async function loadState(statePath) {
|
|
1243
|
+
return await readJsonFile2(statePath, { version: 1, tables: {} });
|
|
1244
|
+
}
|
|
1245
|
+
async function saveState(statePath, state) {
|
|
1246
|
+
const dirPath = import_path4.default.dirname(statePath);
|
|
1247
|
+
await ensureDir2(dirPath);
|
|
1248
|
+
await writeJsonFile2(statePath, state);
|
|
1249
|
+
}
|
|
1250
|
+
var import_path4;
|
|
1251
|
+
var init_state_manager = __esm({
|
|
1252
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/state-manager.js"() {
|
|
1253
|
+
"use strict";
|
|
1254
|
+
import_path4 = __toESM(require("path"), 1);
|
|
1255
|
+
init_fs();
|
|
1256
|
+
init_state_transform();
|
|
1257
|
+
init_state_transform();
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
// node_modules/@xubylele/schema-forge-core/dist/generator/sql-generator.js
|
|
1262
|
+
function generateSql(diff2, provider, sqlConfig) {
|
|
1263
|
+
const statements = [];
|
|
1264
|
+
for (const operation of diff2.operations) {
|
|
1265
|
+
const sql = generateOperation(operation, provider, sqlConfig);
|
|
1266
|
+
if (sql) {
|
|
1267
|
+
statements.push(sql);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return statements.join("\n\n");
|
|
1271
|
+
}
|
|
1272
|
+
function generateOperation(operation, provider, sqlConfig) {
|
|
1273
|
+
switch (operation.kind) {
|
|
1274
|
+
case "create_table":
|
|
1275
|
+
return generateCreateTable(operation.table, provider, sqlConfig);
|
|
1276
|
+
case "drop_table":
|
|
1277
|
+
return generateDropTable(operation.tableName);
|
|
1278
|
+
case "column_type_changed":
|
|
1279
|
+
return generateAlterColumnType(operation.tableName, operation.columnName, operation.toType);
|
|
1280
|
+
case "column_nullability_changed":
|
|
1281
|
+
return generateAlterColumnNullability(operation.tableName, operation.columnName, operation.to);
|
|
1282
|
+
case "add_column":
|
|
1283
|
+
return generateAddColumn(operation.tableName, operation.column, provider, sqlConfig);
|
|
1284
|
+
case "column_default_changed":
|
|
1285
|
+
return generateAlterColumnDefault(operation.tableName, operation.columnName, operation.toDefault);
|
|
1286
|
+
case "drop_column":
|
|
1287
|
+
return generateDropColumn(operation.tableName, operation.columnName);
|
|
1288
|
+
case "column_unique_changed":
|
|
1289
|
+
return operation.to ? generateAddUniqueConstraint(operation.tableName, operation.columnName) : generateDropUniqueConstraint(operation.tableName, operation.columnName);
|
|
1290
|
+
case "drop_primary_key_constraint":
|
|
1291
|
+
return generateDropPrimaryKeyConstraint(operation.tableName);
|
|
1292
|
+
case "add_primary_key_constraint":
|
|
1293
|
+
return generateAddPrimaryKeyConstraint(operation.tableName, operation.columnName);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function generateCreateTable(table, provider, sqlConfig) {
|
|
1297
|
+
const columnDefs = table.columns.map((col) => generateColumnDefinition(col, provider, sqlConfig));
|
|
1298
|
+
const lines = ["CREATE TABLE " + table.name + " ("];
|
|
1299
|
+
columnDefs.forEach((colDef, index) => {
|
|
1300
|
+
const isLast = index === columnDefs.length - 1;
|
|
1301
|
+
lines.push(" " + colDef + (isLast ? "" : ","));
|
|
1302
|
+
});
|
|
1303
|
+
lines.push(");");
|
|
1304
|
+
return lines.join("\n");
|
|
1305
|
+
}
|
|
1306
|
+
function generateColumnDefinition(column, provider, sqlConfig) {
|
|
1307
|
+
const parts = [column.name, column.type];
|
|
1308
|
+
if (column.foreignKey) {
|
|
1309
|
+
parts.push(`references ${column.foreignKey.table}(${column.foreignKey.column})`);
|
|
1310
|
+
}
|
|
1311
|
+
if (column.primaryKey) {
|
|
1312
|
+
parts.push("primary key");
|
|
1313
|
+
}
|
|
1314
|
+
if (column.unique) {
|
|
1315
|
+
parts.push("unique");
|
|
1316
|
+
}
|
|
1317
|
+
if (column.nullable === false) {
|
|
1318
|
+
parts.push("not null");
|
|
1319
|
+
}
|
|
1320
|
+
if (column.default !== void 0) {
|
|
1321
|
+
parts.push("default " + column.default);
|
|
1322
|
+
} else if (column.type === "uuid" && column.primaryKey && provider === "supabase") {
|
|
1323
|
+
parts.push("default gen_random_uuid()");
|
|
1324
|
+
}
|
|
1325
|
+
return parts.join(" ");
|
|
1326
|
+
}
|
|
1327
|
+
function generateDropTable(tableName) {
|
|
1328
|
+
return `DROP TABLE ${tableName};`;
|
|
1329
|
+
}
|
|
1330
|
+
function generateAddColumn(tableName, column, provider, sqlConfig) {
|
|
1331
|
+
const colDef = generateColumnDefinition(column, provider, sqlConfig);
|
|
1332
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${colDef};`;
|
|
1333
|
+
}
|
|
1334
|
+
function generateDropColumn(tableName, columnName) {
|
|
1335
|
+
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
1336
|
+
}
|
|
1337
|
+
function generateAlterColumnType(tableName, columnName, newType) {
|
|
1338
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} TYPE ${newType} USING ${columnName}::${newType};`;
|
|
1339
|
+
}
|
|
1340
|
+
function generateAddUniqueConstraint(tableName, columnName) {
|
|
1341
|
+
const deterministicConstraintName = uqName(tableName, columnName);
|
|
1342
|
+
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${deterministicConstraintName} UNIQUE (${columnName});`;
|
|
1343
|
+
}
|
|
1344
|
+
function generateDropUniqueConstraint(tableName, columnName) {
|
|
1345
|
+
const deterministicConstraintName = uqName(tableName, columnName);
|
|
1346
|
+
const fallbackConstraintName = legacyUqName(tableName, columnName);
|
|
1347
|
+
return generateDropConstraintStatements(tableName, [deterministicConstraintName, fallbackConstraintName]);
|
|
1348
|
+
}
|
|
1349
|
+
function generateDropPrimaryKeyConstraint(tableName) {
|
|
1350
|
+
const deterministicConstraintName = pkName(tableName);
|
|
1351
|
+
const fallbackConstraintName = legacyPkName(tableName);
|
|
1352
|
+
return generateDropConstraintStatements(tableName, [deterministicConstraintName, fallbackConstraintName]);
|
|
1353
|
+
}
|
|
1354
|
+
function generateAddPrimaryKeyConstraint(tableName, columnName) {
|
|
1355
|
+
const deterministicConstraintName = pkName(tableName);
|
|
1356
|
+
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${deterministicConstraintName} PRIMARY KEY (${columnName});`;
|
|
1357
|
+
}
|
|
1358
|
+
function generateDropConstraintStatements(tableName, constraintNames) {
|
|
1359
|
+
const uniqueConstraintNames = Array.from(new Set(constraintNames));
|
|
1360
|
+
return uniqueConstraintNames.map((constraintName) => `ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`).join("\n");
|
|
1361
|
+
}
|
|
1362
|
+
function generateAlterColumnDefault(tableName, columnName, newDefault) {
|
|
1363
|
+
if (newDefault === null) {
|
|
1364
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP DEFAULT;`;
|
|
1365
|
+
}
|
|
1366
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET DEFAULT ${newDefault};`;
|
|
1367
|
+
}
|
|
1368
|
+
function generateAlterColumnNullability(tableName, columnName, toNullable) {
|
|
1369
|
+
if (toNullable) {
|
|
1370
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP NOT NULL;`;
|
|
1371
|
+
}
|
|
1372
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET NOT NULL;`;
|
|
1373
|
+
}
|
|
1374
|
+
var init_sql_generator = __esm({
|
|
1375
|
+
"node_modules/@xubylele/schema-forge-core/dist/generator/sql-generator.js"() {
|
|
1376
|
+
"use strict";
|
|
1377
|
+
init_normalize();
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/split-statements.js
|
|
1382
|
+
function splitSqlStatements(sql) {
|
|
1383
|
+
const statements = [];
|
|
1384
|
+
let current = "";
|
|
1385
|
+
let inSingleQuote = false;
|
|
1386
|
+
let inDoubleQuote = false;
|
|
1387
|
+
let inLineComment = false;
|
|
1388
|
+
let inBlockComment = false;
|
|
1389
|
+
let dollarTag = null;
|
|
1390
|
+
let index = 0;
|
|
1391
|
+
while (index < sql.length) {
|
|
1392
|
+
const char = sql[index];
|
|
1393
|
+
const next = index + 1 < sql.length ? sql[index + 1] : "";
|
|
1394
|
+
if (inLineComment) {
|
|
1395
|
+
current += char;
|
|
1396
|
+
if (char === "\n") {
|
|
1397
|
+
inLineComment = false;
|
|
1398
|
+
}
|
|
1399
|
+
index++;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (inBlockComment) {
|
|
1403
|
+
current += char;
|
|
1404
|
+
if (char === "*" && next === "/") {
|
|
1405
|
+
current += next;
|
|
1406
|
+
inBlockComment = false;
|
|
1407
|
+
index += 2;
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
index++;
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (!inSingleQuote && !inDoubleQuote && dollarTag === null) {
|
|
1414
|
+
if (char === "-" && next === "-") {
|
|
1415
|
+
current += char + next;
|
|
1416
|
+
inLineComment = true;
|
|
1417
|
+
index += 2;
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (char === "/" && next === "*") {
|
|
1421
|
+
current += char + next;
|
|
1422
|
+
inBlockComment = true;
|
|
1423
|
+
index += 2;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (!inDoubleQuote && dollarTag === null && char === "'") {
|
|
1428
|
+
current += char;
|
|
1429
|
+
if (inSingleQuote && next === "'") {
|
|
1430
|
+
current += next;
|
|
1431
|
+
index += 2;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
inSingleQuote = !inSingleQuote;
|
|
1435
|
+
index++;
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (!inSingleQuote && dollarTag === null && char === '"') {
|
|
1439
|
+
current += char;
|
|
1440
|
+
if (inDoubleQuote && next === '"') {
|
|
1441
|
+
current += next;
|
|
1442
|
+
index += 2;
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1446
|
+
index++;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
1450
|
+
if (dollarTag === null && char === "$") {
|
|
1451
|
+
const remainder = sql.slice(index);
|
|
1452
|
+
const match = remainder.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*\$|^\$\$/);
|
|
1453
|
+
if (match) {
|
|
1454
|
+
dollarTag = match[0];
|
|
1455
|
+
current += match[0];
|
|
1456
|
+
index += match[0].length;
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (dollarTag !== null && sql.startsWith(dollarTag, index)) {
|
|
1461
|
+
current += dollarTag;
|
|
1462
|
+
index += dollarTag.length;
|
|
1463
|
+
dollarTag = null;
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
if (!inSingleQuote && !inDoubleQuote && dollarTag === null && char === ";") {
|
|
1468
|
+
if (current.trim().length > 0) {
|
|
1469
|
+
statements.push(current.trim());
|
|
1470
|
+
}
|
|
1471
|
+
current = "";
|
|
1472
|
+
index++;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
current += char;
|
|
1476
|
+
index++;
|
|
1477
|
+
}
|
|
1478
|
+
if (current.trim().length > 0) {
|
|
1479
|
+
statements.push(current.trim());
|
|
1480
|
+
}
|
|
1481
|
+
return statements;
|
|
1482
|
+
}
|
|
1483
|
+
var init_split_statements = __esm({
|
|
1484
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/split-statements.js"() {
|
|
1485
|
+
"use strict";
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/parse-migration.js
|
|
1490
|
+
function normalizeSqlType(type) {
|
|
1491
|
+
return type.trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*,\s*/g, ",").replace(/\s*\)\s*/g, ")");
|
|
1492
|
+
}
|
|
1493
|
+
function unquoteIdentifier(value) {
|
|
1494
|
+
const trimmed = value.trim();
|
|
1495
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
1496
|
+
return trimmed.slice(1, -1).replace(/""/g, '"');
|
|
1497
|
+
}
|
|
1498
|
+
return trimmed;
|
|
1499
|
+
}
|
|
1500
|
+
function normalizeIdentifier(identifier) {
|
|
1501
|
+
const parts = identifier.trim().split(".").map((part) => unquoteIdentifier(part)).filter((part) => part.length > 0);
|
|
1502
|
+
const leaf = parts.length > 0 ? parts[parts.length - 1] : identifier.trim();
|
|
1503
|
+
return leaf.toLowerCase();
|
|
1504
|
+
}
|
|
1505
|
+
function removeSqlComments(statement) {
|
|
1506
|
+
let result = "";
|
|
1507
|
+
let inSingleQuote = false;
|
|
1508
|
+
let inDoubleQuote = false;
|
|
1509
|
+
let inLineComment = false;
|
|
1510
|
+
let inBlockComment = false;
|
|
1511
|
+
for (let index = 0; index < statement.length; index++) {
|
|
1512
|
+
const char = statement[index];
|
|
1513
|
+
const next = index + 1 < statement.length ? statement[index + 1] : "";
|
|
1514
|
+
if (inLineComment) {
|
|
1515
|
+
if (char === "\n") {
|
|
1516
|
+
inLineComment = false;
|
|
1517
|
+
result += char;
|
|
1518
|
+
}
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (inBlockComment) {
|
|
1522
|
+
if (char === "*" && next === "/") {
|
|
1523
|
+
inBlockComment = false;
|
|
1524
|
+
index++;
|
|
1525
|
+
}
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
1529
|
+
if (char === "-" && next === "-") {
|
|
1530
|
+
inLineComment = true;
|
|
1531
|
+
index++;
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (char === "/" && next === "*") {
|
|
1535
|
+
inBlockComment = true;
|
|
1536
|
+
index++;
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (char === "'" && !inDoubleQuote) {
|
|
1541
|
+
if (inSingleQuote && next === "'") {
|
|
1542
|
+
result += "''";
|
|
1543
|
+
index++;
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
inSingleQuote = !inSingleQuote;
|
|
1547
|
+
result += char;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (char === '"' && !inSingleQuote) {
|
|
1551
|
+
if (inDoubleQuote && next === '"') {
|
|
1552
|
+
result += '""';
|
|
1553
|
+
index++;
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1557
|
+
result += char;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
result += char;
|
|
1561
|
+
}
|
|
1562
|
+
return result.trim();
|
|
1563
|
+
}
|
|
1564
|
+
function splitTopLevelComma(input) {
|
|
1565
|
+
const parts = [];
|
|
1566
|
+
let current = "";
|
|
1567
|
+
let depth = 0;
|
|
1568
|
+
let inSingleQuote = false;
|
|
1569
|
+
let inDoubleQuote = false;
|
|
1570
|
+
for (let index = 0; index < input.length; index++) {
|
|
1571
|
+
const char = input[index];
|
|
1572
|
+
const next = index + 1 < input.length ? input[index + 1] : "";
|
|
1573
|
+
if (char === "'" && !inDoubleQuote) {
|
|
1574
|
+
current += char;
|
|
1575
|
+
if (inSingleQuote && next === "'") {
|
|
1576
|
+
current += next;
|
|
1577
|
+
index++;
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
inSingleQuote = !inSingleQuote;
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
if (char === '"' && !inSingleQuote) {
|
|
1584
|
+
current += char;
|
|
1585
|
+
if (inDoubleQuote && next === '"') {
|
|
1586
|
+
current += next;
|
|
1587
|
+
index++;
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
1594
|
+
if (char === "(") {
|
|
1595
|
+
depth++;
|
|
1596
|
+
} else if (char === ")") {
|
|
1597
|
+
depth = Math.max(0, depth - 1);
|
|
1598
|
+
} else if (char === "," && depth === 0) {
|
|
1599
|
+
const segment = current.trim();
|
|
1600
|
+
if (segment.length > 0) {
|
|
1601
|
+
parts.push(segment);
|
|
1602
|
+
}
|
|
1603
|
+
current = "";
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
current += char;
|
|
1608
|
+
}
|
|
1609
|
+
const tail = current.trim();
|
|
1610
|
+
if (tail.length > 0) {
|
|
1611
|
+
parts.push(tail);
|
|
1612
|
+
}
|
|
1613
|
+
return parts;
|
|
1614
|
+
}
|
|
1615
|
+
function tokenize(segment) {
|
|
1616
|
+
const tokens = [];
|
|
1617
|
+
let current = "";
|
|
1618
|
+
let depth = 0;
|
|
1619
|
+
let inSingleQuote = false;
|
|
1620
|
+
let inDoubleQuote = false;
|
|
1621
|
+
for (let index = 0; index < segment.length; index++) {
|
|
1622
|
+
const char = segment[index];
|
|
1623
|
+
const next = index + 1 < segment.length ? segment[index + 1] : "";
|
|
1624
|
+
if (char === "'" && !inDoubleQuote) {
|
|
1625
|
+
current += char;
|
|
1626
|
+
if (inSingleQuote && next === "'") {
|
|
1627
|
+
current += next;
|
|
1628
|
+
index++;
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
inSingleQuote = !inSingleQuote;
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
if (char === '"' && !inSingleQuote) {
|
|
1635
|
+
current += char;
|
|
1636
|
+
if (inDoubleQuote && next === '"') {
|
|
1637
|
+
current += next;
|
|
1638
|
+
index++;
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
1645
|
+
if (char === "(") {
|
|
1646
|
+
depth++;
|
|
1647
|
+
} else if (char === ")") {
|
|
1648
|
+
depth = Math.max(0, depth - 1);
|
|
1649
|
+
}
|
|
1650
|
+
if (/\s/.test(char) && depth === 0) {
|
|
1651
|
+
if (current.length > 0) {
|
|
1652
|
+
tokens.push(current);
|
|
1653
|
+
current = "";
|
|
1654
|
+
}
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
current += char;
|
|
1659
|
+
}
|
|
1660
|
+
if (current.length > 0) {
|
|
1661
|
+
tokens.push(current);
|
|
1662
|
+
}
|
|
1663
|
+
return tokens;
|
|
1664
|
+
}
|
|
1665
|
+
function parseColumnDefinition(segment) {
|
|
1666
|
+
const tokens = tokenize(segment);
|
|
1667
|
+
if (tokens.length < 2) {
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
const name = normalizeIdentifier(tokens[0]);
|
|
1671
|
+
let cursor = 1;
|
|
1672
|
+
const typeTokens = [];
|
|
1673
|
+
while (cursor < tokens.length) {
|
|
1674
|
+
const lower = tokens[cursor].toLowerCase();
|
|
1675
|
+
if (COLUMN_CONSTRAINT_KEYWORDS.has(lower)) {
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
typeTokens.push(tokens[cursor]);
|
|
1679
|
+
cursor++;
|
|
1680
|
+
}
|
|
1681
|
+
if (typeTokens.length === 0) {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
const parsed = {
|
|
1685
|
+
name,
|
|
1686
|
+
type: normalizeSqlType(typeTokens.join(" ")),
|
|
1687
|
+
nullable: true
|
|
1688
|
+
};
|
|
1689
|
+
while (cursor < tokens.length) {
|
|
1690
|
+
const lower = tokens[cursor].toLowerCase();
|
|
1691
|
+
if (lower === "primary" && tokens[cursor + 1]?.toLowerCase() === "key") {
|
|
1692
|
+
parsed.primaryKey = true;
|
|
1693
|
+
parsed.nullable = false;
|
|
1694
|
+
cursor += 2;
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
if (lower === "unique") {
|
|
1698
|
+
parsed.unique = true;
|
|
1699
|
+
cursor++;
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
if (lower === "not" && tokens[cursor + 1]?.toLowerCase() === "null") {
|
|
1703
|
+
parsed.nullable = false;
|
|
1704
|
+
cursor += 2;
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
if (lower === "null") {
|
|
1708
|
+
parsed.nullable = true;
|
|
1709
|
+
cursor++;
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
if (lower === "default") {
|
|
1713
|
+
cursor++;
|
|
1714
|
+
const defaultTokens = [];
|
|
1715
|
+
while (cursor < tokens.length) {
|
|
1716
|
+
const probe = tokens[cursor].toLowerCase();
|
|
1717
|
+
if (probe === "constraint" || probe === "references" || probe === "check" || probe === "not" && tokens[cursor + 1]?.toLowerCase() === "null" || probe === "null" || probe === "unique" || probe === "primary" && tokens[cursor + 1]?.toLowerCase() === "key") {
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
defaultTokens.push(tokens[cursor]);
|
|
1721
|
+
cursor++;
|
|
1722
|
+
}
|
|
1723
|
+
parsed.default = normalizeDefault(defaultTokens.join(" "));
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
cursor++;
|
|
1727
|
+
}
|
|
1728
|
+
return parsed;
|
|
1729
|
+
}
|
|
1730
|
+
function parseCreateTableConstraint(segment) {
|
|
1731
|
+
const normalized = segment.trim().replace(/\s+/g, " ");
|
|
1732
|
+
const constraintMatch = normalized.match(/^constraint\s+([^\s]+)\s+(primary\s+key|unique)\s*\((.+)\)$/i);
|
|
1733
|
+
if (constraintMatch) {
|
|
1734
|
+
const [, rawName, kind, rawColumns] = constraintMatch;
|
|
1735
|
+
const columns = splitTopLevelComma(rawColumns).map((item) => normalizeIdentifier(item));
|
|
1736
|
+
if (kind.toLowerCase().includes("primary")) {
|
|
1737
|
+
return { type: "PRIMARY_KEY", name: normalizeIdentifier(rawName), columns };
|
|
1738
|
+
}
|
|
1739
|
+
return { type: "UNIQUE", name: normalizeIdentifier(rawName), columns };
|
|
1740
|
+
}
|
|
1741
|
+
const barePk = normalized.match(/^primary\s+key\s*\((.+)\)$/i);
|
|
1742
|
+
if (barePk) {
|
|
1743
|
+
const columns = splitTopLevelComma(barePk[1]).map((item) => normalizeIdentifier(item));
|
|
1744
|
+
return { type: "PRIMARY_KEY", columns };
|
|
1745
|
+
}
|
|
1746
|
+
const bareUnique = normalized.match(/^unique\s*\((.+)\)$/i);
|
|
1747
|
+
if (bareUnique) {
|
|
1748
|
+
const columns = splitTopLevelComma(bareUnique[1]).map((item) => normalizeIdentifier(item));
|
|
1749
|
+
return { type: "UNIQUE", columns };
|
|
1750
|
+
}
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
function parseAlterTablePrefix(stmt) {
|
|
1754
|
+
const match = stmt.match(/^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?(.+)$/i);
|
|
1755
|
+
if (!match) {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const remainder = match[1].trim();
|
|
1759
|
+
const tokens = tokenize(remainder);
|
|
1760
|
+
if (tokens.length < 2) {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
const tableToken = tokens[0];
|
|
1764
|
+
const table = normalizeIdentifier(tableToken);
|
|
1765
|
+
const rest = remainder.slice(tableToken.length).trim();
|
|
1766
|
+
return { table, rest };
|
|
1767
|
+
}
|
|
1768
|
+
function parseCreateTable(stmt) {
|
|
1769
|
+
const match = stmt.match(/^create\s+table\s+(?:if\s+not\s+exists\s+)?(.+?)\s*\((.*)\)$/is);
|
|
1770
|
+
if (!match) {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
const table = normalizeIdentifier(match[1]);
|
|
1774
|
+
const body = match[2];
|
|
1775
|
+
const segments = splitTopLevelComma(body);
|
|
1776
|
+
const columns = [];
|
|
1777
|
+
const constraints = [];
|
|
1778
|
+
for (const segment of segments) {
|
|
1779
|
+
const constraint = parseCreateTableConstraint(segment);
|
|
1780
|
+
if (constraint) {
|
|
1781
|
+
constraints.push(constraint);
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
const column = parseColumnDefinition(segment);
|
|
1785
|
+
if (column) {
|
|
1786
|
+
columns.push(column);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
kind: "CREATE_TABLE",
|
|
1791
|
+
table,
|
|
1792
|
+
columns,
|
|
1793
|
+
constraints
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
function parseAlterTableAddColumn(stmt) {
|
|
1797
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1798
|
+
if (!prefix) {
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
const match = prefix.rest.match(/^add\s+column\s+(?:if\s+not\s+exists\s+)?(.+)$/i);
|
|
1802
|
+
if (!match) {
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
const column = parseColumnDefinition(match[1]);
|
|
1806
|
+
if (!column) {
|
|
1807
|
+
return null;
|
|
1808
|
+
}
|
|
1809
|
+
return { kind: "ADD_COLUMN", table: prefix.table, column };
|
|
1810
|
+
}
|
|
1811
|
+
function parseAlterColumnType(stmt) {
|
|
1812
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1813
|
+
if (!prefix) {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
const match = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+type\s+(.+)$/i);
|
|
1817
|
+
if (!match) {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
const column = normalizeIdentifier(match[1]);
|
|
1821
|
+
const toType = normalizeSqlType(match[2].replace(/\s+using\s+[\s\S]*$/i, "").trim());
|
|
1822
|
+
return {
|
|
1823
|
+
kind: "ALTER_COLUMN_TYPE",
|
|
1824
|
+
table: prefix.table,
|
|
1825
|
+
column,
|
|
1826
|
+
toType
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
function parseSetDropNotNull(stmt) {
|
|
1830
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1831
|
+
if (!prefix) {
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
const setMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+set\s+not\s+null$/i);
|
|
1835
|
+
if (setMatch) {
|
|
1836
|
+
return {
|
|
1837
|
+
kind: "SET_NOT_NULL",
|
|
1838
|
+
table: prefix.table,
|
|
1839
|
+
column: normalizeIdentifier(setMatch[1])
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
const dropMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+drop\s+not\s+null$/i);
|
|
1843
|
+
if (dropMatch) {
|
|
1844
|
+
return {
|
|
1845
|
+
kind: "DROP_NOT_NULL",
|
|
1846
|
+
table: prefix.table,
|
|
1847
|
+
column: normalizeIdentifier(dropMatch[1])
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
function parseSetDropDefault(stmt) {
|
|
1853
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1854
|
+
if (!prefix) {
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
const setMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+set\s+default\s+(.+)$/i);
|
|
1858
|
+
if (setMatch) {
|
|
1859
|
+
return {
|
|
1860
|
+
kind: "SET_DEFAULT",
|
|
1861
|
+
table: prefix.table,
|
|
1862
|
+
column: normalizeIdentifier(setMatch[1]),
|
|
1863
|
+
expr: normalizeDefault(setMatch[2].trim()) ?? setMatch[2].trim()
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
const dropMatch = prefix.rest.match(/^alter\s+column\s+([^\s]+)\s+drop\s+default$/i);
|
|
1867
|
+
if (dropMatch) {
|
|
1868
|
+
return {
|
|
1869
|
+
kind: "DROP_DEFAULT",
|
|
1870
|
+
table: prefix.table,
|
|
1871
|
+
column: normalizeIdentifier(dropMatch[1])
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
function parseAddDropConstraint(stmt) {
|
|
1877
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1878
|
+
if (!prefix) {
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
const addMatch = prefix.rest.match(/^add\s+constraint\s+([^\s]+)\s+(primary\s+key|unique)\s*\((.+)\)$/i);
|
|
1882
|
+
if (addMatch) {
|
|
1883
|
+
const [, rawName, kind, rawColumns] = addMatch;
|
|
1884
|
+
const columns = splitTopLevelComma(rawColumns).map((item) => normalizeIdentifier(item));
|
|
1885
|
+
const constraint = kind.toLowerCase().includes("primary") ? { type: "PRIMARY_KEY", name: normalizeIdentifier(rawName), columns } : { type: "UNIQUE", name: normalizeIdentifier(rawName), columns };
|
|
1886
|
+
return {
|
|
1887
|
+
kind: "ADD_CONSTRAINT",
|
|
1888
|
+
table: prefix.table,
|
|
1889
|
+
constraint
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
const dropMatch = prefix.rest.match(/^drop\s+constraint\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
|
|
1893
|
+
if (dropMatch) {
|
|
1894
|
+
return {
|
|
1895
|
+
kind: "DROP_CONSTRAINT",
|
|
1896
|
+
table: prefix.table,
|
|
1897
|
+
name: normalizeIdentifier(dropMatch[1])
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
function parseDropColumn(stmt) {
|
|
1903
|
+
const prefix = parseAlterTablePrefix(stmt);
|
|
1904
|
+
if (!prefix) {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
const match = prefix.rest.match(/^drop\s+column\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
|
|
1908
|
+
if (!match) {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
return {
|
|
1912
|
+
kind: "DROP_COLUMN",
|
|
1913
|
+
table: prefix.table,
|
|
1914
|
+
column: normalizeIdentifier(match[1])
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
function parseDropTable(stmt) {
|
|
1918
|
+
const match = stmt.match(/^drop\s+table\s+(?:if\s+exists\s+)?([^\s]+)(?:\s+cascade)?$/i);
|
|
1919
|
+
if (!match) {
|
|
1920
|
+
return null;
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
kind: "DROP_TABLE",
|
|
1924
|
+
table: normalizeIdentifier(match[1])
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function parseMigrationSql(sql) {
|
|
1928
|
+
const statements = splitSqlStatements(sql);
|
|
1929
|
+
const ops = [];
|
|
1930
|
+
const warnings = [];
|
|
1931
|
+
for (const raw of statements) {
|
|
1932
|
+
const stmt = removeSqlComments(raw).trim();
|
|
1933
|
+
if (!stmt) {
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
let parsed = null;
|
|
1937
|
+
for (const parseFn of PARSERS) {
|
|
1938
|
+
parsed = parseFn(stmt);
|
|
1939
|
+
if (parsed) {
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (parsed) {
|
|
1944
|
+
ops.push(parsed);
|
|
1945
|
+
} else {
|
|
1946
|
+
warnings.push({
|
|
1947
|
+
statement: stmt,
|
|
1948
|
+
reason: "Unsupported or unrecognized statement"
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
return { ops, warnings };
|
|
1953
|
+
}
|
|
1954
|
+
var COLUMN_CONSTRAINT_KEYWORDS, PARSERS;
|
|
1955
|
+
var init_parse_migration = __esm({
|
|
1956
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/parse-migration.js"() {
|
|
1957
|
+
"use strict";
|
|
1958
|
+
init_normalize();
|
|
1959
|
+
init_split_statements();
|
|
1960
|
+
COLUMN_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1961
|
+
"primary",
|
|
1962
|
+
"unique",
|
|
1963
|
+
"not",
|
|
1964
|
+
"null",
|
|
1965
|
+
"default",
|
|
1966
|
+
"constraint",
|
|
1967
|
+
"references",
|
|
1968
|
+
"check"
|
|
1969
|
+
]);
|
|
1970
|
+
PARSERS = [
|
|
1971
|
+
parseCreateTable,
|
|
1972
|
+
parseAlterTableAddColumn,
|
|
1973
|
+
parseAlterColumnType,
|
|
1974
|
+
parseSetDropNotNull,
|
|
1975
|
+
parseSetDropDefault,
|
|
1976
|
+
parseAddDropConstraint,
|
|
1977
|
+
parseDropColumn,
|
|
1978
|
+
parseDropTable
|
|
1979
|
+
];
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/apply-ops.js
|
|
1984
|
+
function toSchemaColumn(column) {
|
|
1985
|
+
return {
|
|
1986
|
+
name: column.name,
|
|
1987
|
+
type: column.type,
|
|
1988
|
+
nullable: column.nullable,
|
|
1989
|
+
...column.default !== void 0 ? { default: column.default } : {},
|
|
1990
|
+
...column.unique !== void 0 ? { unique: column.unique } : {},
|
|
1991
|
+
...column.primaryKey !== void 0 ? { primaryKey: column.primaryKey } : {}
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
function applySingleColumnConstraint(table, constraint) {
|
|
1995
|
+
if (constraint.columns.length !== 1) {
|
|
1996
|
+
return false;
|
|
1997
|
+
}
|
|
1998
|
+
const targetColumn = table.columns.find((column) => column.name === constraint.columns[0]);
|
|
1999
|
+
if (!targetColumn) {
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
if (constraint.type === "PRIMARY_KEY") {
|
|
2003
|
+
table.primaryKey = targetColumn.name;
|
|
2004
|
+
targetColumn.primaryKey = true;
|
|
2005
|
+
targetColumn.nullable = false;
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
targetColumn.unique = true;
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
2011
|
+
function clearConstraintByName(table, name) {
|
|
2012
|
+
if (name.endsWith("_pkey") || name.startsWith("pk_")) {
|
|
2013
|
+
if (table.primaryKey) {
|
|
2014
|
+
const pkColumn = table.columns.find((column) => column.name === table.primaryKey);
|
|
2015
|
+
if (pkColumn) {
|
|
2016
|
+
pkColumn.primaryKey = false;
|
|
2017
|
+
}
|
|
2018
|
+
table.primaryKey = null;
|
|
2019
|
+
}
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
if (name.endsWith("_key") || name.startsWith("uq_")) {
|
|
2023
|
+
for (const column of table.columns) {
|
|
2024
|
+
if (column.unique) {
|
|
2025
|
+
column.unique = false;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
function getOrCreateTable(tables, name) {
|
|
2031
|
+
if (!tables[name]) {
|
|
2032
|
+
tables[name] = { name, columns: [] };
|
|
2033
|
+
}
|
|
2034
|
+
return tables[name];
|
|
2035
|
+
}
|
|
2036
|
+
function applySqlOps(ops) {
|
|
2037
|
+
const tables = {};
|
|
2038
|
+
const warnings = [];
|
|
2039
|
+
for (const op of ops) {
|
|
2040
|
+
switch (op.kind) {
|
|
2041
|
+
case "CREATE_TABLE": {
|
|
2042
|
+
const table = {
|
|
2043
|
+
name: op.table,
|
|
2044
|
+
columns: op.columns.map(toSchemaColumn)
|
|
2045
|
+
};
|
|
2046
|
+
for (const column of table.columns) {
|
|
2047
|
+
if (column.primaryKey) {
|
|
2048
|
+
table.primaryKey = column.name;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
for (const constraint of op.constraints) {
|
|
2052
|
+
const applied = applySingleColumnConstraint(table, constraint);
|
|
2053
|
+
if (!applied) {
|
|
2054
|
+
warnings.push({
|
|
2055
|
+
statement: `CREATE TABLE ${op.table}`,
|
|
2056
|
+
reason: `Constraint ${constraint.type}${constraint.name ? ` (${constraint.name})` : ""} is unsupported for schema reconstruction`
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
tables[op.table] = table;
|
|
2061
|
+
break;
|
|
2062
|
+
}
|
|
2063
|
+
case "ADD_COLUMN": {
|
|
2064
|
+
const table = getOrCreateTable(tables, op.table);
|
|
2065
|
+
table.columns = table.columns.filter((column) => column.name !== op.column.name);
|
|
2066
|
+
table.columns.push(toSchemaColumn(op.column));
|
|
2067
|
+
if (op.column.primaryKey) {
|
|
2068
|
+
table.primaryKey = op.column.name;
|
|
2069
|
+
}
|
|
2070
|
+
break;
|
|
2071
|
+
}
|
|
2072
|
+
case "ALTER_COLUMN_TYPE": {
|
|
2073
|
+
const table = tables[op.table];
|
|
2074
|
+
if (!table) {
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
const column = table.columns.find((item) => item.name === op.column);
|
|
2078
|
+
if (column) {
|
|
2079
|
+
column.type = op.toType;
|
|
2080
|
+
}
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
case "SET_NOT_NULL": {
|
|
2084
|
+
const table = tables[op.table];
|
|
2085
|
+
const column = table?.columns.find((item) => item.name === op.column);
|
|
2086
|
+
if (column) {
|
|
2087
|
+
column.nullable = false;
|
|
2088
|
+
}
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
case "DROP_NOT_NULL": {
|
|
2092
|
+
const table = tables[op.table];
|
|
2093
|
+
const column = table?.columns.find((item) => item.name === op.column);
|
|
2094
|
+
if (column) {
|
|
2095
|
+
column.nullable = true;
|
|
2096
|
+
}
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
case "SET_DEFAULT": {
|
|
2100
|
+
const table = tables[op.table];
|
|
2101
|
+
const column = table?.columns.find((item) => item.name === op.column);
|
|
2102
|
+
if (column) {
|
|
2103
|
+
column.default = op.expr;
|
|
2104
|
+
}
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
case "DROP_DEFAULT": {
|
|
2108
|
+
const table = tables[op.table];
|
|
2109
|
+
const column = table?.columns.find((item) => item.name === op.column);
|
|
2110
|
+
if (column) {
|
|
2111
|
+
column.default = null;
|
|
2112
|
+
}
|
|
2113
|
+
break;
|
|
2114
|
+
}
|
|
2115
|
+
case "ADD_CONSTRAINT": {
|
|
2116
|
+
const table = tables[op.table];
|
|
2117
|
+
if (!table) {
|
|
2118
|
+
break;
|
|
2119
|
+
}
|
|
2120
|
+
const applied = applySingleColumnConstraint(table, op.constraint);
|
|
2121
|
+
if (!applied) {
|
|
2122
|
+
warnings.push({
|
|
2123
|
+
statement: `ALTER TABLE ${op.table} ADD CONSTRAINT ${op.constraint.name ?? "<unnamed>"}`,
|
|
2124
|
+
reason: `Constraint ${op.constraint.type} is unsupported for schema reconstruction`
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
2129
|
+
case "DROP_CONSTRAINT": {
|
|
2130
|
+
const table = tables[op.table];
|
|
2131
|
+
if (!table) {
|
|
2132
|
+
break;
|
|
2133
|
+
}
|
|
2134
|
+
clearConstraintByName(table, op.name);
|
|
2135
|
+
break;
|
|
2136
|
+
}
|
|
2137
|
+
case "DROP_COLUMN": {
|
|
2138
|
+
const table = tables[op.table];
|
|
2139
|
+
if (!table) {
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
table.columns = table.columns.filter((column) => column.name !== op.column);
|
|
2143
|
+
if (table.primaryKey === op.column) {
|
|
2144
|
+
table.primaryKey = null;
|
|
2145
|
+
}
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
case "DROP_TABLE": {
|
|
2149
|
+
delete tables[op.table];
|
|
2150
|
+
break;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
const schema = { tables };
|
|
2155
|
+
return { schema, warnings };
|
|
2156
|
+
}
|
|
2157
|
+
var init_apply_ops = __esm({
|
|
2158
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/apply-ops.js"() {
|
|
2159
|
+
"use strict";
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/schema-to-dsl.js
|
|
2164
|
+
function renderColumn(column) {
|
|
2165
|
+
const parts = [column.name, column.type];
|
|
2166
|
+
if (column.primaryKey) {
|
|
2167
|
+
parts.push("pk");
|
|
2168
|
+
}
|
|
2169
|
+
if (column.unique) {
|
|
2170
|
+
parts.push("unique");
|
|
2171
|
+
}
|
|
2172
|
+
if (column.nullable === false && !column.primaryKey) {
|
|
2173
|
+
parts.push("not null");
|
|
2174
|
+
}
|
|
2175
|
+
if (column.default !== void 0 && column.default !== null) {
|
|
2176
|
+
parts.push(`default ${column.default}`);
|
|
2177
|
+
}
|
|
2178
|
+
return ` ${parts.join(" ")}`;
|
|
2179
|
+
}
|
|
2180
|
+
function schemaToDsl(schema) {
|
|
2181
|
+
const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
|
|
2182
|
+
const blocks = tableNames.map((tableName) => {
|
|
2183
|
+
const table = schema.tables[tableName];
|
|
2184
|
+
const lines = [`table ${table.name} {`];
|
|
2185
|
+
for (const column of table.columns) {
|
|
2186
|
+
lines.push(renderColumn(column));
|
|
2187
|
+
}
|
|
2188
|
+
lines.push("}");
|
|
2189
|
+
return lines.join("\n");
|
|
2190
|
+
});
|
|
2191
|
+
if (blocks.length === 0) {
|
|
2192
|
+
return "# SchemaForge schema definition\n";
|
|
2193
|
+
}
|
|
2194
|
+
return `# SchemaForge schema definition
|
|
2195
|
+
|
|
2196
|
+
${blocks.join("\n\n")}
|
|
2197
|
+
`;
|
|
2198
|
+
}
|
|
2199
|
+
var init_schema_to_dsl = __esm({
|
|
2200
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/schema-to-dsl.js"() {
|
|
2201
|
+
"use strict";
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/load-migrations.js
|
|
2206
|
+
async function loadMigrationSqlInput(inputPath) {
|
|
2207
|
+
const stats = await import_fs4.promises.stat(inputPath);
|
|
2208
|
+
if (stats.isFile()) {
|
|
2209
|
+
if (!inputPath.toLowerCase().endsWith(".sql")) {
|
|
2210
|
+
throw new Error(`Input file must be a .sql file: ${inputPath}`);
|
|
2211
|
+
}
|
|
2212
|
+
return [{ filePath: inputPath, sql: await readTextFile2(inputPath) }];
|
|
2213
|
+
}
|
|
2214
|
+
if (!stats.isDirectory()) {
|
|
2215
|
+
throw new Error(`Input path must be a .sql file or directory: ${inputPath}`);
|
|
2216
|
+
}
|
|
2217
|
+
const sqlFiles = await findFiles(inputPath, /\.sql$/i);
|
|
2218
|
+
sqlFiles.sort((left, right) => import_path5.default.basename(left).localeCompare(import_path5.default.basename(right)));
|
|
2219
|
+
const result = [];
|
|
2220
|
+
for (const filePath of sqlFiles) {
|
|
2221
|
+
result.push({
|
|
2222
|
+
filePath,
|
|
2223
|
+
sql: await readTextFile2(filePath)
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
return result;
|
|
2227
|
+
}
|
|
2228
|
+
var import_fs4, import_path5;
|
|
2229
|
+
var init_load_migrations = __esm({
|
|
2230
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/load-migrations.js"() {
|
|
2231
|
+
"use strict";
|
|
2232
|
+
import_fs4 = require("fs");
|
|
2233
|
+
import_path5 = __toESM(require("path"), 1);
|
|
2234
|
+
init_fs();
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/sql/introspect-postgres.js
|
|
2239
|
+
function toTableKey(schema, table) {
|
|
2240
|
+
if (schema === DEFAULT_SCHEMA) {
|
|
2241
|
+
return table;
|
|
2242
|
+
}
|
|
2243
|
+
return `${schema}.${table}`;
|
|
2244
|
+
}
|
|
2245
|
+
function normalizeSchemas(schemas) {
|
|
2246
|
+
const values = schemas ?? [DEFAULT_SCHEMA];
|
|
2247
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
2248
|
+
for (const schema of values) {
|
|
2249
|
+
const trimmed = schema.trim();
|
|
2250
|
+
if (trimmed.length > 0) {
|
|
2251
|
+
deduped.add(trimmed);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
if (deduped.size === 0) {
|
|
2255
|
+
deduped.add(DEFAULT_SCHEMA);
|
|
2256
|
+
}
|
|
2257
|
+
return Array.from(deduped).sort((a, b) => a.localeCompare(b));
|
|
2258
|
+
}
|
|
2259
|
+
function normalizeColumnType5(row) {
|
|
2260
|
+
const dataType = row.data_type.toLowerCase();
|
|
2261
|
+
const udtName = row.udt_name.toLowerCase();
|
|
2262
|
+
if (dataType === "character varying") {
|
|
2263
|
+
if (row.character_maximum_length !== null) {
|
|
2264
|
+
return `varchar(${row.character_maximum_length})`;
|
|
2265
|
+
}
|
|
2266
|
+
return "varchar";
|
|
2267
|
+
}
|
|
2268
|
+
if (dataType === "timestamp with time zone") {
|
|
2269
|
+
return "timestamptz";
|
|
2270
|
+
}
|
|
2271
|
+
if (dataType === "integer" || udtName === "int4") {
|
|
2272
|
+
return "int";
|
|
2273
|
+
}
|
|
2274
|
+
if (dataType === "bigint" || udtName === "int8") {
|
|
2275
|
+
return "bigint";
|
|
2276
|
+
}
|
|
2277
|
+
if (dataType === "numeric") {
|
|
2278
|
+
if (row.numeric_precision !== null && row.numeric_scale !== null) {
|
|
2279
|
+
return `numeric(${row.numeric_precision},${row.numeric_scale})`;
|
|
2280
|
+
}
|
|
2281
|
+
return "numeric";
|
|
2282
|
+
}
|
|
2283
|
+
if (dataType === "boolean" || udtName === "bool") {
|
|
2284
|
+
return "boolean";
|
|
2285
|
+
}
|
|
2286
|
+
if (dataType === "uuid") {
|
|
2287
|
+
return "uuid";
|
|
2288
|
+
}
|
|
2289
|
+
if (dataType === "text") {
|
|
2290
|
+
return "text";
|
|
2291
|
+
}
|
|
2292
|
+
if (dataType === "date") {
|
|
2293
|
+
return "date";
|
|
2294
|
+
}
|
|
2295
|
+
return dataType;
|
|
2296
|
+
}
|
|
2297
|
+
function normalizeConstraints(constraintRows, foreignKeyRows) {
|
|
2298
|
+
const constraints = /* @__PURE__ */ new Map();
|
|
2299
|
+
for (const row of constraintRows) {
|
|
2300
|
+
const key = `${row.table_schema}.${row.table_name}.${row.constraint_name}.${row.constraint_type}`;
|
|
2301
|
+
const existing = constraints.get(key);
|
|
2302
|
+
if (existing) {
|
|
2303
|
+
if (row.column_name !== null) {
|
|
2304
|
+
existing.columns.push({
|
|
2305
|
+
name: row.column_name,
|
|
2306
|
+
position: row.ordinal_position ?? Number.MAX_SAFE_INTEGER
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
if (!existing.checkClause && row.check_clause) {
|
|
2310
|
+
existing.checkClause = row.check_clause;
|
|
2311
|
+
}
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
constraints.set(key, {
|
|
2315
|
+
tableSchema: row.table_schema,
|
|
2316
|
+
tableName: row.table_name,
|
|
2317
|
+
name: row.constraint_name,
|
|
2318
|
+
type: row.constraint_type,
|
|
2319
|
+
columns: row.column_name === null ? [] : [{
|
|
2320
|
+
name: row.column_name,
|
|
2321
|
+
position: row.ordinal_position ?? Number.MAX_SAFE_INTEGER
|
|
2322
|
+
}],
|
|
2323
|
+
...row.check_clause ? { checkClause: row.check_clause } : {}
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
const normalized = [];
|
|
2327
|
+
for (const value of constraints.values()) {
|
|
2328
|
+
value.columns.sort((left, right) => {
|
|
2329
|
+
if (left.position !== right.position) {
|
|
2330
|
+
return left.position - right.position;
|
|
2331
|
+
}
|
|
2332
|
+
return left.name.localeCompare(right.name);
|
|
2333
|
+
});
|
|
2334
|
+
normalized.push({
|
|
2335
|
+
tableSchema: value.tableSchema,
|
|
2336
|
+
tableName: value.tableName,
|
|
2337
|
+
name: value.name,
|
|
2338
|
+
type: value.type,
|
|
2339
|
+
columns: value.columns.map((item) => item.name),
|
|
2340
|
+
...value.checkClause ? { checkClause: value.checkClause } : {}
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
const foreignKeys = /* @__PURE__ */ new Map();
|
|
2344
|
+
for (const row of foreignKeyRows) {
|
|
2345
|
+
const key = `${row.table_schema}.${row.table_name}.${row.constraint_name}`;
|
|
2346
|
+
const existing = foreignKeys.get(key);
|
|
2347
|
+
if (existing) {
|
|
2348
|
+
existing.local.push({ name: row.local_column_name, position: row.position });
|
|
2349
|
+
existing.referenced.push({ name: row.referenced_column_name, position: row.position });
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
foreignKeys.set(key, {
|
|
2353
|
+
tableSchema: row.table_schema,
|
|
2354
|
+
tableName: row.table_name,
|
|
2355
|
+
name: row.constraint_name,
|
|
2356
|
+
local: [{ name: row.local_column_name, position: row.position }],
|
|
2357
|
+
referencedTableSchema: row.referenced_table_schema,
|
|
2358
|
+
referencedTableName: row.referenced_table_name,
|
|
2359
|
+
referenced: [{ name: row.referenced_column_name, position: row.position }]
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
for (const value of foreignKeys.values()) {
|
|
2363
|
+
value.local.sort((left, right) => left.position - right.position || left.name.localeCompare(right.name));
|
|
2364
|
+
value.referenced.sort((left, right) => left.position - right.position || left.name.localeCompare(right.name));
|
|
2365
|
+
normalized.push({
|
|
2366
|
+
tableSchema: value.tableSchema,
|
|
2367
|
+
tableName: value.tableName,
|
|
2368
|
+
name: value.name,
|
|
2369
|
+
type: "FOREIGN KEY",
|
|
2370
|
+
columns: value.local.map((item) => item.name),
|
|
2371
|
+
referencedTableSchema: value.referencedTableSchema,
|
|
2372
|
+
referencedTableName: value.referencedTableName,
|
|
2373
|
+
referencedColumns: value.referenced.map((item) => item.name)
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
normalized.sort((left, right) => {
|
|
2377
|
+
if (left.tableSchema !== right.tableSchema) {
|
|
2378
|
+
return left.tableSchema.localeCompare(right.tableSchema);
|
|
2379
|
+
}
|
|
2380
|
+
if (left.tableName !== right.tableName) {
|
|
2381
|
+
return left.tableName.localeCompare(right.tableName);
|
|
2382
|
+
}
|
|
2383
|
+
const typeOrderDiff = CONSTRAINT_TYPE_ORDER[left.type] - CONSTRAINT_TYPE_ORDER[right.type];
|
|
2384
|
+
if (typeOrderDiff !== 0) {
|
|
2385
|
+
return typeOrderDiff;
|
|
2386
|
+
}
|
|
2387
|
+
if (left.name !== right.name) {
|
|
2388
|
+
return left.name.localeCompare(right.name);
|
|
2389
|
+
}
|
|
2390
|
+
return left.columns.join(",").localeCompare(right.columns.join(","));
|
|
2391
|
+
});
|
|
2392
|
+
return normalized;
|
|
2393
|
+
}
|
|
2394
|
+
async function introspectPostgresSchema(options) {
|
|
2395
|
+
const schemaFilter = normalizeSchemas(options.schemas);
|
|
2396
|
+
const [tableRows, columnRows, constraintRows, foreignKeyRows] = await Promise.all([
|
|
2397
|
+
options.query(TABLES_QUERY, [schemaFilter]),
|
|
2398
|
+
options.query(COLUMNS_QUERY, [schemaFilter]),
|
|
2399
|
+
options.query(CONSTRAINTS_QUERY, [schemaFilter]),
|
|
2400
|
+
options.query(FOREIGN_KEYS_QUERY, [schemaFilter])
|
|
2401
|
+
]);
|
|
2402
|
+
const sortedTables = [...tableRows].sort((left, right) => {
|
|
2403
|
+
if (left.table_schema !== right.table_schema) {
|
|
2404
|
+
return left.table_schema.localeCompare(right.table_schema);
|
|
2405
|
+
}
|
|
2406
|
+
return left.table_name.localeCompare(right.table_name);
|
|
2407
|
+
});
|
|
2408
|
+
const sortedColumns = [...columnRows].sort((left, right) => {
|
|
2409
|
+
if (left.table_schema !== right.table_schema) {
|
|
2410
|
+
return left.table_schema.localeCompare(right.table_schema);
|
|
2411
|
+
}
|
|
2412
|
+
if (left.table_name !== right.table_name) {
|
|
2413
|
+
return left.table_name.localeCompare(right.table_name);
|
|
2414
|
+
}
|
|
2415
|
+
if (left.ordinal_position !== right.ordinal_position) {
|
|
2416
|
+
return left.ordinal_position - right.ordinal_position;
|
|
2417
|
+
}
|
|
2418
|
+
return left.column_name.localeCompare(right.column_name);
|
|
2419
|
+
});
|
|
2420
|
+
const normalizedConstraints = normalizeConstraints(constraintRows, foreignKeyRows);
|
|
2421
|
+
const tableMap = /* @__PURE__ */ new Map();
|
|
2422
|
+
const columnMap = /* @__PURE__ */ new Map();
|
|
2423
|
+
for (const row of sortedTables) {
|
|
2424
|
+
const key = toTableKey(row.table_schema, row.table_name);
|
|
2425
|
+
const table = { name: key, columns: [], primaryKey: null };
|
|
2426
|
+
tableMap.set(key, table);
|
|
2427
|
+
columnMap.set(key, /* @__PURE__ */ new Map());
|
|
2428
|
+
}
|
|
2429
|
+
for (const row of sortedColumns) {
|
|
2430
|
+
const key = toTableKey(row.table_schema, row.table_name);
|
|
2431
|
+
const table = tableMap.get(key);
|
|
2432
|
+
const columnsByName = columnMap.get(key);
|
|
2433
|
+
if (!table || !columnsByName) {
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
const column = {
|
|
2437
|
+
name: row.column_name,
|
|
2438
|
+
type: normalizeColumnType5(row),
|
|
2439
|
+
nullable: row.is_nullable === "YES"
|
|
2440
|
+
};
|
|
2441
|
+
const normalizedDefault = normalizeDefault(row.column_default);
|
|
2442
|
+
if (normalizedDefault !== null) {
|
|
2443
|
+
column.default = normalizedDefault;
|
|
2444
|
+
}
|
|
2445
|
+
table.columns.push(column);
|
|
2446
|
+
columnsByName.set(column.name, column);
|
|
2447
|
+
}
|
|
2448
|
+
for (const constraint of normalizedConstraints) {
|
|
2449
|
+
const tableKey = toTableKey(constraint.tableSchema, constraint.tableName);
|
|
2450
|
+
const table = tableMap.get(tableKey);
|
|
2451
|
+
const columnsByName = columnMap.get(tableKey);
|
|
2452
|
+
if (!table || !columnsByName) {
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
if (constraint.type === "PRIMARY KEY") {
|
|
2456
|
+
if (constraint.columns.length === 1) {
|
|
2457
|
+
const column = columnsByName.get(constraint.columns[0]);
|
|
2458
|
+
if (column) {
|
|
2459
|
+
column.primaryKey = true;
|
|
2460
|
+
column.nullable = false;
|
|
2461
|
+
table.primaryKey = column.name;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
2466
|
+
if (constraint.type === "UNIQUE") {
|
|
2467
|
+
if (constraint.columns.length === 1) {
|
|
2468
|
+
const column = columnsByName.get(constraint.columns[0]);
|
|
2469
|
+
if (column) {
|
|
2470
|
+
column.unique = true;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (constraint.type === "FOREIGN KEY") {
|
|
2476
|
+
if (constraint.columns.length === 1 && constraint.referencedColumns && constraint.referencedColumns.length === 1 && constraint.referencedTableName) {
|
|
2477
|
+
const column = columnsByName.get(constraint.columns[0]);
|
|
2478
|
+
if (column) {
|
|
2479
|
+
column.foreignKey = {
|
|
2480
|
+
table: toTableKey(constraint.referencedTableSchema ?? DEFAULT_SCHEMA, constraint.referencedTableName),
|
|
2481
|
+
column: constraint.referencedColumns[0]
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const orderedTableNames = Array.from(tableMap.keys()).sort((left, right) => left.localeCompare(right));
|
|
2488
|
+
const tables = {};
|
|
2489
|
+
for (const tableName of orderedTableNames) {
|
|
2490
|
+
const table = tableMap.get(tableName);
|
|
2491
|
+
if (table) {
|
|
2492
|
+
tables[tableName] = table;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
return { tables };
|
|
2496
|
+
}
|
|
2497
|
+
var DEFAULT_SCHEMA, CONSTRAINT_TYPE_ORDER, TABLES_QUERY, COLUMNS_QUERY, CONSTRAINTS_QUERY, FOREIGN_KEYS_QUERY;
|
|
2498
|
+
var init_introspect_postgres = __esm({
|
|
2499
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/sql/introspect-postgres.js"() {
|
|
2500
|
+
"use strict";
|
|
2501
|
+
init_normalize();
|
|
2502
|
+
DEFAULT_SCHEMA = "public";
|
|
2503
|
+
CONSTRAINT_TYPE_ORDER = {
|
|
2504
|
+
"PRIMARY KEY": 0,
|
|
2505
|
+
UNIQUE: 1,
|
|
2506
|
+
"FOREIGN KEY": 2,
|
|
2507
|
+
CHECK: 3
|
|
2508
|
+
};
|
|
2509
|
+
TABLES_QUERY = `
|
|
2510
|
+
SELECT
|
|
2511
|
+
table_schema,
|
|
2512
|
+
table_name
|
|
2513
|
+
FROM information_schema.tables
|
|
2514
|
+
WHERE table_type = 'BASE TABLE'
|
|
2515
|
+
AND table_schema = ANY($1::text[])
|
|
2516
|
+
`;
|
|
2517
|
+
COLUMNS_QUERY = `
|
|
2518
|
+
SELECT
|
|
2519
|
+
table_schema,
|
|
2520
|
+
table_name,
|
|
2521
|
+
column_name,
|
|
2522
|
+
ordinal_position,
|
|
2523
|
+
is_nullable,
|
|
2524
|
+
data_type,
|
|
2525
|
+
udt_name,
|
|
2526
|
+
character_maximum_length,
|
|
2527
|
+
numeric_precision,
|
|
2528
|
+
numeric_scale,
|
|
2529
|
+
column_default
|
|
2530
|
+
FROM information_schema.columns
|
|
2531
|
+
WHERE table_schema = ANY($1::text[])
|
|
2532
|
+
`;
|
|
2533
|
+
CONSTRAINTS_QUERY = `
|
|
2534
|
+
SELECT
|
|
2535
|
+
tc.table_schema,
|
|
2536
|
+
tc.table_name,
|
|
2537
|
+
tc.constraint_name,
|
|
2538
|
+
tc.constraint_type,
|
|
2539
|
+
kcu.column_name,
|
|
2540
|
+
kcu.ordinal_position,
|
|
2541
|
+
cc.check_clause
|
|
2542
|
+
FROM information_schema.table_constraints tc
|
|
2543
|
+
LEFT JOIN information_schema.key_column_usage kcu
|
|
2544
|
+
ON tc.constraint_catalog = kcu.constraint_catalog
|
|
2545
|
+
AND tc.constraint_schema = kcu.constraint_schema
|
|
2546
|
+
AND tc.constraint_name = kcu.constraint_name
|
|
2547
|
+
AND tc.table_schema = kcu.table_schema
|
|
2548
|
+
AND tc.table_name = kcu.table_name
|
|
2549
|
+
LEFT JOIN information_schema.check_constraints cc
|
|
2550
|
+
ON tc.constraint_catalog = cc.constraint_catalog
|
|
2551
|
+
AND tc.constraint_schema = cc.constraint_schema
|
|
2552
|
+
AND tc.constraint_name = cc.constraint_name
|
|
2553
|
+
WHERE tc.table_schema = ANY($1::text[])
|
|
2554
|
+
AND tc.constraint_type IN ('PRIMARY KEY', 'UNIQUE', 'CHECK')
|
|
2555
|
+
`;
|
|
2556
|
+
FOREIGN_KEYS_QUERY = `
|
|
2557
|
+
SELECT
|
|
2558
|
+
src_ns.nspname AS table_schema,
|
|
2559
|
+
src.relname AS table_name,
|
|
2560
|
+
con.conname AS constraint_name,
|
|
2561
|
+
src_attr.attname AS local_column_name,
|
|
2562
|
+
ref_ns.nspname AS referenced_table_schema,
|
|
2563
|
+
ref.relname AS referenced_table_name,
|
|
2564
|
+
ref_attr.attname AS referenced_column_name,
|
|
2565
|
+
src_key.ord AS position
|
|
2566
|
+
FROM pg_constraint con
|
|
2567
|
+
JOIN pg_class src
|
|
2568
|
+
ON src.oid = con.conrelid
|
|
2569
|
+
JOIN pg_namespace src_ns
|
|
2570
|
+
ON src_ns.oid = src.relnamespace
|
|
2571
|
+
JOIN pg_class ref
|
|
2572
|
+
ON ref.oid = con.confrelid
|
|
2573
|
+
JOIN pg_namespace ref_ns
|
|
2574
|
+
ON ref_ns.oid = ref.relnamespace
|
|
2575
|
+
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS src_key(attnum, ord)
|
|
2576
|
+
ON TRUE
|
|
2577
|
+
JOIN LATERAL unnest(con.confkey) WITH ORDINALITY AS ref_key(attnum, ord)
|
|
2578
|
+
ON ref_key.ord = src_key.ord
|
|
2579
|
+
JOIN pg_attribute src_attr
|
|
2580
|
+
ON src_attr.attrelid = con.conrelid
|
|
2581
|
+
AND src_attr.attnum = src_key.attnum
|
|
2582
|
+
JOIN pg_attribute ref_attr
|
|
2583
|
+
ON ref_attr.attrelid = con.confrelid
|
|
2584
|
+
AND ref_attr.attnum = ref_key.attnum
|
|
2585
|
+
WHERE con.contype = 'f'
|
|
2586
|
+
AND src_ns.nspname = ANY($1::text[])
|
|
2587
|
+
`;
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/paths.js
|
|
2592
|
+
function getProjectRoot2(cwd = process.cwd()) {
|
|
2593
|
+
return cwd;
|
|
2594
|
+
}
|
|
2595
|
+
function getSchemaForgeDir2(root) {
|
|
2596
|
+
return import_path6.default.join(root, "schemaforge");
|
|
2597
|
+
}
|
|
2598
|
+
function getSchemaFilePath2(root, config) {
|
|
2599
|
+
const schemaForgeDir = getSchemaForgeDir2(root);
|
|
2600
|
+
const fileName = config?.schemaFile || "schema.sf";
|
|
2601
|
+
return import_path6.default.join(schemaForgeDir, fileName);
|
|
2602
|
+
}
|
|
2603
|
+
function getConfigPath2(root) {
|
|
2604
|
+
const schemaForgeDir = getSchemaForgeDir2(root);
|
|
2605
|
+
return import_path6.default.join(schemaForgeDir, "config.json");
|
|
2606
|
+
}
|
|
2607
|
+
function getStatePath2(root, config) {
|
|
2608
|
+
const schemaForgeDir = getSchemaForgeDir2(root);
|
|
2609
|
+
const fileName = config?.stateFile || "state.json";
|
|
2610
|
+
return import_path6.default.join(schemaForgeDir, fileName);
|
|
2611
|
+
}
|
|
2612
|
+
var import_path6;
|
|
2613
|
+
var init_paths = __esm({
|
|
2614
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/paths.js"() {
|
|
2615
|
+
"use strict";
|
|
2616
|
+
import_path6 = __toESM(require("path"), 1);
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/utils.js
|
|
2621
|
+
function nowTimestamp() {
|
|
2622
|
+
const date = /* @__PURE__ */ new Date();
|
|
2623
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2624
|
+
return String(date.getFullYear()) + pad(date.getMonth() + 1) + pad(date.getDate()) + pad(date.getHours()) + pad(date.getMinutes()) + pad(date.getSeconds());
|
|
2625
|
+
}
|
|
2626
|
+
function slugifyName(name) {
|
|
2627
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "migration";
|
|
2628
|
+
}
|
|
2629
|
+
var init_utils = __esm({
|
|
2630
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/utils.js"() {
|
|
2631
|
+
"use strict";
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
|
|
2635
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/errors.js
|
|
2636
|
+
var SchemaValidationError;
|
|
2637
|
+
var init_errors = __esm({
|
|
2638
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/errors.js"() {
|
|
2639
|
+
"use strict";
|
|
2640
|
+
SchemaValidationError = class extends Error {
|
|
2641
|
+
constructor(message) {
|
|
2642
|
+
super(message);
|
|
2643
|
+
this.name = "SchemaValidationError";
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
|
|
2649
|
+
// node_modules/@xubylele/schema-forge-core/dist/index.js
|
|
2650
|
+
var dist_exports = {};
|
|
2651
|
+
__export(dist_exports, {
|
|
2652
|
+
SchemaValidationError: () => SchemaValidationError,
|
|
2653
|
+
analyzeSchemaDrift: () => analyzeSchemaDrift,
|
|
2654
|
+
applySqlOps: () => applySqlOps,
|
|
2655
|
+
checkOperationSafety: () => checkOperationSafety,
|
|
2656
|
+
checkSchemaSafety: () => checkSchemaSafety,
|
|
2657
|
+
classifyOperation: () => classifyOperation,
|
|
2658
|
+
diffSchemas: () => diffSchemas,
|
|
2659
|
+
ensureDir: () => ensureDir2,
|
|
2660
|
+
fileExists: () => fileExists2,
|
|
2661
|
+
findFiles: () => findFiles,
|
|
2662
|
+
generateSql: () => generateSql,
|
|
2663
|
+
getColumnNamesFromSchema: () => getColumnNamesFromSchema,
|
|
2664
|
+
getColumnNamesFromState: () => getColumnNamesFromState,
|
|
2665
|
+
getConfigPath: () => getConfigPath2,
|
|
2666
|
+
getProjectRoot: () => getProjectRoot2,
|
|
2667
|
+
getSchemaFilePath: () => getSchemaFilePath2,
|
|
2668
|
+
getSchemaForgeDir: () => getSchemaForgeDir2,
|
|
2669
|
+
getStatePath: () => getStatePath2,
|
|
2670
|
+
getTableNamesFromSchema: () => getTableNamesFromSchema,
|
|
2671
|
+
getTableNamesFromState: () => getTableNamesFromState,
|
|
2672
|
+
introspectPostgresSchema: () => introspectPostgresSchema,
|
|
2673
|
+
legacyPkName: () => legacyPkName,
|
|
2674
|
+
legacyUqName: () => legacyUqName,
|
|
2675
|
+
loadMigrationSqlInput: () => loadMigrationSqlInput,
|
|
2676
|
+
loadState: () => loadState,
|
|
2677
|
+
normalizeDefault: () => normalizeDefault,
|
|
2678
|
+
normalizeIdent: () => normalizeIdent,
|
|
2679
|
+
nowTimestamp: () => nowTimestamp,
|
|
2680
|
+
parseAddDropConstraint: () => parseAddDropConstraint,
|
|
2681
|
+
parseAlterColumnType: () => parseAlterColumnType,
|
|
2682
|
+
parseAlterTableAddColumn: () => parseAlterTableAddColumn,
|
|
2683
|
+
parseCreateTable: () => parseCreateTable,
|
|
2684
|
+
parseDropColumn: () => parseDropColumn,
|
|
2685
|
+
parseDropTable: () => parseDropTable,
|
|
2686
|
+
parseMigrationSql: () => parseMigrationSql,
|
|
2687
|
+
parseSchema: () => parseSchema,
|
|
2688
|
+
parseSetDropDefault: () => parseSetDropDefault,
|
|
2689
|
+
parseSetDropNotNull: () => parseSetDropNotNull,
|
|
2690
|
+
pkName: () => pkName,
|
|
2691
|
+
readJsonFile: () => readJsonFile2,
|
|
2692
|
+
readTextFile: () => readTextFile2,
|
|
2693
|
+
saveState: () => saveState,
|
|
2694
|
+
schemaToDsl: () => schemaToDsl,
|
|
2695
|
+
schemaToState: () => schemaToState,
|
|
2696
|
+
slugifyName: () => slugifyName,
|
|
2697
|
+
splitSqlStatements: () => splitSqlStatements,
|
|
2698
|
+
toValidationReport: () => toValidationReport,
|
|
2699
|
+
uqName: () => uqName,
|
|
2700
|
+
validateSchema: () => validateSchema,
|
|
2701
|
+
validateSchemaChanges: () => validateSchemaChanges,
|
|
2702
|
+
writeJsonFile: () => writeJsonFile2,
|
|
2703
|
+
writeTextFile: () => writeTextFile2
|
|
2704
|
+
});
|
|
2705
|
+
var init_dist = __esm({
|
|
2706
|
+
"node_modules/@xubylele/schema-forge-core/dist/index.js"() {
|
|
2707
|
+
"use strict";
|
|
2708
|
+
init_parser();
|
|
2709
|
+
init_diff();
|
|
2710
|
+
init_drift_analyzer();
|
|
2711
|
+
init_validator();
|
|
2712
|
+
init_validate();
|
|
2713
|
+
init_safety();
|
|
2714
|
+
init_state_manager();
|
|
2715
|
+
init_sql_generator();
|
|
2716
|
+
init_parse_migration();
|
|
2717
|
+
init_apply_ops();
|
|
2718
|
+
init_schema_to_dsl();
|
|
2719
|
+
init_load_migrations();
|
|
2720
|
+
init_split_statements();
|
|
2721
|
+
init_introspect_postgres();
|
|
2722
|
+
init_fs();
|
|
2723
|
+
init_normalize();
|
|
2724
|
+
init_paths();
|
|
2725
|
+
init_utils();
|
|
2726
|
+
init_errors();
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
|
|
2730
|
+
// src/api.ts
|
|
2731
|
+
var api_exports = {};
|
|
2732
|
+
__export(api_exports, {
|
|
2733
|
+
EXIT_CODES: () => EXIT_CODES,
|
|
2734
|
+
diff: () => diff,
|
|
2735
|
+
doctor: () => doctor,
|
|
2736
|
+
generate: () => generate,
|
|
2737
|
+
importSchema: () => importSchema,
|
|
2738
|
+
init: () => init,
|
|
2739
|
+
introspect: () => introspect,
|
|
2740
|
+
validate: () => validate
|
|
2741
|
+
});
|
|
2742
|
+
module.exports = __toCommonJS(api_exports);
|
|
2743
|
+
|
|
2744
|
+
// src/commands/diff.ts
|
|
2745
|
+
var import_commander = require("commander");
|
|
2746
|
+
var import_path7 = __toESM(require("path"));
|
|
2747
|
+
|
|
2748
|
+
// src/core/fs.ts
|
|
2749
|
+
var import_fs = require("fs");
|
|
2750
|
+
var import_path = __toESM(require("path"));
|
|
2751
|
+
async function ensureDir(dirPath) {
|
|
2752
|
+
try {
|
|
2753
|
+
await import_fs.promises.mkdir(dirPath, { recursive: true });
|
|
2754
|
+
} catch (error2) {
|
|
2755
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error2}`);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
async function fileExists(filePath) {
|
|
2759
|
+
try {
|
|
2760
|
+
await import_fs.promises.access(filePath);
|
|
2761
|
+
return true;
|
|
2762
|
+
} catch {
|
|
2763
|
+
return false;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
async function readTextFile(filePath) {
|
|
2767
|
+
try {
|
|
2768
|
+
return await import_fs.promises.readFile(filePath, "utf-8");
|
|
2769
|
+
} catch (error2) {
|
|
2770
|
+
throw new Error(`Failed to read file ${filePath}: ${error2}`);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
async function writeTextFile(filePath, content) {
|
|
2774
|
+
try {
|
|
2775
|
+
const dir = import_path.default.dirname(filePath);
|
|
2776
|
+
await ensureDir(dir);
|
|
2777
|
+
await import_fs.promises.writeFile(filePath, content, "utf-8");
|
|
2778
|
+
} catch (error2) {
|
|
2779
|
+
throw new Error(`Failed to write file ${filePath}: ${error2}`);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
async function readJsonFile(filePath, fallback) {
|
|
2783
|
+
try {
|
|
2784
|
+
const exists = await fileExists(filePath);
|
|
2785
|
+
if (!exists) {
|
|
2786
|
+
return fallback;
|
|
2787
|
+
}
|
|
2788
|
+
const content = await readTextFile(filePath);
|
|
2789
|
+
return JSON.parse(content);
|
|
2790
|
+
} catch (error2) {
|
|
2791
|
+
throw new Error(`Failed to read JSON file ${filePath}: ${error2}`);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
async function writeJsonFile(filePath, data) {
|
|
2795
|
+
try {
|
|
2796
|
+
const content = JSON.stringify(data, null, 2);
|
|
2797
|
+
await writeTextFile(filePath, content);
|
|
2798
|
+
} catch (error2) {
|
|
2799
|
+
throw new Error(`Failed to write JSON file ${filePath}: ${error2}`);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/core/paths.ts
|
|
2804
|
+
var import_path2 = __toESM(require("path"));
|
|
2805
|
+
function getProjectRoot(cwd = process.cwd()) {
|
|
2806
|
+
return cwd;
|
|
2807
|
+
}
|
|
2808
|
+
function getSchemaForgeDir(root) {
|
|
2809
|
+
return import_path2.default.join(root, "schemaforge");
|
|
2810
|
+
}
|
|
2811
|
+
function getSchemaFilePath(root, config) {
|
|
2812
|
+
const schemaForgeDir = getSchemaForgeDir(root);
|
|
2813
|
+
const fileName = config?.schemaFile || "schema.sf";
|
|
2814
|
+
return import_path2.default.join(schemaForgeDir, fileName);
|
|
2815
|
+
}
|
|
2816
|
+
function getConfigPath(root) {
|
|
2817
|
+
const schemaForgeDir = getSchemaForgeDir(root);
|
|
2818
|
+
return import_path2.default.join(schemaForgeDir, "config.json");
|
|
2819
|
+
}
|
|
2820
|
+
function getStatePath(root, config) {
|
|
2821
|
+
const schemaForgeDir = getSchemaForgeDir(root);
|
|
2822
|
+
const fileName = config?.stateFile || "state.json";
|
|
2823
|
+
return import_path2.default.join(schemaForgeDir, fileName);
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// src/core/provider.ts
|
|
2827
|
+
var DEFAULT_PROVIDER = "postgres";
|
|
2828
|
+
function resolveProvider(provider) {
|
|
2829
|
+
if (!provider) {
|
|
2830
|
+
return { provider: DEFAULT_PROVIDER, usedDefault: true };
|
|
2831
|
+
}
|
|
2832
|
+
return { provider, usedDefault: false };
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// src/domain.ts
|
|
2836
|
+
var corePromise;
|
|
2837
|
+
async function loadCore() {
|
|
2838
|
+
if (!corePromise) {
|
|
2839
|
+
corePromise = Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
2840
|
+
}
|
|
2841
|
+
return corePromise;
|
|
2842
|
+
}
|
|
2843
|
+
async function parseSchema2(source) {
|
|
2844
|
+
const core = await loadCore();
|
|
2845
|
+
return core.parseSchema(source);
|
|
2846
|
+
}
|
|
2847
|
+
async function validateSchema2(schema) {
|
|
2848
|
+
const core = await loadCore();
|
|
2849
|
+
core.validateSchema(schema);
|
|
2850
|
+
}
|
|
2851
|
+
async function diffSchemas2(previousState, currentSchema) {
|
|
2852
|
+
const core = await loadCore();
|
|
2853
|
+
return core.diffSchemas(previousState, currentSchema);
|
|
2854
|
+
}
|
|
2855
|
+
async function generateSql2(diff2, provider, config) {
|
|
2856
|
+
const core = await loadCore();
|
|
2857
|
+
return core.generateSql(diff2, provider, config);
|
|
2858
|
+
}
|
|
2859
|
+
async function schemaToState2(schema) {
|
|
2860
|
+
const core = await loadCore();
|
|
2861
|
+
return core.schemaToState(schema);
|
|
2862
|
+
}
|
|
2863
|
+
async function loadState2(statePath) {
|
|
2864
|
+
const core = await loadCore();
|
|
2865
|
+
return core.loadState(statePath);
|
|
2866
|
+
}
|
|
2867
|
+
async function saveState2(statePath, state) {
|
|
2868
|
+
const core = await loadCore();
|
|
2869
|
+
return core.saveState(statePath, state);
|
|
2870
|
+
}
|
|
2871
|
+
async function validateSchemaChanges2(previousState, currentSchema) {
|
|
2872
|
+
const core = await loadCore();
|
|
2873
|
+
return core.validateSchemaChanges(previousState, currentSchema);
|
|
2874
|
+
}
|
|
2875
|
+
async function toValidationReport2(findings) {
|
|
2876
|
+
const core = await loadCore();
|
|
2877
|
+
return core.toValidationReport(findings);
|
|
2878
|
+
}
|
|
2879
|
+
async function parseMigrationSql2(sql) {
|
|
2880
|
+
const core = await loadCore();
|
|
2881
|
+
return core.parseMigrationSql(sql);
|
|
2882
|
+
}
|
|
2883
|
+
async function introspectPostgresSchema2(options) {
|
|
2884
|
+
const core = await loadCore();
|
|
2885
|
+
return core.introspectPostgresSchema(options);
|
|
2886
|
+
}
|
|
2887
|
+
async function analyzeSchemaDrift2(state, liveSchema) {
|
|
2888
|
+
const core = await loadCore();
|
|
2889
|
+
return core.analyzeSchemaDrift(state, liveSchema);
|
|
2890
|
+
}
|
|
2891
|
+
async function applySqlOps2(ops) {
|
|
2892
|
+
const core = await loadCore();
|
|
2893
|
+
return core.applySqlOps(ops);
|
|
2894
|
+
}
|
|
2895
|
+
async function schemaToDsl2(schema) {
|
|
2896
|
+
const core = await loadCore();
|
|
2897
|
+
return core.schemaToDsl(schema);
|
|
2898
|
+
}
|
|
2899
|
+
async function loadMigrationSqlInput2(inputPath) {
|
|
2900
|
+
const core = await loadCore();
|
|
2901
|
+
return core.loadMigrationSqlInput(inputPath);
|
|
2902
|
+
}
|
|
2903
|
+
async function createSchemaValidationError(message) {
|
|
2904
|
+
const core = await loadCore();
|
|
2905
|
+
return new core.SchemaValidationError(message);
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
// src/core/postgres.ts
|
|
2909
|
+
var import_pg = require("pg");
|
|
2910
|
+
function resolvePostgresConnectionString(options = {}) {
|
|
2911
|
+
const explicitUrl = options.url?.trim();
|
|
2912
|
+
if (explicitUrl) {
|
|
2913
|
+
return explicitUrl;
|
|
2914
|
+
}
|
|
2915
|
+
const envUrl = process.env.DATABASE_URL?.trim();
|
|
2916
|
+
if (envUrl) {
|
|
2917
|
+
return envUrl;
|
|
2918
|
+
}
|
|
2919
|
+
throw new Error("PostgreSQL connection URL is required. Pass --url or set DATABASE_URL.");
|
|
2920
|
+
}
|
|
2921
|
+
function parseSchemaList(value) {
|
|
2922
|
+
if (!value) {
|
|
2923
|
+
return void 0;
|
|
2924
|
+
}
|
|
2925
|
+
const schemas = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2926
|
+
return schemas.length > 0 ? schemas : void 0;
|
|
2927
|
+
}
|
|
2928
|
+
async function withPostgresQueryExecutor(connectionString, run) {
|
|
2929
|
+
const client = new import_pg.Client({ connectionString });
|
|
2930
|
+
await client.connect();
|
|
2931
|
+
const query = async (sql, params) => {
|
|
2932
|
+
const result = await client.query(sql, params ? [...params] : void 0);
|
|
2933
|
+
return result.rows;
|
|
2934
|
+
};
|
|
2935
|
+
try {
|
|
2936
|
+
return await run(query);
|
|
2937
|
+
} finally {
|
|
2938
|
+
await client.end();
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/utils/exitCodes.ts
|
|
2943
|
+
var EXIT_CODES = {
|
|
2944
|
+
/** Successful operation */
|
|
2945
|
+
SUCCESS: 0,
|
|
2946
|
+
/** Validation error (invalid DSL syntax, config errors, missing files, etc.) */
|
|
2947
|
+
VALIDATION_ERROR: 1,
|
|
2948
|
+
/** Drift detected - Reserved for future use when comparing actual DB state vs schema */
|
|
2949
|
+
DRIFT_DETECTED: 2,
|
|
2950
|
+
/** Destructive operation detected in CI environment without --force */
|
|
2951
|
+
CI_DESTRUCTIVE: 3
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2954
|
+
// src/utils/output.ts
|
|
2955
|
+
var import_boxen = __toESM(require("boxen"));
|
|
2956
|
+
var import_chalk = require("chalk");
|
|
2957
|
+
var isInteractive = Boolean(process.stdout?.isTTY);
|
|
2958
|
+
var colorsEnabled = isInteractive && process.env.FORCE_COLOR !== "0" && !("NO_COLOR" in process.env);
|
|
2959
|
+
var color = new import_chalk.Chalk({ level: colorsEnabled ? 3 : 0 });
|
|
2960
|
+
var theme = {
|
|
2961
|
+
primary: color.cyanBright,
|
|
2962
|
+
success: color.hex("#00FF88"),
|
|
2963
|
+
warning: color.hex("#FFD166"),
|
|
2964
|
+
error: color.hex("#EF476F"),
|
|
2965
|
+
accent: color.magentaBright
|
|
2966
|
+
};
|
|
2967
|
+
function success(message) {
|
|
2968
|
+
const text = theme.success(`[OK] ${message}`);
|
|
2969
|
+
if (!isInteractive) {
|
|
2970
|
+
console.log(text);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
try {
|
|
2974
|
+
console.log(
|
|
2975
|
+
(0, import_boxen.default)(text, {
|
|
2976
|
+
padding: 1,
|
|
2977
|
+
borderColor: "cyan",
|
|
2978
|
+
borderStyle: "round"
|
|
2979
|
+
})
|
|
2980
|
+
);
|
|
2981
|
+
} catch {
|
|
2982
|
+
console.log(text);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
function info(message) {
|
|
2986
|
+
console.log(theme.primary(message));
|
|
2987
|
+
}
|
|
2988
|
+
function warning(message) {
|
|
2989
|
+
console.warn(theme.warning(`[WARN] ${message}`));
|
|
2990
|
+
}
|
|
2991
|
+
function error(message) {
|
|
2992
|
+
console.error(theme.error(`[ERROR] ${message}`));
|
|
2993
|
+
}
|
|
2994
|
+
function forceWarning(message) {
|
|
2995
|
+
console.error(theme.warning(`[FORCE] ${message}`));
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
// src/utils/prompt.ts
|
|
2999
|
+
var import_node_readline = __toESM(require("readline"));
|
|
3000
|
+
function isCI() {
|
|
3001
|
+
return process.env.CI === "true" || process.env.CONTINUOUS_INTEGRATION === "true";
|
|
3002
|
+
}
|
|
3003
|
+
function formatFindingsSummary(findings) {
|
|
3004
|
+
const errors = findings.filter((f) => f.severity === "error");
|
|
3005
|
+
const warnings = findings.filter((f) => f.severity === "warning");
|
|
3006
|
+
const lines = [];
|
|
3007
|
+
if (errors.length > 0) {
|
|
3008
|
+
lines.push(theme.error("DESTRUCTIVE OPERATIONS:"));
|
|
3009
|
+
for (const finding of errors) {
|
|
3010
|
+
const columnPart = finding.column ? `.${finding.column}` : "";
|
|
3011
|
+
const fromTo = finding.from && finding.to ? ` (${finding.from} \u2192 ${finding.to})` : "";
|
|
3012
|
+
lines.push(theme.error(` \u2022 ${finding.code}: ${finding.table}${columnPart}${fromTo}`));
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
if (warnings.length > 0) {
|
|
3016
|
+
if (lines.length > 0) lines.push("");
|
|
3017
|
+
lines.push(theme.warning("WARNING OPERATIONS:"));
|
|
3018
|
+
for (const finding of warnings) {
|
|
3019
|
+
const columnPart = finding.column ? `.${finding.column}` : "";
|
|
3020
|
+
const fromTo = finding.from && finding.to ? ` (${finding.from} \u2192 ${finding.to})` : "";
|
|
3021
|
+
lines.push(theme.warning(` \u2022 ${finding.code}: ${finding.table}${columnPart}${fromTo}`));
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return lines.join("\n");
|
|
3025
|
+
}
|
|
3026
|
+
async function readConfirmation(input = process.stdin, output = process.stdout) {
|
|
3027
|
+
const rl = import_node_readline.default.createInterface({
|
|
3028
|
+
input,
|
|
3029
|
+
output
|
|
3030
|
+
});
|
|
3031
|
+
return new Promise((resolve) => {
|
|
3032
|
+
const askQuestion = () => {
|
|
3033
|
+
rl.question(theme.primary("Proceed with these changes? (yes/no): "), (answer) => {
|
|
3034
|
+
const normalized = answer.trim().toLowerCase();
|
|
3035
|
+
if (normalized === "yes" || normalized === "y") {
|
|
3036
|
+
rl.close();
|
|
3037
|
+
resolve(true);
|
|
3038
|
+
} else if (normalized === "no" || normalized === "n") {
|
|
3039
|
+
rl.close();
|
|
3040
|
+
resolve(false);
|
|
3041
|
+
} else {
|
|
3042
|
+
console.log(theme.warning('Please answer "yes" or "no".'));
|
|
3043
|
+
askQuestion();
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
};
|
|
3047
|
+
askQuestion();
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
async function confirmDestructiveOps(findings, input, output) {
|
|
3051
|
+
const riskyFindings = findings.filter(
|
|
3052
|
+
(f) => f.severity === "error" || f.severity === "warning"
|
|
3053
|
+
);
|
|
3054
|
+
if (riskyFindings.length === 0) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
if (isCI()) {
|
|
3058
|
+
error("Cannot run interactive prompts in CI environment. Use --force flag to bypass safety checks.");
|
|
3059
|
+
process.exitCode = EXIT_CODES.CI_DESTRUCTIVE;
|
|
3060
|
+
return false;
|
|
3061
|
+
}
|
|
3062
|
+
console.log("");
|
|
3063
|
+
console.log(formatFindingsSummary(riskyFindings));
|
|
3064
|
+
console.log("");
|
|
3065
|
+
const confirmed = await readConfirmation(input, output);
|
|
3066
|
+
if (!confirmed) {
|
|
3067
|
+
warning("Operation cancelled by user.");
|
|
3068
|
+
}
|
|
3069
|
+
return confirmed;
|
|
3070
|
+
}
|
|
3071
|
+
function hasDestructiveFindings(findings) {
|
|
3072
|
+
return findings.some((f) => f.severity === "error" || f.severity === "warning");
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
// src/commands/diff.ts
|
|
3076
|
+
function resolveConfigPath(root, targetPath) {
|
|
3077
|
+
return import_path7.default.isAbsolute(targetPath) ? targetPath : import_path7.default.join(root, targetPath);
|
|
3078
|
+
}
|
|
3079
|
+
async function runDiff(options = {}) {
|
|
3080
|
+
if (options.safe && options.force) {
|
|
3081
|
+
throw new Error("Cannot use --safe and --force flags together. Choose one:\n --safe: Block destructive operations\n --force: Bypass safety checks");
|
|
3082
|
+
}
|
|
3083
|
+
const root = getProjectRoot();
|
|
3084
|
+
const configPath = getConfigPath(root);
|
|
3085
|
+
if (!await fileExists(configPath)) {
|
|
3086
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
3087
|
+
}
|
|
3088
|
+
const config = await readJsonFile(configPath, {});
|
|
3089
|
+
const useLiveDatabase = Boolean(options.url || process.env.DATABASE_URL);
|
|
3090
|
+
const requiredFields = useLiveDatabase ? ["schemaFile"] : ["schemaFile", "stateFile"];
|
|
3091
|
+
for (const field of requiredFields) {
|
|
3092
|
+
const value = config[field];
|
|
3093
|
+
if (!value || typeof value !== "string") {
|
|
3094
|
+
throw new Error(`Invalid config: '${field}' is required`);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
const schemaPath = resolveConfigPath(root, config.schemaFile);
|
|
3098
|
+
const statePath = config.stateFile ? resolveConfigPath(root, config.stateFile) : null;
|
|
3099
|
+
const { provider } = resolveProvider(config.provider);
|
|
3100
|
+
const schemaSource = await readTextFile(schemaPath);
|
|
3101
|
+
const schema = await parseSchema2(schemaSource);
|
|
3102
|
+
try {
|
|
3103
|
+
await validateSchema2(schema);
|
|
3104
|
+
} catch (error2) {
|
|
3105
|
+
if (error2 instanceof Error) {
|
|
3106
|
+
throw await createSchemaValidationError(error2.message);
|
|
3107
|
+
}
|
|
3108
|
+
throw error2;
|
|
3109
|
+
}
|
|
3110
|
+
const previousState = useLiveDatabase ? await withPostgresQueryExecutor(
|
|
3111
|
+
resolvePostgresConnectionString({ url: options.url }),
|
|
3112
|
+
async (query) => {
|
|
3113
|
+
const schemaFilters = parseSchemaList(options.schema);
|
|
3114
|
+
const liveSchema = await introspectPostgresSchema2({
|
|
3115
|
+
query,
|
|
3116
|
+
...schemaFilters ? { schemas: schemaFilters } : {}
|
|
3117
|
+
});
|
|
3118
|
+
return schemaToState2(liveSchema);
|
|
3119
|
+
}
|
|
3120
|
+
) : await loadState2(statePath ?? "");
|
|
3121
|
+
const diff2 = await diffSchemas2(previousState, schema);
|
|
3122
|
+
if (options.force) {
|
|
3123
|
+
forceWarning("Are you sure to use --force? This option will bypass safety checks for destructive operations.");
|
|
3124
|
+
}
|
|
3125
|
+
if (options.safe && !options.force && diff2.operations.length > 0) {
|
|
3126
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
3127
|
+
const destructiveFindings = findings.filter((f) => f.severity === "error");
|
|
3128
|
+
if (destructiveFindings.length > 0) {
|
|
3129
|
+
const errorMessages = destructiveFindings.map((f) => {
|
|
3130
|
+
const target = f.column ? `${f.table}.${f.column}` : f.table;
|
|
3131
|
+
const typeRange = f.from && f.to ? ` (${f.from} -> ${f.to})` : "";
|
|
3132
|
+
return ` - ${f.code}: ${target}${typeRange}`;
|
|
3133
|
+
}).join("\n");
|
|
3134
|
+
throw await createSchemaValidationError(
|
|
3135
|
+
`Cannot proceed with --safe flag: Found ${destructiveFindings.length} destructive operation(s):
|
|
3136
|
+
${errorMessages}
|
|
3137
|
+
|
|
3138
|
+
Remove --safe flag or modify schema to avoid destructive changes.`
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
if (!options.safe && !options.force && diff2.operations.length > 0) {
|
|
3143
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
3144
|
+
const riskyFindings = findings.filter((f) => f.severity === "error" || f.severity === "warning");
|
|
3145
|
+
if (riskyFindings.length > 0) {
|
|
3146
|
+
const confirmed = await confirmDestructiveOps(findings);
|
|
3147
|
+
if (!confirmed) {
|
|
3148
|
+
if (process.exitCode !== EXIT_CODES.CI_DESTRUCTIVE) {
|
|
3149
|
+
process.exitCode = EXIT_CODES.VALIDATION_ERROR;
|
|
3150
|
+
}
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
if (diff2.operations.length === 0) {
|
|
3156
|
+
success("No changes detected");
|
|
3157
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
const sql = await generateSql2(diff2, provider, config.sql);
|
|
3161
|
+
console.log(sql);
|
|
3162
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// src/commands/doctor.ts
|
|
3166
|
+
var import_commander2 = require("commander");
|
|
3167
|
+
var import_path8 = __toESM(require("path"));
|
|
3168
|
+
function resolveConfigPath2(root, targetPath) {
|
|
3169
|
+
return import_path8.default.isAbsolute(targetPath) ? targetPath : import_path8.default.join(root, targetPath);
|
|
3170
|
+
}
|
|
3171
|
+
function hasDrift(report) {
|
|
3172
|
+
return report.missingTables.length > 0 || report.extraTables.length > 0 || report.columnDifferences.length > 0 || report.typeMismatches.length > 0;
|
|
3173
|
+
}
|
|
3174
|
+
function printDriftReport(report) {
|
|
3175
|
+
if (report.missingTables.length > 0) {
|
|
3176
|
+
console.log(`Missing tables in live DB: ${report.missingTables.join(", ")}`);
|
|
3177
|
+
}
|
|
3178
|
+
if (report.extraTables.length > 0) {
|
|
3179
|
+
console.log(`Extra tables in live DB: ${report.extraTables.join(", ")}`);
|
|
3180
|
+
}
|
|
3181
|
+
for (const difference of report.columnDifferences) {
|
|
3182
|
+
if (difference.missingInLive.length > 0) {
|
|
3183
|
+
console.log(`Missing columns in ${difference.tableName}: ${difference.missingInLive.join(", ")}`);
|
|
3184
|
+
}
|
|
3185
|
+
if (difference.extraInLive.length > 0) {
|
|
3186
|
+
console.log(`Extra columns in ${difference.tableName}: ${difference.extraInLive.join(", ")}`);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
for (const mismatch of report.typeMismatches) {
|
|
3190
|
+
console.log(`Type mismatch ${mismatch.tableName}.${mismatch.columnName}: ${mismatch.expectedType} -> ${mismatch.actualType}`);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
async function runDoctor(options = {}) {
|
|
3194
|
+
const root = getProjectRoot();
|
|
3195
|
+
const configPath = getConfigPath(root);
|
|
3196
|
+
if (!await fileExists(configPath)) {
|
|
3197
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
3198
|
+
}
|
|
3199
|
+
const config = await readJsonFile(configPath, {});
|
|
3200
|
+
if (!config.stateFile || typeof config.stateFile !== "string") {
|
|
3201
|
+
throw new Error("Invalid config: 'stateFile' is required");
|
|
3202
|
+
}
|
|
3203
|
+
const statePath = resolveConfigPath2(root, config.stateFile);
|
|
3204
|
+
const previousState = await loadState2(statePath);
|
|
3205
|
+
const schemaFilters = parseSchemaList(options.schema);
|
|
3206
|
+
const liveSchema = await withPostgresQueryExecutor(
|
|
3207
|
+
resolvePostgresConnectionString({ url: options.url }),
|
|
3208
|
+
(query) => introspectPostgresSchema2({
|
|
3209
|
+
query,
|
|
3210
|
+
...schemaFilters ? { schemas: schemaFilters } : {}
|
|
3211
|
+
})
|
|
3212
|
+
);
|
|
3213
|
+
const driftReport = await analyzeSchemaDrift2(previousState, liveSchema);
|
|
3214
|
+
const detected = hasDrift(driftReport);
|
|
3215
|
+
process.exitCode = detected ? EXIT_CODES.DRIFT_DETECTED : EXIT_CODES.SUCCESS;
|
|
3216
|
+
if (options.json) {
|
|
3217
|
+
console.log(JSON.stringify(driftReport, null, 2));
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
if (!detected) {
|
|
3221
|
+
success("No schema drift detected");
|
|
3222
|
+
return;
|
|
3223
|
+
}
|
|
3224
|
+
console.log("Schema drift detected");
|
|
3225
|
+
printDriftReport(driftReport);
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// src/commands/generate.ts
|
|
3229
|
+
var import_commander3 = require("commander");
|
|
3230
|
+
var import_path9 = __toESM(require("path"));
|
|
3231
|
+
|
|
3232
|
+
// src/core/utils.ts
|
|
3233
|
+
function nowTimestamp2() {
|
|
3234
|
+
const date = /* @__PURE__ */ new Date();
|
|
3235
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3236
|
+
return String(date.getFullYear()) + pad(date.getMonth() + 1) + pad(date.getDate()) + pad(date.getHours()) + pad(date.getMinutes()) + pad(date.getSeconds());
|
|
3237
|
+
}
|
|
3238
|
+
function slugifyName2(name) {
|
|
3239
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "migration";
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// src/commands/generate.ts
|
|
3243
|
+
var REQUIRED_CONFIG_FIELDS = [
|
|
3244
|
+
"schemaFile",
|
|
3245
|
+
"stateFile",
|
|
3246
|
+
"outputDir"
|
|
3247
|
+
];
|
|
3248
|
+
function resolveConfigPath3(root, targetPath) {
|
|
3249
|
+
return import_path9.default.isAbsolute(targetPath) ? targetPath : import_path9.default.join(root, targetPath);
|
|
3250
|
+
}
|
|
3251
|
+
async function runGenerate(options) {
|
|
3252
|
+
if (options.safe && options.force) {
|
|
3253
|
+
throw new Error("Cannot use --safe and --force flags together. Choose one:\n --safe: Block destructive operations\n --force: Bypass safety checks");
|
|
3254
|
+
}
|
|
3255
|
+
const root = getProjectRoot();
|
|
3256
|
+
const configPath = getConfigPath(root);
|
|
3257
|
+
if (!await fileExists(configPath)) {
|
|
3258
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
3259
|
+
}
|
|
3260
|
+
const config = await readJsonFile(configPath, {});
|
|
3261
|
+
for (const field of REQUIRED_CONFIG_FIELDS) {
|
|
3262
|
+
const value = config[field];
|
|
3263
|
+
if (!value || typeof value !== "string") {
|
|
3264
|
+
throw new Error(`Invalid config: '${field}' is required`);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
const schemaPath = resolveConfigPath3(root, config.schemaFile);
|
|
3268
|
+
const statePath = resolveConfigPath3(root, config.stateFile);
|
|
3269
|
+
const outputDir = resolveConfigPath3(root, config.outputDir);
|
|
3270
|
+
const { provider, usedDefault } = resolveProvider(config.provider);
|
|
3271
|
+
if (usedDefault) {
|
|
3272
|
+
info("Provider not set; defaulting to postgres.");
|
|
3273
|
+
}
|
|
3274
|
+
info("Generating SQL...");
|
|
3275
|
+
const schemaSource = await readTextFile(schemaPath);
|
|
3276
|
+
const schema = await parseSchema2(schemaSource);
|
|
3277
|
+
try {
|
|
3278
|
+
await validateSchema2(schema);
|
|
3279
|
+
} catch (error2) {
|
|
3280
|
+
if (error2 instanceof Error) {
|
|
3281
|
+
throw await createSchemaValidationError(error2.message);
|
|
3282
|
+
}
|
|
3283
|
+
throw error2;
|
|
3284
|
+
}
|
|
3285
|
+
const previousState = await loadState2(statePath);
|
|
3286
|
+
const diff2 = await diffSchemas2(previousState, schema);
|
|
3287
|
+
if (options.force) {
|
|
3288
|
+
forceWarning("Are you sure to use --force? This option will bypass safety checks for destructive operations.");
|
|
3289
|
+
}
|
|
3290
|
+
if (options.safe && !options.force && diff2.operations.length > 0) {
|
|
3291
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
3292
|
+
const destructiveFindings = findings.filter((f) => f.severity === "error");
|
|
3293
|
+
if (destructiveFindings.length > 0) {
|
|
3294
|
+
const errorMessages = destructiveFindings.map((f) => {
|
|
3295
|
+
const target = f.column ? `${f.table}.${f.column}` : f.table;
|
|
3296
|
+
const typeRange = f.from && f.to ? ` (${f.from} -> ${f.to})` : "";
|
|
3297
|
+
return ` - ${f.code}: ${target}${typeRange}`;
|
|
3298
|
+
}).join("\n");
|
|
3299
|
+
throw await createSchemaValidationError(
|
|
3300
|
+
`Cannot proceed with --safe flag: Found ${destructiveFindings.length} destructive operation(s):
|
|
3301
|
+
${errorMessages}
|
|
3302
|
+
|
|
3303
|
+
Remove --safe flag or modify schema to avoid destructive changes.`
|
|
3304
|
+
);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (!options.safe && !options.force && diff2.operations.length > 0) {
|
|
3308
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
3309
|
+
const riskyFindings = findings.filter((f) => f.severity === "error" || f.severity === "warning");
|
|
3310
|
+
if (riskyFindings.length > 0) {
|
|
3311
|
+
const confirmed = await confirmDestructiveOps(findings);
|
|
3312
|
+
if (!confirmed) {
|
|
3313
|
+
if (process.exitCode !== EXIT_CODES.CI_DESTRUCTIVE) {
|
|
3314
|
+
process.exitCode = EXIT_CODES.VALIDATION_ERROR;
|
|
3315
|
+
}
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
if (diff2.operations.length === 0) {
|
|
3321
|
+
info("No changes detected");
|
|
3322
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
const sql = await generateSql2(diff2, provider, config.sql);
|
|
3326
|
+
const timestamp = nowTimestamp2();
|
|
3327
|
+
const slug = slugifyName2(options.name ?? "migration");
|
|
3328
|
+
const fileName = `${timestamp}-${slug}.sql`;
|
|
3329
|
+
await ensureDir(outputDir);
|
|
3330
|
+
const migrationPath = import_path9.default.join(outputDir, fileName);
|
|
3331
|
+
await writeTextFile(migrationPath, sql + "\n");
|
|
3332
|
+
const nextState = await schemaToState2(schema);
|
|
3333
|
+
await saveState2(statePath, nextState);
|
|
3334
|
+
success(`SQL generated successfully: ${migrationPath}`);
|
|
3335
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/commands/import.ts
|
|
3339
|
+
var import_commander4 = require("commander");
|
|
3340
|
+
var import_path10 = __toESM(require("path"));
|
|
3341
|
+
function resolveConfigPath4(root, targetPath) {
|
|
3342
|
+
return import_path10.default.isAbsolute(targetPath) ? targetPath : import_path10.default.join(root, targetPath);
|
|
3343
|
+
}
|
|
3344
|
+
async function runImport(inputPath, options = {}) {
|
|
3345
|
+
const root = getProjectRoot();
|
|
3346
|
+
const absoluteInputPath = resolveConfigPath4(root, inputPath);
|
|
3347
|
+
const inputs = await loadMigrationSqlInput2(absoluteInputPath);
|
|
3348
|
+
if (inputs.length === 0) {
|
|
3349
|
+
throw new Error(`No .sql migration files found in: ${absoluteInputPath}`);
|
|
3350
|
+
}
|
|
3351
|
+
const allOps = [];
|
|
3352
|
+
const parseWarnings = [];
|
|
3353
|
+
for (const input of inputs) {
|
|
3354
|
+
const result = await parseMigrationSql2(input.sql);
|
|
3355
|
+
allOps.push(...result.ops);
|
|
3356
|
+
parseWarnings.push(...result.warnings.map((item) => ({
|
|
3357
|
+
statement: `[${import_path10.default.basename(input.filePath)}] ${item.statement}`,
|
|
3358
|
+
reason: item.reason
|
|
3359
|
+
})));
|
|
3360
|
+
}
|
|
3361
|
+
const applied = await applySqlOps2(allOps);
|
|
3362
|
+
const dsl = await schemaToDsl2(applied.schema);
|
|
3363
|
+
let targetPath = options.out;
|
|
3364
|
+
if (!targetPath) {
|
|
3365
|
+
const configPath = getConfigPath(root);
|
|
3366
|
+
if (await fileExists(configPath)) {
|
|
3367
|
+
const config = await readJsonFile(configPath, {});
|
|
3368
|
+
if (typeof config.schemaFile === "string" && config.schemaFile.length > 0) {
|
|
3369
|
+
targetPath = config.schemaFile;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
const schemaPath = targetPath ? resolveConfigPath4(root, targetPath) : getSchemaFilePath(root);
|
|
3374
|
+
await writeTextFile(schemaPath, dsl);
|
|
3375
|
+
success(`Imported ${inputs.length} migration file(s) into ${schemaPath}`);
|
|
3376
|
+
info(`Parsed ${allOps.length} supported DDL operation(s)`);
|
|
3377
|
+
const warnings = [...parseWarnings, ...applied.warnings];
|
|
3378
|
+
if (warnings.length > 0) {
|
|
3379
|
+
warning(`Ignored ${warnings.length} unsupported item(s)`);
|
|
3380
|
+
for (const item of warnings.slice(0, 10)) {
|
|
3381
|
+
warning(`${item.reason}: ${item.statement}`);
|
|
3382
|
+
}
|
|
3383
|
+
if (warnings.length > 10) {
|
|
3384
|
+
warning(`...and ${warnings.length - 10} more`);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/commands/init.ts
|
|
3391
|
+
var import_commander5 = require("commander");
|
|
3392
|
+
async function runInit() {
|
|
3393
|
+
const root = getProjectRoot();
|
|
3394
|
+
const schemaForgeDir = getSchemaForgeDir(root);
|
|
3395
|
+
if (await fileExists(schemaForgeDir)) {
|
|
3396
|
+
throw new Error("schemaforge/ directory already exists. Please remove it or run init in a different directory.");
|
|
3397
|
+
}
|
|
3398
|
+
const schemaFilePath = getSchemaFilePath(root);
|
|
3399
|
+
const configPath = getConfigPath(root);
|
|
3400
|
+
const statePath = getStatePath(root);
|
|
3401
|
+
if (await fileExists(schemaFilePath)) {
|
|
3402
|
+
throw new Error(`${schemaFilePath} already exists`);
|
|
3403
|
+
}
|
|
3404
|
+
if (await fileExists(configPath)) {
|
|
3405
|
+
throw new Error(`${configPath} already exists`);
|
|
3406
|
+
}
|
|
3407
|
+
if (await fileExists(statePath)) {
|
|
3408
|
+
throw new Error(`${statePath} already exists`);
|
|
3409
|
+
}
|
|
3410
|
+
info("Initializing schema project...");
|
|
3411
|
+
await ensureDir(schemaForgeDir);
|
|
3412
|
+
const schemaContent = `# SchemaForge schema definition
|
|
3413
|
+
# Run: schema-forge generate
|
|
3414
|
+
|
|
3415
|
+
table users {
|
|
3416
|
+
id uuid pk
|
|
3417
|
+
created_at timestamptz default now()
|
|
3418
|
+
}
|
|
3419
|
+
`;
|
|
3420
|
+
await writeTextFile(schemaFilePath, schemaContent);
|
|
3421
|
+
success(`Created ${schemaFilePath}`);
|
|
3422
|
+
const config = {
|
|
3423
|
+
provider: "postgres",
|
|
3424
|
+
outputDir: "migrations",
|
|
3425
|
+
schemaFile: "schemaforge/schema.sf",
|
|
3426
|
+
stateFile: "schemaforge/state.json",
|
|
3427
|
+
sql: {
|
|
3428
|
+
uuidDefault: "gen_random_uuid()",
|
|
3429
|
+
timestampDefault: "now()"
|
|
3430
|
+
}
|
|
3431
|
+
};
|
|
3432
|
+
await writeJsonFile(configPath, config);
|
|
3433
|
+
success(`Created ${configPath}`);
|
|
3434
|
+
const state = {
|
|
3435
|
+
version: 1,
|
|
3436
|
+
tables: {}
|
|
3437
|
+
};
|
|
3438
|
+
await writeJsonFile(statePath, state);
|
|
3439
|
+
success(`Created ${statePath}`);
|
|
3440
|
+
const outputDir = "migrations";
|
|
3441
|
+
await ensureDir(outputDir);
|
|
3442
|
+
success(`Created ${outputDir}`);
|
|
3443
|
+
success("Project initialized successfully");
|
|
3444
|
+
info("Next steps:");
|
|
3445
|
+
info(" 1. Edit schemaforge/schema.sf to define your schema");
|
|
3446
|
+
info(" 2. Run: schema-forge generate");
|
|
3447
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
// src/commands/introspect.ts
|
|
3451
|
+
var import_commander6 = require("commander");
|
|
3452
|
+
var import_path11 = __toESM(require("path"));
|
|
3453
|
+
function resolveOutputPath(root, outputPath) {
|
|
3454
|
+
return import_path11.default.isAbsolute(outputPath) ? outputPath : import_path11.default.join(root, outputPath);
|
|
3455
|
+
}
|
|
3456
|
+
async function runIntrospect(options = {}) {
|
|
3457
|
+
const connectionString = resolvePostgresConnectionString({ url: options.url });
|
|
3458
|
+
const schemas = parseSchemaList(options.schema);
|
|
3459
|
+
const root = getProjectRoot();
|
|
3460
|
+
const schema = await withPostgresQueryExecutor(connectionString, (query) => introspectPostgresSchema2({
|
|
3461
|
+
query,
|
|
3462
|
+
...schemas ? { schemas } : {}
|
|
3463
|
+
}));
|
|
3464
|
+
const output = JSON.stringify(schema, null, 2);
|
|
3465
|
+
if (!options.json && !options.out) {
|
|
3466
|
+
info(`Introspected ${Object.keys(schema.tables).length} table(s) from PostgreSQL.`);
|
|
3467
|
+
}
|
|
3468
|
+
if (options.out) {
|
|
3469
|
+
const outputPath = resolveOutputPath(root, options.out);
|
|
3470
|
+
await writeTextFile(outputPath, `${output}
|
|
3471
|
+
`);
|
|
3472
|
+
success(`Live schema written to ${outputPath}`);
|
|
3473
|
+
}
|
|
3474
|
+
if (options.json || !options.out) {
|
|
3475
|
+
console.log(output);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
// src/commands/validate.ts
|
|
3480
|
+
var import_commander7 = require("commander");
|
|
3481
|
+
var import_path12 = __toESM(require("path"));
|
|
3482
|
+
function resolveConfigPath5(root, targetPath) {
|
|
3483
|
+
return import_path12.default.isAbsolute(targetPath) ? targetPath : import_path12.default.join(root, targetPath);
|
|
3484
|
+
}
|
|
3485
|
+
async function runValidate(options = {}) {
|
|
3486
|
+
const root = getProjectRoot();
|
|
3487
|
+
const configPath = getConfigPath(root);
|
|
3488
|
+
const useLiveDatabase = Boolean(options.url || process.env.DATABASE_URL);
|
|
3489
|
+
if (!await fileExists(configPath)) {
|
|
3490
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
3491
|
+
}
|
|
3492
|
+
const config = await readJsonFile(configPath, {});
|
|
3493
|
+
const requiredFields = ["schemaFile", "stateFile"];
|
|
3494
|
+
for (const field of requiredFields) {
|
|
3495
|
+
const value = config[field];
|
|
3496
|
+
if (!value || typeof value !== "string") {
|
|
3497
|
+
throw new Error(`Invalid config: '${field}' is required`);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
const schemaPath = resolveConfigPath5(root, config.schemaFile);
|
|
3501
|
+
if (!config.stateFile) {
|
|
3502
|
+
throw new Error("Invalid config: 'stateFile' is required");
|
|
3503
|
+
}
|
|
3504
|
+
const statePath = resolveConfigPath5(root, config.stateFile);
|
|
3505
|
+
const schemaSource = await readTextFile(schemaPath);
|
|
3506
|
+
const schema = await parseSchema2(schemaSource);
|
|
3507
|
+
try {
|
|
3508
|
+
await validateSchema2(schema);
|
|
3509
|
+
} catch (error2) {
|
|
3510
|
+
if (error2 instanceof Error) {
|
|
3511
|
+
throw await createSchemaValidationError(error2.message);
|
|
3512
|
+
}
|
|
3513
|
+
throw error2;
|
|
3514
|
+
}
|
|
3515
|
+
const previousState = await loadState2(statePath);
|
|
3516
|
+
if (useLiveDatabase) {
|
|
3517
|
+
const schemaFilters = parseSchemaList(options.schema);
|
|
3518
|
+
const liveSchema = await withPostgresQueryExecutor(
|
|
3519
|
+
resolvePostgresConnectionString({ url: options.url }),
|
|
3520
|
+
(query) => introspectPostgresSchema2({
|
|
3521
|
+
query,
|
|
3522
|
+
...schemaFilters ? { schemas: schemaFilters } : {}
|
|
3523
|
+
})
|
|
3524
|
+
);
|
|
3525
|
+
const driftReport = await analyzeSchemaDrift2(previousState, liveSchema);
|
|
3526
|
+
const hasDrift2 = driftReport.missingTables.length > 0 || driftReport.extraTables.length > 0 || driftReport.columnDifferences.length > 0 || driftReport.typeMismatches.length > 0;
|
|
3527
|
+
process.exitCode = hasDrift2 ? EXIT_CODES.DRIFT_DETECTED : EXIT_CODES.SUCCESS;
|
|
3528
|
+
if (options.json) {
|
|
3529
|
+
console.log(JSON.stringify(driftReport, null, 2));
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
if (!hasDrift2) {
|
|
3533
|
+
success("No schema drift detected");
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
if (driftReport.missingTables.length > 0) {
|
|
3537
|
+
console.log(`Missing tables in live DB: ${driftReport.missingTables.join(", ")}`);
|
|
3538
|
+
}
|
|
3539
|
+
if (driftReport.extraTables.length > 0) {
|
|
3540
|
+
console.log(`Extra tables in live DB: ${driftReport.extraTables.join(", ")}`);
|
|
3541
|
+
}
|
|
3542
|
+
for (const difference of driftReport.columnDifferences) {
|
|
3543
|
+
if (difference.missingInLive.length > 0) {
|
|
3544
|
+
console.log(`Missing columns in ${difference.tableName}: ${difference.missingInLive.join(", ")}`);
|
|
3545
|
+
}
|
|
3546
|
+
if (difference.extraInLive.length > 0) {
|
|
3547
|
+
console.log(`Extra columns in ${difference.tableName}: ${difference.extraInLive.join(", ")}`);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
for (const mismatch of driftReport.typeMismatches) {
|
|
3551
|
+
console.log(`Type mismatch ${mismatch.tableName}.${mismatch.columnName}: ${mismatch.expectedType} -> ${mismatch.actualType}`);
|
|
3552
|
+
}
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3555
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
3556
|
+
const report = await toValidationReport2(findings);
|
|
3557
|
+
if (isCI() && hasDestructiveFindings(findings)) {
|
|
3558
|
+
process.exitCode = EXIT_CODES.CI_DESTRUCTIVE;
|
|
3559
|
+
} else {
|
|
3560
|
+
process.exitCode = report.hasErrors ? EXIT_CODES.VALIDATION_ERROR : EXIT_CODES.SUCCESS;
|
|
3561
|
+
}
|
|
3562
|
+
if (options.json) {
|
|
3563
|
+
console.log(JSON.stringify(report, null, 2));
|
|
3564
|
+
return;
|
|
3565
|
+
}
|
|
3566
|
+
if (findings.length === 0) {
|
|
3567
|
+
success("No destructive changes detected");
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
console.log(
|
|
3571
|
+
`Validation Summary: ${report.errors.length} error(s), ${report.warnings.length} warning(s)`
|
|
3572
|
+
);
|
|
3573
|
+
const tableOrder = Array.from(new Set(findings.map((finding) => finding.table)));
|
|
3574
|
+
for (const tableName of tableOrder) {
|
|
3575
|
+
console.log(tableName);
|
|
3576
|
+
for (const finding of findings.filter((entry) => entry.table === tableName)) {
|
|
3577
|
+
const target = finding.column ? `${finding.table}.${finding.column}` : finding.table;
|
|
3578
|
+
const typeRange = finding.from && finding.to ? ` (${finding.from} -> ${finding.to})` : "";
|
|
3579
|
+
console.log(
|
|
3580
|
+
`${finding.severity.toUpperCase()}: ${finding.code} ${target}${typeRange} - ${finding.message}`
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
// src/api.ts
|
|
3587
|
+
function captureExitCode() {
|
|
3588
|
+
const raw = process.exitCode;
|
|
3589
|
+
process.exitCode = void 0;
|
|
3590
|
+
return typeof raw === "number" ? raw : 0;
|
|
3591
|
+
}
|
|
3592
|
+
async function runWithResult(fn) {
|
|
3593
|
+
try {
|
|
3594
|
+
await fn();
|
|
3595
|
+
return { exitCode: captureExitCode() };
|
|
3596
|
+
} catch {
|
|
3597
|
+
process.exitCode = void 0;
|
|
3598
|
+
return { exitCode: EXIT_CODES.VALIDATION_ERROR };
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
async function init() {
|
|
3602
|
+
return runWithResult(() => runInit());
|
|
3603
|
+
}
|
|
3604
|
+
async function generate(options = {}) {
|
|
3605
|
+
return runWithResult(() => runGenerate(options));
|
|
3606
|
+
}
|
|
3607
|
+
async function diff(options = {}) {
|
|
3608
|
+
return runWithResult(() => runDiff(options));
|
|
3609
|
+
}
|
|
3610
|
+
async function doctor(options = {}) {
|
|
3611
|
+
return runWithResult(() => runDoctor(options));
|
|
3612
|
+
}
|
|
3613
|
+
async function validate(options = {}) {
|
|
3614
|
+
return runWithResult(() => runValidate(options));
|
|
3615
|
+
}
|
|
3616
|
+
async function introspect(options = {}) {
|
|
3617
|
+
return runWithResult(() => runIntrospect(options));
|
|
3618
|
+
}
|
|
3619
|
+
async function importSchema(inputPath, options = {}) {
|
|
3620
|
+
return runWithResult(() => runImport(inputPath, options));
|
|
3621
|
+
}
|
|
3622
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3623
|
+
0 && (module.exports = {
|
|
3624
|
+
EXIT_CODES,
|
|
3625
|
+
diff,
|
|
3626
|
+
doctor,
|
|
3627
|
+
generate,
|
|
3628
|
+
importSchema,
|
|
3629
|
+
init,
|
|
3630
|
+
introspect,
|
|
3631
|
+
validate
|
|
3632
|
+
});
|