supatool 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -285
- package/dist/bin/helptext.js +4 -3
- package/dist/bin/supatool.js +182 -74
- package/dist/generator/client.js +1 -2
- package/dist/generator/crudGenerator.js +22 -23
- package/dist/generator/docGenerator.js +12 -13
- package/dist/generator/rlsGenerator.js +21 -21
- package/dist/generator/sqlGenerator.js +20 -21
- package/dist/generator/typeGenerator.js +6 -7
- package/dist/index.js +23 -23
- package/dist/parser/modelParser.js +3 -4
- package/dist/sync/config.js +10 -10
- package/dist/sync/definitionExtractor.js +489 -321
- package/dist/sync/fetchRemoteSchemas.js +59 -43
- package/dist/sync/generateMigration.js +42 -42
- package/dist/sync/parseLocalSchemas.js +14 -11
- package/dist/sync/seedGenerator.js +15 -14
- package/dist/sync/sync.js +75 -140
- package/dist/sync/utils.js +19 -7
- package/dist/sync/writeSchema.js +22 -22
- package/package.json +8 -8
package/dist/sync/sync.js
CHANGED
|
@@ -7,102 +7,90 @@ const writeSchema_1 = require("./writeSchema");
|
|
|
7
7
|
const generateMigration_1 = require("./generateMigration");
|
|
8
8
|
const diff_1 = require("diff");
|
|
9
9
|
const utils_1 = require("./utils");
|
|
10
|
-
//
|
|
10
|
+
// Global approval state (shared with writeSchema.ts)
|
|
11
11
|
let globalApproveAll = false;
|
|
12
12
|
/**
|
|
13
|
-
* DDL
|
|
13
|
+
* Normalize DDL string (unify spaces, newlines, tabs)
|
|
14
14
|
*/
|
|
15
15
|
function normalizeDDL(ddl) {
|
|
16
16
|
return ddl
|
|
17
|
-
.replace(/\s+/g, ' ') //
|
|
18
|
-
.replace(/;\s+/g, ';\n') //
|
|
19
|
-
.trim(); //
|
|
17
|
+
.replace(/\s+/g, ' ') // Replace consecutive whitespace with single space
|
|
18
|
+
.replace(/;\s+/g, ';\n') // Add newline after semicolon
|
|
19
|
+
.trim(); // Remove leading/trailing whitespace
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* SQL
|
|
22
|
+
* Format SQL for readability
|
|
23
23
|
*/
|
|
24
24
|
function formatSQL(sql) {
|
|
25
25
|
return sql
|
|
26
|
-
.replace(/,\s*/g, ',\n ') //
|
|
27
|
-
.replace(/\(\s*/g, ' (\n ') //
|
|
28
|
-
.replace(/\s*\)/g, '\n)') //
|
|
29
|
-
.replace(/\bCREATE\s+TABLE\b/g, '\nCREATE TABLE') // CREATE TABLE
|
|
30
|
-
.replace(/\bPRIMARY\s+KEY\b/g, '\n PRIMARY KEY') // PRIMARY KEY
|
|
31
|
-
.replace(/;\s*/g, ';\n') //
|
|
26
|
+
.replace(/,\s*/g, ',\n ') // Add newline and indent after comma
|
|
27
|
+
.replace(/\(\s*/g, ' (\n ') // Add newline and indent after opening parenthesis
|
|
28
|
+
.replace(/\s*\)/g, '\n)') // Add newline before closing parenthesis
|
|
29
|
+
.replace(/\bCREATE\s+TABLE\b/g, '\nCREATE TABLE') // Add newline before CREATE TABLE
|
|
30
|
+
.replace(/\bPRIMARY\s+KEY\b/g, '\n PRIMARY KEY') // Add newline and indent before PRIMARY KEY
|
|
31
|
+
.replace(/;\s*/g, ';\n') // Add newline after semicolon
|
|
32
32
|
.split('\n')
|
|
33
33
|
.map(line => line.trim())
|
|
34
34
|
.filter(line => line)
|
|
35
35
|
.join('\n');
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Synchronize all table schemas
|
|
39
39
|
*/
|
|
40
|
-
async function syncAllTables({ connectionString, schemaDir, tablePattern = '*', force = false }) {
|
|
41
|
-
//
|
|
40
|
+
async function syncAllTables({ connectionString, schemaDir, tablePattern = '*', force = false, dryRun = false, generateOnly = false, requireConfirmation = false }) {
|
|
41
|
+
// Reset approval state
|
|
42
42
|
(0, writeSchema_1.resetApprovalState)();
|
|
43
43
|
const localSchemas = await (0, parseLocalSchemas_1.parseLocalSchemas)(schemaDir);
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
// Filter local tables based on pattern first
|
|
45
|
+
const targetLocalTables = Object.keys(localSchemas).filter(tableName => (0, utils_1.wildcardMatch)(tableName, tablePattern));
|
|
46
|
+
console.log(`Pattern: ${tablePattern}`);
|
|
47
|
+
console.log(`Schema directory: ${schemaDir}`);
|
|
48
|
+
console.log(`Available local tables: ${Object.keys(localSchemas).join(', ') || 'none'}`);
|
|
49
|
+
console.log(`Matched local tables: ${targetLocalTables.join(', ') || 'none'}`);
|
|
50
|
+
if (targetLocalTables.length === 0) {
|
|
51
|
+
console.log('No matching local tables found for the specified pattern.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Fetch only the remote schemas for target tables
|
|
55
|
+
const remoteSchemas = await (0, fetchRemoteSchemas_1.fetchRemoteSchemas)(connectionString, targetLocalTables);
|
|
56
|
+
for (const tableName of targetLocalTables) {
|
|
53
57
|
const local = localSchemas[tableName];
|
|
54
58
|
const remote = remoteSchemas[tableName];
|
|
59
|
+
// Always use local as the source (never overwrite local files)
|
|
55
60
|
if (local && !remote) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// Local only: generate CREATE TABLE migration
|
|
62
|
+
console.log(`[${tableName}] Local only - will generate CREATE TABLE migration`);
|
|
63
|
+
if (dryRun) {
|
|
64
|
+
console.log(`[${tableName}] 🔍 CREATE TABLE migration will be generated`);
|
|
65
|
+
console.log(`[${tableName}] 📄 Migration file: supabase/migrations/YYYYMMDDHHMMSS_create_${tableName}.sql`);
|
|
66
|
+
}
|
|
67
|
+
else if (generateOnly) {
|
|
68
|
+
console.log(`[${tableName}] 📝 generateOnly mode: skipping migration generation for local-only table`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Generate CREATE TABLE migration
|
|
72
|
+
// For now, skip actual migration generation for local-only tables
|
|
73
|
+
console.log(`[${tableName}] CREATE TABLE migration generation not yet implemented`);
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
else if (local && remote) {
|
|
66
|
-
//
|
|
77
|
+
// Both local and remote exist: generate UPDATE migration (local → remote)
|
|
67
78
|
const normalizedLocal = local.normalizedDdl;
|
|
68
79
|
const normalizedRemote = normalizeDDL(remote.ddl);
|
|
69
80
|
const diff = (0, diff_1.diffLines)(normalizedLocal, normalizedRemote);
|
|
70
81
|
const hasDiff = diff.some(part => part.added || part.removed);
|
|
71
|
-
// 差分がある場合のみ処理・表示
|
|
72
82
|
if (hasDiff) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const isLocalFileNewer = local.fileTimestamp > remote.timestamp;
|
|
76
|
-
const timeDiff = remote.timestamp - local.timestamp;
|
|
77
|
-
const fileDiff = local.fileTimestamp - remote.timestamp;
|
|
78
|
-
const timeDiffHours = Math.abs(timeDiff) / 3600;
|
|
79
|
-
const fileDiffHours = Math.abs(fileDiff) / 3600;
|
|
80
|
-
// ローカルファイルの方が新しい場合はリモートへマイグレーション提案
|
|
81
|
-
if (isLocalFileNewer) {
|
|
82
|
-
console.log(`[${tableName}] ローカルが ${fileDiffHours.toFixed(1)}時間新しい - マイグレーション生成`);
|
|
83
|
-
}
|
|
84
|
-
else if (isRemoteNewer) {
|
|
85
|
-
console.log(`[${tableName}] リモートが ${timeDiffHours.toFixed(1)}時間新しい - ローカル更新`);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
console.log(`[${tableName}] 差分を検出 - 確認が必要`);
|
|
89
|
-
}
|
|
90
|
-
// 整形されたSQLで行単位の差分を表示
|
|
83
|
+
console.log(`[${tableName}] Differences detected - will generate UPDATE migration`);
|
|
84
|
+
// Show formatted diff (local → remote)
|
|
91
85
|
const formattedLocal = formatSQL(normalizedLocal);
|
|
92
86
|
const formattedRemote = formatSQL(normalizedRemote);
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
const lineDiff = isLocalFileNewer
|
|
96
|
-
? (0, diff_1.diffLines)(formattedRemote, formattedLocal) // ローカル→リモート(マイグレーション用)
|
|
97
|
-
: (0, diff_1.diffLines)(formattedLocal, formattedRemote); // リモート→ローカル(ローカル更新用)
|
|
98
|
-
// 前後のコンテキスト行数
|
|
87
|
+
const lineDiff = (0, diff_1.diffLines)(formattedRemote, formattedLocal);
|
|
88
|
+
// Display diff for confirmation
|
|
99
89
|
const contextLines = 1;
|
|
100
|
-
let lineNumber = 0;
|
|
101
90
|
let outputLines = [];
|
|
102
91
|
lineDiff.forEach((part, index) => {
|
|
103
92
|
const lines = part.value.split('\n').filter(line => line.trim() || index === lineDiff.length - 1);
|
|
104
93
|
if (part.added) {
|
|
105
|
-
// リモートから追加される行
|
|
106
94
|
lines.forEach(line => {
|
|
107
95
|
if (line.trim()) {
|
|
108
96
|
outputLines.push(`\x1b[32m+ ${line}\x1b[0m`);
|
|
@@ -110,99 +98,46 @@ async function syncAllTables({ connectionString, schemaDir, tablePattern = '*',
|
|
|
110
98
|
});
|
|
111
99
|
}
|
|
112
100
|
else if (part.removed) {
|
|
113
|
-
// ローカルから削除される行
|
|
114
101
|
lines.forEach(line => {
|
|
115
102
|
if (line.trim()) {
|
|
116
103
|
outputLines.push(`\x1b[31m- ${line}\x1b[0m`);
|
|
117
104
|
}
|
|
118
105
|
});
|
|
119
106
|
}
|
|
120
|
-
else {
|
|
121
|
-
// 変更されていない行(コンテキスト)
|
|
122
|
-
const unchangedLines = lines.filter(line => line.trim());
|
|
123
|
-
// 差分の前後で適切にコンテキストを表示
|
|
124
|
-
const hasChangeBefore = index > 0 && (lineDiff[index - 1].added || lineDiff[index - 1].removed);
|
|
125
|
-
const hasChangeAfter = index < lineDiff.length - 1 && (lineDiff[index + 1].added || lineDiff[index + 1].removed);
|
|
126
|
-
if (hasChangeBefore && hasChangeAfter) {
|
|
127
|
-
// 前後に変更がある場合は全行表示
|
|
128
|
-
unchangedLines.forEach(line => {
|
|
129
|
-
outputLines.push(` ${line}`);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
else if (hasChangeBefore) {
|
|
133
|
-
// 前に変更がある場合は最初の数行のみ表示
|
|
134
|
-
const showLines = Math.min(contextLines, unchangedLines.length);
|
|
135
|
-
for (let i = 0; i < showLines; i++) {
|
|
136
|
-
outputLines.push(` ${unchangedLines[i]}`);
|
|
137
|
-
}
|
|
138
|
-
if (unchangedLines.length > contextLines) {
|
|
139
|
-
outputLines.push(` \x1b[36m...(${unchangedLines.length - contextLines} more lines)...\x1b[0m`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else if (hasChangeAfter) {
|
|
143
|
-
// 後に変更がある場合は最後の数行のみ表示
|
|
144
|
-
const showLines = Math.min(contextLines, unchangedLines.length);
|
|
145
|
-
const startIndex = unchangedLines.length - showLines;
|
|
146
|
-
// 冒頭のCREATE TABLE行は常に表示
|
|
147
|
-
const hasCreateTable = unchangedLines.some(line => line.trim().toUpperCase().startsWith('CREATE TABLE'));
|
|
148
|
-
if (hasCreateTable && startIndex > 0) {
|
|
149
|
-
// CREATE TABLE行を探して表示
|
|
150
|
-
const createTableIndex = unchangedLines.findIndex(line => line.trim().toUpperCase().startsWith('CREATE TABLE'));
|
|
151
|
-
if (createTableIndex >= 0) {
|
|
152
|
-
outputLines.push(` ${unchangedLines[createTableIndex]}`);
|
|
153
|
-
if (createTableIndex < startIndex - 1) {
|
|
154
|
-
outputLines.push(` \x1b[36m...(${startIndex - createTableIndex - 1} lines)...\x1b[0m`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else if (startIndex > 0) {
|
|
159
|
-
outputLines.push(` \x1b[36m...(${startIndex} lines)...\x1b[0m`);
|
|
160
|
-
}
|
|
161
|
-
for (let i = startIndex; i < unchangedLines.length; i++) {
|
|
162
|
-
outputLines.push(` ${unchangedLines[i]}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
else if (unchangedLines.length <= contextLines * 2) {
|
|
166
|
-
// 短い場合は全部表示
|
|
167
|
-
unchangedLines.forEach(line => {
|
|
168
|
-
outputLines.push(` ${line}`);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// 長い場合でも冒頭のCREATE TABLE行は表示
|
|
173
|
-
const hasCreateTable = unchangedLines.some(line => line.trim().toUpperCase().startsWith('CREATE TABLE'));
|
|
174
|
-
if (hasCreateTable) {
|
|
175
|
-
const createTableIndex = unchangedLines.findIndex(line => line.trim().toUpperCase().startsWith('CREATE TABLE'));
|
|
176
|
-
if (createTableIndex >= 0) {
|
|
177
|
-
outputLines.push(` ${unchangedLines[createTableIndex]}`);
|
|
178
|
-
if (unchangedLines.length > 1) {
|
|
179
|
-
outputLines.push(` \x1b[36m...(${unchangedLines.length - 1} unchanged lines)...\x1b[0m`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
outputLines.push(` \x1b[36m...(${unchangedLines.length} unchanged lines)...\x1b[0m`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
107
|
});
|
|
189
|
-
outputLines.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
108
|
+
if (outputLines.length > 0) {
|
|
109
|
+
console.log(`[${tableName}] Changes to apply:`);
|
|
110
|
+
outputLines.forEach(line => console.log(line));
|
|
111
|
+
}
|
|
112
|
+
// Generate or preview migration
|
|
113
|
+
if (dryRun) {
|
|
114
|
+
console.log(`[${tableName}] 🔍 UPDATE migration will be generated`);
|
|
115
|
+
console.log(`[${tableName}] 📄 File to be generated: supabase/migrations/YYYYMMDDHHMMSS_update_${tableName}.sql`);
|
|
116
|
+
}
|
|
117
|
+
else if (generateOnly) {
|
|
118
|
+
console.log(`[${tableName}] 📝 generateOnly mode: skipping migration generation`);
|
|
195
119
|
}
|
|
196
120
|
else {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
121
|
+
// Confirmation check for confirmation mode
|
|
122
|
+
if (requireConfirmation && !force) {
|
|
123
|
+
const confirmed = await (0, utils_1.askUserConfirmation)(`Generate UPDATE migration for table ${tableName}?`);
|
|
124
|
+
if (!confirmed) {
|
|
125
|
+
console.log(`[${tableName}] Skipped by user confirmation`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Generate migration file (local → remote diff)
|
|
130
|
+
const migrationPath = await (0, generateMigration_1.generateMigrationFile)(tableName, normalizedRemote, // from (current remote state)
|
|
131
|
+
normalizedLocal, // to (local target state)
|
|
132
|
+
process.cwd());
|
|
133
|
+
if (migrationPath) {
|
|
134
|
+
console.log(`[${tableName}] 📝 UPDATE migration generated: ${migrationPath}`);
|
|
202
135
|
}
|
|
203
136
|
}
|
|
204
137
|
}
|
|
205
|
-
|
|
138
|
+
else {
|
|
139
|
+
console.log(`[${tableName}] No differences found`);
|
|
140
|
+
}
|
|
206
141
|
}
|
|
207
142
|
}
|
|
208
143
|
}
|
package/dist/sync/utils.js
CHANGED
|
@@ -3,23 +3,35 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.wildcardMatch = wildcardMatch;
|
|
5
5
|
exports.askUserConfirmation = askUserConfirmation;
|
|
6
|
-
//
|
|
6
|
+
// Wildcard matching function
|
|
7
7
|
function wildcardMatch(str, pattern) {
|
|
8
|
-
|
|
8
|
+
if (pattern === '*') {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
// Exact match if pattern contains no wildcards
|
|
12
|
+
if (!pattern.includes('*') && !pattern.includes('?')) {
|
|
13
|
+
return str === pattern;
|
|
14
|
+
}
|
|
15
|
+
// Simple wildcard pattern matching
|
|
16
|
+
const regexPattern = pattern
|
|
17
|
+
.replace(/\*/g, '.*')
|
|
18
|
+
.replace(/\?/g, '.');
|
|
19
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
20
|
+
return regex.test(str);
|
|
9
21
|
}
|
|
10
22
|
/**
|
|
11
|
-
*
|
|
23
|
+
* Ask user for confirmation (immediate single character input)
|
|
12
24
|
*/
|
|
13
25
|
function askUserConfirmation(message, options) {
|
|
14
26
|
return new Promise((resolve) => {
|
|
15
27
|
const promptMessage = options?.message || '(y/N): ';
|
|
16
28
|
process.stdout.write(`${message} ${promptMessage}`);
|
|
17
|
-
// raw
|
|
29
|
+
// Enable raw mode to receive single character input
|
|
18
30
|
process.stdin.setRawMode(true);
|
|
19
31
|
process.stdin.resume();
|
|
20
32
|
process.stdin.setEncoding('utf8');
|
|
21
33
|
const onData = (key) => {
|
|
22
|
-
//
|
|
34
|
+
// Cleanup
|
|
23
35
|
process.stdin.setRawMode(false);
|
|
24
36
|
process.stdin.pause();
|
|
25
37
|
process.stdin.removeListener('data', onData);
|
|
@@ -33,7 +45,7 @@ function askUserConfirmation(message, options) {
|
|
|
33
45
|
resolve('all');
|
|
34
46
|
}
|
|
35
47
|
else if (lowerKey === 'n' || key === '\r' || key === '\n' || key === '\u0003') {
|
|
36
|
-
// n
|
|
48
|
+
// n, Enter, or Ctrl+C
|
|
37
49
|
if (key === '\u0003') {
|
|
38
50
|
console.log('\nOperation cancelled');
|
|
39
51
|
process.exit(0);
|
|
@@ -42,7 +54,7 @@ function askUserConfirmation(message, options) {
|
|
|
42
54
|
resolve(false);
|
|
43
55
|
}
|
|
44
56
|
else {
|
|
45
|
-
//
|
|
57
|
+
// Invalid key: default to false
|
|
46
58
|
console.log(`${key} (invalid input, treating as N)`);
|
|
47
59
|
resolve(false);
|
|
48
60
|
}
|
package/dist/sync/writeSchema.js
CHANGED
|
@@ -40,16 +40,16 @@ exports.moveToBackup = moveToBackup;
|
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
|
-
//
|
|
43
|
+
// Manage global "approve all" state
|
|
44
44
|
let globalApproveAll = false;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Reset approve all mode
|
|
47
47
|
*/
|
|
48
48
|
function resetApprovalState() {
|
|
49
49
|
globalApproveAll = false;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* Move existing file to backup directory
|
|
53
53
|
*/
|
|
54
54
|
async function backupExistingFile(filePath, schemaDir, force = false) {
|
|
55
55
|
if (!fs.existsSync(filePath)) {
|
|
@@ -57,16 +57,16 @@ async function backupExistingFile(filePath, schemaDir, force = false) {
|
|
|
57
57
|
}
|
|
58
58
|
if (!force && !globalApproveAll) {
|
|
59
59
|
const fileName = path.basename(filePath);
|
|
60
|
-
const response = await (0, utils_1.askUserConfirmation)(`↓
|
|
60
|
+
const response = await (0, utils_1.askUserConfirmation)(`↓ Overwrite existing file ${fileName}?`, {
|
|
61
61
|
allowAll: true,
|
|
62
62
|
message: '(y/N/a[all]): '
|
|
63
63
|
});
|
|
64
64
|
if (response === 'all') {
|
|
65
65
|
globalApproveAll = true;
|
|
66
|
-
console.log('✅
|
|
66
|
+
console.log('✅ Approving all files');
|
|
67
67
|
}
|
|
68
68
|
else if (response === false) {
|
|
69
|
-
console.log('
|
|
69
|
+
console.log('Skipped');
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -79,11 +79,11 @@ async function backupExistingFile(filePath, schemaDir, force = false) {
|
|
|
79
79
|
const backupFileName = `${timestamp}_${fileName}`;
|
|
80
80
|
const backupPath = path.join(backupDir, backupFileName);
|
|
81
81
|
fs.renameSync(filePath, backupPath);
|
|
82
|
-
console.log(
|
|
82
|
+
console.log(`Existing file backed up: ${backupPath}`);
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* Move files for non-existent tables to backup
|
|
87
87
|
*/
|
|
88
88
|
async function backupOrphanedFiles(schemaDir, existingTables, force = false) {
|
|
89
89
|
if (!fs.existsSync(schemaDir)) {
|
|
@@ -103,48 +103,48 @@ async function backupOrphanedFiles(schemaDir, existingTables, force = false) {
|
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
if (!force) {
|
|
106
|
-
console.log('\
|
|
106
|
+
console.log('\nThe following files correspond to non-existent tables:');
|
|
107
107
|
orphanedFiles.forEach(file => console.log(` - ${file}`));
|
|
108
|
-
const confirmed = await (0, utils_1.askUserConfirmation)('
|
|
108
|
+
const confirmed = await (0, utils_1.askUserConfirmation)('Move these files to backup folder?');
|
|
109
109
|
if (!confirmed) {
|
|
110
|
-
console.log('
|
|
110
|
+
console.log('Skipped moving orphaned files');
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
for (const file of orphanedFiles) {
|
|
115
115
|
const tableName = path.basename(file, '.sql');
|
|
116
116
|
const filePath = path.join(schemaDir, file);
|
|
117
|
-
console.log(`[${tableName}]
|
|
118
|
-
await backupExistingFile(filePath, schemaDir, true); //
|
|
117
|
+
console.log(`[${tableName}] Backing up file for non-existent table`);
|
|
118
|
+
await backupExistingFile(filePath, schemaDir, true); // force=true since already confirmed
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
*
|
|
122
|
+
* Write schema to file
|
|
123
123
|
*/
|
|
124
124
|
async function writeSchemaToFile(ddl, schemaDir, timestamp, fileName, tableName, force = false) {
|
|
125
125
|
if (!fs.existsSync(schemaDir)) {
|
|
126
126
|
fs.mkdirSync(schemaDir, { recursive: true });
|
|
127
127
|
}
|
|
128
128
|
const filePath = path.join(schemaDir, fileName);
|
|
129
|
-
//
|
|
129
|
+
// Backup existing file if exists
|
|
130
130
|
const canProceed = await backupExistingFile(filePath, schemaDir, force);
|
|
131
131
|
if (!canProceed) {
|
|
132
132
|
return false;
|
|
133
133
|
}
|
|
134
|
-
//
|
|
134
|
+
// Record remote database last updated time (metadata for comparison - do not edit)
|
|
135
135
|
const remoteTimestamp = `-- Remote last updated: ${new Date(timestamp * 1000).toISOString()}\n`;
|
|
136
|
-
//
|
|
136
|
+
// Sync execution time (reference info)
|
|
137
137
|
const syncTimestamp = `-- Synced by supatool at: ${new Date().toISOString()}\n`;
|
|
138
|
-
//
|
|
139
|
-
const warningComment = `-- ⚠️
|
|
138
|
+
// Warning comment
|
|
139
|
+
const warningComment = `-- ⚠️ This file is auto-generated by supatool. Manual edits may be lost during sync.\n`;
|
|
140
140
|
const tableComment = `-- Table: ${tableName}\n\n`;
|
|
141
141
|
const finalContent = remoteTimestamp + syncTimestamp + warningComment + tableComment + ddl;
|
|
142
142
|
fs.writeFileSync(filePath, finalContent, 'utf-8');
|
|
143
|
-
console.log(`📁 ${tableName}.sql
|
|
143
|
+
console.log(`📁 Saved to ${tableName}.sql`);
|
|
144
144
|
return true;
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
147
|
+
* Move orphaned files to backup folder
|
|
148
148
|
*/
|
|
149
149
|
function moveToBackup(schemaDir, fileName) {
|
|
150
150
|
const backupDir = path.join(schemaDir, 'backup');
|
|
@@ -156,6 +156,6 @@ function moveToBackup(schemaDir, fileName) {
|
|
|
156
156
|
const backupFile = path.join(backupDir, `${path.basename(fileName, '.sql')}_${timestamp}.sql`);
|
|
157
157
|
if (fs.existsSync(sourceFile)) {
|
|
158
158
|
fs.renameSync(sourceFile, backupFile);
|
|
159
|
-
console.log(`📦 ${fileName}
|
|
159
|
+
console.log(`📦 Backed up ${fileName} to ${backupFile}`);
|
|
160
160
|
}
|
|
161
161
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supatool",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "CLI for Supabase: extract schema (tables, views, RLS, RPC) to files + llms.txt for LLM, deploy local schema, seed export. CRUD code gen deprecated.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
8
|
"supatool": "dist/bin/supatool.js"
|
|
9
9
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "tsx src/bin/supatool.ts",
|
|
13
|
+
"local": "tsx src/bin/supatool.ts"
|
|
14
|
+
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"bin"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/diff": "^7.0.2",
|
|
45
45
|
"@types/js-yaml": "^4.0.9",
|
|
46
|
-
"@types/node": "^20.
|
|
46
|
+
"@types/node": "^20.19.22",
|
|
47
47
|
"@types/pg": "^8.11.0"
|
|
48
48
|
},
|
|
49
49
|
"exports": {
|