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,17 +1,8 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import * as readline from 'readline';
4
3
  import { requireValidConfig } from "../utils/config-loader.js";
5
4
  import { getConnectionDescription } from "../utils/env-loader.js";
6
- const colors = {
7
- reset: '\x1b[0m',
8
- bold: '\x1b[1m',
9
- dim: '\x1b[2m',
10
- red: '\x1b[31m',
11
- green: '\x1b[32m',
12
- yellow: '\x1b[33m',
13
- cyan: '\x1b[36m',
14
- };
5
+ import { colors, confirm, fatal, warning } from "../utils/cli-utils.js";
15
6
  function parseMigration(content) {
16
7
  const upMatch = content.match(/--\s*UP\s*\n([\s\S]*?)(?=--\s*DOWN|$)/i);
17
8
  const downMatch = content.match(/--\s*DOWN\s*\n([\s\S]*?)$/i);
@@ -20,36 +11,24 @@ function parseMigration(content) {
20
11
  down: downMatch?.[1]?.trim() || '',
21
12
  };
22
13
  }
23
- function askConfirm(question) {
24
- const rl = readline.createInterface({
25
- input: process.stdin,
26
- output: process.stdout,
27
- });
28
- return new Promise((resolve) => {
29
- rl.question(`${question} [y/N]: `, (answer) => {
30
- rl.close();
31
- resolve(answer.trim().toLowerCase() === 'y');
32
- });
33
- });
34
- }
35
14
  export async function rollbackCommand(context) {
36
15
  const { config, args, flags } = context;
37
16
  if (!config) {
38
- console.error('Error: No configuration found.');
39
- process.exit(1);
17
+ fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
18
+ return;
40
19
  }
41
- requireValidConfig(config);
20
+ await requireValidConfig(config, { calledFrom: 'rollback' });
42
21
  const connection = config.connection;
43
22
  const migrationsDir = config.migrations?.directory || './migrations';
44
23
  const tableName = config.migrations?.tableName || '_relq_migrations';
45
24
  const count = parseInt(args[0]) || 1;
46
25
  const dryRun = flags['dry-run'] === true;
47
26
  const force = flags['force'] === true;
48
- console.log(`${colors.bold}Rolling back ${count} migration(s)...${colors.reset}`);
27
+ console.log(`Rolling back ${count} migration(s)...`);
49
28
  console.log(` Connection: ${getConnectionDescription(connection)}`);
50
29
  console.log('');
51
30
  try {
52
- const { Pool } = await import("../../addon/pg.js");
31
+ const { Pool } = await import("../../addon/pg/index.js");
53
32
  const pool = new Pool({
54
33
  host: connection.host,
55
34
  port: connection.port || 5432,
@@ -65,17 +44,17 @@ export async function rollbackCommand(context) {
65
44
  LIMIT $1;
66
45
  `, [count]);
67
46
  if (result.rows.length === 0) {
68
- console.log(`${colors.yellow}No migrations to rollback.${colors.reset}`);
47
+ console.log('No migrations to rollback.');
69
48
  return;
70
49
  }
71
50
  const toRollback = result.rows.map(r => r.name);
72
- console.log(`${colors.bold}Migrations to rollback:${colors.reset}`);
51
+ console.log('Migrations to rollback:');
73
52
  for (const name of toRollback) {
74
- console.log(` ${colors.red}• ${name}${colors.reset}`);
53
+ console.log(` - ${name}`);
75
54
  }
76
55
  console.log('');
77
56
  if (!force && !dryRun) {
78
- const proceed = await askConfirm(`${colors.red}This will undo ${toRollback.length} migration(s). Continue?${colors.reset}`);
57
+ const proceed = await confirm(`${colors.red('This will undo ' + toRollback.length + ' migration(s). Continue?')}`, false);
79
58
  if (!proceed) {
80
59
  console.log('Cancelled.');
81
60
  return;
@@ -84,18 +63,18 @@ export async function rollbackCommand(context) {
84
63
  for (const name of toRollback) {
85
64
  const filePath = path.join(migrationsDir, name);
86
65
  if (!fs.existsSync(filePath)) {
87
- console.log(`${colors.yellow}⚠️ Migration file not found: ${name}${colors.reset}`);
66
+ warning(`Migration file not found: ${name}`);
88
67
  continue;
89
68
  }
90
69
  const content = fs.readFileSync(filePath, 'utf-8');
91
70
  const { down } = parseMigration(content);
92
71
  if (!down) {
93
- console.log(`${colors.yellow}⚠️ No DOWN section in: ${name}${colors.reset}`);
72
+ warning(`No DOWN section in: ${name}`);
94
73
  continue;
95
74
  }
96
75
  if (dryRun) {
97
- console.log(`${colors.cyan}[dry-run] Would rollback: ${name}${colors.reset}`);
98
- console.log(`${colors.dim}${down}${colors.reset}`);
76
+ console.log(`[dry-run] Would rollback: ${name}`);
77
+ console.log(down);
99
78
  console.log('');
100
79
  }
101
80
  else {
@@ -106,7 +85,7 @@ export async function rollbackCommand(context) {
106
85
  await client.query(down);
107
86
  await client.query(`DELETE FROM "${tableName}" WHERE name = $1`, [name]);
108
87
  await client.query('COMMIT');
109
- console.log(` ${colors.green}✓ Rolled back${colors.reset}`);
88
+ console.log(' Rolled back');
110
89
  }
111
90
  catch (error) {
112
91
  await client.query('ROLLBACK');
@@ -119,7 +98,7 @@ export async function rollbackCommand(context) {
119
98
  }
120
99
  if (!dryRun) {
121
100
  console.log('');
122
- console.log(`${colors.green}✓ Rolled back ${toRollback.length} migration(s).${colors.reset}`);
101
+ console.log(`Rolled back ${toRollback.length} migration(s).`);
123
102
  }
124
103
  }
125
104
  finally {
@@ -127,7 +106,6 @@ export async function rollbackCommand(context) {
127
106
  }
128
107
  }
129
108
  catch (error) {
130
- console.error('Error:', error instanceof Error ? error.message : error);
131
- process.exit(1);
109
+ fatal('Rollback failed', error instanceof Error ? error.message : String(error));
132
110
  }
133
111
  }
@@ -0,0 +1,116 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors, fatal } from "../utils/spinner.js";
4
+ import { isInitialized, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
5
+ export async function stashCommand(context) {
6
+ const { args, flags, projectRoot } = context;
7
+ const subcommand = args[0] || 'push';
8
+ console.log('');
9
+ if (!isInitialized(projectRoot)) {
10
+ fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
11
+ }
12
+ const stashDir = path.join(projectRoot, '.relq', 'stash');
13
+ switch (subcommand) {
14
+ case 'push':
15
+ case 'save':
16
+ await stashPush(projectRoot, stashDir, flags['m'] || 'WIP');
17
+ break;
18
+ case 'pop':
19
+ await stashPop(projectRoot, stashDir);
20
+ break;
21
+ case 'list':
22
+ await stashList(stashDir);
23
+ break;
24
+ case 'drop':
25
+ await stashDrop(stashDir);
26
+ break;
27
+ default:
28
+ await stashPush(projectRoot, stashDir, subcommand);
29
+ }
30
+ }
31
+ async function stashPush(projectRoot, stashDir, message) {
32
+ const staged = getStagedChanges(projectRoot);
33
+ const unstaged = getUnstagedChanges(projectRoot);
34
+ if (staged.length === 0 && unstaged.length === 0) {
35
+ console.log(`${colors.muted('No changes to stash.')}`);
36
+ console.log('');
37
+ return;
38
+ }
39
+ if (!fs.existsSync(stashDir)) {
40
+ fs.mkdirSync(stashDir, { recursive: true });
41
+ }
42
+ const stashFiles = fs.readdirSync(stashDir).filter(f => f.endsWith('.json'));
43
+ const stashIdx = stashFiles.length;
44
+ const stash = {
45
+ message,
46
+ timestamp: new Date().toISOString(),
47
+ staged,
48
+ unstaged,
49
+ };
50
+ fs.writeFileSync(path.join(stashDir, `stash-${stashIdx}.json`), JSON.stringify(stash, null, 2));
51
+ const stagedPath = path.join(projectRoot, '.relq', 'staged.json');
52
+ const unstagedPath = path.join(projectRoot, '.relq', 'unstaged.json');
53
+ if (fs.existsSync(stagedPath))
54
+ fs.writeFileSync(stagedPath, '[]');
55
+ if (fs.existsSync(unstagedPath))
56
+ fs.writeFileSync(unstagedPath, '[]');
57
+ console.log('Saved working directory');
58
+ console.log(` stash@{${stashIdx}}: ${message}`);
59
+ console.log('');
60
+ }
61
+ async function stashPop(projectRoot, stashDir) {
62
+ if (!fs.existsSync(stashDir)) {
63
+ console.log(`${colors.muted('No stashes found.')}`);
64
+ return;
65
+ }
66
+ const stashFiles = fs.readdirSync(stashDir).filter(f => f.endsWith('.json')).sort().reverse();
67
+ if (stashFiles.length === 0) {
68
+ console.log(`${colors.muted('No stashes found.')}`);
69
+ return;
70
+ }
71
+ const stashPath = path.join(stashDir, stashFiles[0]);
72
+ const stash = JSON.parse(fs.readFileSync(stashPath, 'utf-8'));
73
+ const stagedPath = path.join(projectRoot, '.relq', 'staged.json');
74
+ const unstagedPath = path.join(projectRoot, '.relq', 'unstaged.json');
75
+ if (stash.staged.length > 0) {
76
+ fs.writeFileSync(stagedPath, JSON.stringify(stash.staged, null, 2));
77
+ }
78
+ if (stash.unstaged.length > 0) {
79
+ fs.writeFileSync(unstagedPath, JSON.stringify(stash.unstaged, null, 2));
80
+ }
81
+ fs.unlinkSync(stashPath);
82
+ console.log('Applied stash and dropped');
83
+ console.log(` ${stash.message}`);
84
+ console.log('');
85
+ }
86
+ async function stashList(stashDir) {
87
+ if (!fs.existsSync(stashDir)) {
88
+ console.log(`${colors.muted('No stashes.')}`);
89
+ return;
90
+ }
91
+ const stashFiles = fs.readdirSync(stashDir).filter(f => f.endsWith('.json')).sort();
92
+ if (stashFiles.length === 0) {
93
+ console.log(`${colors.muted('No stashes.')}`);
94
+ return;
95
+ }
96
+ for (let i = 0; i < stashFiles.length; i++) {
97
+ const stash = JSON.parse(fs.readFileSync(path.join(stashDir, stashFiles[i]), 'utf-8'));
98
+ console.log(`stash@{${i}}: ${stash.message} (${stash.staged.length + stash.unstaged.length} changes)`);
99
+ }
100
+ console.log('');
101
+ }
102
+ async function stashDrop(stashDir) {
103
+ if (!fs.existsSync(stashDir)) {
104
+ console.log(`${colors.muted('No stashes.')}`);
105
+ return;
106
+ }
107
+ const stashFiles = fs.readdirSync(stashDir).filter(f => f.endsWith('.json')).sort().reverse();
108
+ if (stashFiles.length === 0) {
109
+ console.log(`${colors.muted('No stashes.')}`);
110
+ return;
111
+ }
112
+ fs.unlinkSync(path.join(stashDir, stashFiles[0]));
113
+ console.log('Dropped stash');
114
+ console.log('');
115
+ }
116
+ export default stashCommand;
@@ -1,17 +1,15 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
1
3
  import { getConnectionDescription } from "../utils/env-loader.js";
2
- import { colors } from "../utils/spinner.js";
4
+ import { colors, fatal } from "../utils/spinner.js";
5
+ import { loadRelqignore, } from "../utils/relqignore.js";
3
6
  import { isInitialized, getHead, loadCommit, shortHash, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
4
7
  export async function statusCommand(context) {
5
- const { config, flags } = context;
6
- const projectRoot = process.cwd();
8
+ const { config, flags, projectRoot } = context;
7
9
  const connection = config?.connection;
8
10
  console.log('');
9
11
  if (!isInitialized(projectRoot)) {
10
- console.log(`${colors.red('fatal:')} not a relq repository`);
11
- console.log('');
12
- console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
13
- console.log('');
14
- return;
12
+ fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
15
13
  }
16
14
  const head = getHead(projectRoot);
17
15
  const staged = getStagedChanges(projectRoot);
@@ -34,7 +32,7 @@ export async function statusCommand(context) {
34
32
  console.log(`${colors.green('Changes to be committed:')}`);
35
33
  console.log(` ${colors.muted('(use "relq restore --staged <name>..." to unstage)')}`);
36
34
  console.log('');
37
- displayChanges(staged, ' ');
35
+ displayChanges(staged, ' ');
38
36
  console.log('');
39
37
  }
40
38
  if (unstaged.length > 0) {
@@ -42,9 +40,21 @@ export async function statusCommand(context) {
42
40
  console.log(` ${colors.muted('(use "relq add <name>..." to stage)')}`);
43
41
  console.log(` ${colors.muted('(use "relq restore <name>..." to discard)')}`);
44
42
  console.log('');
45
- displayChanges(unstaged, ' ');
43
+ displayChanges(unstaged, ' ');
46
44
  console.log('');
47
45
  }
46
+ const ignorePatterns = loadRelqignore(projectRoot);
47
+ const relqignorePath = path.join(projectRoot, '.relqignore');
48
+ if (ignorePatterns.length > 0 && fs.existsSync(relqignorePath)) {
49
+ const userPatterns = ignorePatterns.filter(p => !p.raw.startsWith('_relq_') &&
50
+ !p.raw.startsWith('pg_') &&
51
+ !p.raw.startsWith('_temp_') &&
52
+ !p.raw.startsWith('tmp_'));
53
+ if (userPatterns.length > 0) {
54
+ console.log(`${colors.muted(`${userPatterns.length} pattern(s) active from .relqignore`)}`);
55
+ console.log('');
56
+ }
57
+ }
48
58
  if (staged.length === 0 && unstaged.length === 0) {
49
59
  console.log(`${colors.green('nothing to commit, working tree clean')}`);
50
60
  console.log('');
@@ -1,76 +1,56 @@
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, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, getRepoStatus, } from "../utils/repo-manager.js";
3
+ import { colors, createSpinner, fatal, success } from "../utils/cli-utils.js";
4
+ import { isInitialized, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, } from "../utils/repo-manager.js";
5
5
  import { pullCommand } from "./pull.js";
6
6
  export async function syncCommand(context) {
7
7
  const { config, flags } = context;
8
8
  if (!config) {
9
- console.error('Error: No configuration found.');
10
- process.exit(1);
9
+ fatal('No configuration found', `Run ${colors.cyan('relq init')} to create one.`);
11
10
  }
12
- requireValidConfig(config);
11
+ await requireValidConfig(config, { calledFrom: 'sync' });
13
12
  const connection = config.connection;
14
- const projectRoot = process.cwd();
13
+ const { projectRoot } = context;
15
14
  console.log('');
16
15
  if (!isInitialized(projectRoot)) {
17
- console.log(`${colors.red('fatal:')} not a relq repository`);
18
- console.log('');
19
- console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
20
- return;
16
+ fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
21
17
  }
22
18
  const spinner = createSpinner();
23
19
  try {
24
- spinner.start('Checking sync status...');
25
- const status = await getRepoStatus(connection, projectRoot);
26
- spinner.stop();
27
- console.log(`On database: ${colors.cyan(getConnectionDescription(connection))}`);
20
+ console.log(`Syncing with ${colors.cyan(getConnectionDescription(connection))}...`);
28
21
  console.log('');
29
- if (status.behindBy > 0) {
30
- console.log(`${colors.yellow('↓')} ${status.behindBy} commit(s) to pull`);
31
- }
32
- if (status.aheadBy > 0) {
33
- console.log(`${colors.green('↑')} ${status.aheadBy} commit(s) to push`);
34
- }
35
- if (status.behindBy === 0 && status.aheadBy === 0) {
36
- console.log(`${colors.green('✓')} Already in sync`);
22
+ await pullCommand(context);
23
+ await ensureRemoteTable(connection);
24
+ const remoteCommits = await fetchRemoteCommits(connection, 100);
25
+ const remoteHashes = new Set(remoteCommits.map(c => c.hash));
26
+ const localCommitsAfter = getAllCommits(projectRoot);
27
+ const toPush = localCommitsAfter.filter(c => !remoteHashes.has(c.hash));
28
+ if (toPush.length === 0) {
29
+ success('Sync complete - up to date');
37
30
  console.log('');
38
31
  return;
39
32
  }
33
+ console.log(`Pushing ${toPush.length} local commit(s)...`);
40
34
  console.log('');
41
- if (status.behindBy > 0) {
42
- console.log(`${colors.cyan('●')} Pulling changes...`);
43
- console.log('');
44
- const pullContext = {
45
- ...context,
46
- flags: { ...flags, force: true },
47
- };
48
- await pullCommand(pullContext);
35
+ spinner.start(`Pushing ${toPush.length} commit(s)...`);
36
+ for (const commit of toPush.reverse()) {
37
+ await pushCommit(connection, commit);
49
38
  }
50
- if (status.aheadBy > 0) {
51
- console.log(`${colors.cyan('')} Pushing commits...`);
52
- console.log('');
53
- await ensureRemoteTable(connection);
54
- const localCommits = getAllCommits(projectRoot);
55
- const remoteCommits = await fetchRemoteCommits(connection, 100);
56
- const remoteHashes = new Set(remoteCommits.map(c => c.hash));
57
- const toPush = localCommits.filter(c => !remoteHashes.has(c.hash));
58
- spinner.start(`Pushing ${toPush.length} commit(s)...`);
59
- for (const commit of toPush.reverse()) {
60
- await pushCommit(connection, commit);
61
- }
62
- spinner.succeed(`Pushed ${toPush.length} commit(s)`);
39
+ spinner.succeed(`Pushed ${toPush.length} commit(s)`);
40
+ console.log('');
41
+ console.log('Pushed commits:');
42
+ for (const commit of toPush.slice(0, 5)) {
43
+ console.log(` ${colors.yellow(shortHash(commit.hash))} ${commit.message}`);
44
+ }
45
+ if (toPush.length > 5) {
46
+ console.log(` ${colors.muted(`... and ${toPush.length - 5} more`)}`);
63
47
  }
64
48
  console.log('');
65
- const pulledText = status.behindBy > 0 ? `${colors.yellow(`↓ ${status.behindBy}`)} pulled` : '';
66
- const pushedText = status.aheadBy > 0 ? `${colors.green(`↑ ${status.aheadBy}`)} pushed` : '';
67
- const separator = pulledText && pushedText ? ' | ' : '';
68
- console.log(`${colors.green('✓')} Sync complete: ${pulledText}${separator}${pushedText}`);
49
+ success(`Sync complete - pushed ${toPush.length} commit(s)`);
69
50
  }
70
- catch (error) {
51
+ catch (err) {
71
52
  spinner.fail('Sync failed');
72
- console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
73
- process.exit(1);
53
+ fatal(err instanceof Error ? err.message : String(err));
74
54
  }
75
55
  console.log('');
76
56
  }
@@ -0,0 +1,110 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors } from "../utils/spinner.js";
4
+ import { isInitialized, getHead, loadCommit, shortHash } from "../utils/repo-manager.js";
5
+ function loadTags(projectRoot) {
6
+ const tagPath = path.join(projectRoot, '.relq', 'tags.json');
7
+ if (fs.existsSync(tagPath)) {
8
+ return JSON.parse(fs.readFileSync(tagPath, 'utf-8'));
9
+ }
10
+ return {};
11
+ }
12
+ function saveTags(tags, projectRoot) {
13
+ const tagPath = path.join(projectRoot, '.relq', 'tags.json');
14
+ fs.writeFileSync(tagPath, JSON.stringify(tags, null, 2));
15
+ }
16
+ export async function tagCommand(context) {
17
+ const { args, flags, projectRoot } = context;
18
+ console.log('');
19
+ if (!isInitialized(projectRoot)) {
20
+ console.log(`${colors.red('fatal:')} not a relq repository`);
21
+ return;
22
+ }
23
+ const tags = loadTags(projectRoot);
24
+ const deleteFlag = flags['d'] === true || flags['delete'] === true;
25
+ const force = flags['f'] === true || flags['force'] === true;
26
+ if (deleteFlag) {
27
+ const tagName = args[0];
28
+ if (!tagName) {
29
+ console.log(`${colors.red('error:')} Please specify tag name`);
30
+ console.log('');
31
+ console.log(`Usage: ${colors.cyan('relq tag -d <name>')}`);
32
+ console.log('');
33
+ return;
34
+ }
35
+ if (!tags[tagName]) {
36
+ console.log(`${colors.red('error:')} Tag not found: ${tagName}`);
37
+ return;
38
+ }
39
+ delete tags[tagName];
40
+ saveTags(tags, projectRoot);
41
+ console.log(`Deleted tag '${tagName}'`);
42
+ console.log('');
43
+ return;
44
+ }
45
+ if (args[0]) {
46
+ const tagName = args[0];
47
+ const targetHash = args[1];
48
+ if (!/^[a-zA-Z0-9._-]+$/.test(tagName)) {
49
+ console.log(`${colors.red('error:')} Invalid tag name`);
50
+ console.log('Tag names can only contain: letters, numbers, dots, dashes, underscores');
51
+ console.log('');
52
+ return;
53
+ }
54
+ if (tags[tagName] && !force) {
55
+ console.log(`${colors.red('error:')} Tag already exists: ${tagName}`);
56
+ console.log('');
57
+ console.log(`Use ${colors.cyan(`relq tag -f ${tagName}`)} to overwrite`);
58
+ console.log('');
59
+ return;
60
+ }
61
+ let hash;
62
+ if (targetHash) {
63
+ const commit = loadCommit(targetHash, projectRoot);
64
+ if (!commit) {
65
+ console.log(`${colors.red('error:')} Commit not found: ${targetHash}`);
66
+ return;
67
+ }
68
+ hash = commit.hash;
69
+ }
70
+ else {
71
+ const head = getHead(projectRoot);
72
+ if (!head) {
73
+ console.log(`${colors.red('error:')} No commits yet`);
74
+ return;
75
+ }
76
+ hash = head;
77
+ }
78
+ const commit = loadCommit(hash, projectRoot);
79
+ if (!commit) {
80
+ console.log(`${colors.red('error:')} Cannot load commit`);
81
+ return;
82
+ }
83
+ tags[tagName] = {
84
+ hash,
85
+ message: commit.message,
86
+ createdAt: new Date().toISOString(),
87
+ };
88
+ saveTags(tags, projectRoot);
89
+ console.log(`Tagged ${colors.yellow(shortHash(hash))} as '${tagName}'`);
90
+ console.log(` ${commit.message}`);
91
+ console.log('');
92
+ return;
93
+ }
94
+ const tagNames = Object.keys(tags).sort();
95
+ if (tagNames.length === 0) {
96
+ console.log(`${colors.muted('No tags.')}`);
97
+ console.log('');
98
+ console.log(`Create one with: ${colors.cyan('relq tag <name>')}`);
99
+ console.log('');
100
+ return;
101
+ }
102
+ for (const name of tagNames) {
103
+ const tag = tags[name];
104
+ const hash = colors.yellow(shortHash(tag.hash));
105
+ const tagName = colors.cyan(name);
106
+ console.log(`${tagName.padEnd(20)} ${hash} ${tag.message}`);
107
+ }
108
+ console.log('');
109
+ }
110
+ export default tagCommand;