sql-kite 1.0.6 → 1.0.8

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 (89) hide show
  1. package/README.md +10 -2
  2. package/package.json +12 -4
  3. package/server/db/connections.js +114 -0
  4. package/server/db/meta-db.js +0 -0
  5. package/server/db/user-db.js +0 -0
  6. package/server/index.js +214 -0
  7. package/server/routes/branches.js +484 -0
  8. package/server/routes/compare.js +109 -0
  9. package/server/routes/export.js +201 -0
  10. package/server/routes/import.js +375 -0
  11. package/server/routes/migrations.js +332 -0
  12. package/server/routes/query.js +67 -0
  13. package/server/routes/schema.js +206 -0
  14. package/server/routes/snapshots.js +322 -0
  15. package/server/routes/tables.js +121 -0
  16. package/server/routes/timeline.js +108 -0
  17. package/server/server.js +0 -0
  18. package/src/commands/import-server.js +2 -5
  19. package/src/commands/import.js +5 -9
  20. package/src/commands/start.js +6 -7
  21. package/src/commands/stop.js +7 -2
  22. package/src/utils/paths.js +61 -1
  23. package/studio-out/404/index.html +1 -0
  24. package/studio-out/404.html +1 -0
  25. package/studio-out/__next.__PAGE__.txt +10 -0
  26. package/studio-out/__next._full.txt +20 -0
  27. package/studio-out/__next._head.txt +6 -0
  28. package/studio-out/__next._index.txt +6 -0
  29. package/studio-out/__next._tree.txt +3 -0
  30. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_buildManifest.js +11 -0
  31. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_clientMiddlewareManifest.json +1 -0
  32. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_ssgManifest.js +1 -0
  33. package/studio-out/_next/static/chunks/118fc599da2f27aa.css +2 -0
  34. package/studio-out/_next/static/chunks/240f2fa81d4fb687.js +1 -0
  35. package/studio-out/_next/static/chunks/42c33ca704af9b68.js +1 -0
  36. package/studio-out/_next/static/chunks/99b69e65b599be96.js +5 -0
  37. package/studio-out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  38. package/studio-out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  39. package/studio-out/_next/static/chunks/b20313408e970968.css +1 -0
  40. package/studio-out/_next/static/chunks/d104f42a7b0c57b2.js +2 -0
  41. package/studio-out/_next/static/chunks/d4aa9be9c80c98d6.js +1 -0
  42. package/studio-out/_next/static/chunks/f2f58a7e93290fbb.js +1 -0
  43. package/studio-out/_next/static/chunks/f547e106c8e2aa8e.js +1 -0
  44. package/studio-out/_next/static/chunks/f5cb054219e2eeb8.js +109 -0
  45. package/studio-out/_next/static/chunks/turbopack-1577480078e795df.js +4 -0
  46. package/studio-out/_not-found/__next._full.txt +15 -0
  47. package/studio-out/_not-found/__next._head.txt +6 -0
  48. package/studio-out/_not-found/__next._index.txt +6 -0
  49. package/studio-out/_not-found/__next._not-found/__PAGE__.txt +5 -0
  50. package/studio-out/_not-found/__next._not-found.txt +4 -0
  51. package/studio-out/_not-found/__next._tree.txt +2 -0
  52. package/studio-out/_not-found/index.html +1 -0
  53. package/studio-out/_not-found/index.txt +15 -0
  54. package/studio-out/favicon.ico +10 -0
  55. package/studio-out/index.html +37 -0
  56. package/studio-out/index.txt +20 -0
  57. package/studio-out/logo.svg +5 -0
  58. package/studio-out/snapshots/__next._full.txt +15 -0
  59. package/studio-out/snapshots/__next._head.txt +6 -0
  60. package/studio-out/snapshots/__next._index.txt +6 -0
  61. package/studio-out/snapshots/__next._tree.txt +2 -0
  62. package/studio-out/snapshots/__next.snapshots/__PAGE__.txt +5 -0
  63. package/studio-out/snapshots/__next.snapshots.txt +4 -0
  64. package/studio-out/snapshots/index.html +1 -0
  65. package/studio-out/snapshots/index.txt +15 -0
  66. package/studio-out/sql/__next._full.txt +15 -0
  67. package/studio-out/sql/__next._head.txt +6 -0
  68. package/studio-out/sql/__next._index.txt +6 -0
  69. package/studio-out/sql/__next._tree.txt +2 -0
  70. package/studio-out/sql/__next.sql/__PAGE__.txt +5 -0
  71. package/studio-out/sql/__next.sql.txt +4 -0
  72. package/studio-out/sql/index.html +1 -0
  73. package/studio-out/sql/index.txt +15 -0
  74. package/studio-out/tables/__next._full.txt +15 -0
  75. package/studio-out/tables/__next._head.txt +6 -0
  76. package/studio-out/tables/__next._index.txt +6 -0
  77. package/studio-out/tables/__next._tree.txt +2 -0
  78. package/studio-out/tables/__next.tables/__PAGE__.txt +5 -0
  79. package/studio-out/tables/__next.tables.txt +4 -0
  80. package/studio-out/tables/index.html +1 -0
  81. package/studio-out/tables/index.txt +15 -0
  82. package/studio-out/timeline/__next._full.txt +15 -0
  83. package/studio-out/timeline/__next._head.txt +6 -0
  84. package/studio-out/timeline/__next._index.txt +6 -0
  85. package/studio-out/timeline/__next._tree.txt +2 -0
  86. package/studio-out/timeline/__next.timeline/__PAGE__.txt +5 -0
  87. package/studio-out/timeline/__next.timeline.txt +4 -0
  88. package/studio-out/timeline/index.html +1 -0
  89. package/studio-out/timeline/index.txt +15 -0
