supatool 0.4.3 → 0.6.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.
@@ -72,27 +72,86 @@ function loadConfig(configPath) {
72
72
  */
73
73
  function resolveConfig(options, configPath) {
74
74
  const fileConfig = loadConfig(configPath);
75
+ const connectionString = options.connectionString ||
76
+ process.env.DB_CONNECTION_STRING ||
77
+ process.env.SUPABASE_CONNECTION_STRING ||
78
+ process.env.DATABASE_URL ||
79
+ fileConfig.connectionString;
75
80
  return {
76
- connectionString: options.connectionString ||
77
- process.env.SUPABASE_CONNECTION_STRING ||
78
- process.env.DATABASE_URL ||
79
- fileConfig.connectionString,
80
- schemaDir: options.schemaDir || fileConfig.schemaDir || './supabase/schemas',
81
- tablePattern: options.tablePattern || fileConfig.tablePattern || '*'
81
+ connectionString,
82
+ schemaDir: options.schemaDir || fileConfig.schemaDir || './db/schemas',
83
+ tablePattern: options.tablePattern || fileConfig.tablePattern || '*',
84
+ migration: fileConfig.migration
82
85
  };
83
86
  }
84
87
  /**
85
- * Generate config file template
88
+ * Generate config file template (no connection string — use .env.local)
86
89
  */
87
90
  function createConfigTemplate(outputPath) {
88
91
  const template = {
89
- connectionString: "postgresql://user:password@host:port/database",
90
- schemaDir: "./supabase/schemas",
92
+ schemaDir: "./db/schemas",
91
93
  tablePattern: "*",
92
- "_comment": "It is recommended to set connection string in .env or .env.local file using SUPABASE_CONNECTION_STRING or DATABASE_URL"
94
+ migration: {
95
+ naming: "timestamp",
96
+ "_naming_comment": "Use 'sequential' for NNN_description.sql format, 'timestamp' for YYYYMMDDHHMMSS_description.sql",
97
+ dir: "db/migrations"
98
+ },
99
+ "_comment": "Set credentials in .env.local — never put secrets in this file."
93
100
  };
94
101
  fs.writeFileSync(outputPath, JSON.stringify(template, null, 2), 'utf-8');
95
- console.log(`Configuration template generated: ${outputPath}`);
96
- console.log('⚠️ Remember to add the configuration file to .gitignore!');
97
- console.log('💡 Manage connection string in .env or .env.local file');
102
+ console.log(`Config template generated: ${outputPath}`);
103
+ ensureEnvLocalTemplate();
104
+ checkGitignore(outputPath);
105
+ }
106
+ /**
107
+ * Write .env.local template if it doesn't exist yet.
108
+ */
109
+ function ensureEnvLocalTemplate() {
110
+ const envLocalPath = path.join(process.cwd(), '.env.local');
111
+ if (fs.existsSync(envLocalPath))
112
+ return;
113
+ const template = [
114
+ '# supatool credentials — never commit this file',
115
+ '# PostgreSQL connection string (Cloud SQL, Supabase, or any PostgreSQL)',
116
+ 'DB_CONNECTION_STRING=postgresql://user:password@host:port/database',
117
+ '',
118
+ '# Legacy aliases (still accepted for backward compatibility)',
119
+ '# SUPABASE_CONNECTION_STRING=postgresql://user:password@host:port/database',
120
+ '# DATABASE_URL=postgresql://user:password@host:port/database',
121
+ ].join('\n') + '\n';
122
+ fs.writeFileSync(envLocalPath, template, 'utf-8');
123
+ console.log('.env.local template created — fill in your credentials.');
124
+ }
125
+ /**
126
+ * Warn if the config file or .env.local are not covered by .gitignore.
127
+ */
128
+ function checkGitignore(configPath) {
129
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
130
+ if (!fs.existsSync(gitignorePath)) {
131
+ console.warn('Warning: .gitignore not found. Make sure to exclude .env.local and supatool.config.json.');
132
+ return;
133
+ }
134
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
135
+ const lines = content.split('\n').map(l => l.trim());
136
+ const missing = [];
137
+ const configFile = path.basename(configPath);
138
+ if (!lines.some(l => l === configFile || l === `/${configFile}`)) {
139
+ missing.push(configFile);
140
+ }
141
+ if (!lines.some(l => l === '.env.local' || l === '*.local')) {
142
+ missing.push('.env.local');
143
+ }
144
+ if (missing.length > 0) {
145
+ console.warn(`\nWarning: The following are NOT in .gitignore — add them to avoid committing secrets:`);
146
+ for (const f of missing) {
147
+ console.warn(` ${f}`);
148
+ }
149
+ // Auto-append to .gitignore
150
+ const toAdd = missing.map(f => f).join('\n') + '\n';
151
+ fs.appendFileSync(gitignorePath, '\n# supatool\n' + toAdd);
152
+ console.log(`Auto-added to .gitignore: ${missing.join(', ')}`);
153
+ }
154
+ else {
155
+ console.log('.gitignore OK — credentials files are excluded.');
156
+ }
98
157
  }
@@ -1344,7 +1344,9 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
1344
1344
  * Classify and output definitions
1345
1345
  */
1346
1346
  async function extractDefinitions(options) {
1347
- const { connectionString, outputDir, separateDirectories = true, tablesOnly = false, viewsOnly = false, all = false, tablePattern = '*', force = false, schemas = ['public'], version } = options;
1347
+ const { connectionString, outputDir, separateDirectories = true, tablesOnly = false, viewsOnly = false, all = false, tablePattern = '*', force = false, schemas: schemasOption = ['public'], excludeSchemas = [], allSchemas: useAllSchemas = false, version } = options;
1348
+ // schemas will be resolved after DB connect when useAllSchemas is true
1349
+ let schemas = schemasOption;
1348
1350
  // Disable Node.js SSL certificate verification
1349
1351
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
1350
1352
  // Connection string validation
@@ -1463,6 +1465,17 @@ async function extractDefinitions(options) {
1463
1465
  console.log(` Connection string length: ${encodedConnectionString.length}`);
1464
1466
  await client.connect();
1465
1467
  spinner.text = 'Connected to database';
1468
+ // Resolve schemas: when --all-schemas, fetch all from DB and subtract excludeSchemas
1469
+ if (useAllSchemas) {
1470
+ const SYSTEM_SCHEMAS = ['information_schema', 'pg_catalog', 'pg_toast', 'pg_temp_1', 'pg_toast_temp_1'];
1471
+ const discovered = await fetchAllSchemas(client);
1472
+ schemas = discovered.filter(s => !SYSTEM_SCHEMAS.includes(s) && !excludeSchemas.includes(s));
1473
+ console.log(`Schemas (all minus excluded): ${schemas.join(', ')}`);
1474
+ }
1475
+ else if (excludeSchemas.length > 0) {
1476
+ schemas = schemas.filter(s => !excludeSchemas.includes(s));
1477
+ console.log(`Schemas (filtered): ${schemas.join(', ')}`);
1478
+ }
1466
1479
  let allDefinitions = [];
1467
1480
  // Initialize progress tracker
1468
1481
  const progress = {
@@ -56,8 +56,10 @@ async function fetchRemoteSchemas(connectionString, targetTableNames) {
56
56
  // console.log('Connecting to database...');
57
57
  try {
58
58
  // Basic connection string check
59
- if (!connectionString || !connectionString.startsWith('postgresql://')) {
60
- throw new Error('Invalid connection string. Please specify a valid postgresql:// format.');
59
+ if (!connectionString ||
60
+ (!connectionString.startsWith('postgresql://') &&
61
+ !connectionString.startsWith('postgres://'))) {
62
+ throw new Error('Invalid connection string. Please specify a valid postgresql:// or postgres:// format.');
61
63
  }
62
64
  // Parse URL and display connection info
63
65
  const url = new URL(connectionString);
@@ -34,9 +34,36 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateMigrationFile = generateMigrationFile;
37
+ exports.generateRenameTableMigrationFile = generateRenameTableMigrationFile;
38
+ exports.generateFunctionMigrationFile = generateFunctionMigrationFile;
39
+ exports.generateRlsMigrationFile = generateRlsMigrationFile;
37
40
  const fs = __importStar(require("fs"));
38
41
  const path = __importStar(require("path"));
39
42
  const diff_1 = require("diff");
43
+ /**
44
+ * Resolve migration filename based on naming config
45
+ */
46
+ function resolveMigrationFilename(migrationDir, description, naming = 'timestamp') {
47
+ if (naming === 'sequential') {
48
+ // Find max NNN from existing files
49
+ let max = 0;
50
+ if (fs.existsSync(migrationDir)) {
51
+ for (const f of fs.readdirSync(migrationDir)) {
52
+ const m = f.match(/^(\d+)_/);
53
+ if (m)
54
+ max = Math.max(max, parseInt(m[1], 10));
55
+ }
56
+ }
57
+ const next = String(max + 1).padStart(3, '0');
58
+ return `${next}_${description}.sql`;
59
+ }
60
+ // Default: timestamp
61
+ const ts = new Date().toISOString()
62
+ .replace(/[-:]/g, '')
63
+ .replace(/\..+/, '')
64
+ .replace('T', '');
65
+ return `${ts}_${description}.sql`;
66
+ }
40
67
  /**
41
68
  * Generate ALTER TABLE statements from DDL diff
42
69
  */
@@ -112,8 +139,8 @@ function generateAlterStatements(tableName, fromDdl, toDdl) {
112
139
  /**
113
140
  * Generate migration file
114
141
  */
115
- async function generateMigrationFile(tableName, fromDdl, toDdl, projectDir = '.') {
116
- const migrationDir = path.join(projectDir, 'supabase', 'migrations');
142
+ async function generateMigrationFile(tableName, fromDdl, toDdl, projectDir = '.', migrationConfig) {
143
+ const migrationDir = path.join(projectDir, migrationConfig?.dir ?? 'db/migrations');
117
144
  // Create migrations directory
118
145
  if (!fs.existsSync(migrationDir)) {
119
146
  fs.mkdirSync(migrationDir, { recursive: true });
@@ -121,20 +148,14 @@ async function generateMigrationFile(tableName, fromDdl, toDdl, projectDir = '.'
121
148
  // Generate ALTER TABLE statements
122
149
  const alterStatements = generateAlterStatements(tableName, fromDdl, toDdl);
123
150
  if (alterStatements.length === 0) {
124
- return await generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir);
151
+ return await generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir, migrationConfig);
125
152
  }
126
- // Generate filename from timestamp
127
- const now = new Date();
128
- const timestamp = now.toISOString()
129
- .replace(/[-:]/g, '')
130
- .replace(/\..+/, '')
131
- .replace('T', '');
132
- const filename = `${timestamp}_update_${tableName}.sql`;
153
+ const filename = resolveMigrationFilename(migrationDir, `update_${tableName}`, migrationConfig?.naming);
133
154
  const filepath = path.join(migrationDir, filename);
134
155
  // Build migration file content
135
156
  const content = `-- Migration generated by supatool
136
157
  -- Table: ${tableName}
137
- -- Generated at: ${now.toISOString()}
158
+ -- Generated at: ${new Date().toISOString()}
138
159
 
139
160
  ${alterStatements.join('\n')}
140
161
  `;
@@ -187,20 +208,14 @@ function analyzeDiffForTemplate(fromDdl, toDdl) {
187
208
  /**
188
209
  * Generate manual migration template
189
210
  */
190
- async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir) {
191
- const migrationDir = path.join(projectDir, 'supabase', 'migrations');
211
+ async function generateManualMigrationTemplate(tableName, fromDdl, toDdl, projectDir, migrationConfig) {
212
+ const migrationDir = path.join(projectDir, migrationConfig?.dir ?? 'db/migrations');
192
213
  if (!fs.existsSync(migrationDir)) {
193
214
  fs.mkdirSync(migrationDir, { recursive: true });
194
215
  }
195
216
  // Analyze diff
196
217
  const { removedColumns, addedColumns } = analyzeDiffForTemplate(fromDdl, toDdl);
197
- // Generate filename from timestamp
198
- const now = new Date();
199
- const timestamp = now.toISOString()
200
- .replace(/[-:]/g, '')
201
- .replace(/\..+/, '')
202
- .replace('T', '');
203
- const filename = `${timestamp}_manual_update_${tableName}.sql`;
218
+ const filename = resolveMigrationFilename(migrationDir, `manual_update_${tableName}`, migrationConfig?.naming);
204
219
  const filepath = path.join(migrationDir, filename);
205
220
  // Build template from actual changes
206
221
  let migrationStatements = [];
@@ -263,14 +278,100 @@ ${migrationStatements.join('\n')}
263
278
  return filepath;
264
279
  }
265
280
  /**
266
- * Advanced diff analysis (detect column changes)
281
+ * Generate a RENAME TABLE migration when a table appears renamed.
282
+ * (old exists on remote, new exists on local, columns are highly similar)
283
+ */
284
+ async function generateRenameTableMigrationFile(schema, oldName, newName, projectDir = '.', migrationConfig) {
285
+ const migrationDir = path.join(projectDir, migrationConfig?.dir ?? 'db/migrations');
286
+ if (!fs.existsSync(migrationDir)) {
287
+ fs.mkdirSync(migrationDir, { recursive: true });
288
+ }
289
+ const filename = resolveMigrationFilename(migrationDir, `rename_${schema}_${oldName}_to_${newName}`, migrationConfig?.naming);
290
+ const filepath = path.join(migrationDir, filename);
291
+ const content = `-- Migration generated by supatool
292
+ -- Rename table: ${schema}.${oldName} → ${schema}.${newName}
293
+ -- Generated at: ${new Date().toISOString()}
294
+ -- WARNING: Review carefully before applying. supatool detected this as a rename
295
+ -- based on column similarity, but it may be an unrelated add/drop pair.
296
+
297
+ ALTER TABLE ${schema === 'public' ? '' : schema + '.'}${oldName} RENAME TO ${newName};
298
+ `;
299
+ fs.writeFileSync(filepath, content, 'utf-8');
300
+ console.log(`Rename migration generated: ${filename}`);
301
+ return filepath;
302
+ }
303
+ /**
304
+ * Normalize function DDL for comparison (strip supatool header comment, trailing whitespace)
267
305
  */
268
- function analyzeColumnChanges(tableName, localDdl, remoteDdl) {
306
+ function normalizeFunctionDdl(ddl) {
307
+ return ddl
308
+ .split('\n')
309
+ .filter(line => !line.startsWith('-- Generated by supatool'))
310
+ .join('\n')
311
+ .replace(/\s+$/, '')
312
+ .trim();
313
+ }
314
+ /**
315
+ * Generate a migration file for a changed function.
316
+ * localDdl is used as-is (must be valid CREATE OR REPLACE FUNCTION DDL).
317
+ */
318
+ async function generateFunctionMigrationFile(schema, funcName, localDdl, remoteDdl, projectDir = '.', migrationConfig) {
319
+ const normalizedLocal = normalizeFunctionDdl(localDdl);
320
+ const normalizedRemote = normalizeFunctionDdl(remoteDdl);
321
+ if (normalizedLocal === normalizedRemote)
322
+ return null;
323
+ const migrationDir = path.join(projectDir, migrationConfig?.dir ?? 'db/migrations');
324
+ if (!fs.existsSync(migrationDir)) {
325
+ fs.mkdirSync(migrationDir, { recursive: true });
326
+ }
327
+ const filename = resolveMigrationFilename(migrationDir, `update_fn_${schema}_${funcName}`, migrationConfig?.naming);
328
+ const filepath = path.join(migrationDir, filename);
329
+ const content = `-- Migration generated by supatool
330
+ -- Function: ${schema}.${funcName}
331
+ -- Generated at: ${new Date().toISOString()}
332
+
333
+ ${normalizedLocal.endsWith(';') ? normalizedLocal : normalizedLocal + ';'}
334
+ `;
335
+ fs.writeFileSync(filepath, content, 'utf-8');
336
+ console.log(`Function migration generated: ${filename}`);
337
+ return filepath;
338
+ }
339
+ /**
340
+ * Generate DROP POLICY IF EXISTS + CREATE POLICY statements for changed policies.
341
+ */
342
+ async function generateRlsMigrationFile(changedPolicies, droppedPolicies, projectDir = '.', migrationConfig) {
343
+ if (changedPolicies.length === 0 && droppedPolicies.length === 0)
344
+ return null;
345
+ const migrationDir = path.join(projectDir, migrationConfig?.dir ?? 'db/migrations');
346
+ if (!fs.existsSync(migrationDir)) {
347
+ fs.mkdirSync(migrationDir, { recursive: true });
348
+ }
269
349
  const statements = [];
270
- // Simple case: detect column name change
271
- const localLines = localDdl.split('\n').map(line => line.trim()).filter(line => line);
272
- const remoteLines = remoteDdl.split('\n').map(line => line.trim()).filter(line => line);
273
- // Advanced detection (type change, default change) to be implemented later
274
- // Currently only basic add/remove
275
- return statements;
350
+ for (const p of droppedPolicies) {
351
+ statements.push(`DROP POLICY IF EXISTS "${p.policyName}" ON ${p.tableName};`);
352
+ }
353
+ for (const p of changedPolicies) {
354
+ statements.push(`DROP POLICY IF EXISTS "${p.policyName}" ON ${p.tableName};`);
355
+ const permissive = p.permissive ? 'PERMISSIVE' : 'RESTRICTIVE';
356
+ const roles = p.roles || 'public';
357
+ let sql = `CREATE POLICY "${p.policyName}" ON ${p.tableName}\n` +
358
+ ` AS ${permissive} FOR ${p.cmd} TO ${roles}`;
359
+ if (p.qual)
360
+ sql += `\n USING (${p.qual})`;
361
+ if (p.withCheck)
362
+ sql += `\n WITH CHECK (${p.withCheck})`;
363
+ sql += ';';
364
+ statements.push(sql);
365
+ }
366
+ const filename = resolveMigrationFilename(migrationDir, 'update_rls', migrationConfig?.naming);
367
+ const filepath = path.join(migrationDir, filename);
368
+ const content = `-- Migration generated by supatool
369
+ -- RLS policies: ${changedPolicies.length} changed, ${droppedPolicies.length} dropped
370
+ -- Generated at: ${new Date().toISOString()}
371
+
372
+ ${statements.join('\n\n')}
373
+ `;
374
+ fs.writeFileSync(filepath, content, 'utf-8');
375
+ console.log(`RLS migration generated: ${filename}`);
376
+ return filepath;
276
377
  }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.migrateRemote = migrateRemote;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const pg_1 = require("pg");
40
+ const MIGRATIONS_TABLE = '_supatool_migrations';
41
+ /**
42
+ * Apply pending SQL migration files to remote DB.
43
+ *
44
+ * Tracks applied migrations in _supatool_migrations table.
45
+ * Files are applied in alphabetical order (timestamp or sequential naming).
46
+ */
47
+ async function migrateRemote(options) {
48
+ const { connectionString, migrationsDir, dryRun = false } = options;
49
+ // Collect .sql files
50
+ if (!fs.existsSync(migrationsDir)) {
51
+ console.error(`❌ Migrations directory not found: ${migrationsDir}`);
52
+ process.exit(1);
53
+ }
54
+ const allFiles = fs.readdirSync(migrationsDir)
55
+ .filter(f => f.endsWith('.sql'))
56
+ .sort();
57
+ if (allFiles.length === 0) {
58
+ console.log('No migration files found.');
59
+ return;
60
+ }
61
+ const client = new pg_1.Client({
62
+ connectionString,
63
+ ssl: { rejectUnauthorized: false }
64
+ });
65
+ try {
66
+ await client.connect();
67
+ // Ensure tracking table exists
68
+ await client.query(`
69
+ CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
70
+ id SERIAL PRIMARY KEY,
71
+ filename TEXT NOT NULL UNIQUE,
72
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
73
+ )
74
+ `);
75
+ // Get already-applied migrations
76
+ const applied = await client.query(`SELECT filename FROM ${MIGRATIONS_TABLE} ORDER BY filename`);
77
+ const appliedSet = new Set(applied.rows.map(r => r.filename));
78
+ const pending = allFiles.filter(f => !appliedSet.has(f));
79
+ if (pending.length === 0) {
80
+ console.log('✅ All migrations already applied.');
81
+ return;
82
+ }
83
+ console.log(`Pending migrations: ${pending.length}`);
84
+ for (const f of pending) {
85
+ console.log(` • ${f}`);
86
+ }
87
+ if (dryRun) {
88
+ console.log('\n(dry-run) No changes applied.');
89
+ return;
90
+ }
91
+ // Apply each pending migration in a transaction
92
+ for (const filename of pending) {
93
+ const filepath = path.join(migrationsDir, filename);
94
+ const sql = fs.readFileSync(filepath, 'utf-8');
95
+ process.stdout.write(`Applying ${filename}... `);
96
+ await client.query('BEGIN');
97
+ try {
98
+ await client.query(sql);
99
+ await client.query(`INSERT INTO ${MIGRATIONS_TABLE} (filename) VALUES ($1)`, [filename]);
100
+ await client.query('COMMIT');
101
+ console.log('✅');
102
+ }
103
+ catch (err) {
104
+ await client.query('ROLLBACK');
105
+ console.log('❌');
106
+ throw err;
107
+ }
108
+ }
109
+ console.log(`\n✅ Applied ${pending.length} migration(s).`);
110
+ }
111
+ finally {
112
+ await client.end();
113
+ }
114
+ }
@@ -56,6 +56,7 @@ function parseTablesYaml(yamlPath) {
56
56
  * @param options SeedGenOptions
57
57
  */
58
58
  async function generateSeedsFromRemote(options) {
59
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
59
60
  const tables = parseTablesYaml(options.tablesYamlPath);
60
61
  // Generate datetime subdir name (e.g. 20250705_1116_supatool)
61
62
  const now = new Date();
@@ -67,7 +68,10 @@ async function generateSeedsFromRemote(options) {
67
68
  const folderName = `${y}${m}${d}_${hh}${mm}_supatool`;
68
69
  const outDir = path_1.default.join(options.outputDir, folderName);
69
70
  // DB connection
70
- const client = new pg_1.Client({ connectionString: options.connectionString });
71
+ const client = new pg_1.Client({
72
+ connectionString: options.connectionString,
73
+ ssl: { rejectUnauthorized: false }
74
+ });
71
75
  await client.connect();
72
76
  const processedFiles = [];
73
77
  for (const { schema, table } of tables) {
@@ -77,8 +81,15 @@ async function generateSeedsFromRemote(options) {
77
81
  fs_1.default.mkdirSync(schemaDir, { recursive: true });
78
82
  }
79
83
  // Fetch data
80
- const res = await client.query(`SELECT * FROM "${schema}"."${table}"`);
81
- const rows = res.rows;
84
+ let rows;
85
+ try {
86
+ const res = await client.query(`SELECT * FROM "${schema}"."${table}"`);
87
+ rows = res.rows;
88
+ }
89
+ catch (err) {
90
+ console.warn(`⚠️ Skip: ${schema}.${table} — ${err.message}`);
91
+ continue;
92
+ }
82
93
  // Output JSON
83
94
  const fileName = `${table}_seed.json`;
84
95
  const filePath = path_1.default.join(schemaDir, fileName);
@@ -118,7 +129,7 @@ async function generateSeedsFromRemote(options) {
118
129
  }
119
130
  /** Utility to get table comment */
120
131
  async function getTableComment(connectionString, schema, table) {
121
- const client = new pg_1.Client({ connectionString });
132
+ const client = new pg_1.Client({ connectionString, ssl: { rejectUnauthorized: false } });
122
133
  await client.connect();
123
134
  try {
124
135
  const res = await client.query(`SELECT obj_description(c.oid) as comment FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = $1 AND n.nspname = $2 AND c.relkind = 'r'`, [table, schema]);