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.
@@ -40,63 +40,63 @@ const util_1 = require("util");
40
40
  const definitionExtractor_1 = require("./definitionExtractor");
41
41
  const dnsLookup = (0, util_1.promisify)(dns.lookup);
42
42
  /**
43
- * DDL文字列を正規化(空白・改行・タブを統一)
43
+ * Normalize DDL string (unify whitespace, newlines, tabs)
44
44
  */
45
45
  function normalizeDDL(ddl) {
46
46
  return ddl
47
- .replace(/\s+/g, ' ') // 連続する空白文字を1つのスペースに
48
- .replace(/;\s+/g, ';\n') // セミコロン後に改行
49
- .trim(); // 前後の空白を削除
47
+ .replace(/\s+/g, ' ') // collapse consecutive whitespace to single space
48
+ .replace(/;\s+/g, ';\n') // newline after semicolon
49
+ .trim(); // trim leading/trailing
50
50
  }
51
51
  /**
52
- * リモートSupabaseからスキーマを取得
52
+ * Fetch schema from remote Supabase
53
53
  */
54
- async function fetchRemoteSchemas(connectionString) {
54
+ async function fetchRemoteSchemas(connectionString, targetTableNames) {
55
55
  const schemas = {};
56
- // console.log('データベースに接続中...');
56
+ // console.log('Connecting to database...');
57
57
  try {
58
- // 接続文字列の基本チェック
58
+ // Basic connection string check
59
59
  if (!connectionString || !connectionString.startsWith('postgresql://')) {
60
- throw new Error('無効な接続文字列です。postgresql://で始まる形式で指定してください。');
60
+ throw new Error('Invalid connection string. Please specify a valid postgresql:// format.');
61
61
  }
62
- // URL解析して接続先を表示
62
+ // Parse URL and display connection info
63
63
  const url = new URL(connectionString);
64
- console.log(`接続先: ${url.hostname}:${url.port}`);
65
- console.log(`ユーザー: ${url.username}`);
66
- // パスワードに特殊文字が含まれている場合の対処
64
+ console.log(`Target: ${url.hostname}:${url.port}`);
65
+ console.log(`User: ${url.username}`);
66
+ // Handle special characters in password
67
67
  const decodedPassword = decodeURIComponent(url.password || '');
68
- // 接続文字列を再構築(パスワードを適切にエンコード)
68
+ // Rebuild connection string with proper password encoding
69
69
  const encodedPassword = encodeURIComponent(decodedPassword);
70
- // Session poolerのモードを明示的に指定
70
+ // Explicit session pooler mode
71
71
  let reconstructedConnectionString = `postgresql://${url.username}:${encodedPassword}@${url.hostname}:${url.port}${url.pathname}`;
72
- // Session poolingモードのパラメータを追加
72
+ // Add session pooling params
73
73
  const searchParams = new URLSearchParams(url.search);
74
74
  searchParams.set('sslmode', 'require');
75
75
  searchParams.set('application_name', 'supatool');
76
76
  reconstructedConnectionString += `?${searchParams.toString()}`;
77
- // IPv4を優先に変更
77
+ // Prefer IPv4
78
78
  dns.setDefaultResultOrder('ipv4first');
79
- // DNS解決テスト
79
+ // DNS resolution test
80
80
  try {
81
81
  const addresses = await dns.promises.lookup(url.hostname, { all: true });
82
82
  }
83
83
  catch (dnsError) {
84
- console.error('❌ DNS解決に失敗しました');
84
+ console.error('❌ DNS resolution failed');
85
85
  throw dnsError;
86
86
  }
87
- // Session pooler用の設定
87
+ // Session pooler config
88
88
  const clientConfig = {
89
89
  connectionString: reconstructedConnectionString,
90
90
  ssl: {
91
91
  rejectUnauthorized: false
92
92
  },
93
- // Session pooler用の追加設定
93
+ // Session pooler extra options
94
94
  statement_timeout: 30000,
95
95
  query_timeout: 30000,
96
96
  connectionTimeoutMillis: 10000,
97
97
  idleTimeoutMillis: 10000
98
98
  };
99
- console.log('Session pooler経由で接続中...');
99
+ console.log('Connecting via session pooler...');
100
100
  let client;
101
101
  try {
102
102
  client = new pg_1.Client(clientConfig);
@@ -107,8 +107,8 @@ async function fetchRemoteSchemas(connectionString) {
107
107
  sslError.message.includes('SCRAM') ||
108
108
  sslError.message.includes('certificate') ||
109
109
  sslError.message.includes('SELF_SIGNED_CERT'))) {
110
- console.log('SSL接続不可のため、SSL無効化で再試行中...');
111
- // SSL無効化バージョンを試行
110
+ console.log('SSL connection failed, retrying with SSL disabled...');
111
+ // Retry with SSL disabled
112
112
  const noSslConnectionString = reconstructedConnectionString.replace('sslmode=require', 'sslmode=disable');
113
113
  const noSslConfig = {
114
114
  ...clientConfig,
@@ -118,12 +118,12 @@ async function fetchRemoteSchemas(connectionString) {
118
118
  try {
119
119
  client = new pg_1.Client(noSslConfig);
120
120
  await client.connect();
121
- console.log('SSL無効化での接続に成功');
121
+ console.log('Connection successful with SSL disabled');
122
122
  }
123
123
  catch (noSslError) {
124
124
  if (noSslError instanceof Error && (noSslError.message.includes('SASL') || noSslError.message.includes('SCRAM'))) {
125
- console.log('Session poolerでSASLエラー継続。Direct connectionで再試行中...');
126
- // Direct connection (ポート5432) で再試行
125
+ console.log('SASL error continues with session pooler. Retrying with direct connection...');
126
+ // Retry with direct connection (port 5432)
127
127
  const directConnectionString = noSslConnectionString.replace('pooler.supabase.com:5432', 'supabase.co:5432');
128
128
  const directConfig = {
129
129
  ...noSslConfig,
@@ -131,7 +131,7 @@ async function fetchRemoteSchemas(connectionString) {
131
131
  };
132
132
  client = new pg_1.Client(directConfig);
133
133
  await client.connect();
134
- console.log('Direct connectionでの接続に成功');
134
+ console.log('Direct connection successful');
135
135
  }
136
136
  else {
137
137
  throw noSslError;
@@ -142,29 +142,45 @@ async function fetchRemoteSchemas(connectionString) {
142
142
  throw sslError;
143
143
  }
144
144
  }
145
- // 接続テストクエリ
145
+ // Connection test query
146
146
  const testResult = await client.query('SELECT version()');
147
- // テーブル一覧を取得
148
- const tablesResult = await client.query(`
149
- SELECT tablename
150
- FROM pg_tables
151
- WHERE schemaname = 'public'
152
- ORDER BY tablename
153
- `);
154
- console.log(`リモートテーブル:${tablesResult.rows.length}`);
155
- // ローディングアニメーション用
147
+ // Fetch table list
148
+ let tablesResult;
149
+ if (targetTableNames && targetTableNames.length > 0) {
150
+ // When targeting specific tables only
151
+ const placeholders = targetTableNames.map((_, index) => `$${index + 1}`).join(',');
152
+ tablesResult = await client.query(`
153
+ SELECT tablename
154
+ FROM pg_tables
155
+ WHERE schemaname = 'public'
156
+ AND tablename IN (${placeholders})
157
+ ORDER BY tablename
158
+ `, targetTableNames);
159
+ console.log(`Remote tables (filtered): ${tablesResult.rows.length}/${targetTableNames.length}`);
160
+ }
161
+ else {
162
+ // All tables
163
+ tablesResult = await client.query(`
164
+ SELECT tablename
165
+ FROM pg_tables
166
+ WHERE schemaname = 'public'
167
+ ORDER BY tablename
168
+ `);
169
+ console.log(`Remote tables: ${tablesResult.rows.length}`);
170
+ }
171
+ // For loading animation
156
172
  const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
157
173
  let spinnerIndex = 0;
158
174
  const startTime = Date.now();
159
175
  const spinnerInterval = setInterval(() => {
160
176
  const elapsed = Math.floor((Date.now() - startTime) / 1000);
161
- process.stdout.write(`\r${spinner[spinnerIndex]} スキーマ取得中... ${elapsed}s`);
177
+ process.stdout.write(`\r${spinner[spinnerIndex]} Fetching schemas... ${elapsed}s`);
162
178
  spinnerIndex = (spinnerIndex + 1) % spinner.length;
163
179
  }, 100);
164
- // 各テーブルのスキーマ情報を取得
180
+ // Fetch schema info for each table
165
181
  for (const row of tablesResult.rows) {
166
182
  const tableName = row.tablename;
167
- // テーブルの最終更新時刻を取得(省略: 既存ロジック流用)
183
+ // Get table last updated time (reuse existing logic)
168
184
  let timestamp = Math.floor(Date.now() / 1000);
169
185
  try {
170
186
  const tableStatsResult = await client.query(`
@@ -188,14 +204,14 @@ async function fetchRemoteSchemas(connectionString) {
188
204
  catch {
189
205
  timestamp = Math.floor(new Date('2020-01-01').getTime() / 1000);
190
206
  }
191
- // DDL生成をdefinitionExtractorの関数で統一
207
+ // DDL generation unified with definitionExtractor
192
208
  const ddl = await (0, definitionExtractor_1.generateCreateTableDDL)(client, tableName, 'public');
193
209
  schemas[tableName] = {
194
210
  ddl,
195
211
  timestamp
196
212
  };
197
213
  }
198
- // ローディングアニメーション停止
214
+ // Stop loading animation
199
215
  clearInterval(spinnerInterval);
200
216
  const totalTime = Math.floor((Date.now() - startTime) / 1000);
201
217
  process.stdout.write(`\rSchema fetch completed (${totalTime}s) \n`);
@@ -38,22 +38,22 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const diff_1 = require("diff");
40
40
  /**
41
- * DDL差分からALTER TABLE文を生成
41
+ * Generate ALTER TABLE statements from DDL diff
42
42
  */
43
43
  function generateAlterStatements(tableName, fromDdl, toDdl) {
44
44
  const statements = [];
45
45
  const diff = (0, diff_1.diffLines)(fromDdl, toDdl);
46
- // デバッグ情報削除 - シンプルな表示
47
- // カラム名変更の検出用
46
+ // Debug output removed - simple display
47
+ // For column rename detection
48
48
  const removedColumns = [];
49
49
  const addedColumns = [];
50
50
  for (const part of diff) {
51
51
  if (part.added) {
52
- // 追加された行をALTER TABLE文に変換
52
+ // Convert added lines to ALTER TABLE
53
53
  const lines = part.value.split('\n').filter(line => line.trim());
54
54
  for (const line of lines) {
55
55
  const trimmed = line.trim();
56
- // カラム定義の追加を検出
56
+ // Detect column definition addition
57
57
  if (trimmed.includes(' ') && !trimmed.startsWith('CREATE') && !trimmed.startsWith('PRIMARY') && !trimmed.startsWith('CONSTRAINT')) {
58
58
  const columnDef = trimmed.replace(/,$/, '').trim();
59
59
  const columnMatch = columnDef.match(/^(\w+)\s+(.+)$/);
@@ -66,11 +66,11 @@ function generateAlterStatements(tableName, fromDdl, toDdl) {
66
66
  }
67
67
  }
68
68
  else if (part.removed) {
69
- // 削除された行をALTER TABLE文に変換
69
+ // Convert removed lines to ALTER TABLE
70
70
  const lines = part.value.split('\n').filter(line => line.trim());
71
71
  for (const line of lines) {
72
72
  const trimmed = line.trim();
73
- // カラム定義の削除を検出
73
+ // Detect column definition removal
74
74
  if (trimmed.includes(' ') && !trimmed.startsWith('CREATE') && !trimmed.startsWith('PRIMARY') && !trimmed.startsWith('CONSTRAINT')) {
75
75
  const columnDef = trimmed.replace(/,$/, '').trim();
76
76
  const columnMatch = columnDef.match(/^(\w+)\s+(.+)$/);
@@ -83,47 +83,47 @@ function generateAlterStatements(tableName, fromDdl, toDdl) {
83
83
  }
84
84
  }
85
85
  }
86
- // カラム名変更の検出(型定義が同じで名前が違う場合)
86
+ // Column rename detection (same type, different name)
87
87
  const renames = [];
88
88
  for (const removed of removedColumns) {
89
89
  const matching = addedColumns.find(added => added.definition === removed.definition);
90
90
  if (matching) {
91
91
  renames.push({ from: removed.name, to: matching.name });
92
- // 処理済みとしてマーク
92
+ // Mark as processed
93
93
  removedColumns.splice(removedColumns.indexOf(removed), 1);
94
94
  addedColumns.splice(addedColumns.indexOf(matching), 1);
95
95
  }
96
96
  }
97
- // デバッグログ削除 - シンプルな表示
98
- // RENAME COLUMN文を生成
97
+ // Debug log removed - simple display
98
+ // Generate RENAME COLUMN statements
99
99
  for (const rename of renames) {
100
100
  statements.push(`ALTER TABLE ${tableName} RENAME COLUMN ${rename.from} TO ${rename.to};`);
101
101
  }
102
- // 残りの削除されたカラム
102
+ // Remaining removed columns
103
103
  for (const removed of removedColumns) {
104
104
  statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${removed.name};`);
105
105
  }
106
- // 残りの追加されたカラム
106
+ // Remaining added columns
107
107
  for (const added of addedColumns) {
108
108
  statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${added.name} ${added.definition};`);
109
109
  }
110
110
  return statements;
111
111
  }
112
112
  /**
113
- * マイグレーションファイルを生成
113
+ * Generate migration file
114
114
  */
115
115
  async function generateMigrationFile(tableName, fromDdl, toDdl, projectDir = '.') {
116
116
  const migrationDir = path.join(projectDir, 'supabase', 'migrations');
117
- // migrations ディレクトリを作成
117
+ // Create migrations directory
118
118
  if (!fs.existsSync(migrationDir)) {
119
119
  fs.mkdirSync(migrationDir, { recursive: true });
120
120
  }
121
- // ALTER TABLE文を生成
121
+ // Generate ALTER TABLE statements
122
122
  const alterStatements = generateAlterStatements(tableName, fromDdl, toDdl);
123
123
  if (alterStatements.length === 0) {
124
124
  return await generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir);
125
125
  }
126
- // タイムスタンプでファイル名を生成
126
+ // Generate filename from timestamp
127
127
  const now = new Date();
128
128
  const timestamp = now.toISOString()
129
129
  .replace(/[-:]/g, '')
@@ -131,37 +131,37 @@ async function generateMigrationFile(tableName, fromDdl, toDdl, projectDir = '.'
131
131
  .replace('T', '');
132
132
  const filename = `${timestamp}_update_${tableName}.sql`;
133
133
  const filepath = path.join(migrationDir, filename);
134
- // マイグレーションファイルの内容を生成
134
+ // Build migration file content
135
135
  const content = `-- Migration generated by supatool
136
136
  -- Table: ${tableName}
137
137
  -- Generated at: ${now.toISOString()}
138
138
 
139
139
  ${alterStatements.join('\n')}
140
140
  `;
141
- // ファイルを書き込み
141
+ // Write file
142
142
  fs.writeFileSync(filepath, content, 'utf-8');
143
- console.log(`マイグレーションファイル生成: ${filename}`);
143
+ console.log(`Migration file generated: ${filename}`);
144
144
  return filepath;
145
145
  }
146
146
  /**
147
- * DDLからカラム定義を抽出
147
+ * Extract column definitions from DDL
148
148
  */
149
149
  function extractColumns(ddl) {
150
150
  const columns = [];
151
- // CREATE TABLE部分を抽出
151
+ // Extract CREATE TABLE part
152
152
  const createTableMatch = ddl.match(/CREATE TABLE[^(]*\((.*)\);?/is);
153
153
  if (!createTableMatch) {
154
154
  return columns;
155
155
  }
156
156
  const tableContent = createTableMatch[1];
157
- // カラム定義とCONSTRAINTを分離
157
+ // Split column definitions and CONSTRAINTs
158
158
  const parts = tableContent.split(',').map(part => part.trim());
159
159
  for (const part of parts) {
160
- // CONSTRAINTPRIMARY KEYは除外
160
+ // Exclude CONSTRAINT, PRIMARY KEY
161
161
  if (part.match(/^(PRIMARY|CONSTRAINT|UNIQUE|FOREIGN|CHECK)/i)) {
162
162
  continue;
163
163
  }
164
- // カラム名とデータ型を分離
164
+ // Split column name and data type
165
165
  const columnMatch = part.trim().match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s+(.+)$/);
166
166
  if (columnMatch) {
167
167
  const columnName = columnMatch[1];
@@ -172,29 +172,29 @@ function extractColumns(ddl) {
172
172
  return columns;
173
173
  }
174
174
  /**
175
- * 差分からカラム変更を解析
175
+ * Analyze column changes from diff
176
176
  */
177
177
  function analyzeDiffForTemplate(fromDdl, toDdl) {
178
- // DDLからカラムを抽出
178
+ // Extract columns from each DDL
179
179
  const fromColumns = extractColumns(fromDdl);
180
180
  const toColumns = extractColumns(toDdl);
181
- // 削除されたカラム(FROMにあってTOにない)
181
+ // Removed columns (in FROM but not in TO)
182
182
  const removedColumns = fromColumns.filter(fromCol => !toColumns.some(toCol => toCol.name === fromCol.name));
183
- // 追加されたカラム(TOにあってFROMにない)
183
+ // Added columns (in TO but not in FROM)
184
184
  const addedColumns = toColumns.filter(toCol => !fromColumns.some(fromCol => fromCol.name === toCol.name));
185
185
  return { removedColumns, addedColumns };
186
186
  }
187
187
  /**
188
- * 手動マイグレーションテンプレートを生成
188
+ * Generate manual migration template
189
189
  */
190
190
  async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir) {
191
191
  const migrationDir = path.join(projectDir, 'supabase', 'migrations');
192
192
  if (!fs.existsSync(migrationDir)) {
193
193
  fs.mkdirSync(migrationDir, { recursive: true });
194
194
  }
195
- // 差分を解析
195
+ // Analyze diff
196
196
  const { removedColumns, addedColumns } = analyzeDiffForTemplate(fromDdl, toDdl);
197
- // タイムスタンプでファイル名を生成
197
+ // Generate filename from timestamp
198
198
  const now = new Date();
199
199
  const timestamp = now.toISOString()
200
200
  .replace(/[-:]/g, '')
@@ -202,9 +202,9 @@ async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projec
202
202
  .replace('T', '');
203
203
  const filename = `${timestamp}_manual_update_${tableName}.sql`;
204
204
  const filepath = path.join(migrationDir, filename);
205
- // 実際の変更を基にテンプレートを生成
205
+ // Build template from actual changes
206
206
  let migrationStatements = [];
207
- // カラム名変更の可能性を検出
207
+ // Detect possible column renames
208
208
  const potentialRenames = [];
209
209
  for (const removed of removedColumns) {
210
210
  const matching = addedColumns.find(added => added.definition === removed.definition);
@@ -216,10 +216,10 @@ async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projec
216
216
  });
217
217
  }
218
218
  }
219
- // 残りの削除・追加
219
+ // Remaining removals and additions
220
220
  const remainingRemoved = removedColumns.filter(r => !potentialRenames.some(p => p.from === r.name));
221
221
  const remainingAdded = addedColumns.filter(a => !potentialRenames.some(p => p.to === a.name));
222
- // テンプレート文を生成(DROP ADD RENAME の順序)
222
+ // Generate template statements (DROP -> ADD -> RENAME order)
223
223
  if (remainingRemoved.length > 0) {
224
224
  migrationStatements.push('-- Column removals:');
225
225
  for (const removed of remainingRemoved) {
@@ -242,7 +242,7 @@ async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projec
242
242
  migrationStatements.push(`ALTER TABLE ${tableName} RENAME COLUMN ${rename.from} TO ${rename.to};`);
243
243
  }
244
244
  }
245
- // フォールバック: 何も検出されなかった場合
245
+ // Fallback when nothing detected
246
246
  if (migrationStatements.length === 0) {
247
247
  migrationStatements = [
248
248
  `-- Manual migration for ${tableName}`,
@@ -259,18 +259,18 @@ async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projec
259
259
  ${migrationStatements.join('\n')}
260
260
  `;
261
261
  fs.writeFileSync(filepath, content, 'utf-8');
262
- console.log(`手動マイグレーションテンプレート生成: ${filename}`);
262
+ console.log(`Manual migration template generated: ${filename}`);
263
263
  return filepath;
264
264
  }
265
265
  /**
266
- * より高度な差分解析(カラム変更を検出)
266
+ * Advanced diff analysis (detect column changes)
267
267
  */
268
268
  function analyzeColumnChanges(tableName, localDdl, remoteDdl) {
269
269
  const statements = [];
270
- // 簡単な例:カラム名の変更を検出
270
+ // Simple case: detect column name change
271
271
  const localLines = localDdl.split('\n').map(line => line.trim()).filter(line => line);
272
272
  const remoteLines = remoteDdl.split('\n').map(line => line.trim()).filter(line => line);
273
- // カラムの型変更やデフォルト値変更などの高度な検出は今後実装
274
- // 現在は基本的な追加/削除のみ対応
273
+ // Advanced detection (type change, default change) to be implemented later
274
+ // Currently only basic add/remove
275
275
  return statements;
276
276
  }
@@ -37,16 +37,16 @@ exports.parseLocalSchemas = parseLocalSchemas;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  /**
40
- * DDL文字列を正規化(空白・改行・タブを統一)
40
+ * Normalize DDL string (unify whitespace, newlines, tabs)
41
41
  */
42
42
  function normalizeDDL(ddl) {
43
43
  return ddl
44
- .replace(/\s+/g, ' ') // 連続する空白文字を1つのスペースに
45
- .replace(/;\s+/g, ';\n') // セミコロン後に改行
46
- .trim(); // 前後の空白を削除
44
+ .replace(/\s+/g, ' ') // collapse consecutive whitespace to single space
45
+ .replace(/;\s+/g, ';\n') // newline after semicolon
46
+ .trim(); // trim leading/trailing
47
47
  }
48
48
  /**
49
- * ローカルSQLファイルからスキーマを解析
49
+ * Parse schema from local SQL files
50
50
  */
51
51
  async function parseLocalSchemas(schemaDir) {
52
52
  const schemas = {};
@@ -54,16 +54,19 @@ async function parseLocalSchemas(schemaDir) {
54
54
  return schemas;
55
55
  }
56
56
  const files = fs.readdirSync(schemaDir);
57
+ console.log(`Reading schema directory: ${schemaDir}`);
58
+ console.log(`Found SQL files: ${files.filter(f => f.endsWith('.sql')).join(', ') || 'none'}`);
57
59
  for (const file of files) {
58
60
  if (!file.endsWith('.sql'))
59
61
  continue;
60
62
  const filePath = path.join(schemaDir, file);
61
63
  const stats = fs.statSync(filePath);
62
64
  const fileContent = fs.readFileSync(filePath, 'utf-8');
63
- // ファイル名からテーブル名を取得(.sqlを除く)
65
+ // Get table name from filename (without .sql)
64
66
  const tableName = path.basename(file, '.sql');
65
- // ファイル内のコメントからタイムスタンプを抽出
66
- let timestamp = Math.floor(stats.mtime.getTime() / 1000); // フォールバック
67
+ console.log(`Processing file: ${file} -> tableName: ${tableName}`);
68
+ // Extract timestamp from comment in file; fallback to mtime
69
+ let timestamp = Math.floor(stats.mtime.getTime() / 1000);
67
70
  const timestampMatch = fileContent.match(/-- Remote last updated: (.+)/);
68
71
  if (timestampMatch) {
69
72
  try {
@@ -74,16 +77,16 @@ async function parseLocalSchemas(schemaDir) {
74
77
  }
75
78
  }
76
79
  catch (error) {
77
- // エラーの場合はファイル更新日時を使用
80
+ // On error use file mtime
78
81
  }
79
82
  }
80
- // DDL部分のみを抽出(コメント行と空白行を完全に除外)
83
+ // Extract DDL only (exclude comment and blank lines)
81
84
  const ddlLines = fileContent.split('\n').filter(line => {
82
85
  const trimmed = line.trim();
83
86
  return trimmed && !trimmed.startsWith('--');
84
87
  });
85
88
  const rawDDL = ddlLines.join('\n').trim();
86
- // DDLを正規化
89
+ // Normalize DDL
87
90
  const ddl = normalizeDDL(rawDDL);
88
91
  schemas[tableName] = {
89
92
  ddl: rawDDL,
@@ -9,17 +9,17 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const js_yaml_1 = __importDefault(require("js-yaml"));
11
11
  /**
12
- * 指定テーブルのデータをリモートDBから取得し、AI用シードJSONを生成
12
+ * Fetch table data from remote DB and generate AI seed JSON
13
13
  * @param options SeedGenOptions
14
14
  */
15
15
  async function generateSeedsFromRemote(options) {
16
- // tables.yaml読み込み
16
+ // Load tables.yaml
17
17
  const yamlObj = js_yaml_1.default.load(fs_1.default.readFileSync(options.tablesYamlPath, 'utf8'));
18
18
  if (!yamlObj || !Array.isArray(yamlObj.tables)) {
19
- throw new Error('tables.yamlの形式が不正です。tables: [ ... ] で指定してください');
19
+ throw new Error('Invalid tables.yaml format. Specify as tables: [ ... ]');
20
20
  }
21
21
  const tables = yamlObj.tables;
22
- // 日時付きサブディレクトリ名生成(例: 20250705_1116_supatool
22
+ // Generate datetime subdir name (e.g. 20250705_1116_supatool)
23
23
  const now = new Date();
24
24
  const y = now.getFullYear();
25
25
  const m = String(now.getMonth() + 1).padStart(2, '0');
@@ -28,28 +28,28 @@ async function generateSeedsFromRemote(options) {
28
28
  const mm = String(now.getMinutes()).padStart(2, '0');
29
29
  const folderName = `${y}${m}${d}_${hh}${mm}_supatool`;
30
30
  const outDir = path_1.default.join(options.outputDir, folderName);
31
- // 出力ディレクトリ作成
31
+ // Create output directory
32
32
  if (!fs_1.default.existsSync(outDir)) {
33
33
  fs_1.default.mkdirSync(outDir, { recursive: true });
34
34
  }
35
- // DB接続
35
+ // DB connection
36
36
  const client = new pg_1.Client({ connectionString: options.connectionString });
37
37
  await client.connect();
38
38
  let processedCount = 0;
39
39
  for (const tableFullName of tables) {
40
- // スキーマ指定なし→public
40
+ // No schema specified -> public
41
41
  let schema = 'public';
42
42
  let table = tableFullName;
43
43
  if (tableFullName.includes('.')) {
44
44
  [schema, table] = tableFullName.split('.');
45
45
  }
46
- // データ取得
46
+ // Fetch data
47
47
  const res = await client.query(`SELECT * FROM "${schema}"."${table}"`);
48
48
  const rows = res.rows;
49
- // ファイル名
49
+ // File name
50
50
  const fileName = `${table}_seed.json`;
51
51
  const filePath = path_1.default.join(outDir, fileName);
52
- // 出力JSON
52
+ // Output JSON
53
53
  const json = {
54
54
  table: `${schema}.${table}`,
55
55
  fetched_at: now.toISOString(),
@@ -61,16 +61,17 @@ async function generateSeedsFromRemote(options) {
61
61
  processedCount++;
62
62
  }
63
63
  await client.end();
64
- // llms.txtインデックス出力(supabase/seeds直下に毎回上書き)
64
+ // llms.txt index output (overwrite under supabase/seeds each run)
65
65
  const files = fs_1.default.readdirSync(outDir);
66
66
  const seedFiles = files.filter(f => f.endsWith('_seed.json'));
67
67
  let llmsTxt = `# AI seed data index (generated by supatool)\n`;
68
68
  llmsTxt += `# fetched_at: ${now.toISOString()}\n`;
69
69
  llmsTxt += `# folder: ${folderName}\n`;
70
+ llmsTxt += `# Schema catalog: ../schemas/llms.txt\n`;
70
71
  for (const basename of seedFiles) {
71
72
  const file = path_1.default.join(outDir, basename);
72
73
  const content = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
73
- // テーブルコメント(なければ空)
74
+ // Table comment (empty if none)
74
75
  let tableComment = '';
75
76
  try {
76
77
  const [schema, table] = content.table.split('.');
@@ -86,11 +87,11 @@ async function generateSeedsFromRemote(options) {
86
87
  }
87
88
  const llmsPath = path_1.default.join(options.outputDir, 'llms.txt');
88
89
  fs_1.default.writeFileSync(llmsPath, llmsTxt, 'utf8');
89
- // 英語でまとめて出力
90
+ // Output summary in English
90
91
  console.log(`Seed export completed. Processed tables: ${processedCount}`);
91
92
  console.log(`llms.txt index written to: ${llmsPath}`);
92
93
  }
93
- // テーブルコメント取得ユーティリティ
94
+ /** Utility to get table comment */
94
95
  async function getTableComment(connectionString, schema, table) {
95
96
  const client = new pg_1.Client({ connectionString });
96
97
  await client.connect();