@hypequery/cli 1.1.1 → 1.2.0

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 (77) hide show
  1. package/README.md +71 -149
  2. package/dist/bin/cli.js +21 -81
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +74 -105
  6. package/dist/commands/dev.d.ts +1 -0
  7. package/dist/commands/dev.d.ts.map +1 -1
  8. package/dist/commands/dev.js +180 -255
  9. package/dist/commands/generate-datasets.d.ts +14 -0
  10. package/dist/commands/generate-datasets.d.ts.map +1 -0
  11. package/dist/commands/generate-datasets.js +96 -0
  12. package/dist/commands/generate.d.ts +2 -0
  13. package/dist/commands/generate.d.ts.map +1 -1
  14. package/dist/commands/generate.js +112 -167
  15. package/dist/commands/init.d.ts +7 -1
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +337 -390
  18. package/dist/generators/clickhouse.d.ts +1 -1
  19. package/dist/generators/clickhouse.d.ts.map +1 -1
  20. package/dist/generators/clickhouse.js +10 -269
  21. package/dist/generators/dataset-generator.d.ts +16 -0
  22. package/dist/generators/dataset-generator.d.ts.map +1 -0
  23. package/dist/generators/dataset-generator.js +213 -0
  24. package/dist/generators/index.js +4 -4
  25. package/dist/templates/api.d.ts +9 -0
  26. package/dist/templates/api.d.ts.map +1 -0
  27. package/dist/templates/api.js +26 -0
  28. package/dist/templates/auth-scaffold.d.ts +17 -0
  29. package/dist/templates/auth-scaffold.d.ts.map +1 -0
  30. package/dist/templates/auth-scaffold.js +41 -0
  31. package/dist/templates/client.js +10 -1
  32. package/dist/templates/datasets.d.ts +5 -0
  33. package/dist/templates/datasets.d.ts.map +1 -0
  34. package/dist/templates/datasets.js +23 -0
  35. package/dist/templates/env.d.ts +2 -1
  36. package/dist/templates/env.d.ts.map +1 -1
  37. package/dist/templates/env.js +19 -6
  38. package/dist/templates/gitignore.js +4 -1
  39. package/dist/templates/queries.d.ts +3 -0
  40. package/dist/templates/queries.d.ts.map +1 -1
  41. package/dist/templates/queries.js +66 -9
  42. package/dist/test-utils.d.ts +0 -72
  43. package/dist/test-utils.d.ts.map +1 -1
  44. package/dist/test-utils.js +12 -138
  45. package/dist/utils/clickhouse-client.js +11 -58
  46. package/dist/utils/clickhouse-sql.d.ts +6 -0
  47. package/dist/utils/clickhouse-sql.d.ts.map +1 -0
  48. package/dist/utils/clickhouse-sql.js +18 -0
  49. package/dist/utils/clickhouse-type-utils.d.ts +6 -0
  50. package/dist/utils/clickhouse-type-utils.d.ts.map +1 -0
  51. package/dist/utils/clickhouse-type-utils.js +59 -0
  52. package/dist/utils/dependency-installer.d.ts +5 -1
  53. package/dist/utils/dependency-installer.d.ts.map +1 -1
  54. package/dist/utils/dependency-installer.js +117 -134
  55. package/dist/utils/detect-database.js +84 -195
  56. package/dist/utils/find-files.d.ts +3 -1
  57. package/dist/utils/find-files.d.ts.map +1 -1
  58. package/dist/utils/find-files.js +82 -148
  59. package/dist/utils/load-api.d.ts.map +1 -1
  60. package/dist/utils/load-api.js +207 -396
  61. package/dist/utils/logger.js +38 -42
  62. package/dist/utils/prompts.d.ts +14 -5
  63. package/dist/utils/prompts.d.ts.map +1 -1
  64. package/dist/utils/prompts.js +170 -244
  65. package/dist/utils/runtime-guards.d.ts +4 -0
  66. package/dist/utils/runtime-guards.d.ts.map +1 -0
  67. package/dist/utils/runtime-guards.js +9 -0
  68. package/dist/utils/sha256.d.ts +2 -0
  69. package/dist/utils/sha256.d.ts.map +1 -0
  70. package/dist/utils/sha256.js +4 -0
  71. package/package.json +4 -4
  72. package/dist/utils/error-messages.d.ts +0 -6
  73. package/dist/utils/error-messages.d.ts.map +0 -1
  74. package/dist/utils/error-messages.js +0 -19
  75. package/dist/utils/load-hypequery-config.d.ts +0 -7
  76. package/dist/utils/load-hypequery-config.d.ts.map +0 -1
  77. package/dist/utils/load-hypequery-config.js +0 -89
