supatool 0.6.0 → 0.6.2
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 +12 -2
- package/dist/bin/helptext.js +1 -0
- package/dist/bin/supatool.js +16 -1
- package/dist/sync/definitionExtractor.js +93 -55
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -89,9 +89,19 @@ Pull schema from remote DB into local files:
|
|
|
89
89
|
```bash
|
|
90
90
|
supatool extract --all -o db/schemas
|
|
91
91
|
# Options:
|
|
92
|
-
# --schema public,agent Specify schemas
|
|
92
|
+
# --schema public,agent Specify schemas (explicit list)
|
|
93
|
+
# -e auth,storage Exclude schemas — targets all others automatically
|
|
93
94
|
# -t "user_*" Filter tables by pattern
|
|
94
|
-
# --force
|
|
95
|
+
# --force Delete .sql files for objects removed from DB
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Unchanged `.sql` files are never overwritten (content is compared excluding the generated header line). Use `--force` to also clean up `.sql` files whose corresponding DB objects have been dropped.
|
|
99
|
+
|
|
100
|
+
When you have many schemas and only want to exclude a few, use `-e` without `--schema`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Extract everything except auth and storage schemas
|
|
104
|
+
supatool extract --all -e auth,storage -o db/schemas
|
|
95
105
|
```
|
|
96
106
|
|
|
97
107
|
### Deploy
|
package/dist/bin/helptext.js
CHANGED
|
@@ -25,6 +25,7 @@ Common Options:
|
|
|
25
25
|
-c, --connection <string> Connection string (postgresql:// or postgres://)
|
|
26
26
|
-o, --output-dir <path> Output directory
|
|
27
27
|
--schema <schemas> Target schemas (comma-separated, default: public)
|
|
28
|
+
--schema-only Regenerate only the specified schema files; skip index files
|
|
28
29
|
--config <path> Configuration file path
|
|
29
30
|
-f, --force Force overwrite
|
|
30
31
|
|
package/dist/bin/supatool.js
CHANGED
|
@@ -76,7 +76,8 @@ program
|
|
|
76
76
|
.option('--no-separate', 'Output all objects in same directory')
|
|
77
77
|
.option('--schema <schemas>', 'Target schemas, comma-separated (default: public)')
|
|
78
78
|
.option('--all-schemas', 'Target all schemas in the DB (use with -e to exclude some)')
|
|
79
|
-
.option('-e, --exclude-schema <schemas>', 'Schemas to exclude, comma-separated
|
|
79
|
+
.option('-e, --exclude-schema <schemas>', 'Schemas to exclude, comma-separated. Without --schema, targets all schemas automatically.')
|
|
80
|
+
.option('--schema-only', 'Regenerate only the specified schema files; do not touch other schemas or index files (llms.txt, schema_index.json, etc.)')
|
|
80
81
|
.option('--config <path>', 'Configuration file path')
|
|
81
82
|
.option('-f, --force', 'Force overwrite without confirmation')
|
|
82
83
|
.action(async (options) => {
|
|
@@ -86,6 +87,18 @@ program
|
|
|
86
87
|
if (!config.connectionString)
|
|
87
88
|
connectionRequiredError();
|
|
88
89
|
try {
|
|
90
|
+
if (options.schemaOnly && !options.schema) {
|
|
91
|
+
console.error('⚠️ --schema-only requires --schema to be specified.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (options.schemaOnly && options.all) {
|
|
95
|
+
console.error('⚠️ --schema-only cannot be combined with --all. Use --schema-only without --all to regenerate only the specified schema files.');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
if (options.schemaOnly && options.allSchemas) {
|
|
99
|
+
console.error('⚠️ --schema-only cannot be combined with --all-schemas.');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
89
102
|
let schemas = ['public'];
|
|
90
103
|
if (options.schema) {
|
|
91
104
|
schemas = options.schema.split(',').map((s) => s.trim());
|
|
@@ -105,6 +118,8 @@ program
|
|
|
105
118
|
schemas: schemas,
|
|
106
119
|
allSchemas: options.allSchemas || false,
|
|
107
120
|
excludeSchemas,
|
|
121
|
+
schemasExplicit: !!options.schema,
|
|
122
|
+
schemaOnly: options.schemaOnly || false,
|
|
108
123
|
version: package_json_1.version
|
|
109
124
|
});
|
|
110
125
|
}
|
|
@@ -257,12 +257,11 @@ async function fetchRelationList(client, schemas = ['public']) {
|
|
|
257
257
|
*/
|
|
258
258
|
async function fetchAllSchemas(client) {
|
|
259
259
|
const result = await client.query(`
|
|
260
|
-
SELECT schema_name
|
|
261
|
-
FROM
|
|
262
|
-
WHERE
|
|
263
|
-
AND
|
|
264
|
-
|
|
265
|
-
ORDER BY schema_name
|
|
260
|
+
SELECT nspname AS schema_name
|
|
261
|
+
FROM pg_catalog.pg_namespace
|
|
262
|
+
WHERE nspname NOT LIKE 'pg_%'
|
|
263
|
+
AND nspname != 'information_schema'
|
|
264
|
+
ORDER BY nspname
|
|
266
265
|
`);
|
|
267
266
|
return result.rows.map((r) => r.schema_name);
|
|
268
267
|
}
|
|
@@ -1039,7 +1038,7 @@ async function generateCreateTableDDL(client, tableName, schemaName = 'public')
|
|
|
1039
1038
|
/**
|
|
1040
1039
|
* Save definitions to files (merge RLS/triggers into table/view; schema folders when multi-schema)
|
|
1041
1040
|
*/
|
|
1042
|
-
async function saveDefinitionsByType(definitions, outputDir, separateDirectories = true, schemas = ['public'], relations = [], rpcTables = [], allSchemas = [], version, tableRlsStatus = []) {
|
|
1041
|
+
async function saveDefinitionsByType(definitions, outputDir, separateDirectories = true, schemas = ['public'], relations = [], rpcTables = [], allSchemas = [], version, tableRlsStatus = [], force = false, schemaOnly = false) {
|
|
1043
1042
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
1044
1043
|
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
1045
1044
|
const outputDate = new Date().toLocaleDateString('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
@@ -1116,7 +1115,10 @@ async function saveDefinitionsByType(definitions, outputDir, separateDirectories
|
|
|
1116
1115
|
...cronJobs,
|
|
1117
1116
|
...customTypes
|
|
1118
1117
|
];
|
|
1119
|
-
|
|
1118
|
+
// schemaOnly always uses schema subdirectories (outputDir/<schema>/type/)
|
|
1119
|
+
// so that single-schema --schema-only runs write to the correct location
|
|
1120
|
+
// and don't interfere with other schemas already on disk.
|
|
1121
|
+
const multiSchema = schemas.length > 1 || schemaOnly;
|
|
1120
1122
|
const typeDirNames = {
|
|
1121
1123
|
table: 'tables',
|
|
1122
1124
|
view: 'views',
|
|
@@ -1128,6 +1130,7 @@ async function saveDefinitionsByType(definitions, outputDir, separateDirectories
|
|
|
1128
1130
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
1129
1131
|
}
|
|
1130
1132
|
const fsPromises = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
1133
|
+
const writtenPaths = new Set();
|
|
1131
1134
|
for (const def of toWrite) {
|
|
1132
1135
|
const typeDir = typeDirNames[def.type];
|
|
1133
1136
|
const baseTypeDir = separateDirectories ? typeDir : '.';
|
|
@@ -1140,10 +1143,67 @@ async function saveDefinitionsByType(definitions, outputDir, separateDirectories
|
|
|
1140
1143
|
const fileName = `${def.name}.sql`;
|
|
1141
1144
|
const filePath = path.join(targetDir, fileName);
|
|
1142
1145
|
const ddlWithNewline = def.ddl.endsWith('\n') ? def.ddl : def.ddl + '\n';
|
|
1143
|
-
|
|
1146
|
+
const newContent = headerComment + ddlWithNewline;
|
|
1147
|
+
writtenPaths.add(filePath);
|
|
1148
|
+
// Skip write if content unchanged (ignore header line which contains the date)
|
|
1149
|
+
if (fs.existsSync(filePath)) {
|
|
1150
|
+
const existingContent = await fsPromises.readFile(filePath, 'utf8');
|
|
1151
|
+
const stripHeader = (c) => c.split('\n').slice(1).join('\n');
|
|
1152
|
+
if (stripHeader(existingContent) === stripHeader(newContent)) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
await fsPromises.writeFile(filePath, newContent);
|
|
1157
|
+
}
|
|
1158
|
+
// When force: delete SQL files that no longer correspond to any extracted object.
|
|
1159
|
+
// With schemaOnly: restrict deletion to the target schema directories only.
|
|
1160
|
+
if (force && fs.existsSync(outputDir)) {
|
|
1161
|
+
const deleteStaleSqlFiles = (dir) => {
|
|
1162
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1163
|
+
const fullPath = path.join(dir, entry.name);
|
|
1164
|
+
if (entry.isDirectory()) {
|
|
1165
|
+
deleteStaleSqlFiles(fullPath);
|
|
1166
|
+
}
|
|
1167
|
+
else if (entry.name.endsWith('.sql') && !writtenPaths.has(fullPath)) {
|
|
1168
|
+
fs.unlinkSync(fullPath);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
if (schemaOnly) {
|
|
1173
|
+
// Only scan within the target schema directories
|
|
1174
|
+
for (const schemaName of schemas) {
|
|
1175
|
+
const schemaDir = path.join(outputDir, schemaName);
|
|
1176
|
+
if (fs.existsSync(schemaDir)) {
|
|
1177
|
+
deleteStaleSqlFiles(schemaDir);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
deleteStaleSqlFiles(outputDir);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
// When schemaOnly: skip index file regeneration to avoid overwriting
|
|
1186
|
+
// shared index files (llms.txt, schema_index.json, etc.) with partial data.
|
|
1187
|
+
if (schemaOnly) {
|
|
1188
|
+
return;
|
|
1144
1189
|
}
|
|
1145
1190
|
await generateIndexFile(toWrite, outputDir, separateDirectories, multiSchema, relations, rpcTables, allSchemas, schemas, version, tableRlsStatus);
|
|
1146
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Write a file only when content has changed, to avoid unnecessary Git diffs.
|
|
1194
|
+
* When skipFirstLine is true, the first line (typically a date-bearing header) is
|
|
1195
|
+
* excluded from the comparison so that daily date changes don't trigger writes.
|
|
1196
|
+
*/
|
|
1197
|
+
function writeFileIfChanged(filePath, newContent, skipFirstLine = false) {
|
|
1198
|
+
const fs = require('fs');
|
|
1199
|
+
if (fs.existsSync(filePath)) {
|
|
1200
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
1201
|
+
const strip = (c) => skipFirstLine ? c.split('\n').slice(1).join('\n') : c;
|
|
1202
|
+
if (strip(existing) === strip(newContent))
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
1206
|
+
}
|
|
1147
1207
|
/**
|
|
1148
1208
|
* Generate index file for DB objects (RLS/triggers already merged into table/view)
|
|
1149
1209
|
*/
|
|
@@ -1265,8 +1325,8 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1265
1325
|
}
|
|
1266
1326
|
const readmePath = path.join(outputDir, 'README.md');
|
|
1267
1327
|
const llmsPath = path.join(outputDir, 'llms.txt');
|
|
1268
|
-
|
|
1269
|
-
|
|
1328
|
+
writeFileIfChanged(readmePath, readmeContent, true);
|
|
1329
|
+
writeFileIfChanged(llmsPath, llmsContent, true);
|
|
1270
1330
|
// schema_index.json (same data for agents that parse JSON)
|
|
1271
1331
|
const schemaIndex = {
|
|
1272
1332
|
objects: definitions.map(def => {
|
|
@@ -1293,7 +1353,7 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1293
1353
|
}
|
|
1294
1354
|
: undefined
|
|
1295
1355
|
};
|
|
1296
|
-
|
|
1356
|
+
writeFileIfChanged(path.join(outputDir, 'schema_index.json'), JSON.stringify(schemaIndex, null, 2) + '\n');
|
|
1297
1357
|
// schema_summary.md (one-file overview for AI) — include RLS status per table
|
|
1298
1358
|
let summaryMd = '# Schema summary\n\n';
|
|
1299
1359
|
const tableDefs = definitions.filter(d => d.type === 'table' || d.type === 'view');
|
|
@@ -1326,7 +1386,7 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1326
1386
|
summaryMd += `- Extracted: ${allSchemas.filter(s => extractedSet.has(s)).join(', ') || '(none)'}\n`;
|
|
1327
1387
|
summaryMd += `- Not extracted: ${allSchemas.filter(s => !extractedSet.has(s)).join(', ') || '(none)'}\n`;
|
|
1328
1388
|
}
|
|
1329
|
-
|
|
1389
|
+
writeFileIfChanged(path.join(outputDir, 'schema_summary.md'), summaryMd);
|
|
1330
1390
|
// RLS disabled tables warning doc (tables only; RLS enabled with 0 policies is not warned)
|
|
1331
1391
|
const rlsNotEnabled = tableRlsStatus.filter(s => !s.rlsEnabled);
|
|
1332
1392
|
if (rlsNotEnabled.length > 0) {
|
|
@@ -1337,14 +1397,14 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1337
1397
|
rlsNotEnabled.forEach(s => {
|
|
1338
1398
|
warnMd += `| ${s.schema} | ${s.table} |\n`;
|
|
1339
1399
|
});
|
|
1340
|
-
|
|
1400
|
+
writeFileIfChanged(path.join(outputDir, 'rls_warnings.md'), warnMd);
|
|
1341
1401
|
}
|
|
1342
1402
|
}
|
|
1343
1403
|
/**
|
|
1344
1404
|
* Classify and output definitions
|
|
1345
1405
|
*/
|
|
1346
1406
|
async function extractDefinitions(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;
|
|
1407
|
+
const { connectionString, outputDir, separateDirectories = true, tablesOnly = false, viewsOnly = false, all = false, tablePattern = '*', force = false, schemas: schemasOption = ['public'], excludeSchemas = [], allSchemas: useAllSchemas = false, schemasExplicit = false, schemaOnly = false, version } = options;
|
|
1348
1408
|
// schemas will be resolved after DB connect when useAllSchemas is true
|
|
1349
1409
|
let schemas = schemasOption;
|
|
1350
1410
|
// Disable Node.js SSL certificate verification
|
|
@@ -1364,18 +1424,13 @@ async function extractDefinitions(options) {
|
|
|
1364
1424
|
}
|
|
1365
1425
|
// URL encode password part
|
|
1366
1426
|
let encodedConnectionString = connectionString;
|
|
1367
|
-
console.log('🔍 Original connection string:', connectionString);
|
|
1368
1427
|
try {
|
|
1369
1428
|
// Special handling when password contains @
|
|
1370
1429
|
if (connectionString.includes('@') && connectionString.split('@').length > 2) {
|
|
1371
|
-
console.log('⚠️ Password contains @, executing special handling');
|
|
1372
1430
|
// Use last @ as delimiter
|
|
1373
1431
|
const parts = connectionString.split('@');
|
|
1374
1432
|
const lastPart = parts.pop(); // Last part (host:port/database)
|
|
1375
1433
|
const firstParts = parts.join('@'); // First part (postgresql://user:password)
|
|
1376
|
-
console.log(' Split result:');
|
|
1377
|
-
console.log(' First part:', firstParts);
|
|
1378
|
-
console.log(' Last part:', lastPart);
|
|
1379
1434
|
// Encode password part
|
|
1380
1435
|
const colonIndex = firstParts.lastIndexOf(':');
|
|
1381
1436
|
if (colonIndex > 0) {
|
|
@@ -1383,50 +1438,40 @@ async function extractDefinitions(options) {
|
|
|
1383
1438
|
const password = firstParts.substring(colonIndex + 1);
|
|
1384
1439
|
const encodedPassword = encodeURIComponent(password);
|
|
1385
1440
|
encodedConnectionString = `${protocolAndUser}:${encodedPassword}@${lastPart}`;
|
|
1386
|
-
console.log(' Encode result:');
|
|
1387
|
-
console.log(' Protocol+User:', protocolAndUser);
|
|
1388
|
-
console.log(' Original password:', password);
|
|
1389
|
-
console.log(' Encoded password:', encodedPassword);
|
|
1390
|
-
console.log(' Final connection string:', encodedConnectionString);
|
|
1391
1441
|
}
|
|
1392
1442
|
}
|
|
1393
1443
|
else {
|
|
1394
|
-
console.log('✅ Executing normal URL parsing');
|
|
1395
1444
|
// Normal URL parsing
|
|
1396
1445
|
const url = new URL(connectionString);
|
|
1397
|
-
// Handle username containing dots
|
|
1398
|
-
if (url.username && url.username.includes('.')) {
|
|
1399
|
-
console.log(`Username (with dots): ${url.username}`);
|
|
1400
|
-
}
|
|
1401
1446
|
if (url.password) {
|
|
1402
1447
|
// Encode only password part
|
|
1403
1448
|
const encodedPassword = encodeURIComponent(url.password);
|
|
1404
1449
|
url.password = encodedPassword;
|
|
1405
1450
|
encodedConnectionString = url.toString();
|
|
1406
|
-
console.log(' Password encoded:', encodedPassword);
|
|
1407
1451
|
}
|
|
1408
1452
|
}
|
|
1409
1453
|
// Add SSL settings for Supabase connection
|
|
1410
1454
|
if (!encodedConnectionString.includes('sslmode=')) {
|
|
1411
1455
|
const separator = encodedConnectionString.includes('?') ? '&' : '?';
|
|
1412
1456
|
encodedConnectionString += `${separator}sslmode=require`;
|
|
1413
|
-
console.log(' SSL setting added:', encodedConnectionString);
|
|
1414
1457
|
}
|
|
1415
|
-
//
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1458
|
+
// Debug-only: connection info (no credentials)
|
|
1459
|
+
if (process.env.SUPATOOL_DEBUG) {
|
|
1460
|
+
const debugUrl = new URL(encodedConnectionString);
|
|
1461
|
+
console.log('🔍 Connection info:');
|
|
1462
|
+
console.log(` Host: ${debugUrl.hostname}`);
|
|
1463
|
+
console.log(` Port: ${debugUrl.port}`);
|
|
1464
|
+
console.log(` Database: ${debugUrl.pathname.slice(1)}`);
|
|
1465
|
+
console.log(` User: ${debugUrl.username}`);
|
|
1466
|
+
console.log(` SSL: ${debugUrl.searchParams.get('sslmode') || 'require'}`);
|
|
1467
|
+
}
|
|
1425
1468
|
}
|
|
1426
1469
|
catch (error) {
|
|
1427
1470
|
// Use original string if URL parsing fails
|
|
1428
|
-
|
|
1429
|
-
|
|
1471
|
+
if (process.env.SUPATOOL_DEBUG) {
|
|
1472
|
+
console.warn('Failed to parse connection string URL. May contain special characters.');
|
|
1473
|
+
console.warn('Error details:', error instanceof Error ? error.message : String(error));
|
|
1474
|
+
}
|
|
1430
1475
|
}
|
|
1431
1476
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
1432
1477
|
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
@@ -1459,14 +1504,11 @@ async function extractDefinitions(options) {
|
|
|
1459
1504
|
}
|
|
1460
1505
|
});
|
|
1461
1506
|
try {
|
|
1462
|
-
// Debug before connect
|
|
1463
|
-
console.log('🔧 Connection settings:');
|
|
1464
|
-
console.log(` SSL: rejectUnauthorized=false`);
|
|
1465
|
-
console.log(` Connection string length: ${encodedConnectionString.length}`);
|
|
1466
1507
|
await client.connect();
|
|
1467
1508
|
spinner.text = 'Connected to database';
|
|
1468
|
-
// Resolve schemas:
|
|
1469
|
-
|
|
1509
|
+
// Resolve schemas: --all-schemas or -e alone (without explicit --schema) → fetch all from DB and subtract excludeSchemas
|
|
1510
|
+
const useAllSchemasEffective = useAllSchemas || (excludeSchemas.length > 0 && !schemasExplicit);
|
|
1511
|
+
if (useAllSchemasEffective) {
|
|
1470
1512
|
const SYSTEM_SCHEMAS = ['information_schema', 'pg_catalog', 'pg_toast', 'pg_temp_1', 'pg_toast_temp_1'];
|
|
1471
1513
|
const discovered = await fetchAllSchemas(client);
|
|
1472
1514
|
schemas = discovered.filter(s => !SYSTEM_SCHEMAS.includes(s) && !excludeSchemas.includes(s));
|
|
@@ -1624,13 +1666,9 @@ async function extractDefinitions(options) {
|
|
|
1624
1666
|
console.warn('RLS status fetch skipped:', err);
|
|
1625
1667
|
}
|
|
1626
1668
|
}
|
|
1627
|
-
// When force: remove output dir then write (so removed tables don't leave files)
|
|
1628
|
-
if (force && fs.existsSync(outputDir)) {
|
|
1629
|
-
fs.rmSync(outputDir, { recursive: true });
|
|
1630
|
-
}
|
|
1631
1669
|
// Save definitions (table+RLS+triggers merged, schema folders)
|
|
1632
1670
|
spinner.text = 'Saving definitions to files...';
|
|
1633
|
-
await saveDefinitionsByType(allDefinitions, outputDir, separateDirectories, schemas, relations, rpcTables, allSchemas, version, tableRlsStatus);
|
|
1671
|
+
await saveDefinitionsByType(allDefinitions, outputDir, separateDirectories, schemas, relations, rpcTables, allSchemas, version, tableRlsStatus, force, schemaOnly);
|
|
1634
1672
|
// Warn at extract time when any table has RLS disabled
|
|
1635
1673
|
const rlsNotEnabled = tableRlsStatus.filter(s => !s.rlsEnabled);
|
|
1636
1674
|
if (rlsNotEnabled.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supatool",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "CLI for PostgreSQL (Cloud SQL / Supabase): extract schema to files, deploy schema diffs, apply migrations, seed export.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
"database",
|
|
28
28
|
"migration"
|
|
29
29
|
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/idea-garage/supatool"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/idea-garage/supatool#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/idea-garage/supatool/issues"
|
|
37
|
+
},
|
|
30
38
|
"author": "IdeaGarage",
|
|
31
39
|
"license": "MIT",
|
|
32
40
|
"dependencies": {
|