relq 1.0.1 → 1.0.3

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.
Files changed (194) hide show
  1. package/dist/cjs/addon/buffer/index.cjs +1881 -0
  2. package/dist/cjs/addon/pg/index.cjs +4812 -0
  3. package/dist/cjs/addon/pg-cursor/index.cjs +1451 -0
  4. package/dist/cjs/addon/pg-format/index.cjs +2270 -0
  5. package/dist/cjs/cli/commands/add.cjs +430 -25
  6. package/dist/cjs/cli/commands/branch.cjs +131 -0
  7. package/dist/cjs/cli/commands/checkout.cjs +121 -0
  8. package/dist/cjs/cli/commands/cherry-pick.cjs +282 -0
  9. package/dist/cjs/cli/commands/commit.cjs +21 -29
  10. package/dist/cjs/cli/commands/diff.cjs +144 -69
  11. package/dist/cjs/cli/commands/export.cjs +70 -11
  12. package/dist/cjs/cli/commands/fetch.cjs +42 -18
  13. package/dist/cjs/cli/commands/generate.cjs +28 -54
  14. package/dist/cjs/cli/commands/history.cjs +19 -40
  15. package/dist/cjs/cli/commands/import.cjs +305 -41
  16. package/dist/cjs/cli/commands/init.cjs +69 -59
  17. package/dist/cjs/cli/commands/introspect.cjs +4 -8
  18. package/dist/cjs/cli/commands/log.cjs +84 -15
  19. package/dist/cjs/cli/commands/merge.cjs +207 -0
  20. package/dist/cjs/cli/commands/migrate.cjs +13 -26
  21. package/dist/cjs/cli/commands/pull.cjs +321 -95
  22. package/dist/cjs/cli/commands/push.cjs +228 -52
  23. package/dist/cjs/cli/commands/remote.cjs +17 -0
  24. package/dist/cjs/cli/commands/reset.cjs +148 -0
  25. package/dist/cjs/cli/commands/resolve.cjs +191 -0
  26. package/dist/cjs/cli/commands/rollback.cjs +17 -39
  27. package/dist/cjs/cli/commands/stash.cjs +152 -0
  28. package/dist/cjs/cli/commands/status.cjs +52 -9
  29. package/dist/cjs/cli/commands/sync.cjs +30 -50
  30. package/dist/cjs/cli/commands/tag.cjs +146 -0
  31. package/dist/cjs/cli/index.cjs +117 -10
  32. package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
  33. package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
  34. package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
  35. package/dist/cjs/cli/utils/config-loader.cjs +34 -8
  36. package/dist/cjs/cli/utils/env-loader.cjs +3 -2
  37. package/dist/cjs/cli/utils/fast-introspect.cjs +110 -4
  38. package/dist/cjs/cli/utils/git-utils.cjs +42 -161
  39. package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
  40. package/dist/cjs/cli/utils/project-root.cjs +107 -0
  41. package/dist/cjs/cli/utils/relqignore.cjs +297 -38
  42. package/dist/cjs/cli/utils/repo-manager.cjs +92 -3
  43. package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
  44. package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
  45. package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
  46. package/dist/cjs/cli/utils/schema-introspect.cjs +9 -5
  47. package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
  48. package/dist/cjs/cli/utils/spinner.cjs +14 -106
  49. package/dist/cjs/cli/utils/sql-generator.cjs +2 -2
  50. package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
  51. package/dist/cjs/cli/utils/type-generator.cjs +28 -16
  52. package/dist/cjs/condition/array-condition-builder.cjs +1 -1
  53. package/dist/cjs/condition/condition-collector.cjs +1 -1
  54. package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
  55. package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
  56. package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
  57. package/dist/cjs/condition/network-condition-builder.cjs +1 -1
  58. package/dist/cjs/condition/range-condition-builder.cjs +1 -1
  59. package/dist/cjs/copy/copy-builder.cjs +1 -1
  60. package/dist/cjs/core/query-builder.cjs +1 -1
  61. package/dist/cjs/core/relq-client.cjs +2 -2
  62. package/dist/cjs/count/count-builder.cjs +1 -1
  63. package/dist/cjs/cte/cte-builder.cjs +1 -1
  64. package/dist/cjs/delete/delete-builder.cjs +1 -1
  65. package/dist/cjs/function/create-function-builder.cjs +1 -1
  66. package/dist/cjs/functions/advanced-functions.cjs +1 -1
  67. package/dist/cjs/functions/case-builder.cjs +1 -1
  68. package/dist/cjs/functions/geometric-functions.cjs +1 -1
  69. package/dist/cjs/functions/network-functions.cjs +1 -1
  70. package/dist/cjs/functions/sql-functions.cjs +1 -1
  71. package/dist/cjs/indexing/create-index-builder.cjs +1 -1
  72. package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
  73. package/dist/cjs/insert/conflict-builder.cjs +1 -1
  74. package/dist/cjs/insert/insert-builder.cjs +1 -1
  75. package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
  76. package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
  77. package/dist/cjs/pubsub/listener-connection.cjs +2 -2
  78. package/dist/cjs/raw/raw-query-builder.cjs +1 -1
  79. package/dist/cjs/schema/schema-builder.cjs +1 -1
  80. package/dist/cjs/schema-definition/table-definition.cjs +1 -1
  81. package/dist/cjs/select/aggregate-builder.cjs +1 -1
  82. package/dist/cjs/select/select-builder.cjs +1 -1
  83. package/dist/cjs/sequence/sequence-builder.cjs +1 -1
  84. package/dist/cjs/table/alter-table-builder.cjs +1 -1
  85. package/dist/cjs/table/constraint-builder.cjs +1 -1
  86. package/dist/cjs/table/create-table-builder.cjs +1 -1
  87. package/dist/cjs/table/partition-builder.cjs +1 -1
  88. package/dist/cjs/table/truncate-builder.cjs +1 -1
  89. package/dist/cjs/transaction/transaction-builder.cjs +1 -1
  90. package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
  91. package/dist/cjs/update/array-update-builder.cjs +1 -1
  92. package/dist/cjs/update/update-builder.cjs +1 -1
  93. package/dist/cjs/utils/index.cjs +1 -1
  94. package/dist/cjs/view/create-view-builder.cjs +1 -1
  95. package/dist/cjs/window/window-builder.cjs +1 -1
  96. package/dist/config.d.ts +16 -25
  97. package/dist/esm/cli/commands/add.js +399 -27
  98. package/dist/esm/cli/commands/branch.js +95 -0
  99. package/dist/esm/cli/commands/checkout.js +85 -0
  100. package/dist/esm/cli/commands/cherry-pick.js +246 -0
  101. package/dist/esm/cli/commands/commit.js +22 -30
  102. package/dist/esm/cli/commands/diff.js +144 -69
  103. package/dist/esm/cli/commands/export.js +71 -12
  104. package/dist/esm/cli/commands/fetch.js +42 -18
  105. package/dist/esm/cli/commands/generate.js +28 -54
  106. package/dist/esm/cli/commands/history.js +11 -32
  107. package/dist/esm/cli/commands/import.js +306 -42
  108. package/dist/esm/cli/commands/init.js +65 -55
  109. package/dist/esm/cli/commands/introspect.js +4 -8
  110. package/dist/esm/cli/commands/log.js +78 -10
  111. package/dist/esm/cli/commands/merge.js +171 -0
  112. package/dist/esm/cli/commands/migrate.js +13 -26
  113. package/dist/esm/cli/commands/pull.js +313 -87
  114. package/dist/esm/cli/commands/push.js +223 -47
  115. package/dist/esm/cli/commands/remote.js +14 -0
  116. package/dist/esm/cli/commands/reset.js +112 -0
  117. package/dist/esm/cli/commands/resolve.js +155 -0
  118. package/dist/esm/cli/commands/rollback.js +17 -39
  119. package/dist/esm/cli/commands/stash.js +116 -0
  120. package/dist/esm/cli/commands/status.js +20 -10
  121. package/dist/esm/cli/commands/sync.js +30 -50
  122. package/dist/esm/cli/commands/tag.js +110 -0
  123. package/dist/esm/cli/index.js +118 -11
  124. package/dist/esm/cli/utils/change-tracker.js +107 -3
  125. package/dist/esm/cli/utils/cli-utils.js +169 -0
  126. package/dist/esm/cli/utils/commit-manager.js +3 -3
  127. package/dist/esm/cli/utils/config-loader.js +34 -8
  128. package/dist/esm/cli/utils/env-loader.js +3 -2
  129. package/dist/esm/cli/utils/fast-introspect.js +110 -4
  130. package/dist/esm/cli/utils/git-utils.js +2 -124
  131. package/dist/esm/cli/utils/pool-manager.js +114 -0
  132. package/dist/esm/cli/utils/project-root.js +69 -0
  133. package/dist/esm/cli/utils/relqignore.js +278 -37
  134. package/dist/esm/cli/utils/repo-manager.js +83 -3
  135. package/dist/esm/cli/utils/schema-comparator.js +301 -11
  136. package/dist/esm/cli/utils/schema-diff.js +202 -1
  137. package/dist/esm/cli/utils/schema-hash.js +2 -1
  138. package/dist/esm/cli/utils/schema-introspect.js +9 -5
  139. package/dist/esm/cli/utils/snapshot-manager.js +1 -0
  140. package/dist/esm/cli/utils/spinner.js +1 -101
  141. package/dist/esm/cli/utils/sql-generator.js +2 -2
  142. package/dist/esm/cli/utils/sql-parser.js +94 -7
  143. package/dist/esm/cli/utils/type-generator.js +28 -16
  144. package/dist/esm/condition/array-condition-builder.js +1 -1
  145. package/dist/esm/condition/condition-collector.js +1 -1
  146. package/dist/esm/condition/fulltext-condition-builder.js +1 -1
  147. package/dist/esm/condition/geometric-condition-builder.js +1 -1
  148. package/dist/esm/condition/jsonb-condition-builder.js +1 -1
  149. package/dist/esm/condition/network-condition-builder.js +1 -1
  150. package/dist/esm/condition/range-condition-builder.js +1 -1
  151. package/dist/esm/copy/copy-builder.js +1 -1
  152. package/dist/esm/core/query-builder.js +1 -1
  153. package/dist/esm/core/relq-client.js +2 -2
  154. package/dist/esm/count/count-builder.js +1 -1
  155. package/dist/esm/cte/cte-builder.js +1 -1
  156. package/dist/esm/delete/delete-builder.js +1 -1
  157. package/dist/esm/function/create-function-builder.js +1 -1
  158. package/dist/esm/functions/advanced-functions.js +1 -1
  159. package/dist/esm/functions/case-builder.js +1 -1
  160. package/dist/esm/functions/geometric-functions.js +1 -1
  161. package/dist/esm/functions/network-functions.js +1 -1
  162. package/dist/esm/functions/sql-functions.js +1 -1
  163. package/dist/esm/indexing/create-index-builder.js +1 -1
  164. package/dist/esm/indexing/drop-index-builder.js +1 -1
  165. package/dist/esm/insert/conflict-builder.js +1 -1
  166. package/dist/esm/insert/insert-builder.js +1 -1
  167. package/dist/esm/maintenance/vacuum-builder.js +1 -1
  168. package/dist/esm/pubsub/listen-notify-builder.js +1 -1
  169. package/dist/esm/pubsub/listener-connection.js +2 -2
  170. package/dist/esm/raw/raw-query-builder.js +1 -1
  171. package/dist/esm/schema/schema-builder.js +1 -1
  172. package/dist/esm/schema-definition/table-definition.js +1 -1
  173. package/dist/esm/select/aggregate-builder.js +1 -1
  174. package/dist/esm/select/select-builder.js +1 -1
  175. package/dist/esm/sequence/sequence-builder.js +1 -1
  176. package/dist/esm/table/alter-table-builder.js +1 -1
  177. package/dist/esm/table/constraint-builder.js +1 -1
  178. package/dist/esm/table/create-table-builder.js +1 -1
  179. package/dist/esm/table/partition-builder.js +1 -1
  180. package/dist/esm/table/truncate-builder.js +1 -1
  181. package/dist/esm/transaction/transaction-builder.js +1 -1
  182. package/dist/esm/trigger/create-trigger-builder.js +1 -1
  183. package/dist/esm/update/array-update-builder.js +1 -1
  184. package/dist/esm/update/update-builder.js +1 -1
  185. package/dist/esm/utils/index.js +1 -1
  186. package/dist/esm/view/create-view-builder.js +1 -1
  187. package/dist/esm/window/window-builder.js +1 -1
  188. package/dist/index.d.ts +25 -8
  189. package/dist/schema-builder.d.ts +16 -6
  190. package/package.json +1 -1
  191. /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
  192. /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
  193. /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
  194. /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
