relq 1.0.25 → 1.0.26
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/commit.cjs +80 -0
- package/dist/cjs/cli/commands/import.cjs +1 -0
- package/dist/cjs/cli/commands/pull.cjs +8 -25
- package/dist/cjs/cli/commands/push.cjs +48 -8
- package/dist/cjs/cli/commands/rollback.cjs +205 -84
- package/dist/cjs/cli/commands/schema-ast.cjs +219 -0
- package/dist/cjs/cli/index.cjs +6 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +95 -3
- package/dist/cjs/cli/utils/ast-transformer.cjs +12 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +135 -0
- package/dist/cjs/cli/utils/commit-manager.cjs +54 -0
- package/dist/cjs/cli/utils/migration-generator.cjs +319 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +99 -3
- package/dist/cjs/cli/utils/schema-diff.cjs +390 -0
- package/dist/cjs/cli/utils/schema-hash.cjs +4 -0
- package/dist/cjs/cli/utils/schema-to-ast.cjs +477 -0
- package/dist/cjs/schema-definition/column-types.cjs +50 -4
- package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
- package/dist/cjs/schema-definition/pg-function.cjs +19 -0
- package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
- package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
- package/dist/cjs/schema-definition/pg-view.cjs +17 -0
- package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
- package/dist/cjs/schema-definition/table-definition.cjs +4 -0
- package/dist/config.d.ts +98 -0
- package/dist/esm/cli/commands/commit.js +83 -3
- package/dist/esm/cli/commands/import.js +1 -0
- package/dist/esm/cli/commands/pull.js +9 -26
- package/dist/esm/cli/commands/push.js +49 -9
- package/dist/esm/cli/commands/rollback.js +206 -85
- package/dist/esm/cli/commands/schema-ast.js +183 -0
- package/dist/esm/cli/index.js +6 -0
- package/dist/esm/cli/utils/ast-codegen.js +93 -3
- package/dist/esm/cli/utils/ast-transformer.js +12 -0
- package/dist/esm/cli/utils/change-tracker.js +134 -0
- package/dist/esm/cli/utils/commit-manager.js +51 -0
- package/dist/esm/cli/utils/migration-generator.js +318 -0
- package/dist/esm/cli/utils/repo-manager.js +96 -3
- package/dist/esm/cli/utils/schema-diff.js +389 -0
- package/dist/esm/cli/utils/schema-hash.js +4 -0
- package/dist/esm/cli/utils/schema-to-ast.js +447 -0
- package/dist/esm/schema-definition/column-types.js +50 -4
- package/dist/esm/schema-definition/pg-enum.js +10 -0
- package/dist/esm/schema-definition/pg-function.js +19 -0
- package/dist/esm/schema-definition/pg-sequence.js +22 -1
- package/dist/esm/schema-definition/pg-trigger.js +39 -0
- package/dist/esm/schema-definition/pg-view.js +17 -0
- package/dist/esm/schema-definition/sql-expressions.js +3 -0
- package/dist/esm/schema-definition/table-definition.js +4 -0
- package/dist/index.d.ts +98 -0
- package/dist/schema-builder.d.ts +223 -24
- package/package.json +1 -1
|
@@ -10,6 +10,14 @@ function pgView(name, definition) {
|
|
|
10
10
|
name,
|
|
11
11
|
definition: definition.trim(),
|
|
12
12
|
isMaterialized: false,
|
|
13
|
+
toAST() {
|
|
14
|
+
return {
|
|
15
|
+
name: this.name,
|
|
16
|
+
definition: this.definition,
|
|
17
|
+
isMaterialized: false,
|
|
18
|
+
trackingId: this.$trackingId,
|
|
19
|
+
};
|
|
20
|
+
},
|
|
13
21
|
};
|
|
14
22
|
}
|
|
15
23
|
function pgMaterializedView(name, definition, options) {
|
|
@@ -19,6 +27,15 @@ function pgMaterializedView(name, definition, options) {
|
|
|
19
27
|
definition: definition.trim(),
|
|
20
28
|
isMaterialized: true,
|
|
21
29
|
withData: options?.withData,
|
|
30
|
+
toAST() {
|
|
31
|
+
return {
|
|
32
|
+
name: this.name,
|
|
33
|
+
definition: this.definition,
|
|
34
|
+
isMaterialized: true,
|
|
35
|
+
withData: this.withData,
|
|
36
|
+
trackingId: this.$trackingId,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
22
39
|
};
|
|
23
40
|
}
|
|
24
41
|
function viewToSQL(view) {
|
|
@@ -7,6 +7,7 @@ exports.defineTable = defineTable;
|
|
|
7
7
|
const pg_format_1 = __importDefault(require("../addon/pg-format/index.cjs"));
|
|
8
8
|
const partitions_1 = require("./partitions.cjs");
|
|
9
9
|
const sql_expressions_1 = require("./sql-expressions.cjs");
|
|
10
|
+
const schema_to_ast_1 = require("../cli/utils/schema-to-ast.cjs");
|
|
10
11
|
function formatWhereValue(val) {
|
|
11
12
|
if (val === null)
|
|
12
13
|
return 'NULL';
|
|
@@ -481,6 +482,9 @@ function defineTable(name, columns, options) {
|
|
|
481
482
|
},
|
|
482
483
|
toCreateIndexSQL() {
|
|
483
484
|
return generateIndexSQL(this);
|
|
485
|
+
},
|
|
486
|
+
toAST() {
|
|
487
|
+
return (0, schema_to_ast_1.tableToAST)(this);
|
|
484
488
|
}
|
|
485
489
|
};
|
|
486
490
|
return definition;
|
package/dist/config.d.ts
CHANGED
|
@@ -261,6 +261,102 @@ export interface DefaultValue {
|
|
|
261
261
|
readonly $sql: string;
|
|
262
262
|
readonly $isDefault: true;
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* AST Type Definitions
|
|
266
|
+
*
|
|
267
|
+
* Type definitions for parsed PostgreSQL schema objects.
|
|
268
|
+
* These are intermediate representations between pgsql-parser AST and TypeScript code generation.
|
|
269
|
+
*/
|
|
270
|
+
export interface ParsedColumn {
|
|
271
|
+
name: string;
|
|
272
|
+
type: string;
|
|
273
|
+
typeParams?: {
|
|
274
|
+
precision?: number;
|
|
275
|
+
scale?: number;
|
|
276
|
+
length?: number;
|
|
277
|
+
};
|
|
278
|
+
isNullable: boolean;
|
|
279
|
+
isPrimaryKey: boolean;
|
|
280
|
+
isUnique: boolean;
|
|
281
|
+
hasDefault: boolean;
|
|
282
|
+
defaultValue?: string;
|
|
283
|
+
isGenerated: boolean;
|
|
284
|
+
generatedExpression?: string;
|
|
285
|
+
generatedExpressionAst?: any;
|
|
286
|
+
checkConstraint?: {
|
|
287
|
+
name: string;
|
|
288
|
+
expression: string;
|
|
289
|
+
expressionAst?: any;
|
|
290
|
+
};
|
|
291
|
+
references?: {
|
|
292
|
+
table: string;
|
|
293
|
+
column: string;
|
|
294
|
+
onDelete?: string;
|
|
295
|
+
onUpdate?: string;
|
|
296
|
+
match?: "SIMPLE" | "FULL";
|
|
297
|
+
deferrable?: boolean;
|
|
298
|
+
initiallyDeferred?: boolean;
|
|
299
|
+
};
|
|
300
|
+
isArray: boolean;
|
|
301
|
+
arrayDimensions?: number;
|
|
302
|
+
comment?: string;
|
|
303
|
+
/** Tracking ID for rename detection in versioning */
|
|
304
|
+
trackingId?: string;
|
|
305
|
+
}
|
|
306
|
+
export interface ParsedConstraint {
|
|
307
|
+
name: string;
|
|
308
|
+
type: "PRIMARY KEY" | "UNIQUE" | "FOREIGN KEY" | "CHECK" | "EXCLUDE";
|
|
309
|
+
columns: string[];
|
|
310
|
+
expression?: string;
|
|
311
|
+
expressionAst?: any;
|
|
312
|
+
comment?: string;
|
|
313
|
+
references?: {
|
|
314
|
+
table: string;
|
|
315
|
+
columns: string[];
|
|
316
|
+
onDelete?: string;
|
|
317
|
+
onUpdate?: string;
|
|
318
|
+
match?: "SIMPLE" | "FULL";
|
|
319
|
+
deferrable?: boolean;
|
|
320
|
+
initiallyDeferred?: boolean;
|
|
321
|
+
};
|
|
322
|
+
/** Tracking ID for rename detection in versioning */
|
|
323
|
+
trackingId?: string;
|
|
324
|
+
}
|
|
325
|
+
export interface ParsedIndex {
|
|
326
|
+
name: string;
|
|
327
|
+
columns: string[];
|
|
328
|
+
isUnique: boolean;
|
|
329
|
+
method?: string;
|
|
330
|
+
whereClause?: string;
|
|
331
|
+
whereClauseAst?: any;
|
|
332
|
+
includeColumns?: string[];
|
|
333
|
+
opclass?: string;
|
|
334
|
+
isExpression?: boolean;
|
|
335
|
+
expressions?: string[];
|
|
336
|
+
comment?: string;
|
|
337
|
+
/** Tracking ID for rename detection in versioning */
|
|
338
|
+
trackingId?: string;
|
|
339
|
+
}
|
|
340
|
+
export interface ParsedTable {
|
|
341
|
+
name: string;
|
|
342
|
+
schema?: string;
|
|
343
|
+
columns: ParsedColumn[];
|
|
344
|
+
constraints: ParsedConstraint[];
|
|
345
|
+
indexes: ParsedIndex[];
|
|
346
|
+
isPartitioned: boolean;
|
|
347
|
+
partitionType?: "RANGE" | "LIST" | "HASH";
|
|
348
|
+
partitionKey?: string[];
|
|
349
|
+
partitionOf?: string;
|
|
350
|
+
partitionBound?: string;
|
|
351
|
+
inherits?: string[];
|
|
352
|
+
comment?: string;
|
|
353
|
+
childPartitions?: {
|
|
354
|
+
name: string;
|
|
355
|
+
partitionBound: string;
|
|
356
|
+
}[];
|
|
357
|
+
/** Tracking ID for rename detection in versioning */
|
|
358
|
+
trackingId?: string;
|
|
359
|
+
}
|
|
264
360
|
declare const EMPTY_OBJECT: unique symbol;
|
|
265
361
|
declare const EMPTY_ARRAY: unique symbol;
|
|
266
362
|
export interface ColumnConfig<T = unknown> {
|
|
@@ -505,6 +601,8 @@ export interface TableDefinition<T extends Record<string, ColumnConfig>> {
|
|
|
505
601
|
$inferInsert: BuildInsertType<T>;
|
|
506
602
|
toSQL(): string;
|
|
507
603
|
toCreateIndexSQL(): string[];
|
|
604
|
+
/** Returns AST for schema diffing and migration generation */
|
|
605
|
+
toAST(): ParsedTable;
|
|
508
606
|
}
|
|
509
607
|
/**
|
|
510
608
|
* AWS regions with autocomplete support
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
|
-
import { fatal, hint } from "../utils/cli-utils.js";
|
|
3
|
-
import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
2
|
+
import { colors, createSpinner, fatal, hint } from "../utils/cli-utils.js";
|
|
3
|
+
import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, loadSnapshot, shortHash, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
4
4
|
import { loadConfig } from "../../config/config.js";
|
|
5
5
|
import { getSchemaPath } from "../utils/config-loader.js";
|
|
6
|
-
import { sortChangesByDependency, generateCombinedSQL, } from "../utils/change-tracker.js";
|
|
6
|
+
import { sortChangesByDependency, generateCombinedSQL, generateDownSQL, } from "../utils/change-tracker.js";
|
|
7
|
+
import { createCommitFromSchema, generateASTHash, } from "../utils/commit-manager.js";
|
|
8
|
+
import { schemaToAST } from "../utils/schema-to-ast.js";
|
|
7
9
|
import * as fs from 'fs';
|
|
8
10
|
import * as path from 'path';
|
|
11
|
+
import { createJiti } from 'jiti';
|
|
9
12
|
export async function commitCommand(context) {
|
|
10
13
|
const { config, flags, args, projectRoot } = context;
|
|
11
14
|
const author = config?.author || 'Developer <dev@example.com>';
|
|
@@ -16,6 +19,9 @@ export async function commitCommand(context) {
|
|
|
16
19
|
const schemaPath = path.resolve(projectRoot, getSchemaPath(config ?? undefined));
|
|
17
20
|
const { requireValidSchema } = await import("../utils/config-loader.js");
|
|
18
21
|
await requireValidSchema(schemaPath, flags);
|
|
22
|
+
if (flags['from-schema']) {
|
|
23
|
+
return commitFromSchema(context, schemaPath, author);
|
|
24
|
+
}
|
|
19
25
|
const staged = getStagedChanges(projectRoot);
|
|
20
26
|
if (staged.length === 0) {
|
|
21
27
|
console.log('nothing to commit, working tree clean');
|
|
@@ -40,6 +46,8 @@ export async function commitCommand(context) {
|
|
|
40
46
|
}
|
|
41
47
|
const sortedChanges = sortChangesByDependency(staged);
|
|
42
48
|
const sql = generateCombinedSQL(sortedChanges);
|
|
49
|
+
const downSQL = generateDownSQL(sortedChanges);
|
|
50
|
+
const snapshot = loadSnapshot(projectRoot);
|
|
43
51
|
const creates = staged.filter(c => c.type === 'CREATE').length;
|
|
44
52
|
const alters = staged.filter(c => c.type === 'ALTER').length;
|
|
45
53
|
const drops = staged.filter(c => c.type === 'DROP').length;
|
|
@@ -60,6 +68,8 @@ export async function commitCommand(context) {
|
|
|
60
68
|
timestamp: new Date().toISOString(),
|
|
61
69
|
changes: sortedChanges,
|
|
62
70
|
sql,
|
|
71
|
+
downSQL,
|
|
72
|
+
schema: snapshot,
|
|
63
73
|
snapshotHash: hash.substring(0, 12),
|
|
64
74
|
stats: {
|
|
65
75
|
creates,
|
|
@@ -113,4 +123,74 @@ export async function commitCommand(context) {
|
|
|
113
123
|
hint("run 'relq export' to export as SQL file");
|
|
114
124
|
console.log('');
|
|
115
125
|
}
|
|
126
|
+
async function commitFromSchema(context, schemaPath, author) {
|
|
127
|
+
const { config, flags, args, projectRoot } = context;
|
|
128
|
+
const spinner = createSpinner();
|
|
129
|
+
let message = flags['m'] || flags['message'];
|
|
130
|
+
if (!message) {
|
|
131
|
+
if (args.length > 0) {
|
|
132
|
+
message = args.join(' ');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
fatal('commit message required', "usage: relq commit --from-schema -m '<message>'");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
spinner.start('Loading schema file');
|
|
139
|
+
let schemaModule;
|
|
140
|
+
try {
|
|
141
|
+
const jiti = createJiti(path.dirname(schemaPath), { interopDefault: true });
|
|
142
|
+
const module = await jiti.import(schemaPath);
|
|
143
|
+
if (module && module.default && typeof module.default === 'object') {
|
|
144
|
+
schemaModule = module.default;
|
|
145
|
+
}
|
|
146
|
+
else if (module && typeof module === 'object') {
|
|
147
|
+
schemaModule = module;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw new Error('Schema file must export an object with table/enum definitions');
|
|
151
|
+
}
|
|
152
|
+
spinner.succeed('Loaded schema file');
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
spinner.fail('Failed to load schema');
|
|
156
|
+
fatal(`Could not load schema: ${err instanceof Error ? err.message : String(err)}`);
|
|
157
|
+
}
|
|
158
|
+
spinner.start('Converting schema to AST');
|
|
159
|
+
const ast = schemaToAST(schemaModule);
|
|
160
|
+
spinner.succeed('Converted schema to AST');
|
|
161
|
+
const schemaHash = generateASTHash(ast);
|
|
162
|
+
spinner.start('Creating commit');
|
|
163
|
+
try {
|
|
164
|
+
const commit = createCommitFromSchema(schemaModule, author, message, config?.commitLimit ?? 1000, projectRoot);
|
|
165
|
+
spinner.succeed('Created commit');
|
|
166
|
+
const tableCount = ast.tables.length;
|
|
167
|
+
const enumCount = ast.enums.length;
|
|
168
|
+
const functionCount = ast.functions.length;
|
|
169
|
+
const viewCount = ast.views.length;
|
|
170
|
+
const triggerCount = ast.triggers.length;
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(`[${shortHash(commit.hash)}] ${message}`);
|
|
173
|
+
const statsParts = [];
|
|
174
|
+
if (tableCount > 0)
|
|
175
|
+
statsParts.push(`${tableCount} table(s)`);
|
|
176
|
+
if (enumCount > 0)
|
|
177
|
+
statsParts.push(`${enumCount} enum(s)`);
|
|
178
|
+
if (functionCount > 0)
|
|
179
|
+
statsParts.push(`${functionCount} function(s)`);
|
|
180
|
+
if (viewCount > 0)
|
|
181
|
+
statsParts.push(`${viewCount} view(s)`);
|
|
182
|
+
if (triggerCount > 0)
|
|
183
|
+
statsParts.push(`${triggerCount} trigger(s)`);
|
|
184
|
+
console.log(` ${statsParts.length > 0 ? statsParts.join(', ') : 'empty schema'}`);
|
|
185
|
+
console.log('');
|
|
186
|
+
console.log(colors.muted(`Schema hash: ${schemaHash.substring(0, 12)}`));
|
|
187
|
+
console.log('');
|
|
188
|
+
hint("run 'relq push' to apply changes to database");
|
|
189
|
+
hint("run 'relq log' to view commit history");
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
spinner.fail('Failed to create commit');
|
|
193
|
+
fatal(`Could not create commit: ${err instanceof Error ? err.message : String(err)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
116
196
|
export default commitCommand;
|
|
@@ -175,6 +175,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
175
175
|
tables: filteredTables,
|
|
176
176
|
enums: filteredEnums,
|
|
177
177
|
domains: filteredDomains,
|
|
178
|
+
compositeTypes: parsedSchema.compositeTypes || [],
|
|
178
179
|
sequences: filteredSequences,
|
|
179
180
|
views: parsedSchema.views,
|
|
180
181
|
functions: parsedSchema.functions,
|
|
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { requireValidConfig, getSchemaPath } from "../utils/config-loader.js";
|
|
4
4
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
5
5
|
import { introspectedToParsedSchema } from "../utils/ast-transformer.js";
|
|
6
|
-
import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
|
|
6
|
+
import { generateTypeScriptFromAST, assignTrackingIds, copyTrackingIdsToNormalized } from "../utils/ast-codegen.js";
|
|
7
7
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
8
8
|
import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning, createMultiProgress } from "../utils/cli-utils.js";
|
|
9
9
|
import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
@@ -377,13 +377,18 @@ export async function pullCommand(context) {
|
|
|
377
377
|
console.log(` ${colors.red(`-${removed.length}`)} tables removed`);
|
|
378
378
|
}
|
|
379
379
|
console.log('');
|
|
380
|
-
|
|
380
|
+
const noAutoMerge = flags['no-auto-merge'] === true;
|
|
381
|
+
if (!dryRun && noAutoMerge) {
|
|
381
382
|
const proceed = await confirm(`${colors.bold('Pull these changes?')}`, true);
|
|
382
383
|
if (!proceed) {
|
|
383
384
|
fatal('Operation cancelled by user');
|
|
384
385
|
}
|
|
385
386
|
console.log('');
|
|
386
387
|
}
|
|
388
|
+
else if (!dryRun) {
|
|
389
|
+
console.log(`${colors.green('Auto-merging')} (no conflicts detected)`);
|
|
390
|
+
console.log('');
|
|
391
|
+
}
|
|
387
392
|
}
|
|
388
393
|
else if (schemaExists && !force) {
|
|
389
394
|
warning('Local schema exists but not tracked');
|
|
@@ -445,6 +450,7 @@ export async function pullCommand(context) {
|
|
|
445
450
|
}
|
|
446
451
|
spinner.start('Generating TypeScript schema...');
|
|
447
452
|
const parsedSchema = await introspectedToParsedSchema(dbSchema);
|
|
453
|
+
assignTrackingIds(parsedSchema);
|
|
448
454
|
const typescript = generateTypeScriptFromAST(parsedSchema, {
|
|
449
455
|
camelCase: config.generate?.camelCase ?? true,
|
|
450
456
|
importPath: 'relq/schema-builder',
|
|
@@ -550,7 +556,7 @@ export async function pullCommand(context) {
|
|
|
550
556
|
triggers: filteredTriggers || [],
|
|
551
557
|
};
|
|
552
558
|
const schemaChanges = compareSchemas(beforeSchema, afterSchema);
|
|
553
|
-
|
|
559
|
+
copyTrackingIdsToNormalized(parsedSchema, currentSchema);
|
|
554
560
|
saveSnapshot(currentSchema, projectRoot);
|
|
555
561
|
const duration = Date.now() - startTime;
|
|
556
562
|
if (noCommit) {
|
|
@@ -656,26 +662,3 @@ function detectObjectConflicts(local, remote) {
|
|
|
656
662
|
}
|
|
657
663
|
return conflicts;
|
|
658
664
|
}
|
|
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, isCommitSyncedWith, markCommitAsPushed, getConnectionLabel, } from "../utils/repo-manager.js";
|
|
8
|
+
import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, isCommitSyncedWith, markCommitAsPushed, markCommitAsApplied, getConnectionLabel, } from "../utils/repo-manager.js";
|
|
9
9
|
export async function pushCommand(context) {
|
|
10
10
|
const { config, flags } = context;
|
|
11
11
|
if (!config) {
|
|
@@ -16,7 +16,7 @@ export async function pushCommand(context) {
|
|
|
16
16
|
const { projectRoot } = context;
|
|
17
17
|
const force = flags['force'] === true;
|
|
18
18
|
const dryRun = flags['dry-run'] === true;
|
|
19
|
-
const
|
|
19
|
+
const metadataOnly = flags['metadata-only'] === true;
|
|
20
20
|
const noVerify = flags['no-verify'] === true;
|
|
21
21
|
const skipPrompt = flags['yes'] === true || flags['y'] === true;
|
|
22
22
|
const includeFunctions = config.includeFunctions ?? false;
|
|
@@ -129,9 +129,46 @@ export async function pushCommand(context) {
|
|
|
129
129
|
console.log('');
|
|
130
130
|
}
|
|
131
131
|
if (dryRun) {
|
|
132
|
-
console.log(`${colors.yellow('Dry run')} -
|
|
132
|
+
console.log(`${colors.yellow('Dry run')} - showing changes that would be applied`);
|
|
133
133
|
console.log('');
|
|
134
|
-
|
|
134
|
+
if (hasObjectsToDrop && force) {
|
|
135
|
+
console.log(`${colors.red('DROP statements:')}`);
|
|
136
|
+
for (const obj of analysis.objectsToDrop.slice(0, 5)) {
|
|
137
|
+
console.log(` ${generateDropSQL(obj)}`);
|
|
138
|
+
}
|
|
139
|
+
if (analysis.objectsToDrop.length > 5) {
|
|
140
|
+
console.log(` ${colors.muted(`... and ${analysis.objectsToDrop.length - 5} more`)}`);
|
|
141
|
+
}
|
|
142
|
+
console.log('');
|
|
143
|
+
}
|
|
144
|
+
const commitsToProcess = [...toPush].reverse();
|
|
145
|
+
let totalStatements = 0;
|
|
146
|
+
for (const commit of commitsToProcess) {
|
|
147
|
+
const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
|
|
148
|
+
if (fs.existsSync(commitPath)) {
|
|
149
|
+
const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
150
|
+
if (enhancedCommit.sql && enhancedCommit.sql.trim()) {
|
|
151
|
+
const statements = enhancedCommit.sql.split(';').filter(s => s.trim());
|
|
152
|
+
totalStatements += statements.length;
|
|
153
|
+
console.log(`${colors.cyan(`Commit ${shortHash(commit.hash)}:`)} ${commit.message}`);
|
|
154
|
+
for (const stmt of statements.slice(0, 3)) {
|
|
155
|
+
console.log(` ${stmt.trim().substring(0, 80)}${stmt.trim().length > 80 ? '...' : ''};`);
|
|
156
|
+
}
|
|
157
|
+
if (statements.length > 3) {
|
|
158
|
+
console.log(` ${colors.muted(`... and ${statements.length - 3} more statements`)}`);
|
|
159
|
+
}
|
|
160
|
+
console.log('');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (totalStatements === 0 && !hasObjectsToDrop) {
|
|
165
|
+
console.log(`${colors.muted('No SQL changes to apply')}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`${colors.muted('Total:')} ${totalStatements + (hasObjectsToDrop ? analysis.objectsToDrop.length : 0)} statements`);
|
|
169
|
+
}
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log(`${colors.muted('Remove')} ${colors.cyan('--dry-run')} ${colors.muted('to execute these changes.')}`);
|
|
135
172
|
console.log('');
|
|
136
173
|
return;
|
|
137
174
|
}
|
|
@@ -144,8 +181,8 @@ export async function pushCommand(context) {
|
|
|
144
181
|
}
|
|
145
182
|
spinner.succeed(`Pushed ${toPush.length} commit(s) to ${getConnectionLabel(connection)}`);
|
|
146
183
|
}
|
|
147
|
-
if (
|
|
148
|
-
spinner.start('Applying
|
|
184
|
+
if (!metadataOnly && !dryRun) {
|
|
185
|
+
spinner.start('Applying schema changes...');
|
|
149
186
|
const pg = await import("../../addon/pg/index.js");
|
|
150
187
|
const client = new pg.Client({
|
|
151
188
|
host: connection.host,
|
|
@@ -182,6 +219,9 @@ export async function pushCommand(context) {
|
|
|
182
219
|
}
|
|
183
220
|
await client.query('COMMIT');
|
|
184
221
|
spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
|
|
222
|
+
for (const commit of commitsToProcess) {
|
|
223
|
+
await markCommitAsApplied(connection, commit.hash);
|
|
224
|
+
}
|
|
185
225
|
let hasRenameOperations = false;
|
|
186
226
|
for (const commit of commitsToProcess) {
|
|
187
227
|
const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
|
|
@@ -226,13 +266,13 @@ export async function pushCommand(context) {
|
|
|
226
266
|
const oldHash = remoteHead ? shortHash(remoteHead) : '(none)';
|
|
227
267
|
const newHash = shortHash(localHead);
|
|
228
268
|
console.log(` ${oldHash}..${newHash} ${colors.muted('main -> main')}`);
|
|
229
|
-
if (hasObjectsToDrop && force &&
|
|
269
|
+
if (hasObjectsToDrop && force && !metadataOnly && !dryRun) {
|
|
230
270
|
console.log('');
|
|
231
271
|
warning(`Dropped ${analysis.objectsToDrop.length} object(s) from remote`);
|
|
232
272
|
}
|
|
233
|
-
if (
|
|
273
|
+
if (metadataOnly) {
|
|
234
274
|
console.log('');
|
|
235
|
-
console.log(`${colors.muted('
|
|
275
|
+
console.log(`${colors.muted('Metadata only - SQL not executed. Remove')} ${colors.cyan('--metadata-only')} ${colors.muted('to apply changes.')}`);
|
|
236
276
|
}
|
|
237
277
|
console.log('');
|
|
238
278
|
}
|