@@ -1,408 +1,355 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- var __generator = (this && this.__generator) || function (thisArg, body) {
11
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
- function verb(n) { return function (v) { return step([n, v]); }; }
14
- function step(op) {
15
- if (f) throw new TypeError("Generator is already executing.");
16
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
- if (y = 0, t) op = [op[0] & 2, t.value];
19
- switch (op[0]) {
20
- case 0: case 1: t = op; break;
21
- case 4: _.label++; return { value: op[1], done: false };
22
- case 5: _.label++; y = op[1]; op = [0]; continue;
23
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
- default:
25
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
- if (t[2]) _.ops.pop();
30
- _.trys.pop(); continue;
31
- }
32
- op = body.call(thisArg, _);
33
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
- }
36
- };
37
1
  import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
38
2
  import path from 'node:path';
39
3
  import ora from 'ora';
40
4
  import { logger } from '../utils/logger.js';
41
- import { promptDatabaseType, promptClickHouseConnection, promptOutputDirectory, promptGenerateExample, promptTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
5
+ import { promptClickHouseConnection, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
42
6
  import { validateConnection, getTableCount, getTables, } from '../utils/detect-database.js';
43
7
  import { hasEnvFile, hasGitignore } from '../utils/find-files.js';
44
8
  import { generateEnvTemplate, appendToEnv } from '../templates/env.js';
45
9
  import { generateClientTemplate } from '../templates/client.js';
46
10
  import { generateQueriesTemplate } from '../templates/queries.js';
11
+ import { generateApiTemplate } from '../templates/api.js';
12
+ import { generateDatasetsPlaceholderTemplate } from '../templates/datasets.js';
47
13
  import { appendToGitignore } from '../templates/gitignore.js';
48
14
  import { getTypeGenerator } from '../generators/index.js';
49
- import { installServeDependencies } from '../utils/dependency-installer.js';
50
- function determineDatabase(options) {
51
- return __awaiter(this, void 0, void 0, function () {
52
- var dbType, _a;
53
- var _b;
54
- return __generator(this, function (_c) {
55
- switch (_c.label) {
56
- case 0:
57
- if (!((_b = options.database) !== null && _b !== void 0)) return [3 /*break*/, 1];
58
- _a = _b;
59
- return [3 /*break*/, 3];
60
- case 1: return [4 /*yield*/, promptDatabaseType()];
61
- case 2:
62
- _a = (_c.sent());
63
- _c.label = 3;
64
- case 3:
65
- dbType = _a;
66
- if (!dbType) {
67
- logger.info('Setup cancelled');
68
- process.exit(0);
69
- }
70
- if (dbType !== 'clickhouse') {
71
- logger.error("".concat(dbType, " is not yet supported. Only ClickHouse is available."));
72
- process.exit(1);
73
- }
74
- return [2 /*return*/, dbType];
75
- }
76
- });
77
- });
15
+ import { generateDatasets } from '../generators/dataset-generator.js';
16
+ import { installScaffoldDependencies } from '../utils/dependency-installer.js';
17
+ function normalizeInitStyle(style) {
18
+ return style === 'datasets' ? 'datasets' : 'queries';
78
19
  }
79
- function resolveConnectionConfig(options) {
80
- return __awaiter(this, void 0, void 0, function () {
81
- var required;
82
- var _a;
83
- return __generator(this, function (_b) {
84
- if (options.noInteractive) {
85
- required = function (key) {
86
- var value = process.env[key];
87
- if (!value) {
88
- throw new Error("Missing ".concat(key, ". Provide ClickHouse connection info via environment variables when using --no-interactive."));
89
- }
90
- return value;
91
- };
92
- return [2 /*return*/, {
93
- host: required('CLICKHOUSE_HOST'),
94
- database: required('CLICKHOUSE_DATABASE'),
95
- username: required('CLICKHOUSE_USERNAME'),
96
- password: (_a = process.env.CLICKHOUSE_PASSWORD) !== null && _a !== void 0 ? _a : '',
97
- }];
20
+ function normalizeAuthMode(auth) {
21
+ if (!auth || auth === 'none') {
22
+ return 'none';
23
+ }
24
+ if (auth === 'context') {
25
+ return 'context';
26
+ }
27
+ throw new Error(`Unsupported auth mode "${auth}". Use "none" or "context".`);
28
+ }
29
+ function parseTableList(value) {
30
+ const parsed = value
31
+ ?.split(',')
32
+ .map((table) => table.trim())
33
+ .filter(Boolean);
34
+ return parsed && parsed.length > 0 ? parsed : undefined;
35
+ }
36
+ async function resolveConnectionConfig(options) {
37
+ if (options.noInteractive) {
38
+ const required = (keys) => {
39
+ const values = Array.isArray(keys) ? keys : [keys];
40
+ const value = values.map((key) => process.env[key]).find(Boolean);
41
+ if (!value) {
42
+ throw new Error(`Missing ${values.join(' or ')}. Provide ClickHouse connection info via environment variables when using --no-interactive.`);
98
43
  }
99
- return [2 /*return*/, promptClickHouseConnection()];
100
- });
101
- });
44
+ return value;
45
+ };
46
+ return {
47
+ host: required(['CLICKHOUSE_URL', 'CLICKHOUSE_HOST']),
48
+ database: required('CLICKHOUSE_DATABASE'),
49
+ username: required(['CLICKHOUSE_USERNAME', 'CLICKHOUSE_USER']),
50
+ password: process.env.CLICKHOUSE_PASSWORD ?? '',
51
+ };
52
+ }
53
+ return promptClickHouseConnection();
54
+ }
55
+ async function testConnection(connectionConfig) {
56
+ const spinner = ora('Testing connection...').start();
57
+ process.env.CLICKHOUSE_URL = connectionConfig.host;
58
+ process.env.CLICKHOUSE_HOST = connectionConfig.host;
59
+ process.env.CLICKHOUSE_DATABASE = connectionConfig.database;
60
+ process.env.CLICKHOUSE_USERNAME = connectionConfig.username;
61
+ process.env.CLICKHOUSE_PASSWORD = connectionConfig.password;
62
+ const isValid = await validateConnection('clickhouse');
63
+ if (!isValid) {
64
+ spinner.fail('Connection failed');
65
+ logger.newline();
66
+ logger.error(`Could not connect to ClickHouse at ${connectionConfig.host}`);
67
+ logger.newline();
68
+ logger.info('Common issues:');
69
+ logger.indent('• Check your host URL includes http:// or https://');
70
+ logger.indent('• Verify username and password');
71
+ logger.indent('• Ensure database exists');
72
+ logger.indent('• Check firewall/network access');
73
+ logger.newline();
74
+ return { hasValidConnection: false, tableCount: 0 };
75
+ }
76
+ const tableCount = await getTableCount('clickhouse');
77
+ spinner.succeed(`Connected successfully (${tableCount} tables found)`);
78
+ logger.newline();
79
+ return { hasValidConnection: true, tableCount };
102
80
  }
103
- function testConnection(connectionConfig, dbType) {
104
- return __awaiter(this, void 0, void 0, function () {
105
- var spinner, isValid, tableCount;
106
- return __generator(this, function (_a) {
107
- switch (_a.label) {
108
- case 0:
109
- spinner = ora('Testing connection...').start();
110
- process.env.CLICKHOUSE_HOST = connectionConfig.host;
111
- process.env.CLICKHOUSE_DATABASE = connectionConfig.database;
112
- process.env.CLICKHOUSE_USERNAME = connectionConfig.username;
113
- process.env.CLICKHOUSE_PASSWORD = connectionConfig.password;
114
- return [4 /*yield*/, validateConnection(dbType)];
115
- case 1:
116
- isValid = _a.sent();
117
- if (!isValid) {
118
- spinner.fail('Connection failed');
119
- logger.newline();
120
- logger.error("Could not connect to ClickHouse at ".concat(connectionConfig.host));
121
- logger.newline();
122
- logger.info('Common issues:');
123
- logger.indent('• Check your host URL includes http:// or https://');
124
- logger.indent('• Verify username and password');
125
- logger.indent('• Ensure database exists');
126
- logger.indent(' Check firewall/network access');
127
- logger.newline();
128
- return [2 /*return*/, { hasValidConnection: false, tableCount: 0 }];
129
- }
130
- return [4 /*yield*/, getTableCount(dbType)];
131
- case 2:
132
- tableCount = _a.sent();
133
- spinner.succeed("Connected successfully (".concat(tableCount, " tables found)"));
134
- logger.newline();
135
- return [2 /*return*/, { hasValidConnection: true, tableCount: tableCount }];
81
+ export async function initCommand(options = {}) {
82
+ const noInteractive = options.noInteractive === true || options.interactive === false;
83
+ logger.newline();
84
+ logger.header('Welcome to hypequery!');
85
+ logger.info("Let's set up your analytics layer.");
86
+ logger.newline();
87
+ // Step 2: Get connection details
88
+ let connectionConfig = await resolveConnectionConfig(options);
89
+ let hasValidConnection = false;
90
+ // Handle user skipping connection details
91
+ if (!connectionConfig) {
92
+ logger.info('Skipping database connection for now.');
93
+ logger.newline();
94
+ }
95
+ else if (options.skipConnection) {
96
+ logger.info('Skipping database connection test (requested).');
97
+ logger.newline();
98
+ }
99
+ else {
100
+ const { hasValidConnection: valid } = await testConnection(connectionConfig);
101
+ hasValidConnection = valid;
102
+ if (!hasValidConnection) {
103
+ if (noInteractive) {
104
+ throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
105
+ }
106
+ const retry = await promptRetry('Try again?');
107
+ if (retry) {
108
+ return initCommand(options);
109
+ }
110
+ const continueWithout = await promptContinueWithoutDb();
111
+ if (!continueWithout) {
112
+ logger.info('Setup cancelled');
113
+ process.exit(0);
136
114
  }
137
- });
138
- });
115
+ logger.newline();
116
+ logger.info('Continuing without database connection.');
117
+ logger.info('You can configure the connection later in .env');
118
+ logger.newline();
119
+ connectionConfig = null;
120
+ }
121
+ }
122
+ // Step 4: Get output directory
123
+ let outputDir = options.path;
124
+ if (!outputDir && !noInteractive) {
125
+ outputDir = await promptOutputDirectory();
126
+ }
127
+ else if (!outputDir) {
128
+ outputDir = 'analytics';
129
+ }
130
+ const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
131
+ let style = normalizeInitStyle(options.style);
132
+ const auth = normalizeAuthMode(options.auth);
133
+ if (!options.style && !noInteractive) {
134
+ style = await promptInitStyle();
135
+ }
136
+ // Step 5: Check for existing files
137
+ const filesToCreate = [
138
+ path.join(resolvedOutputDir, 'client.ts'),
139
+ path.join(resolvedOutputDir, 'schema.ts'),
140
+ ...(style === 'datasets'
141
+ ? [
142
+ path.join(resolvedOutputDir, 'datasets.ts'),
143
+ path.join(resolvedOutputDir, 'api.ts'),
144
+ ]
145
+ : [
146
+ path.join(resolvedOutputDir, 'queries.ts'),
147
+ ]),
148
+ ];
149
+ const existingFiles = [];
150
+ for (const file of filesToCreate) {
151
+ try {
152
+ await access(file);
153
+ existingFiles.push(path.relative(process.cwd(), file));
154
+ }
155
+ catch {
156
+ // File doesn't exist, continue
157
+ }
158
+ }
159
+ if (existingFiles.length > 0 && !options.force) {
160
+ logger.warn('Files already exist');
161
+ logger.newline();
162
+ const shouldOverwrite = noInteractive ? false : await confirmOverwrite(existingFiles);
163
+ if (!shouldOverwrite) {
164
+ logger.info('Setup cancelled');
165
+ process.exit(0);
166
+ }
167
+ logger.newline();
168
+ }
169
+ // Step 6: Ask about example query (only if we have a valid connection)
170
+ let generateExample = !options.noExample && hasValidConnection;
171
+ let selectedTable = null;
172
+ let discoveredTables = null;
173
+ if (generateExample && !noInteractive && hasValidConnection) {
174
+ generateExample = await promptGenerateExample();
175
+ if (generateExample) {
176
+ discoveredTables = await getTables('clickhouse');
177
+ selectedTable = await promptTableSelection(discoveredTables);
178
+ generateExample = selectedTable !== null;
179
+ }
180
+ }
181
+ let datasetTables = parseTableList(options.tables);
182
+ const excludedDatasetTables = parseTableList(options.excludeTables);
183
+ if (style === 'datasets' &&
184
+ hasValidConnection &&
185
+ !options.allTables &&
186
+ !datasetTables &&
187
+ !noInteractive) {
188
+ discoveredTables ??= await getTables('clickhouse');
189
+ datasetTables = await promptDatasetTableSelection(discoveredTables, selectedTable ? [selectedTable] : []);
190
+ }
191
+ logger.newline();
192
+ // Step 7: Create directory
193
+ await mkdir(resolvedOutputDir, { recursive: true });
194
+ // Step 8: Save credentials to .env (if we have connection config)
195
+ if (connectionConfig) {
196
+ const envPath = path.join(process.cwd(), '.env');
197
+ const envExists = await hasEnvFile();
198
+ if (envExists) {
199
+ const existingEnv = await readFile(envPath, 'utf-8');
200
+ const newEnv = appendToEnv(existingEnv, generateEnvTemplate(connectionConfig));
201
+ await writeFile(envPath, newEnv);
202
+ logger.success('Updated .env');
203
+ }
204
+ else {
205
+ await writeFile(envPath, generateEnvTemplate(connectionConfig));
206
+ logger.success('Created .env');
207
+ }
208
+ }
209
+ else {
210
+ // Create placeholder .env
211
+ const envPath = path.join(process.cwd(), '.env');
212
+ const envExists = await hasEnvFile();
213
+ const placeholderConfig = {
214
+ url: 'YOUR_CLICKHOUSE_URL',
215
+ database: 'YOUR_DATABASE',
216
+ username: 'YOUR_USERNAME',
217
+ password: 'YOUR_PASSWORD',
218
+ };
219
+ if (!envExists) {
220
+ await writeFile(envPath, generateEnvTemplate(placeholderConfig));
221
+ logger.success('Created .env (configure your credentials)');
222
+ }
223
+ }
224
+ // Step 9: Generate types from schema (only if we have a valid connection)
225
+ const schemaPath = path.join(resolvedOutputDir, 'schema.ts');
226
+ if (hasValidConnection) {
227
+ const typeSpinner = ora('Generating TypeScript types...').start();
228
+ try {
229
+ const generator = getTypeGenerator('clickhouse');
230
+ await generator({ outputPath: schemaPath });
231
+ typeSpinner.succeed(`Generated TypeScript types (${path.relative(process.cwd(), schemaPath)})`);
232
+ }
233
+ catch (error) {
234
+ typeSpinner.fail('Failed to generate types');
235
+ logger.error(error instanceof Error ? error.message : String(error));
236
+ process.exit(1);
237
+ }
238
+ }
239
+ else {
240
+ // Create placeholder schema file
241
+ await writeFile(schemaPath, `// Generated by hypequery
242
+ // Run 'npx hypequery generate' after configuring your database connection
243
+
244
+ export interface IntrospectedSchema {
245
+ // Your table types will appear here after generation
139
246
  }
140
- export function initCommand() {
141
- return __awaiter(this, arguments, void 0, function (options) {
142
- var dbType, connectionConfig, hasValidConnection, tableCount, _a, valid, count, retry, continueWithout, outputDir, resolvedOutputDir, filesToCreate, existingFiles, _i, filesToCreate_1, file, _b, shouldOverwrite, generateExample, selectedTable, tables, envPath, envExists, existingEnv, newEnv, envPath, envExists, placeholderConfig, schemaPath, typeSpinner, generator, error_1, clientPath, queriesPath, gitignorePath, gitignoreExists, existingGitignore, newGitignore, exampleQueryKey;
143
- if (options === void 0) { options = {}; }
144
- return __generator(this, function (_c) {
145
- switch (_c.label) {
146
- case 0:
147
- logger.newline();
148
- logger.header('Welcome to hypequery!');
149
- logger.info("Let's set up your analytics layer.");
150
- logger.newline();
151
- return [4 /*yield*/, determineDatabase(options)];
152
- case 1:
153
- dbType = _c.sent();
154
- return [4 /*yield*/, resolveConnectionConfig(options)];
155
- case 2:
156
- connectionConfig = _c.sent();
157
- hasValidConnection = false;
158
- tableCount = 0;
159
- if (!!connectionConfig) return [3 /*break*/, 3];
160
- logger.info('Skipping database connection for now.');
161
- logger.newline();
162
- return [3 /*break*/, 8];
163
- case 3:
164
- if (!options.skipConnection) return [3 /*break*/, 4];
165
- logger.info('Skipping database connection test (requested).');
166
- logger.newline();
167
- return [3 /*break*/, 8];
168
- case 4: return [4 /*yield*/, testConnection(connectionConfig, dbType)];
169
- case 5:
170
- _a = _c.sent(), valid = _a.hasValidConnection, count = _a.tableCount;
171
- hasValidConnection = valid;
172
- tableCount = count;
173
- if (!!hasValidConnection) return [3 /*break*/, 8];
174
- return [4 /*yield*/, promptRetry('Try again?')];
175
- case 6:
176
- retry = _c.sent();
177
- if (retry) {
178
- return [2 /*return*/, initCommand(options)];
179
- }
180
- return [4 /*yield*/, promptContinueWithoutDb()];
181
- case 7:
182
- continueWithout = _c.sent();
183
- if (!continueWithout) {
184
- logger.info('Setup cancelled');
185
- process.exit(0);
186
- }
187
- logger.newline();
188
- logger.info('Continuing without database connection.');
189
- logger.info('You can configure the connection later in .env');
190
- logger.newline();
191
- connectionConfig = null;
192
- _c.label = 8;
193
- case 8:
194
- outputDir = options.path;
195
- if (!(!outputDir && !options.noInteractive)) return [3 /*break*/, 10];
196
- return [4 /*yield*/, promptOutputDirectory()];
197
- case 9:
198
- outputDir = _c.sent();
199
- return [3 /*break*/, 11];
200
- case 10:
201
- if (!outputDir) {
202
- outputDir = 'analytics';
203
- }
204
- _c.label = 11;
205
- case 11:
206
- resolvedOutputDir = path.resolve(process.cwd(), outputDir);
207
- filesToCreate = [
208
- path.join(resolvedOutputDir, 'client.ts'),
209
- path.join(resolvedOutputDir, 'schema.ts'),
210
- path.join(resolvedOutputDir, 'queries.ts'),
211
- ];
212
- existingFiles = [];
213
- _i = 0, filesToCreate_1 = filesToCreate;
214
- _c.label = 12;
215
- case 12:
216
- if (!(_i < filesToCreate_1.length)) return [3 /*break*/, 17];
217
- file = filesToCreate_1[_i];
218
- _c.label = 13;
219
- case 13:
220
- _c.trys.push([13, 15, , 16]);
221
- return [4 /*yield*/, access(file)];
222
- case 14:
223
- _c.sent();
224
- existingFiles.push(path.relative(process.cwd(), file));
225
- return [3 /*break*/, 16];
226
- case 15:
227
- _b = _c.sent();
228
- return [3 /*break*/, 16];
229
- case 16:
230
- _i++;
231
- return [3 /*break*/, 12];
232
- case 17:
233
- if (!(existingFiles.length > 0 && !options.force)) return [3 /*break*/, 19];
234
- logger.warn('Files already exist');
235
- logger.newline();
236
- return [4 /*yield*/, confirmOverwrite(existingFiles)];
237
- case 18:
238
- shouldOverwrite = _c.sent();
239
- if (!shouldOverwrite) {
240
- logger.info('Setup cancelled');
241
- process.exit(0);
242
- }
243
- logger.newline();
244
- _c.label = 19;
245
- case 19:
246
- generateExample = !options.noExample && hasValidConnection;
247
- selectedTable = null;
248
- if (!(generateExample && !options.noInteractive && hasValidConnection)) return [3 /*break*/, 23];
249
- return [4 /*yield*/, promptGenerateExample()];
250
- case 20:
251
- generateExample = _c.sent();
252
- if (!generateExample) return [3 /*break*/, 23];
253
- return [4 /*yield*/, getTables(dbType)];
254
- case 21:
255
- tables = _c.sent();
256
- return [4 /*yield*/, promptTableSelection(tables)];
257
- case 22:
258
- selectedTable = _c.sent();
259
- generateExample = selectedTable !== null;
260
- _c.label = 23;
261
- case 23:
262
- logger.newline();
263
- // Step 7: Create directory
264
- return [4 /*yield*/, mkdir(resolvedOutputDir, { recursive: true })];
265
- case 24:
266
- // Step 7: Create directory
267
- _c.sent();
268
- if (!connectionConfig) return [3 /*break*/, 31];
269
- envPath = path.join(process.cwd(), '.env');
270
- return [4 /*yield*/, hasEnvFile()];
271
- case 25:
272
- envExists = _c.sent();
273
- if (!envExists) return [3 /*break*/, 28];
274
- return [4 /*yield*/, readFile(envPath, 'utf-8')];
275
- case 26:
276
- existingEnv = _c.sent();
277
- newEnv = appendToEnv(existingEnv, generateEnvTemplate(connectionConfig));
278
- return [4 /*yield*/, writeFile(envPath, newEnv)];
279
- case 27:
280
- _c.sent();
281
- logger.success('Updated .env');
282
- return [3 /*break*/, 30];
283
- case 28: return [4 /*yield*/, writeFile(envPath, generateEnvTemplate(connectionConfig))];
284
- case 29:
285
- _c.sent();
286
- logger.success('Created .env');
287
- _c.label = 30;
288
- case 30: return [3 /*break*/, 34];
289
- case 31:
290
- envPath = path.join(process.cwd(), '.env');
291
- return [4 /*yield*/, hasEnvFile()];
292
- case 32:
293
- envExists = _c.sent();
294
- placeholderConfig = {
295
- host: 'YOUR_CLICKHOUSE_HOST',
296
- database: 'YOUR_DATABASE',
297
- username: 'YOUR_USERNAME',
298
- password: 'YOUR_PASSWORD',
299
- };
300
- if (!!envExists) return [3 /*break*/, 34];
301
- return [4 /*yield*/, writeFile(envPath, generateEnvTemplate(placeholderConfig))];
302
- case 33:
303
- _c.sent();
304
- logger.success('Created .env (configure your credentials)');
305
- _c.label = 34;
306
- case 34:
307
- schemaPath = path.join(resolvedOutputDir, 'schema.ts');
308
- if (!hasValidConnection) return [3 /*break*/, 39];
309
- typeSpinner = ora('Generating TypeScript types...').start();
310
- _c.label = 35;
311
- case 35:
312
- _c.trys.push([35, 37, , 38]);
313
- generator = getTypeGenerator('clickhouse');
314
- return [4 /*yield*/, generator({ outputPath: schemaPath })];
315
- case 36:
316
- _c.sent();
317
- typeSpinner.succeed("Generated TypeScript types (".concat(path.relative(process.cwd(), schemaPath), ")"));
318
- return [3 /*break*/, 38];
319
- case 37:
320
- error_1 = _c.sent();
321
- typeSpinner.fail('Failed to generate types');
322
- logger.error(error_1 instanceof Error ? error_1.message : String(error_1));
323
- process.exit(1);
324
- return [3 /*break*/, 38];
325
- case 38: return [3 /*break*/, 41];
326
- case 39:
327
- // Create placeholder schema file
328
- return [4 /*yield*/, writeFile(schemaPath, "// Generated by hypequery\n// Run 'npx hypequery generate' after configuring your database connection\n\nexport interface IntrospectedSchema {\n // Your table types will appear here after generation\n}\n")];
329
- case 40:
330
- // Create placeholder schema file
331
- _c.sent();
332
- logger.success("Created placeholder schema (".concat(path.relative(process.cwd(), schemaPath), ")"));
333
- _c.label = 41;
334
- case 41:
335
- clientPath = path.join(resolvedOutputDir, 'client.ts');
336
- return [4 /*yield*/, writeFile(clientPath, generateClientTemplate())];
337
- case 42:
338
- _c.sent();
339
- logger.success("Created ClickHouse client (".concat(path.relative(process.cwd(), clientPath), ")"));
340
- queriesPath = path.join(resolvedOutputDir, 'queries.ts');
341
- return [4 /*yield*/, writeFile(queriesPath, generateQueriesTemplate({
342
- hasExample: generateExample,
343
- tableName: selectedTable || undefined,
344
- }))];
345
- case 43:
346
- _c.sent();
347
- logger.success("Created queries file (".concat(path.relative(process.cwd(), queriesPath), ")"));
348
- if (generateExample && selectedTable) {
349
- logger.success("Created example query using '".concat(selectedTable, "' table"));
350
- }
351
- gitignorePath = path.join(process.cwd(), '.gitignore');
352
- return [4 /*yield*/, hasGitignore()];
353
- case 44:
354
- gitignoreExists = _c.sent();
355
- if (!gitignoreExists) return [3 /*break*/, 48];
356
- return [4 /*yield*/, readFile(gitignorePath, 'utf-8')];
357
- case 45:
358
- existingGitignore = _c.sent();
359
- newGitignore = appendToGitignore(existingGitignore);
360
- if (!(newGitignore !== existingGitignore)) return [3 /*break*/, 47];
361
- return [4 /*yield*/, writeFile(gitignorePath, newGitignore)];
362
- case 46:
363
- _c.sent();
364
- logger.success('Updated .gitignore');
365
- _c.label = 47;
366
- case 47: return [3 /*break*/, 50];
367
- case 48: return [4 /*yield*/, writeFile(gitignorePath, appendToGitignore(''))];
368
- case 49:
369
- _c.sent();
370
- logger.success('Created .gitignore');
371
- _c.label = 50;
372
- case 50:
373
- // Step 13: Ensure required hypequery packages are installed
374
- return [4 /*yield*/, installServeDependencies()];
375
- case 51:
376
- // Step 13: Ensure required hypequery packages are installed
377
- _c.sent();
378
- // Step 14: Success message
379
- logger.newline();
380
- logger.header('Setup complete!');
381
- if (hasValidConnection) {
382
- logger.info('Try your first query:');
383
- logger.newline();
384
- logger.indent("import { api } from './".concat(path.relative(process.cwd(), queriesPath).replace(/\.ts$/, '.js'), "'"));
385
- exampleQueryKey = generateExample && selectedTable
386
- ? "".concat(selectedTable.replace(/_([a-z])/g, function (_, l) { return l.toUpperCase(); }), "Query")
387
- : 'exampleMetric';
388
- logger.indent("const result = await api.execute('".concat(exampleQueryKey, "')"));
389
- logger.newline();
390
- logger.info('Next:');
391
- logger.indent('npx hypequery dev Start development server');
392
- logger.newline();
393
- }
394
- else {
395
- logger.info('Next steps:');
396
- logger.newline();
397
- logger.indent('1. Configure your database connection in .env');
398
- logger.indent('2. Run: npx hypequery generate (to generate types)');
399
- logger.indent('3. Run: npx hypequery dev (to start dev server)');
400
- logger.newline();
401
- }
402
- logger.info('Docs: https://hypequery.com/docs');
403
- logger.newline();
404
- return [2 /*return*/];
247
+ `);
248
+ logger.success(`Created placeholder schema (${path.relative(process.cwd(), schemaPath)})`);
249
+ }
250
+ // Step 10: Create client.ts
251
+ const clientPath = path.join(resolvedOutputDir, 'client.ts');
252
+ await writeFile(clientPath, generateClientTemplate());
253
+ logger.success(`Created ClickHouse client (${path.relative(process.cwd(), clientPath)})`);
254
+ // Step 11: Create API entrypoint
255
+ let apiPath;
256
+ let generatedAnyDatasets = false;
257
+ let generatedSelectedDataset = false;
258
+ if (style === 'datasets') {
259
+ const datasetsPath = path.join(resolvedOutputDir, 'datasets.ts');
260
+ const shouldGenerateDatasets = hasValidConnection && (options.allTables === true ||
261
+ (datasetTables !== undefined && datasetTables.length > 0));
262
+ if (shouldGenerateDatasets) {
263
+ await generateDatasets({
264
+ outputPath: datasetsPath,
265
+ includeTables: options.allTables ? undefined : datasetTables,
266
+ excludeTables: excludedDatasetTables,
267
+ });
268
+ generatedAnyDatasets = true;
269
+ generatedSelectedDataset = selectedTable !== null && (options.allTables === true ||
270
+ datasetTables?.includes(selectedTable) === true);
271
+ }
272
+ else {
273
+ await writeFile(datasetsPath, generateDatasetsPlaceholderTemplate());
274
+ if (hasValidConnection) {
275
+ logger.info('Skipped dataset generation. Run `hypequery generate:datasets --path ' + outputDir + ' --tables table1,table2` when ready.');
405
276
  }
406
- });
407
- });
277
+ }
278
+ logger.success(`Created datasets file (${path.relative(process.cwd(), datasetsPath)})`);
279
+ apiPath = path.join(resolvedOutputDir, 'api.ts');
280
+ await writeFile(apiPath, generateApiTemplate({ auth }));
281
+ logger.success(`Created API file (${path.relative(process.cwd(), apiPath)})`);
282
+ }
283
+ else {
284
+ apiPath = path.join(resolvedOutputDir, 'queries.ts');
285
+ await writeFile(apiPath, generateQueriesTemplate({
286
+ hasExample: generateExample,
287
+ tableName: selectedTable || undefined,
288
+ auth,
289
+ }));
290
+ logger.success(`Created queries file (${path.relative(process.cwd(), apiPath)})`);
291
+ }
292
+ if (generateExample && selectedTable && (style === 'queries' || generatedSelectedDataset)) {
293
+ logger.success(`Created example ${style === 'datasets' ? 'dataset' : 'query'} using '${selectedTable}' table`);
294
+ }
295
+ // Step 12: Update .gitignore
296
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
297
+ const gitignoreExists = await hasGitignore();
298
+ if (gitignoreExists) {
299
+ const existingGitignore = await readFile(gitignorePath, 'utf-8');
300
+ const newGitignore = appendToGitignore(existingGitignore);
301
+ if (newGitignore !== existingGitignore) {
302
+ await writeFile(gitignorePath, newGitignore);
303
+ logger.success('Updated .gitignore');
304
+ }
305
+ }
306
+ else {
307
+ await writeFile(gitignorePath, appendToGitignore(''));
308
+ logger.success('Created .gitignore');
309
+ }
310
+ // Step 13: Ensure required hypequery packages are installed
311
+ await installScaffoldDependencies(style);
312
+ // Step 14: Success message
313
+ logger.newline();
314
+ logger.header('Setup complete!');
315
+ if (hasValidConnection) {
316
+ if (style === 'datasets' && !generatedAnyDatasets) {
317
+ logger.info('Next:');
318
+ logger.indent(`hypequery generate:datasets --path ${outputDir} --tables table1,table2`);
319
+ logger.newline();
320
+ }
321
+ else if (style === 'datasets' && !generatedSelectedDataset) {
322
+ logger.info('Next:');
323
+ logger.indent('npx hypequery dev Start development server');
324
+ logger.newline();
325
+ }
326
+ else {
327
+ logger.info('Try your first query:');
328
+ logger.newline();
329
+ logger.indent(`import { api } from './${path.relative(process.cwd(), apiPath).replace(/\.ts$/, '.js')}'`);
330
+ const exampleQueryKey = generateExample && selectedTable
331
+ ? `${selectedTable.replace(/_([a-z])/g, (_, l) => l.toUpperCase())}Query`
332
+ : 'exampleMetric';
333
+ if (style === 'datasets' && selectedTable) {
334
+ logger.indent(`const result = await api.execute('dataset:${selectedTable}', { input: {} })`);
335
+ }
336
+ else {
337
+ logger.indent(`const result = await api.execute('${exampleQueryKey}')`);
338
+ }
339
+ logger.newline();
340
+ logger.info('Next:');
341
+ logger.indent('npx hypequery dev Start development server');
342
+ logger.newline();
343
+ }
344
+ }
345
+ else {
346
+ logger.info('Next steps:');
347
+ logger.newline();
348
+ logger.indent('1. Configure your database connection in .env');
349
+ logger.indent('2. Run: npx hypequery generate (to generate types)');
350
+ logger.indent('3. Run: npx hypequery dev (to start dev server)');
351
+ logger.newline();
352
+ }
353
+ logger.info('Docs: https://hypequery.com/docs');
354
+ logger.newline();
408
355
  }