relq 1.0.4 → 1.0.6
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/dist/cjs/cli/commands/add.cjs +252 -12
- package/dist/cjs/cli/commands/commit.cjs +12 -1
- package/dist/cjs/cli/commands/export.cjs +25 -19
- package/dist/cjs/cli/commands/import.cjs +219 -100
- package/dist/cjs/cli/commands/init.cjs +86 -14
- package/dist/cjs/cli/commands/pull.cjs +104 -23
- package/dist/cjs/cli/commands/push.cjs +38 -3
- package/dist/cjs/cli/index.cjs +9 -1
- package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
- package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
- package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
- package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
- package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
- package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
- package/dist/cjs/cli/utils/ast/index.cjs +19 -0
- package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
- package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
- package/dist/cjs/cli/utils/ast/types.cjs +2 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
- package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
- package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
- package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
- package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
- package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
- package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
- package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
- package/dist/cjs/cli/utils/type-generator.cjs +114 -15
- package/dist/cjs/core/relq-client.cjs +22 -6
- package/dist/cjs/schema-definition/column-types.cjs +150 -13
- package/dist/cjs/schema-definition/defaults.cjs +72 -0
- package/dist/cjs/schema-definition/index.cjs +15 -1
- package/dist/cjs/schema-definition/introspection.cjs +7 -3
- package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
- package/dist/cjs/schema-definition/pg-view.cjs +30 -0
- package/dist/cjs/schema-definition/table-definition.cjs +110 -4
- package/dist/cjs/types/config-types.cjs +13 -4
- package/dist/cjs/utils/aws-dsql.cjs +177 -0
- package/dist/config.d.ts +146 -1
- package/dist/esm/cli/commands/add.js +250 -13
- package/dist/esm/cli/commands/commit.js +12 -1
- package/dist/esm/cli/commands/export.js +25 -19
- package/dist/esm/cli/commands/import.js +221 -102
- package/dist/esm/cli/commands/init.js +86 -14
- package/dist/esm/cli/commands/pull.js +106 -25
- package/dist/esm/cli/commands/push.js +39 -4
- package/dist/esm/cli/index.js +9 -1
- package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
- package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
- package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
- package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
- package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
- package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
- package/dist/esm/cli/utils/ast/index.js +3 -0
- package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
- package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
- package/dist/esm/cli/utils/ast/types.js +1 -0
- package/dist/esm/cli/utils/ast-codegen.js +945 -0
- package/dist/esm/cli/utils/ast-transformer.js +907 -0
- package/dist/esm/cli/utils/change-tracker.js +50 -1
- package/dist/esm/cli/utils/cli-utils.js +147 -0
- package/dist/esm/cli/utils/fast-introspect.js +149 -23
- package/dist/esm/cli/utils/pg-parser.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +114 -4
- package/dist/esm/cli/utils/schema-comparator.js +98 -14
- package/dist/esm/cli/utils/schema-introspect.js +56 -19
- package/dist/esm/cli/utils/snapshot-manager.js +0 -1
- package/dist/esm/cli/utils/sql-generator.js +353 -64
- package/dist/esm/cli/utils/type-generator.js +114 -15
- package/dist/esm/core/relq-client.js +23 -7
- package/dist/esm/schema-definition/column-types.js +147 -12
- package/dist/esm/schema-definition/defaults.js +69 -0
- package/dist/esm/schema-definition/index.js +3 -0
- package/dist/esm/schema-definition/introspection.js +7 -3
- package/dist/esm/schema-definition/pg-relations.js +161 -0
- package/dist/esm/schema-definition/pg-view.js +24 -0
- package/dist/esm/schema-definition/table-definition.js +110 -4
- package/dist/esm/types/config-types.js +12 -4
- package/dist/esm/utils/aws-dsql.js +139 -0
- package/dist/index.d.ts +159 -1
- package/dist/schema-builder.d.ts +1314 -32
- package/package.json +1 -1
|
@@ -48,21 +48,69 @@ function checkEnvVars() {
|
|
|
48
48
|
DATABASE_NAME: process.env.DATABASE_NAME,
|
|
49
49
|
DATABASE_USER: process.env.DATABASE_USER,
|
|
50
50
|
DATABASE_PASSWORD: process.env.DATABASE_PASSWORD,
|
|
51
|
+
DATABASE_REGION: process.env.DATABASE_REGION,
|
|
52
|
+
AWS_DATABASE_HOST: process.env.AWS_DATABASE_HOST,
|
|
53
|
+
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
|
|
54
|
+
AWS_DATABASE_USER: process.env.AWS_DATABASE_USER,
|
|
55
|
+
AWS_DATABASE_NAME: process.env.AWS_DATABASE_NAME,
|
|
56
|
+
AWS_DATABASE_PORT: process.env.AWS_DATABASE_PORT,
|
|
57
|
+
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
|
|
51
58
|
RELQ_PG_CONN_URL: process.env.RELQ_PG_CONN_URL,
|
|
52
59
|
};
|
|
53
60
|
const hasConnUrl = !!vars.RELQ_PG_CONN_URL;
|
|
54
|
-
const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME);
|
|
61
|
+
const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME && vars.DATABASE_USER && vars.DATABASE_PASSWORD);
|
|
62
|
+
const hasAws = !!(vars.AWS_ACCESS_KEY_ID && vars.AWS_SECRET_ACCESS_KEY && vars.DATABASE_REGION && vars.AWS_DATABASE_NAME && vars.AWS_DATABASE_HOST);
|
|
55
63
|
return {
|
|
56
|
-
found: hasConnUrl || hasIndividual,
|
|
64
|
+
found: hasConnUrl || hasIndividual || hasAws,
|
|
65
|
+
hasConnUrl,
|
|
66
|
+
hasIndividual,
|
|
67
|
+
hasAws,
|
|
57
68
|
vars,
|
|
58
69
|
};
|
|
59
70
|
}
|
|
71
|
+
async function askConnectionType(rl, options) {
|
|
72
|
+
const choices = [];
|
|
73
|
+
if (options.hasConnUrl) {
|
|
74
|
+
choices.push({ key: String(choices.length + 1), label: 'Connection URL (RELQ_PG_CONN_URL)', value: 'url' });
|
|
75
|
+
}
|
|
76
|
+
if (options.hasIndividual) {
|
|
77
|
+
choices.push({ key: String(choices.length + 1), label: 'Individual Config (DATABASE_HOST, DATABASE_NAME, ...)', value: 'individual' });
|
|
78
|
+
}
|
|
79
|
+
if (options.hasAws) {
|
|
80
|
+
choices.push({ key: String(choices.length + 1), label: 'AWS DSQL (AWS_ACCESS_KEY_ID, DATABASE_REGION, ...)', value: 'aws' });
|
|
81
|
+
}
|
|
82
|
+
if (choices.length === 1) {
|
|
83
|
+
return choices[0].value;
|
|
84
|
+
}
|
|
85
|
+
console.log('Multiple connection options found:');
|
|
86
|
+
console.log('');
|
|
87
|
+
for (const choice of choices) {
|
|
88
|
+
console.log(` ${colors.cyan(choice.key)}. ${choice.label}`);
|
|
89
|
+
}
|
|
90
|
+
console.log('');
|
|
91
|
+
const answer = await ask(rl, `Which connection type? [${choices.map(c => c.key).join('/')}]`, '1');
|
|
92
|
+
const selected = choices.find(c => c.key === answer);
|
|
93
|
+
return selected?.value ?? choices[0].value;
|
|
94
|
+
}
|
|
60
95
|
function generateConfig(options) {
|
|
61
96
|
let connectionBlock;
|
|
62
97
|
if (options.useEnv) {
|
|
63
|
-
if (
|
|
98
|
+
if (options.connectionType === 'url') {
|
|
64
99
|
connectionBlock = ` connection: {
|
|
65
100
|
url: process.env.RELQ_PG_CONN_URL,
|
|
101
|
+
},`;
|
|
102
|
+
}
|
|
103
|
+
else if (options.connectionType === 'aws') {
|
|
104
|
+
connectionBlock = ` connection: {
|
|
105
|
+
database: process.env.AWS_DATABASE_NAME,
|
|
106
|
+
aws: {
|
|
107
|
+
hostname: process.env.AWS_DATABASE_HOST!,
|
|
108
|
+
region: process.env.DATABASE_REGION!,
|
|
109
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
110
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
111
|
+
user: process.env.AWS_DATABASE_USER || 'admin',
|
|
112
|
+
port: parseInt(process.env.AWS_DATABASE_PORT || '5432'),
|
|
113
|
+
},
|
|
66
114
|
},`;
|
|
67
115
|
}
|
|
68
116
|
else {
|
|
@@ -287,30 +335,53 @@ export async function initCommand(context) {
|
|
|
287
335
|
console.log('');
|
|
288
336
|
const envCheck = checkEnvVars();
|
|
289
337
|
let useEnv = false;
|
|
338
|
+
let connectionType = 'individual';
|
|
290
339
|
let host = 'localhost';
|
|
291
340
|
let port = '5432';
|
|
292
341
|
let database = '';
|
|
293
342
|
let user = 'postgres';
|
|
294
343
|
let password = '';
|
|
295
344
|
if (envCheck.found) {
|
|
296
|
-
console.log('Found database
|
|
297
|
-
|
|
298
|
-
|
|
345
|
+
console.log('Found database connection options:');
|
|
346
|
+
console.log('');
|
|
347
|
+
if (envCheck.hasConnUrl) {
|
|
348
|
+
console.log(` ${colors.cyan('Connection URL')}`);
|
|
349
|
+
console.log(` RELQ_PG_CONN_URL: ${envCheck.vars.RELQ_PG_CONN_URL?.substring(0, 40)}...`);
|
|
299
350
|
}
|
|
300
|
-
|
|
351
|
+
if (envCheck.hasIndividual) {
|
|
352
|
+
console.log(` ${colors.cyan('Individual Config')}`);
|
|
301
353
|
if (envCheck.vars.DATABASE_HOST)
|
|
302
|
-
console.log(`
|
|
303
|
-
if (envCheck.vars.DATABASE_PORT)
|
|
304
|
-
console.log(` DATABASE_PORT: ${envCheck.vars.DATABASE_PORT}`);
|
|
354
|
+
console.log(` DATABASE_HOST: ${envCheck.vars.DATABASE_HOST}`);
|
|
305
355
|
if (envCheck.vars.DATABASE_NAME)
|
|
306
|
-
console.log(`
|
|
356
|
+
console.log(` DATABASE_NAME: ${envCheck.vars.DATABASE_NAME}`);
|
|
307
357
|
if (envCheck.vars.DATABASE_USER)
|
|
308
|
-
console.log(`
|
|
358
|
+
console.log(` DATABASE_USER: ${envCheck.vars.DATABASE_USER}`);
|
|
309
359
|
if (envCheck.vars.DATABASE_PASSWORD)
|
|
310
|
-
console.log('
|
|
360
|
+
console.log(' DATABASE_PASSWORD: ***');
|
|
361
|
+
}
|
|
362
|
+
if (envCheck.hasAws) {
|
|
363
|
+
console.log(` ${colors.cyan('AWS DSQL')}`);
|
|
364
|
+
if (envCheck.vars.AWS_DATABASE_HOST)
|
|
365
|
+
console.log(` AWS_DATABASE_HOST: ${envCheck.vars.AWS_DATABASE_HOST}`);
|
|
366
|
+
if (envCheck.vars.DATABASE_REGION)
|
|
367
|
+
console.log(` DATABASE_REGION: ${envCheck.vars.DATABASE_REGION}`);
|
|
368
|
+
if (envCheck.vars.AWS_DATABASE_NAME)
|
|
369
|
+
console.log(` AWS_DATABASE_NAME: ${envCheck.vars.AWS_DATABASE_NAME}`);
|
|
370
|
+
if (envCheck.vars.AWS_ACCESS_KEY_ID)
|
|
371
|
+
console.log(` AWS_ACCESS_KEY_ID: ${envCheck.vars.AWS_ACCESS_KEY_ID?.substring(0, 8)}...`);
|
|
372
|
+
console.log(' AWS_SECRET_ACCESS_KEY: ***');
|
|
311
373
|
}
|
|
312
374
|
console.log('');
|
|
313
|
-
|
|
375
|
+
const optionCount = [envCheck.hasConnUrl, envCheck.hasIndividual, envCheck.hasAws].filter(Boolean).length;
|
|
376
|
+
if (optionCount > 1) {
|
|
377
|
+
connectionType = await askConnectionType(rl, envCheck);
|
|
378
|
+
console.log('');
|
|
379
|
+
useEnv = await askYesNo(rl, `Use ${connectionType === 'aws' ? 'AWS DSQL' : connectionType === 'url' ? 'Connection URL' : 'Individual Config'} from environment?`, true);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
connectionType = envCheck.hasConnUrl ? 'url' : envCheck.hasAws ? 'aws' : 'individual';
|
|
383
|
+
useEnv = await askYesNo(rl, 'Use these environment variables?', true);
|
|
384
|
+
}
|
|
314
385
|
console.log('');
|
|
315
386
|
}
|
|
316
387
|
if (!useEnv) {
|
|
@@ -344,6 +415,7 @@ export async function initCommand(context) {
|
|
|
344
415
|
if (!fs.existsSync(configPath)) {
|
|
345
416
|
const configContent = generateConfig({
|
|
346
417
|
useEnv,
|
|
418
|
+
connectionType,
|
|
347
419
|
host,
|
|
348
420
|
port,
|
|
349
421
|
database,
|
|
@@ -2,11 +2,12 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
4
4
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
5
|
-
import {
|
|
5
|
+
import { introspectedToParsedSchema } from "../utils/ast-transformer.js";
|
|
6
|
+
import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
|
|
6
7
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
7
|
-
import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning } from "../utils/cli-utils.js";
|
|
8
|
+
import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning, createMultiProgress } from "../utils/cli-utils.js";
|
|
8
9
|
import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
9
|
-
import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, addUnstagedChanges, getStagedChanges, getUnstagedChanges, clearWorkingState, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
10
|
+
import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, addUnstagedChanges, getStagedChanges, getUnstagedChanges, clearWorkingState, hashFileContent, saveFileHash, markCommitAsPulled, } from "../utils/repo-manager.js";
|
|
10
11
|
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
11
12
|
function toCamelCase(str) {
|
|
12
13
|
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
@@ -145,8 +146,8 @@ export async function pullCommand(context) {
|
|
|
145
146
|
const author = config.author || 'Relq CLI';
|
|
146
147
|
const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
|
|
147
148
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
148
|
-
const includeFunctions = config.
|
|
149
|
-
const includeTriggers = config.
|
|
149
|
+
const includeFunctions = config.includeFunctions ?? true;
|
|
150
|
+
const includeTriggers = config.includeTriggers ?? true;
|
|
150
151
|
const spinner = createSpinner();
|
|
151
152
|
const startTime = Date.now();
|
|
152
153
|
console.log('');
|
|
@@ -202,12 +203,36 @@ export async function pullCommand(context) {
|
|
|
202
203
|
setFetchHead(remoteHead, projectRoot);
|
|
203
204
|
}
|
|
204
205
|
spinner.succeed(`Fetched ${remoteCommits.length} remote commits`);
|
|
205
|
-
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(colors.bold('Introspecting database...'));
|
|
208
|
+
console.log('');
|
|
209
|
+
const progress = createMultiProgress();
|
|
210
|
+
const progressItems = [
|
|
211
|
+
{ id: 'tables', label: 'tables', status: 'pending', count: 0 },
|
|
212
|
+
{ id: 'columns', label: 'columns', status: 'pending', count: 0 },
|
|
213
|
+
{ id: 'constraints', label: 'constraints', status: 'pending', count: 0 },
|
|
214
|
+
{ id: 'indexes', label: 'indexes', status: 'pending', count: 0 },
|
|
215
|
+
{ id: 'checks', label: 'checks', status: 'pending', count: 0 },
|
|
216
|
+
{ id: 'enums', label: 'enums', status: 'pending', count: 0 },
|
|
217
|
+
{ id: 'partitions', label: 'partitions', status: 'pending', count: 0 },
|
|
218
|
+
{ id: 'extensions', label: 'extensions', status: 'pending', count: 0 },
|
|
219
|
+
...(includeFunctions ? [{ id: 'functions', label: 'functions', status: 'pending', count: 0 }] : []),
|
|
220
|
+
...(includeTriggers ? [{ id: 'triggers', label: 'triggers', status: 'pending', count: 0 }] : []),
|
|
221
|
+
{ id: 'collations', label: 'collations', status: 'pending', count: 0 },
|
|
222
|
+
{ id: 'foreign_servers', label: 'foreign servers', status: 'pending', count: 0 },
|
|
223
|
+
{ id: 'foreign_tables', label: 'foreign tables', status: 'pending', count: 0 },
|
|
224
|
+
];
|
|
225
|
+
progress.setItems(progressItems);
|
|
226
|
+
progress.start();
|
|
206
227
|
const dbSchema = await fastIntrospectDatabase(connection, undefined, {
|
|
207
228
|
includeFunctions,
|
|
208
229
|
includeTriggers,
|
|
230
|
+
onDetailedProgress: (update) => {
|
|
231
|
+
progress.updateItem(update.step, { status: update.status, count: update.count });
|
|
232
|
+
},
|
|
209
233
|
});
|
|
210
|
-
|
|
234
|
+
progress.complete();
|
|
235
|
+
console.log('');
|
|
211
236
|
const ignorePatterns = loadRelqignore(projectRoot);
|
|
212
237
|
const filteredTables = dbSchema.tables
|
|
213
238
|
.filter(t => !isTableIgnored(t.name, ignorePatterns).ignored)
|
|
@@ -241,12 +266,21 @@ export async function pullCommand(context) {
|
|
|
241
266
|
default: c.defaultValue,
|
|
242
267
|
primaryKey: c.isPrimaryKey,
|
|
243
268
|
unique: c.isUnique,
|
|
269
|
+
maxLength: c.maxLength ?? null,
|
|
270
|
+
precision: c.precision ?? null,
|
|
271
|
+
scale: c.scale ?? null,
|
|
272
|
+
comment: c.comment || undefined,
|
|
273
|
+
isGenerated: c.isGenerated || false,
|
|
274
|
+
generationExpression: c.generationExpression ?? null,
|
|
244
275
|
})),
|
|
245
276
|
indexes: t.indexes.map(i => ({
|
|
246
277
|
name: i.name,
|
|
247
278
|
columns: i.columns,
|
|
248
279
|
unique: i.isUnique,
|
|
249
280
|
type: i.type,
|
|
281
|
+
definition: i.definition || undefined,
|
|
282
|
+
whereClause: i.whereClause || undefined,
|
|
283
|
+
comment: i.comment || undefined,
|
|
250
284
|
})),
|
|
251
285
|
constraints: t.constraints.map(c => ({
|
|
252
286
|
name: c.name,
|
|
@@ -256,6 +290,12 @@ export async function pullCommand(context) {
|
|
|
256
290
|
isPartitioned: t.isPartitioned,
|
|
257
291
|
partitionType: t.partitionType,
|
|
258
292
|
partitionKey: normalizePartitionKey(t.partitionKey),
|
|
293
|
+
comment: t.comment || undefined,
|
|
294
|
+
partitions: t.childPartitions?.map((p) => ({
|
|
295
|
+
name: p.name,
|
|
296
|
+
bound: p.partitionBound || '',
|
|
297
|
+
boundType: p.partitionType,
|
|
298
|
+
})) || undefined,
|
|
259
299
|
})),
|
|
260
300
|
enums: filteredEnums.map(e => ({
|
|
261
301
|
name: e.name,
|
|
@@ -359,24 +399,43 @@ export async function pullCommand(context) {
|
|
|
359
399
|
}
|
|
360
400
|
}
|
|
361
401
|
else if (!schemaExists) {
|
|
362
|
-
console.log('
|
|
402
|
+
console.log(colors.bold('Creating schema') + colors.dim(' (first pull)'));
|
|
363
403
|
console.log('');
|
|
404
|
+
const tableCount = filteredTables.length;
|
|
405
|
+
const columnCount = filteredTables.reduce((sum, t) => sum + t.columns.length, 0);
|
|
364
406
|
const indexCount = filteredTables.reduce((sum, t) => sum + (t.indexes?.filter(i => !i.isPrimary).length || 0), 0);
|
|
365
|
-
const
|
|
407
|
+
const partitionedCount = filteredTables.filter(t => t.isPartitioned).length;
|
|
408
|
+
const childPartitionCount = filteredTables.reduce((sum, t) => sum + (t.childPartitions?.length || 0), 0);
|
|
366
409
|
const tableCommentCount = filteredTables.filter(t => t.comment).length;
|
|
367
410
|
const columnCommentCount = filteredTables.reduce((sum, t) => sum + t.columns.filter(c => c.comment).length, 0);
|
|
368
|
-
|
|
369
|
-
|
|
411
|
+
const indexCommentCount = filteredTables.reduce((sum, t) => sum + t.indexes.filter(i => i.comment).length, 0);
|
|
412
|
+
const summaryItems = [];
|
|
413
|
+
summaryItems.push({ count: tableCount, label: 'tables' });
|
|
414
|
+
summaryItems.push({ count: columnCount, label: 'columns' });
|
|
370
415
|
if (indexCount > 0)
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
416
|
+
summaryItems.push({ count: indexCount, label: 'indexes' });
|
|
417
|
+
if (partitionedCount > 0) {
|
|
418
|
+
const partLabel = childPartitionCount > 0
|
|
419
|
+
? `partitioned (${childPartitionCount} child partitions)`
|
|
420
|
+
: 'partitioned';
|
|
421
|
+
summaryItems.push({ count: partitionedCount, label: partLabel });
|
|
422
|
+
}
|
|
423
|
+
if (tableCommentCount > 0 || columnCommentCount > 0 || indexCommentCount > 0) {
|
|
424
|
+
const commentParts = [];
|
|
425
|
+
if (tableCommentCount > 0)
|
|
426
|
+
commentParts.push(`${tableCommentCount} table`);
|
|
427
|
+
if (columnCommentCount > 0)
|
|
428
|
+
commentParts.push(`${columnCommentCount} column`);
|
|
429
|
+
if (indexCommentCount > 0)
|
|
430
|
+
commentParts.push(`${indexCommentCount} index`);
|
|
431
|
+
summaryItems.push({ count: tableCommentCount + columnCommentCount + indexCommentCount, label: `comments (${commentParts.join(', ')})` });
|
|
432
|
+
}
|
|
378
433
|
if (dbSchema.extensions.length > 0)
|
|
379
|
-
|
|
434
|
+
summaryItems.push({ count: dbSchema.extensions.length, label: 'extensions' });
|
|
435
|
+
for (const item of summaryItems) {
|
|
436
|
+
const countStr = String(item.count).padStart(4);
|
|
437
|
+
console.log(`${colors.green('[+]')} ${colors.green(countStr)} ${item.label}`);
|
|
438
|
+
}
|
|
380
439
|
console.log('');
|
|
381
440
|
}
|
|
382
441
|
if (dryRun) {
|
|
@@ -385,13 +444,10 @@ export async function pullCommand(context) {
|
|
|
385
444
|
return;
|
|
386
445
|
}
|
|
387
446
|
spinner.start('Generating TypeScript schema...');
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
includeSchema: true,
|
|
391
|
-
includeIndexes: true,
|
|
392
|
-
includeFunctions,
|
|
393
|
-
includeTriggers,
|
|
447
|
+
const parsedSchema = await introspectedToParsedSchema(dbSchema);
|
|
448
|
+
const typescript = generateTypeScriptFromAST(parsedSchema, {
|
|
394
449
|
camelCase: config.generate?.camelCase ?? true,
|
|
450
|
+
importPath: 'relq/schema-builder',
|
|
395
451
|
});
|
|
396
452
|
spinner.succeed('Generated TypeScript schema');
|
|
397
453
|
const schemaDir = path.dirname(schemaPath);
|
|
@@ -494,6 +550,7 @@ export async function pullCommand(context) {
|
|
|
494
550
|
triggers: filteredTriggers || [],
|
|
495
551
|
};
|
|
496
552
|
const schemaChanges = compareSchemas(beforeSchema, afterSchema);
|
|
553
|
+
applyTrackingIdsToSnapshot(typescript, currentSchema);
|
|
497
554
|
saveSnapshot(currentSchema, projectRoot);
|
|
498
555
|
const duration = Date.now() - startTime;
|
|
499
556
|
if (noCommit) {
|
|
@@ -516,6 +573,7 @@ export async function pullCommand(context) {
|
|
|
516
573
|
const connectionDesc = getConnectionDescription(connection);
|
|
517
574
|
const commitMessage = `pull: sync from ${connectionDesc}`;
|
|
518
575
|
const commit = createCommit(currentSchema, author, commitMessage, projectRoot);
|
|
576
|
+
markCommitAsPulled(commit.hash, connection, projectRoot);
|
|
519
577
|
clearWorkingState(projectRoot);
|
|
520
578
|
console.log('');
|
|
521
579
|
console.log(`Pull completed in ${formatDuration(duration)}`);
|
|
@@ -598,3 +656,26 @@ function detectObjectConflicts(local, remote) {
|
|
|
598
656
|
}
|
|
599
657
|
return conflicts;
|
|
600
658
|
}
|
|
659
|
+
function applyTrackingIdsToSnapshot(typescript, snapshot) {
|
|
660
|
+
for (const table of snapshot.tables) {
|
|
661
|
+
const tablePattern = new RegExp(`defineTable\\s*\\(\\s*['"]${table.name}['"]\\s*,\\s*\\{[^}]+\\}\\s*,\\s*\\{[^}]*\\$trackingId:\\s*['"]([^'"]+)['"]`, 's');
|
|
662
|
+
const tableMatch = typescript.match(tablePattern);
|
|
663
|
+
if (tableMatch) {
|
|
664
|
+
table.trackingId = tableMatch[1];
|
|
665
|
+
}
|
|
666
|
+
for (const col of table.columns) {
|
|
667
|
+
const colPattern = new RegExp(`(?:${col.tsName}|${col.name}):\\s*\\w+\\([^)]*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
668
|
+
const colMatch = typescript.match(colPattern);
|
|
669
|
+
if (colMatch) {
|
|
670
|
+
col.trackingId = colMatch[1];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
for (const idx of table.indexes) {
|
|
674
|
+
const idxPattern = new RegExp(`index\\s*\\(\\s*['"]${idx.name}['"]\\s*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
675
|
+
const idxMatch = typescript.match(idxPattern);
|
|
676
|
+
if (idxMatch) {
|
|
677
|
+
idx.trackingId = idxMatch[1];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
@@ -5,7 +5,7 @@ import { getConnectionDescription } from "../utils/env-loader.js";
|
|
|
5
5
|
import { colors, createSpinner, fatal, confirm, warning } from "../utils/cli-utils.js";
|
|
6
6
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
7
7
|
import { loadRelqignore, isTableIgnored, isColumnIgnored, isEnumIgnored, isDomainIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
8
|
-
import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, } from "../utils/repo-manager.js";
|
|
8
|
+
import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, isCommitSyncedWith, markCommitAsPushed, getConnectionLabel, } from "../utils/repo-manager.js";
|
|
9
9
|
export async function pushCommand(context) {
|
|
10
10
|
const { config, flags } = context;
|
|
11
11
|
if (!config) {
|
|
@@ -41,7 +41,15 @@ export async function pushCommand(context) {
|
|
|
41
41
|
const localCommits = getAllCommits(projectRoot);
|
|
42
42
|
const remoteHashes = new Set(remoteCommits.map(c => c.hash));
|
|
43
43
|
const localHashes = new Set(localCommits.map(c => c.hash));
|
|
44
|
-
const toPush = localCommits.filter(c =>
|
|
44
|
+
const toPush = localCommits.filter(c => {
|
|
45
|
+
if (remoteHashes.has(c.hash))
|
|
46
|
+
return false;
|
|
47
|
+
const syncStatus = isCommitSyncedWith(c, connection);
|
|
48
|
+
if (syncStatus.synced) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
});
|
|
45
53
|
const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
|
|
46
54
|
spinner.succeed('Checked remote commits');
|
|
47
55
|
spinner.start('Introspecting remote database...');
|
|
@@ -131,9 +139,10 @@ export async function pushCommand(context) {
|
|
|
131
139
|
const commitsToProcess = [...toPush].reverse();
|
|
132
140
|
spinner.start(`Pushing ${toPush.length} commit(s)...`);
|
|
133
141
|
for (const commit of commitsToProcess) {
|
|
134
|
-
await pushCommit(connection, commit);
|
|
142
|
+
await pushCommit(connection, commit, projectRoot);
|
|
143
|
+
markCommitAsPushed(commit.hash, connection, projectRoot);
|
|
135
144
|
}
|
|
136
|
-
spinner.succeed(`Pushed ${toPush.length} commit(s)`);
|
|
145
|
+
spinner.succeed(`Pushed ${toPush.length} commit(s) to ${getConnectionLabel(connection)}`);
|
|
137
146
|
}
|
|
138
147
|
if (applySQL) {
|
|
139
148
|
spinner.start('Applying SQL changes...');
|
|
@@ -173,6 +182,32 @@ export async function pushCommand(context) {
|
|
|
173
182
|
}
|
|
174
183
|
await client.query('COMMIT');
|
|
175
184
|
spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
|
|
185
|
+
let hasRenameOperations = false;
|
|
186
|
+
for (const commit of commitsToProcess) {
|
|
187
|
+
const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
|
|
188
|
+
if (fs.existsSync(commitPath)) {
|
|
189
|
+
const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
190
|
+
if (enhancedCommit.changes?.some((c) => c.type === 'RENAME')) {
|
|
191
|
+
hasRenameOperations = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (hasRenameOperations) {
|
|
197
|
+
spinner.start('Updating snapshot after RENAME...');
|
|
198
|
+
try {
|
|
199
|
+
const updatedSchema = await fastIntrospectDatabase(connection, undefined, {
|
|
200
|
+
includeFunctions,
|
|
201
|
+
includeTriggers,
|
|
202
|
+
});
|
|
203
|
+
const snapshotPath = path.join(projectRoot, '.relq', 'snapshot.json');
|
|
204
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(updatedSchema, null, 2));
|
|
205
|
+
spinner.succeed('Snapshot updated with renamed objects');
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
spinner.warn('Could not update snapshot after RENAME - run "relq pull" manually');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
176
211
|
}
|
|
177
212
|
catch (error) {
|
|
178
213
|
try {
|
package/dist/esm/cli/index.js
CHANGED
|
@@ -62,7 +62,14 @@ function parseArgs(argv) {
|
|
|
62
62
|
flags[arg.slice(2, eqIndex)] = arg.slice(eqIndex + 1);
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
|
|
65
|
+
const nextArg = argv[i + 1];
|
|
66
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
67
|
+
flags[arg.slice(2)] = nextArg;
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
flags[arg.slice(2)] = true;
|
|
72
|
+
}
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
else if (arg.startsWith('-') && arg.length === 2) {
|
|
@@ -175,6 +182,7 @@ async function main() {
|
|
|
175
182
|
printHelp();
|
|
176
183
|
process.exit(1);
|
|
177
184
|
}
|
|
185
|
+
console.log('Command:', command);
|
|
178
186
|
let config = null;
|
|
179
187
|
let resolvedProjectRoot = process.cwd();
|
|
180
188
|
if (requiresConfig(command)) {
|