supabee 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import readline from 'node:readline/promises';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import { section, sectionWithNote, ok, warn, error as errText, diffAdd, diffRemove, info, title, pass, recommended, } from '../lib/ui.js';
6
+ import { readToolConfig } from '../lib/config.js';
7
+ import { DEFAULT_TOOL_CONFIG_PATH } from '../lib/constants.js';
8
+ const SPLIT_SEED_PATH = './seeds/split/*.sql';
9
+ const SEED_FILES_NOTE = 'This is to configure the data files you will use for seeding the database';
10
+ function parseOptions(args) {
11
+ const options = { help: false };
12
+ for (let i = 0; i < args.length; i += 1) {
13
+ if (args[i] === '--help' || args[i] === '-h') {
14
+ options.help = true;
15
+ }
16
+ }
17
+ return options;
18
+ }
19
+ function getSeedSection(lines) {
20
+ const start = lines.findIndex((line) => line.trim() === '[db.seed]');
21
+ if (start === -1) {
22
+ return { start: -1, end: -1 };
23
+ }
24
+ let end = lines.length;
25
+ for (let i = start + 1; i < lines.length; i += 1) {
26
+ if (lines[i].trim().startsWith('[')) {
27
+ end = i;
28
+ break;
29
+ }
30
+ }
31
+ return { start, end };
32
+ }
33
+ function getCurrentSeedValues(content) {
34
+ const lines = content.split('\n');
35
+ const section = getSeedSection(lines);
36
+ if (section.start === -1) {
37
+ return {};
38
+ }
39
+ const values = {};
40
+ for (let i = section.start + 1; i < section.end; i += 1) {
41
+ const line = lines[i].trim();
42
+ if (!line || line.startsWith('#'))
43
+ continue;
44
+ if (line.startsWith('enabled =')) {
45
+ values.enabled = line.replace(/^enabled\s*=\s*/, '');
46
+ }
47
+ else if (line.startsWith('sql_paths =')) {
48
+ values.sql_paths = line.replace(/^sql_paths\s*=\s*/, '');
49
+ }
50
+ }
51
+ return values;
52
+ }
53
+ function printCurrentSeedValues(values) {
54
+ if (!values.enabled && !values.sql_paths)
55
+ return;
56
+ console.log(sectionWithNote(title('Current [db.seed] values'), SEED_FILES_NOTE));
57
+ const expectedEnabled = 'true';
58
+ const expectedSqlPaths = `[${`'${SPLIT_SEED_PATH}'`}, ...existing custom paths]`;
59
+ const enabledValue = values.enabled ?? '(missing)';
60
+ if (enabledValue === expectedEnabled) {
61
+ console.log(pass(`enabled = ${enabledValue}`));
62
+ }
63
+ else {
64
+ console.log(`enabled = ${enabledValue} ${recommended(`(recommended: ${expectedEnabled})`)}`);
65
+ }
66
+ const sqlPathsValue = values.sql_paths ?? '(missing)';
67
+ const sqlPathEntries = parseTomlArrayString(values.sql_paths);
68
+ if (sqlPathEntries.includes(SPLIT_SEED_PATH)) {
69
+ console.log(pass(`sql_paths = ${sqlPathsValue}`));
70
+ }
71
+ else {
72
+ console.log(`sql_paths = ${sqlPathsValue} ${recommended(`(recommended: ${expectedSqlPaths})`)}`);
73
+ }
74
+ console.log('');
75
+ }
76
+ function parseTomlArrayString(value) {
77
+ if (!value)
78
+ return [];
79
+ const matches = value.matchAll(/['"]([^'"]+)['"]/g);
80
+ return Array.from(matches, (match) => match[1]);
81
+ }
82
+ function toTomlArrayLiteral(values) {
83
+ return `[${values.map((value) => `'${value}'`).join(', ')}]`;
84
+ }
85
+ function dedupePaths(paths) {
86
+ return Array.from(new Set(paths.filter((entry) => typeof entry === 'string' && entry.trim() !== '')));
87
+ }
88
+ function getConfiguredSeedSqlPaths(toolConfig) {
89
+ const configured = toolConfig?.init?.seedSqlPaths;
90
+ if (!Array.isArray(configured) || configured.length === 0)
91
+ return null;
92
+ return dedupePaths(configured);
93
+ }
94
+ function buildSeedSqlPathsTarget(existingSqlPaths, configuredSeedSqlPaths) {
95
+ if (configuredSeedSqlPaths && configuredSeedSqlPaths.length > 0) {
96
+ return `sql_paths = ${toTomlArrayLiteral(configuredSeedSqlPaths)}`;
97
+ }
98
+ const existing = parseTomlArrayString(existingSqlPaths);
99
+ const merged = dedupePaths([SPLIT_SEED_PATH, ...existing.filter((entry) => entry !== SPLIT_SEED_PATH)]);
100
+ return `sql_paths = ${toTomlArrayLiteral(merged)}`;
101
+ }
102
+ function ensureTrailingNewlineGap(lines) {
103
+ if (lines.length === 0)
104
+ return;
105
+ if (lines[lines.length - 1].trim() !== '') {
106
+ lines.push('');
107
+ }
108
+ }
109
+ function buildPlan(content, configuredSeedSqlPaths) {
110
+ const lines = content.split('\n');
111
+ const plan = [];
112
+ const currentValues = getCurrentSeedValues(content);
113
+ const section = getSeedSection(lines);
114
+ if (section.start === -1) {
115
+ plan.push({
116
+ title: 'Add [db.seed] section',
117
+ before: null,
118
+ after: '[db.seed]',
119
+ apply: (targetLines) => {
120
+ const current = getSeedSection(targetLines);
121
+ if (current.start !== -1)
122
+ return;
123
+ ensureTrailingNewlineGap(targetLines);
124
+ targetLines.push('[db.seed]');
125
+ },
126
+ });
127
+ }
128
+ const enabledTarget = 'enabled = true';
129
+ plan.push({
130
+ title: 'Enable [db.seed].enabled',
131
+ before: null,
132
+ after: enabledTarget,
133
+ apply: (targetLines) => {
134
+ const current = getSeedSection(targetLines);
135
+ if (current.start === -1)
136
+ return;
137
+ for (let i = current.start + 1; i < current.end; i += 1) {
138
+ if (/^\s*enabled\s*=/.test(targetLines[i])) {
139
+ targetLines[i] = enabledTarget;
140
+ return;
141
+ }
142
+ }
143
+ targetLines.splice(current.end, 0, enabledTarget);
144
+ },
145
+ detectBefore: (sourceLines) => {
146
+ const current = getSeedSection(sourceLines);
147
+ if (current.start === -1)
148
+ return null;
149
+ for (let i = current.start + 1; i < current.end; i += 1) {
150
+ if (/^\s*enabled\s*=/.test(sourceLines[i])) {
151
+ return sourceLines[i].trim();
152
+ }
153
+ }
154
+ return null;
155
+ },
156
+ });
157
+ const sqlPathsTarget = buildSeedSqlPathsTarget(currentValues.sql_paths, configuredSeedSqlPaths);
158
+ plan.push({
159
+ title: 'Update [db.seed].sql_paths',
160
+ description: SEED_FILES_NOTE,
161
+ before: null,
162
+ after: sqlPathsTarget,
163
+ apply: (targetLines) => {
164
+ const current = getSeedSection(targetLines);
165
+ if (current.start === -1)
166
+ return;
167
+ for (let i = current.start + 1; i < current.end; i += 1) {
168
+ if (/^\s*sql_paths\s*=/.test(targetLines[i])) {
169
+ targetLines[i] = sqlPathsTarget;
170
+ return;
171
+ }
172
+ }
173
+ targetLines.splice(current.end, 0, sqlPathsTarget);
174
+ },
175
+ detectBefore: (sourceLines) => {
176
+ const current = getSeedSection(sourceLines);
177
+ if (current.start === -1)
178
+ return null;
179
+ for (let i = current.start + 1; i < current.end; i += 1) {
180
+ if (/^\s*sql_paths\s*=/.test(sourceLines[i])) {
181
+ return sourceLines[i].trim();
182
+ }
183
+ }
184
+ return null;
185
+ },
186
+ });
187
+ const effective = [];
188
+ for (const item of plan) {
189
+ const before = item.detectBefore ? item.detectBefore(lines) : item.before;
190
+ if (before === item.after) {
191
+ continue;
192
+ }
193
+ effective.push({
194
+ title: item.title,
195
+ description: item.description,
196
+ before,
197
+ after: item.after,
198
+ apply: item.apply,
199
+ });
200
+ }
201
+ return { lines, changes: effective };
202
+ }
203
+ async function confirmChanges(changes) {
204
+ const rl = readline.createInterface({ input, output });
205
+ const accepted = [];
206
+ try {
207
+ for (const change of changes) {
208
+ console.log(change.description
209
+ ? sectionWithNote(title(change.title), change.description)
210
+ : section(title(change.title)));
211
+ if (change.before) {
212
+ console.log(diffRemove(change.before));
213
+ }
214
+ console.log(diffAdd(change.after));
215
+ console.log('');
216
+ const answer = await rl.question('Apply this change? [y/N] ');
217
+ const isAccepted = answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
218
+ if (isAccepted) {
219
+ accepted.push(change);
220
+ }
221
+ }
222
+ }
223
+ finally {
224
+ rl.close();
225
+ }
226
+ return { accepted };
227
+ }
228
+ function ensureConfigFile() {
229
+ const configDest = path.resolve(process.cwd(), DEFAULT_TOOL_CONFIG_PATH);
230
+ if (fs.existsSync(configDest))
231
+ return false;
232
+ // Look for example config bundled with the package
233
+ const examplePaths = [
234
+ path.resolve(process.cwd(), 'node_modules/supabee/supabee.config.example.json'),
235
+ path.resolve(import.meta.dirname, '../../supabee.config.example.json'),
236
+ ];
237
+ for (const examplePath of examplePaths) {
238
+ if (fs.existsSync(examplePath)) {
239
+ fs.copyFileSync(examplePath, configDest);
240
+ console.log(ok(`Created ${DEFAULT_TOOL_CONFIG_PATH} from example config`));
241
+ console.log(info('Review and adjust the config values for your project.'));
242
+ console.log('');
243
+ return true;
244
+ }
245
+ }
246
+ // Fallback: write minimal default config
247
+ const minimal = {
248
+ schema: {
249
+ input: 'supabase/schemas/prod-schemas.sql',
250
+ output: 'supabase/schemas/split',
251
+ reconstructed: 'supabase/schemas/reconstructed-schemas.sql',
252
+ keepFiles: [],
253
+ },
254
+ data: {
255
+ input: 'supabase/seeds/prod-data.sql',
256
+ output: 'supabase/seeds/split',
257
+ reconstructed: 'supabase/seeds/reconstructed-data.sql',
258
+ maxLinesPerFile: 2000,
259
+ maxStatementsPerFile: 20,
260
+ maxRowsPerInsert: 200,
261
+ tableRules: {},
262
+ keepFiles: [],
263
+ ignoreInReconstruct: [],
264
+ },
265
+ init: {
266
+ seedSqlPaths: ['./seeds/split/*.sql'],
267
+ },
268
+ };
269
+ fs.writeFileSync(configDest, JSON.stringify(minimal, null, 2) + '\n', 'utf8');
270
+ console.log(ok(`Created ${DEFAULT_TOOL_CONFIG_PATH} with defaults`));
271
+ console.log(info('Review and adjust the config values for your project.'));
272
+ console.log('');
273
+ return true;
274
+ }
275
+ export async function runInitCommand(args) {
276
+ const options = parseOptions(args);
277
+ if (options.help) {
278
+ console.log(`init command:
279
+ supabee init
280
+
281
+ Creates supabee.config.json (if missing) and updates supabase/config.toml.
282
+ `);
283
+ return;
284
+ }
285
+ ensureConfigFile();
286
+ const configPath = path.resolve(process.cwd(), 'supabase/config.toml');
287
+ const toolConfig = readToolConfig();
288
+ const configuredSeedSqlPaths = getConfiguredSeedSqlPaths(toolConfig);
289
+ if (!fs.existsSync(configPath)) {
290
+ console.error(errText(`config.toml not found: ${configPath}`));
291
+ process.exit(1);
292
+ }
293
+ const original = fs.readFileSync(configPath, 'utf8');
294
+ const { changes } = buildPlan(original, configuredSeedSqlPaths);
295
+ if (changes.length === 0) {
296
+ console.log(ok(`No changes needed: ${configPath}`));
297
+ printCurrentSeedValues(getCurrentSeedValues(original));
298
+ return;
299
+ }
300
+ const { accepted: selectedChanges } = await confirmChanges(changes);
301
+ if (selectedChanges.length === 0) {
302
+ console.log('');
303
+ console.log(warn('No changes selected. Nothing written.'));
304
+ printCurrentSeedValues(getCurrentSeedValues(original));
305
+ return;
306
+ }
307
+ const nextLines = original.split('\n');
308
+ for (const change of selectedChanges) {
309
+ change.apply(nextLines);
310
+ }
311
+ const updated = nextLines.join('\n');
312
+ if (updated === original) {
313
+ console.log(info('Selected changes resulted in no file modifications.'));
314
+ printCurrentSeedValues(getCurrentSeedValues(original));
315
+ return;
316
+ }
317
+ fs.writeFileSync(configPath, updated, 'utf8');
318
+ console.log('');
319
+ console.log(ok(`Updated seed config in: ${configPath}`));
320
+ printCurrentSeedValues(getCurrentSeedValues(updated));
321
+ }
322
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function runSchemaCommand(args: string[]): Promise<void>;
@@ -0,0 +1,301 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { stat, readdir } from 'node:fs/promises';
4
+ import crypto from 'node:crypto';
5
+ import { ok, info, error as errText } from '../lib/ui.js';
6
+ import { resolveCommandConfig } from '../lib/config.js';
7
+ import { prepareSplitOutputDir } from '../lib/output.js';
8
+ import { resolveStepPaths } from '../lib/command-contract.js';
9
+ import { runStepCommand } from '../lib/step-command.js';
10
+ const SCHEMA_FOLDERS = {
11
+ extensions: '00_extensions',
12
+ setup: '01_setup',
13
+ types: '02_types',
14
+ functions: '03_functions',
15
+ tables: '04_tables',
16
+ views: '05_views',
17
+ constraints: '06_constraints',
18
+ indexes: '07_indexes',
19
+ foreign_keys: '08_foreign_keys',
20
+ rls: '09_rls',
21
+ permissions: '10_permissions',
22
+ ownership: '11_ownership',
23
+ others: '12_others',
24
+ };
25
+ function buildRuntimeConfig(options) {
26
+ const resolved = resolveCommandConfig('schema', options);
27
+ return {
28
+ ...resolved,
29
+ indexPath: path.join(resolved.outputDir, 'order-index.json'),
30
+ folders: SCHEMA_FOLDERS,
31
+ };
32
+ }
33
+ function sanitizeForFilename(name) {
34
+ return name.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
35
+ }
36
+ function createFilename(name, seqNum) {
37
+ return `${String(seqNum).padStart(4, '0')}_${sanitizeForFilename(name)}.sql`;
38
+ }
39
+ function extractStatements(sql) {
40
+ const normalizedSql = sql.replace(/\r\n/g, '\n');
41
+ const results = {
42
+ extensions: [],
43
+ setup: [],
44
+ types: [],
45
+ functions: [],
46
+ tables: [],
47
+ views: [],
48
+ constraints: [],
49
+ indexes: [],
50
+ foreign_keys: [],
51
+ rls: [],
52
+ permissions: [],
53
+ ownership: [],
54
+ others: [],
55
+ };
56
+ const statements = [];
57
+ let current = '';
58
+ let inDollarQuote = false;
59
+ let dollarTag = '';
60
+ let inBlockComment = false;
61
+ let lineStart = 1;
62
+ const lines = normalizedSql.split('\n');
63
+ for (let i = 0; i < lines.length; i += 1) {
64
+ const line = lines[i];
65
+ const lineNum = i + 1;
66
+ if (line.includes('/*') && !inDollarQuote && !inBlockComment)
67
+ inBlockComment = true;
68
+ if (line.includes('*/') && inBlockComment)
69
+ inBlockComment = false;
70
+ if (!inDollarQuote) {
71
+ const dollarStart = line.match(/\$([a-zA-Z0-9_]*)\$/);
72
+ if (dollarStart && !line.includes(dollarStart[0], line.indexOf(dollarStart[0]) + dollarStart[0].length)) {
73
+ inDollarQuote = true;
74
+ dollarTag = dollarStart[1];
75
+ }
76
+ }
77
+ else {
78
+ const dollarEndPattern = new RegExp(`\\$${dollarTag}\\$`);
79
+ if (line.match(dollarEndPattern)) {
80
+ inDollarQuote = false;
81
+ dollarTag = '';
82
+ }
83
+ }
84
+ current += `${line}\n`;
85
+ if (line.trim().endsWith(';') && !inDollarQuote && !inBlockComment) {
86
+ statements.push({ sql: current, lineStart, lineEnd: lineNum });
87
+ current = '';
88
+ lineStart = lineNum + 1;
89
+ }
90
+ }
91
+ if (current.trim())
92
+ statements.push({ sql: current, lineStart, lineEnd: lines.length });
93
+ const seqNumbers = {
94
+ extensions: 1,
95
+ setup: 1,
96
+ types: 1,
97
+ functions: 1,
98
+ tables: 1,
99
+ views: 1,
100
+ constraints: 1,
101
+ indexes: 1,
102
+ foreign_keys: 1,
103
+ rls: 1,
104
+ permissions: 1,
105
+ ownership: 1,
106
+ others: 1,
107
+ };
108
+ for (const statement of statements) {
109
+ let categorySql = statement.sql;
110
+ while (categorySql.trim().startsWith('--')) {
111
+ categorySql = categorySql.substring(categorySql.indexOf('\n') + 1);
112
+ }
113
+ categorySql = categorySql.trim();
114
+ const firstLine = categorySql.split('\n')[0].trim();
115
+ const lineRange = `${statement.lineStart}-${statement.lineEnd}`;
116
+ const push = (category, objectName) => {
117
+ const seqNum = seqNumbers[category]++;
118
+ results[category].push({
119
+ sql: statement.sql,
120
+ name: objectName,
121
+ filename: createFilename(objectName, seqNum),
122
+ lineRange,
123
+ });
124
+ };
125
+ if (firstLine.match(/^CREATE\s+EXTENSION\b/i)) {
126
+ push('extensions', categorySql.match(/CREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'extension');
127
+ }
128
+ else if (firstLine.match(/^SET\s+|^SELECT\s+pg_catalog\.set_config|^COMMENT\s+ON\s+SCHEMA/i)) {
129
+ push('setup', `setup_${seqNumbers.setup}`);
130
+ }
131
+ else if (firstLine.match(/^CREATE\s+TYPE\b/i) || categorySql.includes('CREATE TYPE')) {
132
+ push('types', categorySql.match(/CREATE\s+TYPE\s+(?:"?public"?\.)?"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'type');
133
+ }
134
+ else if (firstLine.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\b/i) || categorySql.includes('CREATE FUNCTION')) {
135
+ push('functions', categorySql.match(/CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(?:"?public"?\.)?"?([a-zA-Z0-9_]+)"?/i)?.[1] ||
136
+ 'function');
137
+ }
138
+ else if (firstLine.match(/^CREATE\s+TABLE\b/i) || categorySql.includes('CREATE TABLE')) {
139
+ push('tables', categorySql.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"?public"?\.)?"?([a-zA-Z0-9_]+)"?/i)?.[1] ||
140
+ 'table');
141
+ }
142
+ else if (firstLine.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\b/i) || categorySql.includes('CREATE VIEW')) {
143
+ push('views', categorySql.match(/CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:"?public"?\.)?"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'view');
144
+ }
145
+ else if (firstLine.match(/^\s*ALTER\s+TABLE\s+.*\bADD\s+CONSTRAINT\b.*\b(PRIMARY\s+KEY|UNIQUE)\b/i)) {
146
+ push('constraints', categorySql.match(/ADD\s+CONSTRAINT\s+"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'constraint');
147
+ }
148
+ else if (firstLine.match(/^CREATE\s+(?:UNIQUE\s+)?INDEX\b/i) || categorySql.includes('CREATE INDEX')) {
149
+ push('indexes', categorySql.match(/CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'index');
150
+ }
151
+ else if (firstLine.match(/^\s*ALTER\s+TABLE\s+.*\bADD\s+CONSTRAINT\b.*\bFOREIGN\s+KEY\b/i)) {
152
+ push('foreign_keys', categorySql.match(/ADD\s+CONSTRAINT\s+"?([a-zA-Z0-9_]+)"?/i)?.[1] || 'fk');
153
+ }
154
+ else if (firstLine.match(/^ALTER\s+TABLE\s+.*\bENABLE\s+ROW\s+LEVEL\s+SECURITY\b/i) ||
155
+ firstLine.match(/^CREATE\s+POLICY\b/i)) {
156
+ push('rls', firstLine.includes('CREATE POLICY') ? categorySql.match(/CREATE\s+POLICY\s+"([^"]+)"/i)?.[1] || 'policy' : 'rls_enable');
157
+ }
158
+ else if (firstLine.match(/^GRANT\s+/i)) {
159
+ push('permissions', `grant_${seqNumbers.permissions}`);
160
+ }
161
+ else if (firstLine.match(/^ALTER\s+.*\bOWNER\s+TO\b/i)) {
162
+ push('ownership', `ownership_${seqNumbers.ownership}`);
163
+ }
164
+ else {
165
+ push('others', `other_${seqNumbers.others}`);
166
+ }
167
+ }
168
+ return results;
169
+ }
170
+ async function writeStatements(config, statements) {
171
+ for (const folder of Object.values(config.folders)) {
172
+ await fs.promises.mkdir(path.join(config.outputDir, folder), { recursive: true });
173
+ }
174
+ const allStatements = [];
175
+ for (const [category, items] of Object.entries(statements)) {
176
+ const typedCategory = category;
177
+ for (const item of items) {
178
+ allStatements.push({
179
+ category: typedCategory,
180
+ filename: item.filename,
181
+ lineRange: item.lineRange,
182
+ path: path.join(config.folders[typedCategory], item.filename),
183
+ });
184
+ const filePath = path.join(config.outputDir, config.folders[typedCategory], item.filename);
185
+ const metadataComment = '-- Original SQL from lines ' + item.lineRange + ' in prod.sql\n\n';
186
+ await fs.promises.writeFile(filePath, metadataComment + item.sql, 'utf8');
187
+ }
188
+ }
189
+ allStatements.sort((a, b) => parseInt(a.lineRange.split('-')[0], 10) - parseInt(b.lineRange.split('-')[0], 10));
190
+ const indexData = allStatements.map((item) => {
191
+ const [startLine, endLine] = item.lineRange.split('-').map((n) => parseInt(n, 10));
192
+ return { path: item.path, startLine, endLine };
193
+ });
194
+ await fs.promises.writeFile(config.indexPath, JSON.stringify(indexData, null, 2), 'utf8');
195
+ console.log(ok(`Wrote ${indexData.length} index entries: ${config.indexPath}`));
196
+ }
197
+ async function reconstructSql(config) {
198
+ let reconstructed = '';
199
+ const processedFiles = [];
200
+ for (const folder of Object.values(config.folders)) {
201
+ const folderPath = path.join(config.outputDir, folder);
202
+ try {
203
+ const stats = await stat(folderPath);
204
+ if (!stats.isDirectory())
205
+ continue;
206
+ }
207
+ catch {
208
+ continue;
209
+ }
210
+ const files = await readdir(folderPath);
211
+ for (const file of files) {
212
+ if (path.extname(file) !== '.sql')
213
+ continue;
214
+ const content = await fs.promises.readFile(path.join(folderPath, file), 'utf8');
215
+ const lineRangeMatch = content.match(/^-- Original SQL from lines (\d+)-(\d+)/);
216
+ if (!lineRangeMatch)
217
+ continue;
218
+ processedFiles.push({
219
+ startLine: parseInt(lineRangeMatch[1], 10),
220
+ content: content.replace(/^-- Original SQL from lines.*?\n\n/s, ''),
221
+ });
222
+ }
223
+ }
224
+ processedFiles.sort((a, b) => a.startLine - b.startLine);
225
+ for (const file of processedFiles) {
226
+ reconstructed += file.content;
227
+ if (!file.content.endsWith('\n'))
228
+ reconstructed += '\n';
229
+ }
230
+ await fs.promises.writeFile(config.reconstructedFile, reconstructed, 'utf8');
231
+ console.log(ok(`Reconstructed SQL written to: ${config.reconstructedFile}`));
232
+ }
233
+ async function validateReconstruction(config) {
234
+ const originalContent = await fs.promises.readFile(config.inputFile, 'utf8');
235
+ const reconstructedContent = await fs.promises.readFile(config.reconstructedFile, 'utf8');
236
+ const originalMd5 = crypto.createHash('md5').update(originalContent).digest('hex');
237
+ const reconstructedMd5 = crypto.createHash('md5').update(reconstructedContent).digest('hex');
238
+ if (originalMd5 === reconstructedMd5) {
239
+ console.log(ok('Validation successful: files match exactly.'));
240
+ return true;
241
+ }
242
+ console.log(errText('Validation failed: files do not match.'));
243
+ console.log(info(`Original MD5: ${originalMd5}`));
244
+ console.log(info(`Reconstructed MD5: ${reconstructedMd5}`));
245
+ return false;
246
+ }
247
+ export async function runSchemaCommand(args) {
248
+ await runStepCommand(args, {
249
+ commandName: 'schema',
250
+ commandDisplayName: 'Schema',
251
+ detailNote: (step) => ({
252
+ split: ' Splitting schemas into files...',
253
+ reconstruct: ' Reconstructing schema SQL from split files...',
254
+ validate: ' Validating reconstructed schema against original...',
255
+ })[step],
256
+ buildDefaults: () => buildRuntimeConfig({}),
257
+ buildStepConfig: ({ step, defaults, options, subcommand }) => {
258
+ const stepPaths = resolveStepPaths({ defaults, options, subcommand, step });
259
+ return {
260
+ ...defaults,
261
+ ...stepPaths,
262
+ indexPath: path.join(stepPaths.outputDir, 'order-index.json'),
263
+ };
264
+ },
265
+ getDetailLines: (step, config) => {
266
+ if (step === 'split') {
267
+ return [
268
+ `input = ${config.inputFile}`,
269
+ `output = ${config.outputDir}`,
270
+ `keepFiles = ${config.keepFiles.length}`,
271
+ ];
272
+ }
273
+ if (step === 'reconstruct') {
274
+ return [
275
+ `input = ${config.outputDir}`,
276
+ `reconstructed = ${config.reconstructedFile}`,
277
+ ];
278
+ }
279
+ return [
280
+ `input = ${config.inputFile}`,
281
+ `reconstructed = ${config.reconstructedFile}`,
282
+ ];
283
+ },
284
+ actions: {
285
+ split: async ({ config, options }) => {
286
+ await prepareSplitOutputDir(config.outputDir, options.backup, 'Schema', { keepFiles: config.keepFiles });
287
+ const sqlContent = await fs.promises.readFile(config.inputFile, 'utf8');
288
+ const statements = extractStatements(sqlContent);
289
+ await writeStatements(config, statements);
290
+ console.log(ok(`Schema split completed. Output: ${config.outputDir}`));
291
+ },
292
+ reconstruct: async ({ config }) => {
293
+ await reconstructSql(config);
294
+ },
295
+ validate: async ({ config }) => {
296
+ return validateReconstruction(config);
297
+ },
298
+ },
299
+ });
300
+ }
301
+ //# sourceMappingURL=schema.js.map