@@ -0,0 +1,322 @@
1
+ import { copyFileSync, statSync, existsSync, mkdirSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { closeBranchConnection } from '../db/connections.js';
4
+
5
+ export default async function snapshotsRoutes(fastify, options) {
6
+ /**
7
+ * List snapshots for current branch
8
+ */
9
+ fastify.get('/', async (request, reply) => {
10
+ const snapshotsPath = join(fastify.projectPath, '.studio', 'snapshots');
11
+ const metaDb = fastify.getMetaDb();
12
+ const currentBranch = fastify.getCurrentBranch();
13
+
14
+ try {
15
+ // Ensure snapshots directory exists
16
+ if (!existsSync(snapshotsPath)) {
17
+ mkdirSync(snapshotsPath, { recursive: true });
18
+ }
19
+
20
+ // Get snapshots for current branch only
21
+ const snapshots = metaDb.prepare(`
22
+ SELECT * FROM snapshots
23
+ WHERE branch = ?
24
+ ORDER BY id DESC
25
+ `).all(currentBranch);
26
+
27
+ return snapshots.map(snapshot => {
28
+ const filePath = join(snapshotsPath, snapshot.filename);
29
+ let exists = false;
30
+ let size = snapshot.size;
31
+
32
+ try {
33
+ const stats = statSync(filePath);
34
+ exists = stats.isFile();
35
+ size = stats.size;
36
+ } catch (e) {
37
+ exists = false;
38
+ }
39
+
40
+ return {
41
+ id: snapshot.id,
42
+ branch: snapshot.branch,
43
+ filename: snapshot.filename,
44
+ name: snapshot.name,
45
+ description: snapshot.description,
46
+ type: snapshot.type || 'manual',
47
+ size,
48
+ createdAt: snapshot.created_at,
49
+ exists
50
+ };
51
+ });
52
+ } catch (error) {
53
+ reply.code(500).send({ error: error.message });
54
+ }
55
+ });
56
+
57
+ /**
58
+ * Create snapshot from current branch
59
+ */
60
+ fastify.post('/', async (request, reply) => {
61
+ const { name, description, type = 'manual' } = request.body;
62
+ const snapshotsPath = join(fastify.projectPath, '.studio', 'snapshots');
63
+ const metaDb = fastify.getMetaDb();
64
+ const currentBranch = fastify.getCurrentBranch();
65
+
66
+ if (!name) {
67
+ return reply.code(400).send({ error: 'Snapshot name is required' });
68
+ }
69
+
70
+ // Validate type
71
+ const validTypes = ['manual', 'auto-before-migration', 'auto-before-promote', 'import-baseline', 'auto-risky-query'];
72
+ const snapshotType = validTypes.includes(type) ? type : 'manual';
73
+
74
+ try {
75
+ // Ensure snapshots directory exists
76
+ if (!existsSync(snapshotsPath)) {
77
+ mkdirSync(snapshotsPath, { recursive: true });
78
+ }
79
+
80
+ // Get current branch's DB file
81
+ const branchInfo = metaDb.prepare(`
82
+ SELECT db_file FROM branches WHERE name = ?
83
+ `).get(currentBranch);
84
+
85
+ if (!branchInfo) {
86
+ return reply.code(404).send({ error: 'Current branch not found' });
87
+ }
88
+
89
+ const sourceDbPath = join(fastify.projectPath, branchInfo.db_file);
90
+
91
+ // Create snapshot filename with timestamp
92
+ const timestamp = Date.now();
93
+ const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '_').substring(0, 30);
94
+ const snapshotFilename = `${currentBranch}-${sanitizedName}-${timestamp}.snapshot.db`;
95
+ const snapshotPath = join(snapshotsPath, snapshotFilename);
96
+
97
+ // Checkpoint WAL before copying (ensures all data is in main DB file)
98
+ const db = fastify.getUserDb();
99
+ db.pragma('wal_checkpoint(FULL)');
100
+
101
+ // Copy database file
102
+ copyFileSync(sourceDbPath, snapshotPath);
103
+
104
+ // Get file size
105
+ const stats = statSync(snapshotPath);
106
+
107
+ // Record snapshot in meta DB
108
+ const result = metaDb.prepare(`
109
+ INSERT INTO snapshots (branch, filename, name, size, description, type)
110
+ VALUES (?, ?, ?, ?, ?, ?)
111
+ `).run(currentBranch, snapshotFilename, name, stats.size, description || '', snapshotType);
112
+
113
+ // Log event
114
+ metaDb.prepare(`
115
+ INSERT INTO events (branch, type, data)
116
+ VALUES (?, 'snapshot_created', ?)
117
+ `).run(currentBranch, JSON.stringify({
118
+ name,
119
+ filename: snapshotFilename,
120
+ size: stats.size,
121
+ snapshot_type: snapshotType
122
+ }));
123
+
124
+ return {
125
+ id: result.lastInsertRowid,
126
+ branch: currentBranch,
127
+ filename: snapshotFilename,
128
+ name,
129
+ size: stats.size,
130
+ description: description || '',
131
+ type: snapshotType
132
+ };
133
+ } catch (error) {
134
+ reply.code(500).send({ error: error.message });
135
+ }
136
+ });
137
+
138
+ /**
139
+ * Restore snapshot (current branch only)
140
+ */
141
+ fastify.post('/restore/:id', async (request, reply) => {
142
+ const { id } = request.params;
143
+ const snapshotsPath = join(fastify.projectPath, '.studio', 'snapshots');
144
+ const metaDb = fastify.getMetaDb();
145
+ const currentBranch = fastify.getCurrentBranch();
146
+
147
+ try {
148
+ // Get snapshot
149
+ const snapshot = metaDb.prepare(`
150
+ SELECT * FROM snapshots WHERE id = ?
151
+ `).get(id);
152
+
153
+ if (!snapshot) {
154
+ return reply.code(404).send({ error: 'Snapshot not found' });
155
+ }
156
+
157
+ // Verify snapshot belongs to current branch
158
+ if (snapshot.branch !== currentBranch) {
159
+ return reply.code(400).send({
160
+ error: `Cannot restore snapshot from "${snapshot.branch}" while on "${currentBranch}". Switch branches first.`
161
+ });
162
+ }
163
+
164
+ const snapshotPath = join(snapshotsPath, snapshot.filename);
165
+
166
+ // Verify snapshot file exists
167
+ if (!existsSync(snapshotPath)) {
168
+ return reply.code(404).send({ error: 'Snapshot file not found' });
169
+ }
170
+
171
+ // Get current branch's DB file
172
+ const branchInfo = metaDb.prepare(`
173
+ SELECT db_file FROM branches WHERE name = ?
174
+ `).get(currentBranch);
175
+
176
+ if (!branchInfo) {
177
+ return reply.code(404).send({ error: 'Branch not found' });
178
+ }
179
+
180
+ const dbPath = join(fastify.projectPath, branchInfo.db_file);
181
+
182
+ // Close current branch DB connection
183
+ closeBranchConnection(fastify.projectPath, currentBranch);
184
+
185
+ // Copy snapshot over current DB
186
+ copyFileSync(snapshotPath, dbPath);
187
+
188
+ // Also remove WAL and SHM files (force fresh start)
189
+ ['.wal', '.shm'].forEach(ext => {
190
+ const walPath = dbPath + ext;
191
+ if (existsSync(walPath)) {
192
+ try {
193
+ unlinkSync(walPath);
194
+ } catch (e) {
195
+ // Ignore
196
+ }
197
+ }
198
+ });
199
+
200
+ // Log restore event
201
+ metaDb.prepare(`
202
+ INSERT INTO events (branch, type, data)
203
+ VALUES (?, 'snapshot_restored', ?)
204
+ `).run(currentBranch, JSON.stringify({
205
+ snapshot_id: id,
206
+ snapshot_name: snapshot.name,
207
+ filename: snapshot.filename
208
+ }));
209
+
210
+ return {
211
+ success: true,
212
+ message: 'Snapshot restored successfully',
213
+ snapshot: {
214
+ id: snapshot.id,
215
+ name: snapshot.name,
216
+ branch: snapshot.branch
217
+ }
218
+ };
219
+ } catch (error) {
220
+ reply.code(500).send({ error: error.message });
221
+ }
222
+ });
223
+
224
+ /**
225
+ * Delete snapshot
226
+ */
227
+ fastify.delete('/:id', async (request, reply) => {
228
+ const { id } = request.params;
229
+ const snapshotsPath = join(fastify.projectPath, '.studio', 'snapshots');
230
+ const metaDb = fastify.getMetaDb();
231
+ const currentBranch = fastify.getCurrentBranch();
232
+
233
+ try {
234
+ const snapshot = metaDb.prepare(`
235
+ SELECT * FROM snapshots WHERE id = ?
236
+ `).get(id);
237
+
238
+ if (!snapshot) {
239
+ return reply.code(404).send({ error: 'Snapshot not found' });
240
+ }
241
+
242
+ // Optional: Only allow deleting snapshots from current branch
243
+ if (snapshot.branch !== currentBranch) {
244
+ return reply.code(400).send({
245
+ error: `Cannot delete snapshot from "${snapshot.branch}" while on "${currentBranch}"`
246
+ });
247
+ }
248
+
249
+ // Delete from meta DB
250
+ metaDb.prepare(`DELETE FROM snapshots WHERE id = ?`).run(id);
251
+
252
+ // Also delete the actual snapshot file
253
+ const snapshotFilePath = join(snapshotsPath, snapshot.filename);
254
+ try {
255
+ if (existsSync(snapshotFilePath)) {
256
+ unlinkSync(snapshotFilePath);
257
+ }
258
+ } catch (fileErr) {
259
+ // Log but don't fail if file deletion fails
260
+ console.warn('Failed to delete snapshot file:', fileErr.message);
261
+ }
262
+
263
+ // Log event
264
+ metaDb.prepare(`
265
+ INSERT INTO events (branch, type, data)
266
+ VALUES (?, 'snapshot_deleted', ?)
267
+ `).run(currentBranch, JSON.stringify({
268
+ snapshot_id: id,
269
+ snapshot_name: snapshot.name
270
+ }));
271
+
272
+ return { success: true };
273
+ } catch (error) {
274
+ reply.code(500).send({ error: error.message });
275
+ }
276
+ });
277
+
278
+ /**
279
+ * Get snapshot details
280
+ */
281
+ fastify.get('/:id', async (request, reply) => {
282
+ const { id } = request.params;
283
+ const snapshotsPath = join(fastify.projectPath, '.studio', 'snapshots');
284
+ const metaDb = fastify.getMetaDb();
285
+
286
+ try {
287
+ const snapshot = metaDb.prepare(`
288
+ SELECT * FROM snapshots WHERE id = ?
289
+ `).get(id);
290
+
291
+ if (!snapshot) {
292
+ return reply.code(404).send({ error: 'Snapshot not found' });
293
+ }
294
+
295
+ const filePath = join(snapshotsPath, snapshot.filename);
296
+ let exists = false;
297
+ let size = snapshot.size;
298
+
299
+ try {
300
+ const stats = statSync(filePath);
301
+ exists = stats.isFile();
302
+ size = stats.size;
303
+ } catch (e) {
304
+ exists = false;
305
+ }
306
+
307
+ return {
308
+ id: snapshot.id,
309
+ branch: snapshot.branch,
310
+ filename: snapshot.filename,
311
+ name: snapshot.name,
312
+ description: snapshot.description,
313
+ type: snapshot.type || 'manual',
314
+ size,
315
+ createdAt: snapshot.created_at,
316
+ exists
317
+ };
318
+ } catch (error) {
319
+ reply.code(500).send({ error: error.message });
320
+ }
321
+ });
322
+ }
@@ -0,0 +1,121 @@
1
+ export default async function tablesRoutes(fastify, options) {
2
+ // List all tables
3
+ fastify.get('/', async (request, reply) => {
4
+ const db = fastify.getUserDb();
5
+
6
+ const tables = db.prepare(`
7
+ SELECT name, sql
8
+ FROM sqlite_master
9
+ WHERE type = 'table'
10
+ AND name NOT LIKE 'sqlite_%'
11
+ AND name NOT LIKE '_studio_%'
12
+ ORDER BY name
13
+ `).all();
14
+
15
+ return tables.map(table => ({
16
+ name: table.name,
17
+ sql: table.sql
18
+ }));
19
+ });
20
+
21
+ // Get table info
22
+ fastify.get('/:tableName', async (request, reply) => {
23
+ const { tableName } = request.params;
24
+ const db = fastify.getUserDb();
25
+
26
+ try {
27
+ // Get columns
28
+ const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
29
+
30
+ // Get row count
31
+ const countResult = db.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).get();
32
+
33
+ // Get CREATE statement
34
+ const tableInfo = db.prepare(`
35
+ SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?
36
+ `).get(tableName);
37
+
38
+ return {
39
+ name: tableName,
40
+ columns: columns.map(col => ({
41
+ name: col.name,
42
+ type: col.type,
43
+ notNull: col.notnull === 1,
44
+ defaultValue: col.dflt_value,
45
+ primaryKey: col.pk === 1
46
+ })),
47
+ rowCount: countResult.count,
48
+ sql: tableInfo?.sql
49
+ };
50
+ } catch (error) {
51
+ reply.code(500).send({ error: error.message });
52
+ }
53
+ });
54
+
55
+ // Get table data with pagination
56
+ fastify.get('/:tableName/data', async (request, reply) => {
57
+ const { tableName } = request.params;
58
+ const { limit = 100, offset = 0 } = request.query;
59
+ const db = fastify.getUserDb();
60
+
61
+ try {
62
+ const rows = db.prepare(`
63
+ SELECT * FROM ${tableName}
64
+ LIMIT ? OFFSET ?
65
+ `).all(parseInt(limit), parseInt(offset));
66
+
67
+ const totalResult = db.prepare(`SELECT COUNT(*) as total FROM ${tableName}`).get();
68
+
69
+ return {
70
+ data: rows,
71
+ total: totalResult.total,
72
+ limit: parseInt(limit),
73
+ offset: parseInt(offset)
74
+ };
75
+ } catch (error) {
76
+ reply.code(500).send({ error: error.message });
77
+ }
78
+ });
79
+
80
+ // Create table
81
+ fastify.post('/', async (request, reply) => {
82
+ const { sql } = request.body;
83
+ const db = fastify.getUserDb();
84
+ const metaDb = fastify.getMetaDb();
85
+
86
+ try {
87
+ db.exec(sql);
88
+
89
+ // Log event
90
+ metaDb.prepare(`
91
+ INSERT INTO events (type, data)
92
+ VALUES ('table_created', ?)
93
+ `).run(JSON.stringify({ sql }));
94
+
95
+ return { success: true };
96
+ } catch (error) {
97
+ reply.code(400).send({ error: error.message });
98
+ }
99
+ });
100
+
101
+ // Drop table
102
+ fastify.delete('/:tableName', async (request, reply) => {
103
+ const { tableName } = request.params;
104
+ const db = fastify.getUserDb();
105
+ const metaDb = fastify.getMetaDb();
106
+
107
+ try {
108
+ db.exec(`DROP TABLE ${tableName}`);
109
+
110
+ // Log event
111
+ metaDb.prepare(`
112
+ INSERT INTO events (type, data)
113
+ VALUES ('table_dropped', ?)
114
+ `).run(JSON.stringify({ tableName }));
115
+
116
+ return { success: true };
117
+ } catch (error) {
118
+ reply.code(500).send({ error: error.message });
119
+ }
120
+ });
121
+ }
@@ -0,0 +1,108 @@
1
+ export default async function timelineRoutes(fastify, options) {
2
+ /**
3
+ * Get timeline events for current branch
4
+ */
5
+ fastify.get('/', async (request, reply) => {
6
+ const { limit = 50, offset = 0, all_branches } = request.query;
7
+ const metaDb = fastify.getMetaDb();
8
+ const currentBranch = fastify.getCurrentBranch();
9
+
10
+ try {
11
+ let events, totalResult;
12
+
13
+ if (all_branches === 'true') {
14
+ // Show events from all branches
15
+ events = metaDb.prepare(`
16
+ SELECT id, branch, type, data, created_at
17
+ FROM events
18
+ ORDER BY id DESC
19
+ LIMIT ? OFFSET ?
20
+ `).all(parseInt(limit), parseInt(offset));
21
+
22
+ totalResult = metaDb.prepare(`SELECT COUNT(*) as total FROM events`).get();
23
+ } else {
24
+ // Show events from current branch only (default)
25
+ events = metaDb.prepare(`
26
+ SELECT id, branch, type, data, created_at
27
+ FROM events
28
+ WHERE branch = ?
29
+ ORDER BY id DESC
30
+ LIMIT ? OFFSET ?
31
+ `).all(currentBranch, parseInt(limit), parseInt(offset));
32
+
33
+ totalResult = metaDb.prepare(`
34
+ SELECT COUNT(*) as total FROM events WHERE branch = ?
35
+ `).get(currentBranch);
36
+ }
37
+
38
+ return {
39
+ events: events.map(event => ({
40
+ id: event.id,
41
+ branch: event.branch,
42
+ type: event.type,
43
+ data: JSON.parse(event.data),
44
+ createdAt: event.created_at,
45
+ isCurrentBranch: event.branch === currentBranch
46
+ })),
47
+ currentBranch,
48
+ total: totalResult.total,
49
+ limit: parseInt(limit),
50
+ offset: parseInt(offset)
51
+ };
52
+ } catch (error) {
53
+ reply.code(500).send({ error: error.message });
54
+ }
55
+ });
56
+
57
+ /**
58
+ * Clear timeline for current branch
59
+ */
60
+ fastify.delete('/', async (request, reply) => {
61
+ const metaDb = fastify.getMetaDb();
62
+ const currentBranch = fastify.getCurrentBranch();
63
+
64
+ try {
65
+ metaDb.prepare(`DELETE FROM events WHERE branch = ?`).run(currentBranch);
66
+
67
+ return {
68
+ success: true,
69
+ branch: currentBranch,
70
+ message: `Timeline cleared for branch "${currentBranch}"`
71
+ };
72
+ } catch (error) {
73
+ reply.code(500).send({ error: error.message });
74
+ }
75
+ });
76
+
77
+ /**
78
+ * Get timeline summary/stats
79
+ */
80
+ fastify.get('/stats', async (request, reply) => {
81
+ const metaDb = fastify.getMetaDb();
82
+ const currentBranch = fastify.getCurrentBranch();
83
+
84
+ try {
85
+ const stats = metaDb.prepare(`
86
+ SELECT
87
+ type,
88
+ COUNT(*) as count
89
+ FROM events
90
+ WHERE branch = ?
91
+ GROUP BY type
92
+ ORDER BY count DESC
93
+ `).all(currentBranch);
94
+
95
+ const totalEvents = metaDb.prepare(`
96
+ SELECT COUNT(*) as total FROM events WHERE branch = ?
97
+ `).get(currentBranch);
98
+
99
+ return {
100
+ branch: currentBranch,
101
+ total: totalEvents.total,
102
+ by_type: stats
103
+ };
104
+ } catch (error) {
105
+ reply.code(500).send({ error: error.message });
106
+ }
107
+ });
108
+ }
File without changes
@@ -1,11 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from 'child_process'
4
- import { join, dirname } from 'path'
5
- import { fileURLToPath } from 'url'
6
4
  import chalk from 'chalk'
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url))
5
+ import { getServerEntryPath } from '../utils/paths.js'
9
6
 