@@ -1,14 +1,15 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { introspectDatabase } from "../utils/schema-introspect.js";
4
- import { createSpinner, colors } from "../utils/spinner.js";
4
+ import { createSpinner, colors, warning } from "../utils/spinner.js";
5
5
  import { isInitialized, getStagedChanges, getUnstagedChanges, loadSnapshot, } from "../utils/repo-manager.js";
6
6
  import { generateCombinedSQL, sortChangesByDependency, getChangeDisplayName, } from "../utils/change-tracker.js";
7
7
  import { generateFullSchemaSQL } from "../utils/sql-generator.js";
8
+ import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
+ import { loadConfig } from "../../config/config.js";
8
10
  export async function exportCommand(context) {
9
11
  const spinner = createSpinner();
10
- const { args, flags } = context;
11
- const projectRoot = process.cwd();
12
+ const { args, flags, projectRoot } = context;
12
13
  const changesOnly = Boolean(flags['changes']);
13
14
  const stagedOnly = Boolean(flags['staged']);
14
15
  const fromDb = Boolean(flags['db']);
@@ -46,7 +47,18 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
46
47
  return;
47
48
  }
48
49
  spinner.succeed('Loaded snapshot');
49
- const schema = normalizedToDbSchema(snapshot);
50
+ const config = await loadConfig();
51
+ const ignorePatterns = loadRelqignore(projectRoot);
52
+ const filteredSnapshot = filterNormalizedSchema(snapshot, ignorePatterns, {
53
+ includeFunctions: config.includeFunctions ?? options.includeFunctions ?? false,
54
+ includeTriggers: config.includeTriggers ?? options.includeTriggers ?? false,
55
+ includeViews: config.includeViews ?? false,
56
+ includeFDW: config.includeFDW ?? false,
57
+ });
58
+ const schema = normalizedToDbSchema(filteredSnapshot);
59
+ const ignoredCount = (snapshot.tables.length - filteredSnapshot.tables.length) +
60
+ (snapshot.enums.length - filteredSnapshot.enums.length) +
61
+ ((snapshot.functions?.length || 0) - (filteredSnapshot.functions?.length || 0));
50
62
  console.log('');
