relq 1.0.25 → 1.0.27

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 (52) hide show
  1. package/dist/cjs/cli/commands/commit.cjs +80 -0
  2. package/dist/cjs/cli/commands/import.cjs +1 -0
  3. package/dist/cjs/cli/commands/pull.cjs +108 -34
  4. package/dist/cjs/cli/commands/push.cjs +48 -8
  5. package/dist/cjs/cli/commands/rollback.cjs +205 -84
  6. package/dist/cjs/cli/commands/schema-ast.cjs +219 -0
  7. package/dist/cjs/cli/index.cjs +6 -0
  8. package/dist/cjs/cli/utils/ast-codegen.cjs +95 -3
  9. package/dist/cjs/cli/utils/ast-transformer.cjs +12 -0
  10. package/dist/cjs/cli/utils/change-tracker.cjs +135 -0
  11. package/dist/cjs/cli/utils/commit-manager.cjs +54 -0
  12. package/dist/cjs/cli/utils/migration-generator.cjs +319 -0
  13. package/dist/cjs/cli/utils/repo-manager.cjs +99 -3
  14. package/dist/cjs/cli/utils/schema-diff.cjs +390 -0
  15. package/dist/cjs/cli/utils/schema-hash.cjs +4 -0
  16. package/dist/cjs/cli/utils/schema-to-ast.cjs +477 -0
  17. package/dist/cjs/schema-definition/column-types.cjs +50 -4
  18. package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
  19. package/dist/cjs/schema-definition/pg-function.cjs +19 -0
  20. package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
  21. package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
  22. package/dist/cjs/schema-definition/pg-view.cjs +17 -0
  23. package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
  24. package/dist/cjs/schema-definition/table-definition.cjs +4 -0
  25. package/dist/config.d.ts +98 -0
  26. package/dist/esm/cli/commands/commit.js +83 -3
  27. package/dist/esm/cli/commands/import.js +1 -0
  28. package/dist/esm/cli/commands/pull.js +109 -35
  29. package/dist/esm/cli/commands/push.js +49 -9
  30. package/dist/esm/cli/commands/rollback.js +206 -85
  31. package/dist/esm/cli/commands/schema-ast.js +183 -0
  32. package/dist/esm/cli/index.js +6 -0
  33. package/dist/esm/cli/utils/ast-codegen.js +93 -3
  34. package/dist/esm/cli/utils/ast-transformer.js +12 -0
  35. package/dist/esm/cli/utils/change-tracker.js +134 -0
  36. package/dist/esm/cli/utils/commit-manager.js +51 -0
  37. package/dist/esm/cli/utils/migration-generator.js +318 -0
  38. package/dist/esm/cli/utils/repo-manager.js +96 -3
  39. package/dist/esm/cli/utils/schema-diff.js +389 -0
  40. package/dist/esm/cli/utils/schema-hash.js +4 -0
  41. package/dist/esm/cli/utils/schema-to-ast.js +447 -0
  42. package/dist/esm/schema-definition/column-types.js +50 -4
  43. package/dist/esm/schema-definition/pg-enum.js +10 -0
  44. package/dist/esm/schema-definition/pg-function.js +19 -0
  45. package/dist/esm/schema-definition/pg-sequence.js +22 -1
  46. package/dist/esm/schema-definition/pg-trigger.js +39 -0
  47. package/dist/esm/schema-definition/pg-view.js +17 -0
  48. package/dist/esm/schema-definition/sql-expressions.js +3 -0
  49. package/dist/esm/schema-definition/table-definition.js +4 -0
  50. package/dist/index.d.ts +98 -0
  51. package/dist/schema-builder.d.ts +223 -24
  52. package/package.json +1 -1
@@ -39,109 +39,230 @@ const path = __importStar(require("path"));
39
39
  const config_loader_1 = require("../utils/config-loader.cjs");
40
40
  const env_loader_1 = require("../utils/env-loader.cjs");
41
41
  const cli_utils_1 = require("../utils/cli-utils.cjs");