10
7
  export default function importServerCommand(port = 3000) {
11
8
  console.log(chalk.cyan('→ Starting import server...'))
@@ -13,7 +10,7 @@ export default function importServerCommand(port = 3000) {
13
10
  console.log(chalk.dim(' Mode: Import-only'))
14
11
  console.log('')
15
12
 
16
- const serverPath = join(__dirname, '../../../server/src/index.js')
13
+ const serverPath = getServerEntryPath()
17
14
 
18
15
  const serverProcess = spawn('node', [serverPath], {
19
16
  stdio: 'inherit',
@@ -1,17 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { existsSync, statSync, accessSync, constants, writeFileSync, mkdirSync } from 'fs'
4
- import { resolve, extname, basename, join, dirname } from 'path'
5
- import { fileURLToPath } from 'url'
4
+ import { resolve, extname, basename, join } from 'path'
6
5
  import Database from 'better-sqlite3'
7
6
  import chalk from 'chalk'
8
7
  import { spawn } from 'child_process'
9
8
  import http from 'http'
10
9
  import open from 'open'
11
10
  import { findFreePort } from '../utils/port-finder.js'
12
- import { ensureSqlKiteDirs, LOGS_DIR } from '../utils/paths.js'
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url))
11
+ import { ensureSqlKiteDirs, LOGS_DIR, SQL_KITE_HOME, getStudioOutPath, getServerEntryPath } from '../utils/paths.js'
15
12
 
16
13
  export default async function importCommand(dbPath) {
17
14
  ensureSqlKiteDirs()
@@ -136,8 +133,7 @@ export default async function importCommand(dbPath) {
136
133
  console.log('')
137
134
 
138
135
  // Store import session data
139
- const homeDir = process.env.HOME || process.env.USERPROFILE
140
- const sqlKiteDir = join(homeDir, '.sql-kite')
136
+ const sqlKiteDir = SQL_KITE_HOME
141
137
  const sessionFile = join(sqlKiteDir, 'import-pending.json')
142
138
 
143
139
  // Ensure .sql-kite directory exists
@@ -223,7 +219,7 @@ export default async function importCommand(dbPath) {
223
219
  console.log('')
224
220
  console.log(chalk.dim(`Session saved to: ${sessionFile}`))
225
221
 
226
- const studioPath = join(__dirname, '../../../studio/out')
222
+ const studioPath = getStudioOutPath()
227
223
  if (!existsSync(studioPath)) {
228
224
  console.log(chalk.red(`\n✗ Studio UI not built yet`))
229
225
  console.log(chalk.dim(` Run: ${chalk.cyan(`cd packages/studio && npm run build`)}`))
@@ -234,7 +230,7 @@ export default async function importCommand(dbPath) {
234
230
  const { port, alreadyRunning } = await getImportServerPort()
235
231
 
236
232
  if (!alreadyRunning) {
237
- const serverPath = join(__dirname, '../../../server/src/index.js')
233
+ const serverPath = getServerEntryPath()
238
234
  const logPath = join(LOGS_DIR, `import-server-${Date.now()}.log`)
239
235
  const out = []
240
236
  const serverProcess = spawn('node', [serverPath], {
@@ -1,7 +1,6 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { existsSync, readFileSync, writeFileSync } from 'fs';
3
- import { join, dirname } from 'path';
4
- import { fileURLToPath } from 'url';
3
+ import { join } from 'path';
5
4
  import http from 'http';
6
5
  import chalk from 'chalk';
7
6
  import ora from 'ora';
@@ -9,12 +8,12 @@ import open from 'open';
9
8
  import {
10
9
  getProjectPath,
11
10
  getProjectServerInfoPath,
12
- projectExists
11
+ projectExists,
12
+ getStudioOutPath,
13
+ getServerEntryPath
13
14
  } from '../utils/paths.js';
14
15
  import { findFreePort } from '../utils/port-finder.js';
15
16
 
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
-
18
17
  // Helper function to check if server is ready
19
18
  async function waitForServer(port, maxAttempts = 30) {
20
19
  for (let i = 0; i < maxAttempts; i++) {
@@ -50,7 +49,7 @@ export async function startCommand(name) {
50
49
  }
51
50
 
52
51
  // Check if studio is built
53
- const studioPath = join(__dirname, '../../../studio/out');
52
+ const studioPath = getStudioOutPath();
54
53
  if (!existsSync(studioPath)) {
55
54
  console.log(chalk.red(`✗ Studio UI not built yet`));
56
55
  console.log(chalk.dim(` Run: ${chalk.cyan(`cd packages/studio && npm run build`)}`));
@@ -80,7 +79,7 @@ export async function startCommand(name) {
80
79
  const projectPath = getProjectPath(name);
81
80
 
82
81
  // Path to server package
83
- const serverPath = join(__dirname, '../../../server/src/index.js');
82
+ const serverPath = getServerEntryPath();
84
83
 
85
84
  // Spawn server process
86
85
  const serverProcess = spawn('node', [serverPath], {
@@ -3,6 +3,7 @@ import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { getProjectServerInfoPath, projectExists } from '../utils/paths.js';
5
5
  import { releasePort } from '../utils/port-finder.js';
6
+ import { execSync } from 'child_process';
6
7
 
7
8
  export async function stopCommand(name) {
8
9
  if (!projectExists(name)) {
@@ -22,7 +23,7 @@ export async function stopCommand(name) {
22
23
  try {
23
24
  const serverInfo = JSON.parse(readFileSync(serverInfoPath, 'utf-8'));
24
25
 
25
- // Send SIGTERM signal
26
+ // Try graceful shutdown first
26
27
  try {
27
28
  process.kill(serverInfo.pid, 'SIGTERM');
28
29
  } catch (killError) {
@@ -49,7 +50,11 @@ export async function stopCommand(name) {
49
50
  if (!processTerminated) {
50
51
  // Force kill if graceful shutdown timed out
51
52
  try {
52
- process.kill(serverInfo.pid, 'SIGKILL');
53
+ if (process.platform === 'win32') {
54
+ execSync(`taskkill /PID ${serverInfo.pid} /F /T`, { stdio: 'ignore' });
55
+ } else {
56
+ process.kill(serverInfo.pid, 'SIGKILL');
57
+ }
53
58
  await new Promise(resolve => setTimeout(resolve, 500));
54
59
  } catch (e) {
55
60
  // Process might already be gone