51
63
  console.log(colors.cyan('Schema Summary:'));
52
64
  console.log(` ${colors.green('•')} Tables: ${schema.tables.length}`);
@@ -55,11 +67,14 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
55
67
  console.log(` ${colors.green('•')} Composite Types: ${schema.compositeTypes.length}`);
56
68
  console.log(` ${colors.green('•')} Sequences: ${schema.sequences?.length || 0}`);
57
69
  console.log(` ${colors.green('•')} Extensions: ${schema.extensions.length}`);
58
- if (snapshot.functions?.length) {
59
- console.log(` ${colors.green('•')} Functions: ${snapshot.functions.length}`);
70
+ if (filteredSnapshot.functions?.length) {
71
+ console.log(` ${colors.green('•')} Functions: ${filteredSnapshot.functions.length}`);
72
+ }
73
+ if (filteredSnapshot.triggers?.length) {
74
+ console.log(` ${colors.green('•')} Triggers: ${filteredSnapshot.triggers.length}`);
60
75
  }
61
- if (snapshot.triggers?.length) {
62
- console.log(` ${colors.green('•')} Triggers: ${snapshot.triggers.length}`);
76
+ if (ignoredCount > 0) {
77
+ console.log(` ${colors.muted(`${ignoredCount} object(s) filtered by .relqignore`)}`);
63
78
  }
64
79
  spinner.start('Generating SQL statements');
65
80
  const sqlContent = generateFullSQL(schema, options);
@@ -71,7 +86,7 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
71
86
  fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
72
87
  spinner.succeed(`Written ${options.output} (${(sqlContent.length / 1024).toFixed(1)} KB)`);
73
88
  console.log('');
74
- console.log(colors.green('Export completed!'));
89
+ console.log('Export completed');
75
90
  console.log(` Source: ${colors.muted('snapshot (local)')}`);
76
91
  console.log(` Output: ${colors.muted(options.output || '')}`);
77
92
  }
@@ -111,7 +126,7 @@ async function exportFromDatabase(context, absoluteOutputPath, options) {
111
126
  fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
112
127
  spinner.succeed(`Written ${options.output} (${(sqlContent.length / 1024).toFixed(1)} KB)`);
113
128
  console.log('');
114
- console.log(colors.green('Export completed!'));
129
+ console.log('Export completed');
115
130
  console.log(` Source: ${colors.muted('database (live)')}`);
116
131
  console.log(` Output: ${colors.muted(options.output || '')}`);
117
132
  }
@@ -197,6 +212,7 @@ function normalizedToDbSchema(normalized) {
197
212
  cycle: s.cycle,
198
213
  ownedBy: s.ownedBy || undefined,
199
214
  })),