42
- function parseMigration(content) {
43
- const upMatch = content.match(/--\s*UP\s*\n([\s\S]*?)(?=--\s*DOWN|$)/i);
44
- const downMatch = content.match(/--\s*DOWN\s*\n([\s\S]*?)$/i);
45
- return {
46
- up: upMatch?.[1]?.trim() || '',
47
- down: downMatch?.[1]?.trim() || '',
48
- };
49
- }
42
+ const repo_manager_1 = require("../utils/repo-manager.cjs");
43
+ const schema_diff_1 = require("../utils/schema-diff.cjs");
44
+ const migration_generator_1 = require("../utils/migration-generator.cjs");
50
45
  async function rollbackCommand(context) {
51
46
  const { config, args, flags } = context;
52
47
  if (!config) {
53
- (0, cli_utils_1.fatal)('No configuration found', `run ${cli_utils_1.colors.cyan('relq init')} to create a configuration file`);
54
- return;
48
+ (0, cli_utils_1.fatal)('No configuration found', `Run ${cli_utils_1.colors.cyan('relq init')} to create one.`);
55
49
  }
56
50
  await (0, config_loader_1.requireValidConfig)(config, { calledFrom: 'rollback' });
57
51
  const connection = config.connection;
58
- const migrationsDir = config.migrations?.directory || './migrations';
59
- const tableName = config.migrations?.tableName || '_relq_migrations';
60
- const count = parseInt(args[0]) || 1;
61
- const dryRun = flags['dry-run'] === true;
62
- const force = flags['force'] === true;
63
- console.log(`Rolling back ${count} migration(s)...`);
64
- console.log(` Connection: ${(0, env_loader_1.getConnectionDescription)(connection)}`);
52
+ const { projectRoot } = context;
53
+ const preview = flags['preview'] === true || flags['dry-run'] === true;
54
+ const skipPrompt = flags['yes'] === true || flags['y'] === true;
65
55
  console.log('');
56
+ if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
57
+ (0, cli_utils_1.fatal)('not a relq repository (or any parent directories): .relq', `Run ${cli_utils_1.colors.cyan('relq init')} to initialize.`);
58
+ }
59
+ const localHead = (0, repo_manager_1.getHead)(projectRoot);
60
+ if (!localHead) {
61
+ (0, cli_utils_1.fatal)('no commits to rollback', 'Repository has no commits.');
62
+ }
63
+ const targetRef = args[0] || 'HEAD~1';
64
+ const target = resolveTarget(projectRoot, targetRef, localHead);
65
+ if (!target) {
66
+ (0, cli_utils_1.fatal)(`invalid rollback target: ${targetRef}`, 'Use a commit hash or HEAD~N notation.');
67
+ }
68
+ if (target.commitsToRollback.length === 0) {
69
+ console.log('Nothing to rollback - already at target commit.');
70
+ console.log('');
71
+ return;
72
+ }
73
+ const spinner = (0, cli_utils_1.createSpinner)();
66
74
  try {
67
- const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
68
- const pool = new Pool({
69
- host: connection.host,
70
- port: connection.port || 5432,
71
- database: connection.database,
72
- user: connection.user,
73
- password: connection.password,
74
- connectionString: connection.url,
75
- });
76
- try {
77
- const result = await pool.query(`
78
- SELECT name FROM "${tableName}"
79
- ORDER BY id DESC
80
- LIMIT $1;
81
- `, [count]);
82
- if (result.rows.length === 0) {
83
- console.log('No migrations to rollback.');
84
- return;
85
- }
86
- const toRollback = result.rows.map(r => r.name);
87
- console.log('Migrations to rollback:');
88
- for (const name of toRollback) {
89
- console.log(` - ${name}`);
90
- }
75
+ console.log(`Rolling back to ${cli_utils_1.colors.yellow((0, repo_manager_1.shortHash)(target.hash))}`);
76
+ console.log(`${cli_utils_1.colors.muted(target.message)}`);
77
+ console.log('');
78
+ console.log(`${cli_utils_1.colors.red('Commits to rollback:')} ${target.commitsToRollback.length}`);
79
+ for (const commit of target.commitsToRollback.slice(0, 5)) {
80
+ console.log(` ${cli_utils_1.colors.red('↩')} ${(0, repo_manager_1.shortHash)(commit.hash)} ${commit.message}`);
81
+ }
82
+ if (target.commitsToRollback.length > 5) {
83
+ console.log(` ${cli_utils_1.colors.muted(`... and ${target.commitsToRollback.length - 5} more`)}`);
84
+ }
85
+ console.log('');
86
+ const currentCommitPath = path.join(projectRoot, '.relq', 'commits', `${localHead}.json`);
87
+ const targetCommitPath = path.join(projectRoot, '.relq', 'commits', `${target.hash}.json`);
88
+ if (!fs.existsSync(currentCommitPath)) {
89
+ (0, cli_utils_1.fatal)(`current commit not found: ${localHead}`);
90
+ }
91
+ if (!fs.existsSync(targetCommitPath)) {
92
+ (0, cli_utils_1.fatal)(`target commit not found: ${target.hash}`);
93
+ }
94
+ const currentCommit = JSON.parse(fs.readFileSync(currentCommitPath, 'utf-8'));
95
+ const targetCommit = JSON.parse(fs.readFileSync(targetCommitPath, 'utf-8'));
96
+ let rollbackSQL = [];
97
+ if (currentCommit.schemaAST && targetCommit.schemaAST) {
98
+ const comparison = (0, schema_diff_1.compareSchemas)(currentCommit.schemaAST, targetCommit.schemaAST);
99
+ const migration = (0, migration_generator_1.generateMigrationFromComparison)(comparison);
100
+ rollbackSQL = migration.down;
101
+ }
102
+ else {
103
+ (0, cli_utils_1.warning)('No schema AST found - using commit SQL reversal (may be incomplete)');
91
104
  console.log('');
92
- if (!force && !dryRun) {
93
- const proceed = await (0, cli_utils_1.confirm)(`${cli_utils_1.colors.red('This will undo ' + toRollback.length + ' migration(s). Continue?')}`, false);
94
- if (!proceed) {
95
- console.log('Cancelled.');
96
- return;
105
+ for (const commit of target.commitsToRollback) {
106
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
107
+ if (fs.existsSync(commitPath)) {
108
+ const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
109
+ if (enhancedCommit.downSQL) {
110
+ rollbackSQL.push(enhancedCommit.downSQL);
111
+ }
97
112
  }
98
113
  }
99
- for (const name of toRollback) {
100
- const filePath = path.join(migrationsDir, name);
101
- if (!fs.existsSync(filePath)) {
102
- (0, cli_utils_1.warning)(`Migration file not found: ${name}`);
103
- continue;
104
- }
105
- const content = fs.readFileSync(filePath, 'utf-8');
106
- const { down } = parseMigration(content);
107
- if (!down) {
108
- (0, cli_utils_1.warning)(`No DOWN section in: ${name}`);
109
- continue;
114
+ }
115
+ if (rollbackSQL.length === 0) {
116
+ (0, cli_utils_1.warning)('No rollback SQL generated - manual intervention may be required');
117
+ console.log('');
118
+ }
119
+ if (preview) {
120
+ console.log(`${cli_utils_1.colors.yellow('Preview')} - showing rollback SQL`);
121
+ console.log('');
122
+ if (rollbackSQL.length > 0) {
123
+ for (const sql of rollbackSQL.slice(0, 10)) {
124
+ console.log(` ${sql.substring(0, 100)}${sql.length > 100 ? '...' : ''}`);
110
125
  }
111
- if (dryRun) {
112
- console.log(`[dry-run] Would rollback: ${name}`);
113
- console.log(down);
114
- console.log('');
126
+ if (rollbackSQL.length > 10) {
127
+ console.log(` ${cli_utils_1.colors.muted(`... and ${rollbackSQL.length - 10} more statements`)}`);
115
128
  }
116
- else {
117
- console.log(`Rolling back: ${name}...`);
118
- const client = await pool.connect();
119
- try {
120
- await client.query('BEGIN');
121
- await client.query(down);
122
- await client.query(`DELETE FROM "${tableName}" WHERE name = $1`, [name]);
123
- await client.query('COMMIT');
124
- console.log(' Rolled back');
125
- }
126
- catch (error) {
127
- await client.query('ROLLBACK');
128
- throw error;
129
- }
130
- finally {
131
- client.release();
129
+ }
130
+ else {
131
+ console.log(` ${cli_utils_1.colors.muted('(no SQL statements)')}`);
132
+ }
133
+ console.log('');
134
+ console.log(`${cli_utils_1.colors.muted('Remove')} ${cli_utils_1.colors.cyan('--preview')} ${cli_utils_1.colors.muted('to execute rollback.')}`);
135
+ console.log('');
136
+ return;
137
+ }
138
+ if (!skipPrompt) {
139
+ (0, cli_utils_1.warning)('This will modify your database!');
140
+ console.log('');
141
+ const confirmed = await (0, cli_utils_1.confirm)(`Rollback ${target.commitsToRollback.length} commit(s)?`, false);
142
+ if (!confirmed) {
143
+ (0, cli_utils_1.fatal)('Rollback cancelled by user');
144
+ }
145
+ }
146
+ spinner.start('Connecting to remote...');
147
+ await (0, repo_manager_1.ensureRemoteTable)(connection);
148
+ spinner.succeed(`Connected to ${cli_utils_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
149
+ if (rollbackSQL.length > 0) {
150
+ spinner.start('Executing rollback...');
151
+ const pg = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
152
+ const client = new pg.Client({
153
+ host: connection.host,
154
+ port: connection.port,
155
+ database: connection.database,
156
+ user: connection.user,
157
+ password: connection.password,
158
+ });
159
+ try {
160
+ await client.connect();
161
+ await client.query('BEGIN');
162
+ let statementsRun = 0;
163
+ for (const sql of rollbackSQL) {
164
+ if (sql.trim()) {
165
+ await client.query(sql);
166
+ statementsRun++;
132
167
  }
133
168
  }
169
+ await client.query('COMMIT');
170
+ spinner.succeed(`Executed ${statementsRun} rollback statement(s)`);
171
+ }
172
+ catch (error) {
173
+ try {
174
+ await client.query('ROLLBACK');
175
+ spinner.fail('Rollback failed - transaction rolled back');
176
+ }
177
+ catch {
178
+ spinner.fail('Rollback failed');
179
+ }
180
+ throw error;
134
181
  }
135
- if (!dryRun) {
136
- console.log('');
137
- console.log(`Rolled back ${toRollback.length} migration(s).`);
182
+ finally {
183
+ await client.end();
138
184
  }
139
185
  }
140
- finally {
141
- await pool.end();
186
+ spinner.start('Updating commit status...');
187
+ for (const commit of target.commitsToRollback) {
188
+ await (0, repo_manager_1.markCommitAsRolledBack)(connection, commit.hash);
189
+ }
190
+ spinner.succeed('Marked commits as rolled back');
191
+ const headPath = path.join(projectRoot, '.relq', 'HEAD');
192
+ fs.writeFileSync(headPath, target.hash);
193
+ if (targetCommit.schema) {
194
+ const snapshotPath = path.join(projectRoot, '.relq', 'snapshot.json');
195
+ fs.writeFileSync(snapshotPath, JSON.stringify(targetCommit.schema, null, 2));
142
196
  }
197
+ console.log('');
198
+ console.log(`${cli_utils_1.colors.green('Rollback complete')}`);
199
+ console.log(` ${(0, repo_manager_1.shortHash)(localHead)} → ${(0, repo_manager_1.shortHash)(target.hash)}`);
200
+ console.log('');
143
201
  }
144
- catch (error) {
145
- (0, cli_utils_1.fatal)('Rollback failed', error instanceof Error ? error.message : String(error));
202
+ catch (err) {
203
+ spinner.fail('Rollback failed');
204
+ (0, cli_utils_1.fatal)(err instanceof Error ? err.message : String(err));
146
205
  }
147
206
  }
207
+ function resolveTarget(projectRoot, ref, currentHead) {
208
+ const commits = (0, repo_manager_1.getAllCommits)(projectRoot);
209
+ const commitsByHash = new Map(commits.map(c => [c.hash, c]));
210
+ if (ref.startsWith('HEAD~')) {
211
+ const n = parseInt(ref.slice(5), 10);
212
+ if (isNaN(n) || n < 1)
213
+ return null;
214
+ let current = currentHead;
215
+ const commitsToRollback = [];
216
+ for (let i = 0; i < n; i++) {
217
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${current}.json`);
218
+ if (!fs.existsSync(commitPath))
219
+ return null;
220
+ const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
221
+ commitsToRollback.push(commit);
222
+ if (!commit.parentHash) {
223
+ if (i < n - 1) {
224
+ return null;
225
+ }
226
+ return {
227
+ hash: commit.hash,
228
+ message: '(initial state)',
229
+ commitsToRollback: commitsToRollback.slice(0, -1),
230
+ };
231
+ }
232
+ current = commit.parentHash;
233
+ }
234
+ const targetPath = path.join(projectRoot, '.relq', 'commits', `${current}.json`);
235
+ if (!fs.existsSync(targetPath))
236
+ return null;
237
+ const targetCommit = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
238
+ return {
239
+ hash: current,
240
+ message: targetCommit.message,
241
+ commitsToRollback,
242
+ };
243
+ }
244
+ const normalizedRef = ref.length < 40 ? commits.find(c => c.hash.startsWith(ref))?.hash : ref;
245
+ if (!normalizedRef || !commitsByHash.has(normalizedRef)) {
246
+ return null;
247
+ }
248
+ const commitsToRollback = [];
249
+ let current = currentHead;
250
+ while (current && current !== normalizedRef) {
251
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${current}.json`);
252
+ if (!fs.existsSync(commitPath))
253
+ break;
254
+ const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
255
+ commitsToRollback.push(commit);
256
+ current = commit.parentHash || '';
257
+ }
258
+ if (current !== normalizedRef) {
259
+ return null;
260
+ }
261
+ const targetCommit = commitsByHash.get(normalizedRef);
262
+ return {
263
+ hash: normalizedRef,
264
+ message: targetCommit?.message || '',
265
+ commitsToRollback,
266
+ };
267
+ }
268
+ exports.default = rollbackCommand;
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.schemaAstCommand = schemaAstCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const jiti_1 = require("jiti");
40
+ const config_loader_1 = require("../utils/config-loader.cjs");
41
+ const schema_to_ast_1 = require("../utils/schema-to-ast.cjs");
42
+ const spinner_1 = require("../utils/spinner.cjs");
43
+ async function schemaAstCommand(context) {
44
+ const { config, args, flags, projectRoot } = context;
45
+ const spinner = (0, spinner_1.createSpinner)();
46
+ console.log('');
47
+ const options = {
48
+ json: Boolean(flags.json),
49
+ output: flags.output,
50
+ pretty: flags.pretty !== false,
51
+ };
52
+ let schemaPath;
53
+ if (args.length > 0) {
54
+ schemaPath = path.resolve(projectRoot, args[0]);
55
+ }
56
+ else {
57
+ schemaPath = path.resolve(projectRoot, (0, config_loader_1.getSchemaPath)(config ?? undefined));
58
+ }
59
+ const relativePath = path.relative(process.cwd(), schemaPath);
60
+ spinner.start(`Loading schema from ${spinner_1.colors.cyan(relativePath)}`);
61
+ let schemaModule;
62
+ try {
63
+ const jiti = (0, jiti_1.createJiti)(path.dirname(schemaPath), { interopDefault: true });
64
+ const module = await jiti.import(schemaPath);
65
+ if (module && module.default && typeof module.default === 'object') {
66
+ schemaModule = module.default;
67
+ }
68
+ else if (module && typeof module === 'object') {
69
+ schemaModule = module;
70
+ }
71
+ else {
72
+ throw new Error('Schema file must export an object with table/enum definitions');
73
+ }
74
+ spinner.succeed(`Loaded schema from ${spinner_1.colors.cyan(relativePath)}`);
75
+ }
76
+ catch (error) {
77
+ spinner.fail(`Failed to load schema`);
78
+ console.log('');
79
+ console.log(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
80
+ console.log('');
81
+ console.log(spinner_1.colors.yellow('hint:') + ` Make sure ${relativePath} is a valid TypeScript schema file.`);
82
+ console.log('');
83
+ process.exit(1);
84
+ }
85
+ spinner.start('Converting schema to AST');
86
+ let ast;
87
+ try {
88
+ ast = (0, schema_to_ast_1.schemaToAST)(schemaModule);
89
+ spinner.succeed('Converted schema to AST');
90
+ }
91
+ catch (error) {
92
+ spinner.fail('Failed to convert schema');
93
+ console.log('');
94
+ console.log(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
95
+ console.log('');
96
+ process.exit(1);
97
+ }
98
+ const indent = options.pretty ? 2 : 0;
99
+ const output = JSON.stringify(ast, null, indent);
100
+ if (options.output) {
101
+ const outputPath = path.resolve(projectRoot, options.output);
102
+ const outputDir = path.dirname(outputPath);
103
+ if (!fs.existsSync(outputDir)) {
104
+ fs.mkdirSync(outputDir, { recursive: true });
105
+ }
106
+ fs.writeFileSync(outputPath, output, 'utf-8');
107
+ console.log('');
108
+ console.log(`Written AST to ${spinner_1.colors.cyan(options.output)}`);
109
+ }
110
+ else if (options.json) {
111
+ console.log(output);
112
+ }
113
+ else {
114
+ console.log('');
115
+ printAstSummary(ast);
116
+ console.log('');
117
+ console.log(spinner_1.colors.muted(`Use ${spinner_1.colors.cyan('--json')} for full AST output or ${spinner_1.colors.cyan('--output <file>')} to write to file.`));
118
+ }
119
+ console.log('');
120
+ }
121
+ function printAstSummary(ast) {
122
+ console.log(spinner_1.colors.bold('Schema AST Summary'));
123
+ console.log('');
124
+ if (ast.extensions.length > 0) {
125
+ console.log(spinner_1.colors.cyan('Extensions:') + ` ${ast.extensions.length}`);
126
+ for (const ext of ast.extensions) {
127
+ console.log(` ${spinner_1.colors.green('•')} ${ext}`);
128
+ }
129
+ console.log('');
130
+ }
131
+ if (ast.enums.length > 0) {
132
+ console.log(spinner_1.colors.cyan('Enums:') + ` ${ast.enums.length}`);
133
+ for (const e of ast.enums) {
134
+ const tid = e.trackingId ? spinner_1.colors.muted(` [${e.trackingId}]`) : '';
135
+ console.log(` ${spinner_1.colors.green('•')} ${e.name}${tid}: (${e.values.join(', ')})`);
136
+ }
137
+ console.log('');
138
+ }
139
+ if (ast.domains.length > 0) {
140
+ console.log(spinner_1.colors.cyan('Domains:') + ` ${ast.domains.length}`);
141
+ for (const d of ast.domains) {
142
+ const tid = d.trackingId ? spinner_1.colors.muted(` [${d.trackingId}]`) : '';
143
+ console.log(` ${spinner_1.colors.green('•')} ${d.name}${tid}: ${d.baseType}`);
144
+ }
145
+ console.log('');
146
+ }
147
+ if (ast.compositeTypes.length > 0) {
148
+ console.log(spinner_1.colors.cyan('Composite Types:') + ` ${ast.compositeTypes.length}`);
149
+ for (const c of ast.compositeTypes) {
150
+ const tid = c.trackingId ? spinner_1.colors.muted(` [${c.trackingId}]`) : '';
151
+ const attrs = c.attributes.map(a => a.name).join(', ');
152
+ console.log(` ${spinner_1.colors.green('•')} ${c.name}${tid}: (${attrs})`);
153
+ }
154
+ console.log('');
155
+ }
156
+ if (ast.sequences.length > 0) {
157
+ console.log(spinner_1.colors.cyan('Sequences:') + ` ${ast.sequences.length}`);
158
+ for (const s of ast.sequences) {
159
+ const tid = s.trackingId ? spinner_1.colors.muted(` [${s.trackingId}]`) : '';
160
+ console.log(` ${spinner_1.colors.green('•')} ${s.name}${tid}`);
161
+ }
162
+ console.log('');
163
+ }
164
+ if (ast.tables.length > 0) {
165
+ console.log(spinner_1.colors.cyan('Tables:') + ` ${ast.tables.length}`);
166
+ for (const t of ast.tables) {
167
+ const tid = t.trackingId ? spinner_1.colors.muted(` [${t.trackingId}]`) : '';
168
+ console.log(` ${spinner_1.colors.green('•')} ${t.name}${tid}`);
169
+ console.log(` Columns: ${t.columns.length}`);
170
+ for (const col of t.columns.slice(0, 5)) {
171
+ const colTid = col.trackingId ? spinner_1.colors.muted(` [${col.trackingId}]`) : '';
172
+ const pk = col.isPrimaryKey ? spinner_1.colors.yellow(' PK') : '';
173
+ const nullable = col.isNullable ? '' : spinner_1.colors.red(' NOT NULL');
174
+ console.log(` - ${col.name}${colTid}: ${col.type}${pk}${nullable}`);
175
+ }
176
+ if (t.columns.length > 5) {
177
+ console.log(` ${spinner_1.colors.muted(`... and ${t.columns.length - 5} more`)}`);
178
+ }
179
+ if (t.indexes.length > 0) {
180
+ console.log(` Indexes: ${t.indexes.length}`);
181
+ }
182
+ if (t.constraints.length > 0) {
183
+ console.log(` Constraints: ${t.constraints.length}`);
184
+ }
185
+ }
186
+ console.log('');
187
+ }
188
+ if (ast.views.length > 0) {
189
+ console.log(spinner_1.colors.cyan('Views:') + ` ${ast.views.length}`);
190
+ for (const v of ast.views) {
191
+ const tid = v.trackingId ? spinner_1.colors.muted(` [${v.trackingId}]`) : '';
192
+ const mat = v.isMaterialized ? spinner_1.colors.yellow(' (materialized)') : '';
193
+ console.log(` ${spinner_1.colors.green('•')} ${v.name}${tid}${mat}`);
194
+ }
195
+ console.log('');
196
+ }
197
+ if (ast.functions.length > 0) {
198
+ console.log(spinner_1.colors.cyan('Functions:') + ` ${ast.functions.length}`);
199
+ for (const f of ast.functions) {
200
+ const tid = f.trackingId ? spinner_1.colors.muted(` [${f.trackingId}]`) : '';
201
+ const args = f.args.map(a => a.type).join(', ');
202
+ console.log(` ${spinner_1.colors.green('•')} ${f.name}${tid}(${args}) -> ${f.returnType}`);
203
+ }
204
+ console.log('');
205
+ }
206
+ if (ast.triggers.length > 0) {
207
+ console.log(spinner_1.colors.cyan('Triggers:') + ` ${ast.triggers.length}`);
208
+ for (const tr of ast.triggers) {
209
+ const tid = tr.trackingId ? spinner_1.colors.muted(` [${tr.trackingId}]`) : '';
210
+ console.log(` ${spinner_1.colors.green('•')} ${tr.name}${tid} on ${tr.table}`);
211
+ }
212
+ console.log('');
213
+ }
214
+ const total = ast.tables.length + ast.enums.length + ast.views.length +
215
+ ast.functions.length + ast.triggers.length + ast.sequences.length +
216
+ ast.domains.length + ast.compositeTypes.length;
217
+ console.log(spinner_1.colors.bold(`Total: ${total} schema objects`));
218
+ }
219
+ exports.default = schemaAstCommand;
@@ -58,6 +58,7 @@ const tag_1 = require("./commands/tag.cjs");
58
58
  const cherry_pick_1 = require("./commands/cherry-pick.cjs");
59
59
  const remote_1 = require("./commands/remote.cjs");
60
60
  const validate_1 = require("./commands/validate.cjs");
61
+ const schema_ast_1 = require("./commands/schema-ast.cjs");
61
62
  const fs = __importStar(require("fs"));
62
63
  const path = __importStar(require("path"));
63
64
  function loadEnvFile() {
@@ -168,6 +169,7 @@ Other Commands:
168
169
  introspect Parse database schema
169
170
  import <sql-file> Import SQL file to schema
170
171
  export [file] Export schema to SQL file
172
+ schema:ast [file] Convert schema to AST (JSON)
171
173
 
172
174
  Options:
173
175
  --help, -h Show this help
@@ -359,6 +361,10 @@ async function main() {
359
361
  case 'validate':
360
362
  await (0, validate_1.validateCommand)(context);
361
363
  break;
364
+ case 'schema:ast':
365
+ case 'ast':
366
+ await (0, schema_ast_1.schemaAstCommand)(context);
367
+ break;
362
368
  case 'sync':
363
369
  await (0, sync_1.syncCommand)(context);
364
370
  break;