215
+ collations: [],
200
216
  extensions: normalized.extensions.map(e => e.name),
201
217
  functions: (normalized.functions || []).map(f => ({
202
218
  name: f.name,
@@ -244,7 +260,7 @@ async function exportChanges(projectRoot, flags, args, stagedOnly) {
244
260
  modeLabel = 'uncommitted';
245
261
  }
246
262
  if (changes.length === 0) {
247
- console.log(`${colors.yellow('⚠')} No ${modeLabel} changes to export`);
263
+ warning(`No ${modeLabel} changes to export`);
248
264
  console.log('');
249
265
  if (stagedOnly) {
250
266
  console.log(`${colors.muted('Run')} ${colors.cyan('relq add .')} ${colors.muted('to stage changes.')}`);
@@ -273,7 +289,7 @@ async function exportChanges(projectRoot, flags, args, stagedOnly) {
273
289
  fs.mkdirSync(outputDir, { recursive: true });
274
290
  }
275
291
  fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
276
- console.log(`${colors.green('✓')} Exported ${changes.length} ${modeLabel} change(s) to ${outputPath}`);
292
+ console.log(`Exported ${changes.length} ${modeLabel} change(s) to ${outputPath}`);
277
293
  console.log(`${colors.muted(`${(sqlContent.length / 1024).toFixed(1)} KB`)}`);
278
294
  console.log('');
279
295
  }
@@ -294,4 +310,47 @@ function generateFullSQL(schema, options) {
294
310
  headerComment: header,
295
311
  });
296
312
  }
313
+ function filterNormalizedSchema(schema, patterns, options) {
314
+ const filteredTables = schema.tables
315
+ .filter(table => !isTableIgnored(table.name, patterns).ignored)
316
+ .map(table => ({
317
+ ...table,
318
+ columns: table.columns.filter(col => !isColumnIgnored(table.name, col.name, patterns).ignored),
319
+ indexes: table.indexes.filter(idx => !isIndexIgnored(table.name, idx.name, patterns).ignored),
320
+ constraints: table.constraints?.filter(con => !isConstraintIgnored(table.name, con.name, patterns).ignored) || [],
321
+ }));
322
+ const filteredEnums = schema.enums.filter(e => !isEnumIgnored(e.name, patterns).ignored);
323
+ const filteredDomains = schema.domains.filter(d => !isDomainIgnored(d.name, patterns).ignored);
324
+ const filteredCompositeTypes = schema.compositeTypes.filter(c => !isCompositeTypeIgnored(c.name, patterns).ignored);
325
+ const filteredSequences = schema.sequences.filter(s => !isSequenceIgnored(s.name, patterns).ignored);
326
+ const filteredFunctions = options.includeFunctions
327
+ ? (schema.functions || []).filter(f => !isFunctionIgnored(f.name, patterns).ignored)
328
+ : [];
329
+ const filteredTriggers = options.includeTriggers
330
+ ? schema.triggers || []
331
+ : [];
332
+ const filteredViews = options.includeViews
333
+ ? schema.views || []
334
+ : [];
335
+ const filteredMaterializedViews = options.includeViews
336
+ ? schema.materializedViews || []
337
+ : [];
338
+ const filteredForeignTables = options.includeFDW
339
+ ? schema.foreignTables || []
340
+ : [];
341
+ return {
342
+ extensions: schema.extensions,
343
+ enums: filteredEnums,
344
+ domains: filteredDomains,
345
+ compositeTypes: filteredCompositeTypes,
346
+ sequences: filteredSequences,
347
+ collations: schema.collations,
348
+ tables: filteredTables,
349
+ functions: filteredFunctions,
350
+ triggers: filteredTriggers,
351
+ views: filteredViews,
352
+ materializedViews: filteredMaterializedViews,
353
+ foreignTables: filteredForeignTables,
354
+ };
355
+ }
297
356
  export default exportCommand;
@@ -1,22 +1,18 @@
1
1
  import { requireValidConfig } from "../utils/config-loader.js";
2
2
  import { getConnectionDescription } from "../utils/env-loader.js";
3
- import { colors, createSpinner } from "../utils/spinner.js";
4
- import { isInitialized, getHead, loadCommit, saveCommit, setFetchHead, shortHash, fetchRemoteCommits, ensureRemoteTable, } from "../utils/repo-manager.js";
3
+ import { colors, createSpinner, fatal, hint } from "../utils/cli-utils.js";
4
+ import { isInitialized, getHead, loadCommit, saveCommit, setFetchHead, fetchRemoteCommits, ensureRemoteTable, } from "../utils/repo-manager.js";
5
5
  export async function fetchCommand(context) {
6
6
  const { config, flags } = context;
7
7
  if (!config) {
8
- console.error('Error: No configuration found.');
9
- process.exit(1);
8
+ fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
10
9
  }
11
- requireValidConfig(config);
10
+ await requireValidConfig(config, { calledFrom: 'fetch' });
12
11
  const connection = config.connection;
13
- const projectRoot = process.cwd();
12
+ const { projectRoot } = context;
14
13
  console.log('');
15
14
  if (!isInitialized(projectRoot)) {
16
- console.log(`${colors.red('fatal:')} not a relq repository`);
17
- console.log('');
18
- console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
19
- return;
15
+ fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
20
16
  }
21
17
  const spinner = createSpinner();
22
18
  spinner.start(`Fetching from ${getConnectionDescription(connection)}...`);
@@ -25,8 +21,7 @@ export async function fetchCommand(context) {
25
21
  const remoteCommits = await fetchRemoteCommits(connection, 100);
26
22
  if (remoteCommits.length === 0) {
27
23
  spinner.succeed('No remote commits found');
28
- console.log('');
29
- console.log(`${colors.muted('Run')} ${colors.cyan('relq push')} ${colors.muted('to push your commits.')}`);
24
+ hint("run 'relq push' to push your commits");
30
25
  return;
31
26
  }
32
27
  let newCommits = 0;
@@ -41,16 +36,45 @@ export async function fetchCommand(context) {
41
36
  setFetchHead(latestRemote.hash, projectRoot);
42
37
  spinner.succeed(`Fetched ${remoteCommits.length} commits (${newCommits} new)`);
43
38
  const localHead = getHead(projectRoot);
44
- if (localHead && latestRemote.hash !== localHead) {
45
- console.log('');
46
- console.log(`${colors.muted('From')} ${getConnectionDescription(connection)}`);
47
- console.log(` ${shortHash(localHead)}..${shortHash(latestRemote.hash)} ${colors.muted('main -> origin/main')}`);
39
+ const localCommits = localHead ? getAllLocalCommitHashes(projectRoot) : new Set();
40
+ const remoteHashes = new Set(remoteCommits.map(c => c.hash));
41
+ const behind = remoteCommits.filter(c => !localCommits.has(c.hash)).length;
42
+ const ahead = [...localCommits].filter(h => !remoteHashes.has(h)).length;
43
+ console.log('');
44
+ if (behind > 0 && ahead > 0) {
45
+ console.log(`Your branch and 'origin/main' have diverged,`);
46
+ console.log(`and have ${ahead} and ${behind} different commits each, respectively.`);
47
+ }
48
+ else if (behind > 0) {
49
+ console.log(`Your branch is behind 'origin/main' by ${behind} commit(s).`);
50
+ hint("run 'relq pull' to update your local branch");
51
+ }
52
+ else if (ahead > 0) {
53
+ console.log(`Your branch is ahead of 'origin/main' by ${ahead} commit(s).`);
54
+ hint("run 'relq push' to publish your local commits");
55
+ }
56
+ else {
57
+ console.log("Your branch is up to date with 'origin/main'.");
48
58
  }
49
59
  }
50
60
  catch (error) {
51
61
  spinner.fail('Fetch failed');
52
- console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
53
- process.exit(1);
62
+ fatal('Fetch failed', error instanceof Error ? error.message : String(error));
54
63
  }
55
64
  console.log('');
56
65
  }
66
+ function getAllLocalCommitHashes(projectRoot) {
67
+ const fs = require('fs');
68
+ const path = require('path');
69
+ const commitsDir = path.join(projectRoot, '.relq', 'commits');
70
+ const hashes = new Set();
71
+ if (fs.existsSync(commitsDir)) {
72
+ const files = fs.readdirSync(commitsDir);
73
+ for (const file of files) {
74
+ if (file.endsWith('.json')) {
75
+ hashes.add(file.replace('.json', ''));
76
+ }
77
+ }
78
+ }
79
+ return hashes;
80
+ }
@@ -8,32 +8,7 @@ import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables }
8
8
  import { normalizeSchema } from "../utils/schema-hash.js";
9
9
  import { generateMigrationFile, getNextMigrationNumber, generateTimestampedName } from "../utils/migration-generator.js";
10
10
  import { getConnectionDescription } from "../utils/env-loader.js";
11
- const colors = {
12
- reset: '\x1b[0m',
13
- bold: '\x1b[1m',
14
- dim: '\x1b[2m',
15
- red: '\x1b[31m',
16
- green: '\x1b[32m',
17
- yellow: '\x1b[33m',
18
- cyan: '\x1b[36m',
19
- };
20
- function askConfirm(question, defaultYes = false) {
21
- const rl = readline.createInterface({
22
- input: process.stdin,
23
- output: process.stdout,
24
- });
25
- const suffix = defaultYes ? '[Y/n]' : '[y/N]';
26
- return new Promise((resolve) => {
27
- rl.question(`${question} ${suffix}: `, (answer) => {
28
- rl.close();
29
- const a = answer.trim().toLowerCase();
30
- if (!a)
31
- resolve(defaultYes);
32
- else
33
- resolve(a === 'y' || a === 'yes');
34
- });
35
- });
36
- }
11
+ import { colors, confirm, fatal, warning } from "../utils/cli-utils.js";
37
12
  function askInput(question) {
38
13
  const rl = readline.createInterface({
39
14
  input: process.stdin,
@@ -49,10 +24,10 @@ function askInput(question) {
49
24
  export async function generateCommand(context) {
50
25
  const { config, args, flags } = context;
51
26
  if (!config) {
52
- console.error('Error: No configuration found.');
53
- process.exit(1);
27
+ fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
28
+ return;
54
29
  }
55
- requireValidConfig(config);
30
+ await requireValidConfig(config, { calledFrom: 'generate' });
56
31
  const connection = config.connection;
57
32
  const migrationsDir = config.migrations?.directory || './migrations';
58
33
  const snapshotPath = config.sync?.snapshot || '.relq/snapshot.json';
@@ -75,10 +50,10 @@ export async function generateCommand(context) {
75
50
  migrationName = message.replace(/\s+/g, '_').toLowerCase();
76
51
  }
77
52
  if (!migrationName && !isEmpty) {
78
- migrationName = await askInput(`${colors.cyan}Migration name:${colors.reset} `);
53
+ migrationName = await askInput('Migration name: ');
79
54
  if (!migrationName) {
80
- console.error('Error: Migration name is required.');
81
- process.exit(1);
55
+ fatal('Migration name is required');
56
+ return;
82
57
  }
83
58
  }
84
59
  if (isEmpty) {
@@ -86,48 +61,48 @@ export async function generateCommand(context) {
86
61
  await createEmptyMigration(migrationsDir, migrationName, format, dryRun);
87
62
  return;
88
63
  }
89
- console.log(`${colors.bold}Generating migration...${colors.reset}`);
64
+ console.log('Generating migration...');
90
65
  console.log(` Connection: ${getConnectionDescription(connection)}`);
91
66
  console.log('');
92
67
  try {
93
68
  const dbSchema = await introspectDatabase(connection);
94
69
  const snapshot = loadSnapshot(snapshotPath);
95
70
  if (!snapshot) {
96
- console.log(`${colors.yellow}No snapshot found.${colors.reset}`);
97
- console.log(`Run "${colors.cyan}relq pull${colors.reset}" first to create initial snapshot.`);
71
+ warning('No snapshot found.');
72
+ console.log('Run "relq pull" first to create initial snapshot.');
98
73
  return;
99
74
  }
100
75
  const localSchema = snapshotToDatabaseSchema(snapshot);
101
76
  const diff = diffSchemas(normalizeSchema(localSchema), normalizeSchema(dbSchema));
102
77
  const filteredDiff = filterDiff(diff, ignorePatterns);
103
78
  if (!filteredDiff.hasChanges) {
104
- console.log(`${colors.green}No changes to generate.${colors.reset}`);
79
+ console.log('No changes to generate.');
105
80
  return;
106
81
  }
107
82
  const s = filteredDiff.summary;
108
- console.log(`${colors.bold}Changes to include:${colors.reset}`);
83
+ console.log('Changes to include:');
109
84
  if (s.tablesAdded > 0)
110
- console.log(` ${colors.green}+ ${s.tablesAdded} table(s)${colors.reset}`);
85
+ console.log(` + ${s.tablesAdded} table(s)`);
111
86
  if (s.tablesRemoved > 0)
112
- console.log(` ${colors.red}- ${s.tablesRemoved} table(s)${colors.reset}`);
87
+ console.log(` - ${s.tablesRemoved} table(s)`);
113
88
  if (s.tablesModified > 0)
114
- console.log(` ${colors.yellow}~ ${s.tablesModified} table(s) modified${colors.reset}`);
89
+ console.log(` ~ ${s.tablesModified} table(s) modified`);
115
90
  if (s.columnsAdded > 0)
116
- console.log(` ${colors.green}+ ${s.columnsAdded} column(s)${colors.reset}`);
91
+ console.log(` + ${s.columnsAdded} column(s)`);
117
92
  if (s.columnsRemoved > 0)
118
- console.log(` ${colors.red}- ${s.columnsRemoved} column(s)${colors.reset}`);
93
+ console.log(` - ${s.columnsRemoved} column(s)`);
119
94
  if (s.columnsModified > 0)
120
- console.log(` ${colors.yellow}~ ${s.columnsModified} column(s)${colors.reset}`);
95
+ console.log(` ~ ${s.columnsModified} column(s)`);
121
96
  console.log('');
122
97
  if (hasDestructiveChanges(filteredDiff)) {
123
98
  const tables = getDestructiveTables(filteredDiff);
124
- console.log(`${colors.red}${colors.bold}⚠️ Destructive changes:${colors.reset}`);
99
+ warning('Destructive changes:');
125
100
  for (const t of tables) {
126
- console.log(` ${colors.red}• ${t}${colors.reset}`);
101
+ console.log(` - ${t}`);
127
102
  }
128
103
  console.log('');
129
104
  if (!autoStage) {
130
- const proceed = await askConfirm('Include destructive changes?', false);
105
+ const proceed = await confirm('Include destructive changes?', false);
131
106
  if (!proceed) {
132
107
  console.log('Cancelled.');
133
108
  return;
@@ -149,7 +124,7 @@ export async function generateCommand(context) {
149
124
  }
150
125
  const filePath = path.join(migrationsDir, fileName);
151
126
  if (dryRun) {
152
- console.log(`${colors.cyan}[dry-run] Would create: ${filePath}${colors.reset}`);
127
+ console.log(`[dry-run] Would create: ${filePath}`);
153
128
  console.log('');
154
129
  console.log('--- Generated SQL ---');
155
130
  console.log(migrationFile.content);
@@ -160,16 +135,15 @@ export async function generateCommand(context) {
160
135
  fs.mkdirSync(migrationsDir, { recursive: true });
161
136
  }
162
137
  fs.writeFileSync(filePath, migrationFile.content, 'utf-8');
163
- console.log(`${colors.green}✓ Created:${colors.reset} ${filePath}`);
138
+ console.log(`Created: ${filePath}`);
164
139
  saveSnapshot(dbSchema, snapshotPath, connection.database);
165
- console.log(`${colors.dim}Updated snapshot.${colors.reset}`);
140
+ console.log('Updated snapshot.');
166
141
  }
167
142
  console.log('');
168
- console.log(`${colors.dim}Run "relq push" to apply this migration.${colors.reset}`);
143
+ console.log('Run "relq push" to apply this migration.');
169
144
  }
170
145
  catch (error) {
171
- console.error('Error:', error instanceof Error ? error.message : error);
172
- process.exit(1);
146
+ fatal('Generation failed', error instanceof Error ? error.message : String(error));
173
147
  }
174
148
  }
175
149
  async function createEmptyMigration(migrationsDir, name, format, dryRun) {
@@ -194,13 +168,13 @@ async function createEmptyMigration(migrationsDir, name, format, dryRun) {
194
168
 
195
169
  `;
196
170
  if (dryRun) {
197
- console.log(`${colors.cyan}[dry-run] Would create: ${filePath}${colors.reset}`);
171
+ console.log(`[dry-run] Would create: ${filePath}`);
198
172
  }
199
173
  else {
200
174
  if (!fs.existsSync(migrationsDir)) {
201
175
  fs.mkdirSync(migrationsDir, { recursive: true });
202
176
  }
203
177
  fs.writeFileSync(filePath, content, 'utf-8');
204
- console.log(`${colors.green}✓ Created empty migration:${colors.reset} ${filePath}`);
178
+ console.log(`Created empty migration: ${filePath}`);
205
179
  }
206
180
  }
@@ -1,27 +1,19 @@
1
1
  import * as fs from 'fs';
2
2
  import { requireValidConfig } from "../utils/config-loader.js";
3
3
  import { getConnectionDescription } from "../utils/env-loader.js";
4
- const colors = {
5
- reset: '\x1b[0m',
6
- bold: '\x1b[1m',
7
- dim: '\x1b[2m',
8
- red: '\x1b[31m',
9
- green: '\x1b[32m',
10
- yellow: '\x1b[33m',
11
- cyan: '\x1b[36m',
12
- };
4
+ import { colors, fatal } from "../utils/cli-utils.js";
5
+ import { withPool } from "../utils/pool-manager.js";
13
6
  export async function historyCommand(context) {
14
7
  const { config, flags } = context;
15
8
  if (!config) {
16
- console.error('Error: No configuration found.');
17
- process.exit(1);
9
+ fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
18
10
  }
19
- requireValidConfig(config);
11
+ await requireValidConfig(config, { calledFrom: 'history' });
20
12
  const connection = config.connection;
21
13
  const migrationsDir = config.migrations?.directory || './migrations';
22
14
  const tableName = config.migrations?.tableName || '_relq_migrations';
23
15
  const limit = parseInt(flags['n']) || 20;
24
- console.log(`${colors.bold}Migration History${colors.reset}`);
16
+ console.log('Migration History');
25
17
  console.log(`Database: ${getConnectionDescription(connection)}`);
26
18
  console.log('');
27
19
  try {
@@ -40,8 +32,8 @@ export async function historyCommand(context) {
40
32
  const toShow = history.slice(0, limit);
41
33
  const pendingCount = history.filter(h => h.pending).length;
42
34
  const appliedCount = history.filter(h => !h.pending).length;
43
- console.log(`${colors.dim}Showing ${toShow.length} of ${history.length} migrations${colors.reset}`);
44
- console.log(`${colors.green}${appliedCount} applied${colors.reset}, ${colors.yellow}${pendingCount} pending${colors.reset}`);
35
+ console.log(`Showing ${toShow.length} of ${history.length} migrations`);
36
+ console.log(`${appliedCount} applied, ${pendingCount} pending`);
45
37
  console.log('');
46
38
  for (const record of toShow) {
47
39
  if (record.pending) {
@@ -74,8 +66,7 @@ export async function historyCommand(context) {
74
66
  }
75
67
  return;
76
68
  }
77
- console.error('Error:', error instanceof Error ? error.message : error);
78
- process.exit(1);
69
+ fatal('Failed to load history', error instanceof Error ? error.message : String(error));
79
70
  }
80
71
  }
81
72
  function getMigrationFiles(migrationsDir) {
@@ -87,29 +78,17 @@ function getMigrationFiles(migrationsDir) {
87
78
  .sort();
88
79
  }
89
80
  async function getAppliedMigrations(connection, tableName) {
90
- const { Pool } = await import("../../addon/pg.js");
91
- const pool = new Pool({
92
- host: connection.host,
93
- port: connection.port || 5432,
94
- database: connection.database,
95
- user: connection.user,
96
- password: connection.password,
97
- connectionString: connection.url,
98
- });
99
- try {
81
+ return withPool(connection, async (pool) => {
100
82
  const result = await pool.query(`
101
83
  SELECT name, applied_at
102
84
  FROM "${tableName}"
103
85
  ORDER BY id DESC;
104
86
  `);
105
- return result.rows.map(r => ({
87
+ return result.rows.map((r) => ({
106
88
  name: r.name,
107
89
  appliedAt: new Date(r.applied_at),
108
90
  }));
109
- }
110
- finally {
111
- await pool.end();
112
- }
91
+ });
113
92
  }
114
93
  function formatDate(date) {
115
94
  const now = new Date();