@hypequery/cli 1.22.0 → 1.23.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 (156) hide show
  1. package/dist/bin/cli.js +77 -12
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +20 -15
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +25 -5
  6. package/dist/commands/generate-datasets.d.ts +0 -6
  7. package/dist/commands/generate-datasets.d.ts.map +1 -1
  8. package/dist/commands/generate-datasets.js +17 -1
  9. package/dist/commands/generate-manifest.d.ts.map +1 -1
  10. package/dist/commands/generate-manifest.js +4 -0
  11. package/dist/commands/generate.d.ts.map +1 -1
  12. package/dist/commands/generate.js +14 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +80 -55
  15. package/dist/commands/mcp.d.ts.map +1 -1
  16. package/dist/commands/mcp.js +47 -10
  17. package/dist/commands/telemetry.d.ts +3 -0
  18. package/dist/commands/telemetry.d.ts.map +1 -0
  19. package/dist/commands/telemetry.js +19 -0
  20. package/dist/generators/chdb.d.ts +3 -0
  21. package/dist/generators/chdb.d.ts.map +1 -1
  22. package/dist/generators/chdb.js +2 -0
  23. package/dist/generators/clickhouse.d.ts +3 -1
  24. package/dist/generators/clickhouse.d.ts.map +1 -1
  25. package/dist/generators/clickhouse.js +2 -0
  26. package/dist/typegen/generate-types.d.ts +3 -0
  27. package/dist/typegen/generate-types.d.ts.map +1 -1
  28. package/dist/typegen/generate-types.js +5 -1
  29. package/dist/typegen/type-parsing.d.ts +1 -1
  30. package/dist/typegen/type-parsing.d.ts.map +1 -1
  31. package/dist/typegen/type-parsing.js +11 -7
  32. package/dist/utils/chdb-gitignore-entry.d.ts +2 -0
  33. package/dist/utils/chdb-gitignore-entry.d.ts.map +1 -0
  34. package/dist/utils/chdb-gitignore-entry.js +17 -0
  35. package/dist/utils/cli-version.d.ts +2 -0
  36. package/dist/utils/cli-version.d.ts.map +1 -0
  37. package/dist/utils/cli-version.js +11 -0
  38. package/dist/utils/cloud-credential-store.d.ts.map +1 -1
  39. package/dist/utils/cloud-credential-store.js +2 -13
  40. package/dist/utils/command-exit.d.ts +13 -0
  41. package/dist/utils/command-exit.d.ts.map +1 -0
  42. package/dist/utils/command-exit.js +31 -0
  43. package/dist/utils/config-directory.d.ts +3 -0
  44. package/dist/utils/config-directory.d.ts.map +1 -0
  45. package/dist/utils/config-directory.js +14 -0
  46. package/dist/utils/detect-database-environment.d.ts +3 -0
  47. package/dist/utils/detect-database-environment.d.ts.map +1 -0
  48. package/dist/utils/detect-database-environment.js +8 -0
  49. package/dist/utils/detect-database.d.ts.map +1 -1
  50. package/dist/utils/detect-database.js +4 -8
  51. package/dist/utils/init-options.d.ts +11 -0
  52. package/dist/utils/init-options.d.ts.map +1 -0
  53. package/dist/utils/init-options.js +28 -0
  54. package/dist/utils/load-api.d.ts +2 -0
  55. package/dist/utils/load-api.d.ts.map +1 -1
  56. package/dist/utils/load-api.js +50 -51
  57. package/dist/utils/load-env-files.d.ts +10 -0
  58. package/dist/utils/load-env-files.d.ts.map +1 -0
  59. package/dist/utils/load-env-files.js +27 -0
  60. package/dist/utils/project-root.d.ts +14 -0
  61. package/dist/utils/project-root.d.ts.map +1 -0
  62. package/dist/utils/project-root.js +41 -0
  63. package/dist/utils/telemetry/buckets.d.ts +5 -0
  64. package/dist/utils/telemetry/buckets.d.ts.map +1 -0
  65. package/dist/utils/telemetry/buckets.js +23 -0
  66. package/dist/utils/telemetry/catalog-docs.d.ts +3 -0
  67. package/dist/utils/telemetry/catalog-docs.d.ts.map +1 -0
  68. package/dist/utils/telemetry/catalog-docs.js +52 -0
  69. package/dist/utils/telemetry/catalog.d.ts +1124 -0
  70. package/dist/utils/telemetry/catalog.d.ts.map +1 -0
  71. package/dist/utils/telemetry/catalog.js +154 -0
  72. package/dist/utils/telemetry/command-context.d.ts +17 -0
  73. package/dist/utils/telemetry/command-context.d.ts.map +1 -0
  74. package/dist/utils/telemetry/command-context.js +30 -0
  75. package/dist/utils/telemetry/config-schema.d.ts +12 -0
  76. package/dist/utils/telemetry/config-schema.d.ts.map +1 -0
  77. package/dist/utils/telemetry/config-schema.js +33 -0
  78. package/dist/utils/telemetry/config-store.d.ts +35 -0
  79. package/dist/utils/telemetry/config-store.d.ts.map +1 -0
  80. package/dist/utils/telemetry/config-store.js +147 -0
  81. package/dist/utils/telemetry/delivery.d.ts +13 -0
  82. package/dist/utils/telemetry/delivery.d.ts.map +1 -0
  83. package/dist/utils/telemetry/delivery.js +26 -0
  84. package/dist/utils/telemetry/detached-dispatch.d.ts +12 -0
  85. package/dist/utils/telemetry/detached-dispatch.d.ts.map +1 -0
  86. package/dist/utils/telemetry/detached-dispatch.js +33 -0
  87. package/dist/utils/telemetry/detached-sender.d.ts +2 -0
  88. package/dist/utils/telemetry/detached-sender.d.ts.map +1 -0
  89. package/dist/utils/telemetry/detached-sender.js +31 -0
  90. package/dist/utils/telemetry/dev-session.d.ts +15 -0
  91. package/dist/utils/telemetry/dev-session.d.ts.map +1 -0
  92. package/dist/utils/telemetry/dev-session.js +25 -0
  93. package/dist/utils/telemetry/domains.d.ts +69 -0
  94. package/dist/utils/telemetry/domains.d.ts.map +1 -0
  95. package/dist/utils/telemetry/domains.js +64 -0
  96. package/dist/utils/telemetry/endpoint.d.ts +3 -0
  97. package/dist/utils/telemetry/endpoint.d.ts.map +1 -0
  98. package/dist/utils/telemetry/endpoint.js +15 -0
  99. package/dist/utils/telemetry/environment.d.ts +27 -0
  100. package/dist/utils/telemetry/environment.d.ts.map +1 -0
  101. package/dist/utils/telemetry/environment.js +95 -0
  102. package/dist/utils/telemetry/error-code.d.ts +4 -0
  103. package/dist/utils/telemetry/error-code.d.ts.map +1 -0
  104. package/dist/utils/telemetry/error-code.js +57 -0
  105. package/dist/utils/telemetry/flags.d.ts +5 -0
  106. package/dist/utils/telemetry/flags.d.ts.map +1 -0
  107. package/dist/utils/telemetry/flags.js +41 -0
  108. package/dist/utils/telemetry/generation-metrics.d.ts +15 -0
  109. package/dist/utils/telemetry/generation-metrics.d.ts.map +1 -0
  110. package/dist/utils/telemetry/generation-metrics.js +35 -0
  111. package/dist/utils/telemetry/identity.d.ts +12 -0
  112. package/dist/utils/telemetry/identity.d.ts.map +1 -0
  113. package/dist/utils/telemetry/identity.js +42 -0
  114. package/dist/utils/telemetry/init-funnel.d.ts +15 -0
  115. package/dist/utils/telemetry/init-funnel.d.ts.map +1 -0
  116. package/dist/utils/telemetry/init-funnel.js +25 -0
  117. package/dist/utils/telemetry/invocation.d.ts +22 -0
  118. package/dist/utils/telemetry/invocation.d.ts.map +1 -0
  119. package/dist/utils/telemetry/invocation.js +46 -0
  120. package/dist/utils/telemetry/lifecycle.d.ts +32 -0
  121. package/dist/utils/telemetry/lifecycle.d.ts.map +1 -0
  122. package/dist/utils/telemetry/lifecycle.js +99 -0
  123. package/dist/utils/telemetry/mcp-session.d.ts +10 -0
  124. package/dist/utils/telemetry/mcp-session.d.ts.map +1 -0
  125. package/dist/utils/telemetry/mcp-session.js +36 -0
  126. package/dist/utils/telemetry/notice.d.ts +11 -0
  127. package/dist/utils/telemetry/notice.d.ts.map +1 -0
  128. package/dist/utils/telemetry/notice.js +24 -0
  129. package/dist/utils/telemetry/opt-out.d.ts +16 -0
  130. package/dist/utils/telemetry/opt-out.d.ts.map +1 -0
  131. package/dist/utils/telemetry/opt-out.js +32 -0
  132. package/dist/utils/telemetry/post-json.d.ts +13 -0
  133. package/dist/utils/telemetry/post-json.d.ts.map +1 -0
  134. package/dist/utils/telemetry/post-json.js +32 -0
  135. package/dist/utils/telemetry/project-context.d.ts +15 -0
  136. package/dist/utils/telemetry/project-context.d.ts.map +1 -0
  137. package/dist/utils/telemetry/project-context.js +86 -0
  138. package/dist/utils/telemetry/schema.d.ts +72 -0
  139. package/dist/utils/telemetry/schema.d.ts.map +1 -0
  140. package/dist/utils/telemetry/schema.js +17 -0
  141. package/dist/utils/telemetry/session-config.d.ts +6 -0
  142. package/dist/utils/telemetry/session-config.d.ts.map +1 -0
  143. package/dist/utils/telemetry/session-config.js +17 -0
  144. package/dist/utils/telemetry/settings-lock.d.ts +36 -0
  145. package/dist/utils/telemetry/settings-lock.d.ts.map +1 -0
  146. package/dist/utils/telemetry/settings-lock.js +102 -0
  147. package/dist/utils/telemetry/transport.d.ts +25 -0
  148. package/dist/utils/telemetry/transport.d.ts.map +1 -0
  149. package/dist/utils/telemetry/transport.js +76 -0
  150. package/dist/utils/telemetry/validation.d.ts +10 -0
  151. package/dist/utils/telemetry/validation.d.ts.map +1 -0
  152. package/dist/utils/telemetry/validation.js +77 -0
  153. package/dist/utils/telemetry/value-formats.d.ts +30 -0
  154. package/dist/utils/telemetry/value-formats.d.ts.map +1 -0
  155. package/dist/utils/telemetry/value-formats.js +13 -0
  156. package/package.json +12 -8
package/dist/bin/cli.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from '../cli.js';
3
- import { envFiles } from '../utils/env-files.js';
3
+ import { loadEnvFiles } from '../utils/load-env-files.js';
4
+ import { CommandExit, finishAndExit, installCommandExitFinalizer } from '../utils/command-exit.js';
5
+ import { CommandLifecycle } from '../utils/telemetry/lifecycle.js';
6
+ import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
7
+ import { cleanupLoadedApiArtifacts } from '../utils/load-api.js';
8
+ import { withCommandTelemetry } from '../utils/telemetry/command-context.js';
9
+ import { settleTelemetrySettings } from '../utils/telemetry/config-store.js';
4
10
  async function loadEnv() {
5
11
  try {
6
12
  const dotenvx = await import('@dotenvx/dotenvx');
@@ -14,19 +20,78 @@ async function loadEnv() {
14
20
  catch {
15
21
  // Optional dependency, ignore if missing
16
22
  }
17
- try {
18
- const { config } = await import('dotenv');
19
- for (const path of envFiles(process.env.NODE_ENV)) {
20
- // Missing files are a no-op.
21
- config({ path });
22
- }
23
- }
24
- catch {
25
- // dotenv is optional; continue if not available
26
- }
23
+ await loadEnvFiles(process.cwd());
27
24
  }
28
25
  async function main() {
29
26
  await loadEnv();
30
- program.parse(process.argv);
27
+ const args = process.argv.slice(2);
28
+ const requested = args.find(arg => !arg.startsWith('-'));
29
+ const lifecycle = new CommandLifecycle(args, {
30
+ telemetry: args.includes('--no-telemetry') ? false : undefined,
31
+ });
32
+ let selected;
33
+ let finishing;
34
+ const finish = (exit) => {
35
+ finishing ??= (async () => {
36
+ // finish() never rejects; the guard keeps cleanup independent of telemetry.
37
+ await lifecycle.finish(exit, selected?.name() === 'help' ? selected.args[0] : undefined).catch(() => undefined);
38
+ // Signals end in process.exit(), which skips finally blocks: let any
39
+ // settings write release its lock first (bounded).
40
+ await settleTelemetrySettings();
41
+ await cleanupLoadedApiArtifacts();
42
+ })();
43
+ return finishing;
44
+ };
45
+ installCommandExitFinalizer(finish);
46
+ const crash = (error) => lifecycle.crash(error);
47
+ process.on('uncaughtExceptionMonitor', crash);
48
+ program.exitOverride();
49
+ for (const command of program.commands)
50
+ command.exitOverride();
51
+ program.hook('preAction', async (_root, command) => {
52
+ selected = command;
53
+ await lifecycle.begin(command.name(), command.opts().database);
54
+ });
55
+ const observeSignal = (signal) => {
56
+ lifecycle.markInterrupted();
57
+ // dev and mcp own asynchronous server teardown once their handlers exist.
58
+ // During startup, or for short commands, the executable owns shutdown.
59
+ if ((lifecycle.command === 'dev' || lifecycle.command === 'mcp') && process.listenerCount(signal) > 1)
60
+ return;
61
+ void finishAndExit(lifecycle.command === 'dev' || lifecycle.command === 'mcp' ? 0 : signal === 'SIGINT' ? 130 : 143, 'interrupted');
62
+ };
63
+ process.on('SIGINT', () => observeSignal('SIGINT'));
64
+ process.on('SIGTERM', () => observeSignal('SIGTERM'));
65
+ try {
66
+ await withCommandTelemetry(lifecycle, () => program.parseAsync(process.argv));
67
+ if (lifecycle.command === 'dev')
68
+ return; // Its shutdown handler finalizes after teardown.
69
+ await finish(new CommandExit(Number(process.exitCode ?? 0), process.exitCode ? 'failure' : 'success', process.exitCode ? 'unknown' : undefined));
70
+ }
71
+ catch (error) {
72
+ const commander = error;
73
+ if (commander.code === 'commander.helpDisplayed' || commander.code === 'commander.help') {
74
+ const topic = selected?.name() === 'help' ? selected.args[0] : selected?.name()
75
+ ?? program.commands.find(command => requested === command.name())?.name();
76
+ await lifecycle.begin('help');
77
+ const code = commander.exitCode ?? 0;
78
+ await lifecycle.finish(new CommandExit(code, code ? 'failure' : 'success', code ? 'validation_failed' : undefined), topic)
79
+ .catch(() => undefined);
80
+ process.exitCode = code;
81
+ await settleTelemetrySettings();
82
+ await cleanupLoadedApiArtifacts();
83
+ return;
84
+ }
85
+ if (commander.code === 'commander.version') {
86
+ await lifecycle.begin('version');
87
+ await finish(new CommandExit(0, 'success'));
88
+ return;
89
+ }
90
+ if (!selected)
91
+ await lifecycle.begin(['unknown_command', 'unknown_option'].includes(telemetryErrorCode(error)) ? 'unknown' : requested ?? 'unknown');
92
+ const exit = error instanceof CommandExit ? error
93
+ : new CommandExit(commander.exitCode ?? 1, 'failure', telemetryErrorCode(error));
94
+ await finishAndExit(exit.exitCode, exit.outcome, exit.errorCode);
95
+ }
31
96
  }
32
97
  main();
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAkRD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA8RD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -1,5 +1,3 @@
1
- import { readFileSync } from 'node:fs';
2
- import { fileURLToPath } from 'node:url';
3
1
  import { Command } from 'commander';
4
2
  import { initCommand } from './commands/init.js';
5
3
  import { mcpCommand } from './commands/mcp.js';
@@ -13,16 +11,11 @@ import { deploymentStatusCommand, } from './commands/deployment-status.js';
13
11
  import { loginCommand, logoutCommand, } from './commands/login.js';
14
12
  import { diffCommand, pullCommand, } from './commands/live-source.js';
15
13
  import { isPromptCancelled } from './utils/prompts.js';
14
+ import { telemetryCommand } from './commands/telemetry.js';
15
+ import { getCliVersion } from './utils/cli-version.js';
16
+ import { CommandExit } from './utils/command-exit.js';
17
+ import { telemetryErrorCode } from './utils/telemetry/error-code.js';
16
18
  const program = new Command();
17
- function getCliVersion() {
18
- try {
19
- const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8'));
20
- return pkg.version ?? '0.0.0';
21
- }
22
- catch {
23
- return '0.0.0';
24
- }
25
- }
26
19
  export function normalizeInitOptions(options) {
27
20
  return {
28
21
  ...options,
@@ -32,7 +25,14 @@ export function normalizeInitOptions(options) {
32
25
  program
33
26
  .name('hypequery')
34
27
  .description('Type-safe analytics layer for ClickHouse')
35
- .version(getCliVersion());
28
+ .version(getCliVersion())
29
+ .option('--no-telemetry', 'Disable anonymous usage telemetry for this invocation');
30
+ program
31
+ .command('telemetry [action]')
32
+ .description('Show or change anonymous usage telemetry settings (status|enable|disable)')
33
+ .action(runCommand(async (action) => {
34
+ await telemetryCommand(action, { telemetry: program.opts().telemetry });
35
+ }));
36
36
  program
37
37
  .command('login')
38
38
  .description('Authorize this machine with Hypequery Cloud')
@@ -72,14 +72,16 @@ function runCommand(action) {
72
72
  await action(...args);
73
73
  }
74
74
  catch (error) {
75
+ if (error instanceof CommandExit)
76
+ throw error;
75
77
  // Ctrl+C at a prompt is a deliberate abort, not a failure: report it as
76
78
  // the interrupt it is instead of dumping an error and exiting 1.
77
79
  if (isPromptCancelled(error)) {
78
80
  console.log('\nCancelled.');
79
- process.exit(130);
81
+ throw new CommandExit(130, 'cancelled', 'prompt_cancelled');
80
82
  }
81
83
  console.error(error instanceof Error ? error.message : error);
82
- process.exit(1);
84
+ throw new CommandExit(1, 'failure', telemetryErrorCode(error));
83
85
  }
84
86
  };
85
87
  }
@@ -236,7 +238,7 @@ program
236
238
  }
237
239
  else {
238
240
  console.error(`Unknown command: ${command}`);
239
- process.exit(1);
241
+ throw new CommandExit(1, 'failure', 'unknown_command');
240
242
  }
241
243
  }
242
244
  else {
@@ -247,6 +249,9 @@ program
247
249
  program.on('--help', () => {
248
250
  console.log('');
249
251
  console.log('Examples:');
252
+ console.log(' hypequery telemetry status');
253
+ console.log(' hypequery telemetry disable');
254
+ console.log(' hypequery --no-telemetry init');
250
255
  console.log(' hypequery login');
251
256
  console.log(' hypequery logout');
252
257
  console.log(' hypequery init');
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBAiOvE"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAkCA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBA2OvE"}
@@ -1,3 +1,7 @@
1
+ import { exitWith, finishAndExit } from '../utils/command-exit.js';
2
+ import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
3
+ import { DevSession } from '../utils/telemetry/dev-session.js';
4
+ import { setCommandTelemetryError } from '../utils/telemetry/command-context.js';
1
5
  import { watch } from 'node:fs';
2
6
  import path from 'node:path';
3
7
  import ora from 'ora';
@@ -27,6 +31,7 @@ function displayQueriesFileNotFoundError(commandName) {
27
31
  logger.newline();
28
32
  }
29
33
  export async function devCommand(file, options = {}) {
34
+ const telemetry = new DevSession();
30
35
  // Step 1: Find queries file
31
36
  const queriesFile = file
32
37
  ? await findQueriesFile(file)
@@ -35,10 +40,11 @@ export async function devCommand(file, options = {}) {
35
40
  : await findQueriesFile();
36
41
  if (!queriesFile) {
37
42
  displayQueriesFileNotFoundError('dev');
38
- process.exit(1);
43
+ exitWith(1, 'failure', 'entrypoint_not_found');
39
44
  }
40
45
  logger.info(`Found: ${path.relative(process.cwd(), queriesFile)}`);
41
46
  logger.newline();
47
+ telemetry.start(queriesFile, file, options);
42
48
  let currentServer = null;
43
49
  let lifecycleOperation = Promise.resolve();
44
50
  let shutdownRequested = false;
@@ -61,9 +67,11 @@ export async function devCommand(file, options = {}) {
61
67
  // Loading spinners for slow operations
62
68
  const compileSpinner = ora('Compiling queries...').start();
63
69
  const dbSpinner = ora('Connecting to ClickHouse...').start();
70
+ let loading = true;
64
71
  try {
65
72
  // Load the API module
66
73
  const api = await loadApiModule(queriesFile);
74
+ loading = false;
67
75
  compileSpinner.succeed('Compiled queries');
68
76
  // Get table count for display
69
77
  let tableCount = 0;
@@ -125,8 +133,10 @@ export async function devCommand(file, options = {}) {
125
133
  logger.indent(`Visit: ${baseUrl}`);
126
134
  }
127
135
  }
136
+ return true;
128
137
  }
129
138
  catch (error) {
139
+ const code = loading ? telemetry.loadFailed(error) : telemetryErrorCode(error);
130
140
  // If server startup or later setup fails, tear down any resources
131
141
  // acquired by this attempt before retrying or exiting.
132
142
  try {
@@ -158,21 +168,31 @@ export async function devCommand(file, options = {}) {
158
168
  }
159
169
  logger.newline();
160
170
  if (!shouldWatch) {
161
- process.exit(1);
171
+ setCommandTelemetryError(code);
172
+ exitWith(1, 'failure', code);
162
173
  }
174
+ return false;
163
175
  }
164
176
  };
165
177
  const restartServer = () => queueLifecycleOperation(async () => {
166
178
  if (shutdownRequested)
167
179
  return;
180
+ telemetry.reload();
168
181
  if (currentServer) {
169
182
  logger.newline();
170
183
  logger.reload('File changed, restarting...');
171
184
  logger.newline();
172
- await stopCurrentRuntime();
185
+ try {
186
+ await stopCurrentRuntime();
187
+ }
188
+ catch (error) {
189
+ telemetry.reloadFailed();
190
+ throw error;
191
+ }
173
192
  }
174
193
  if (!shutdownRequested) {
175
- await startServer();
194
+ if (!await startServer())
195
+ telemetry.reloadFailed();
176
196
  }
177
197
  });
178
198
  const shutdown = () => {
@@ -183,7 +203,7 @@ export async function devCommand(file, options = {}) {
183
203
  logger.info('Shutting down dev server...');
184
204
  return queueLifecycleOperation(async () => {
185
205
  await stopCurrentRuntime();
186
- process.exit(0);
206
+ await finishAndExit(0, 'interrupted');
187
207
  });
188
208
  };
189
209
  // Start initial server
@@ -1,9 +1,3 @@
1
- /**
2
- * Generate Datasets Command
3
- *
4
- * Auto-generates dataset definitions from ClickHouse schema.
5
- * Reduces quickstart friction by scaffolding the semantic layer.
6
- */
7
1
  export interface GenerateDatasetsOptions {
8
2
  output?: string;
9
3
  path?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA2BD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBAqKlF"}
1
+ {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA2BD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA+KlF"}
@@ -1,3 +1,5 @@
1
+ import { exitWith } from '../utils/command-exit.js';
2
+ import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
1
3
  /**
2
4
  * Generate Datasets Command
3
5
  *
@@ -13,6 +15,9 @@ import { redactConnectionUrl } from '../utils/redact-connection-url.js';
13
15
  import { logDatasetGenerationWarnings } from '../utils/dataset-generation-warnings.js';
14
16
  import { formatGeneratedFileDiff } from '../utils/generated-file-diff.js';
15
17
  import { readGeneratedFile, writeGeneratedFileAtomically } from '../utils/generated-file.js';
18
+ import { updateCommandTelemetry, setCommandTelemetryError } from '../utils/telemetry/command-context.js';
19
+ import { countBucket } from '../utils/telemetry/buckets.js';
20
+ import { warningBuckets } from '../utils/telemetry/generation-metrics.js';
16
21
  /**
17
22
  * Turns the written path into something pasteable into an import statement:
18
23
  * strips the `.ts` extension and makes it explicitly relative.
@@ -35,6 +40,8 @@ function parseTableList(value) {
35
40
  return parsed && parsed.length > 0 ? parsed : undefined;
36
41
  }
37
42
  export async function generateDatasetsCommand(options = {}) {
43
+ updateCommandTelemetry('generate:datasets', { mode: options.check ? 'check' : options.diff ? 'diff' : 'write', force: !!options.force,
44
+ tenant_column_used: !!options.tenantColumn, tables_filter_used: !!options.tables, exclude_filter_used: !!options.excludeTables });
38
45
  // Determine output path
39
46
  let outputPath;
40
47
  if (options.output) {
@@ -52,6 +59,7 @@ export async function generateDatasetsCommand(options = {}) {
52
59
  const excludedTables = parseTableList(options.excludeTables);
53
60
  if (options.force && (options.check || options.diff)) {
54
61
  logger.error('--force cannot be combined with --check or --diff.');
62
+ setCommandTelemetryError('validation_failed');
55
63
  process.exitCode = 1;
56
64
  return;
57
65
  }
@@ -80,9 +88,16 @@ export async function generateDatasetsCommand(options = {}) {
80
88
  const generatedCount = generatedTables.length || tableCount;
81
89
  datasetSpinner.succeed(`Generated dataset definitions for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
82
90
  logDatasetGenerationWarnings(generated?.warnings);
91
+ updateCommandTelemetry('generate:datasets', { datasets_generated_bucket: countBucket(generated.tables.length), warning_counts: warningBuckets(generated.warnings) });
83
92
  const relativeOutput = path.relative(process.cwd(), outputPath);
84
93
  const currentContents = await readGeneratedFile(outputPath);
85
94
  const contentsMatch = currentContents === generated.contents;
95
+ if (options.check) {
96
+ const result = contentsMatch ? 'up_to_date' : currentContents === undefined ? 'missing' : 'out_of_date';
97
+ updateCommandTelemetry('generate:datasets', { check_result: result });
98
+ if (!contentsMatch)
99
+ setCommandTelemetryError(currentContents === undefined ? 'output_missing' : 'output_out_of_date');
100
+ }
86
101
  if (contentsMatch) {
87
102
  logger.success(options.check || options.diff
88
103
  ? `Dataset definitions are up to date: ${relativeOutput}`
@@ -92,6 +107,7 @@ export async function generateDatasetsCommand(options = {}) {
92
107
  }
93
108
  }
94
109
  else if (options.diff) {
110
+ setCommandTelemetryError('output_out_of_date');
95
111
  logger.raw(formatGeneratedFileDiff(currentContents ?? '', generated.contents, relativeOutput));
96
112
  logger.error(`Generated dataset definitions differ: ${relativeOutput}`);
97
113
  process.exitCode = 1;
@@ -173,6 +189,6 @@ export async function generateDatasetsCommand(options = {}) {
173
189
  logger.error(String(error));
174
190
  }
175
191
  logger.newline();
176
- process.exit(1);
192
+ exitWith(1, 'failure', telemetryErrorCode(error));
177
193
  }
178
194
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-manifest.d.ts","sourceRoot":"","sources":["../../src/commands/generate-manifest.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,uBAA4B,iBAyBtC"}
1
+ {"version":3,"file":"generate-manifest.d.ts","sourceRoot":"","sources":["../../src/commands/generate-manifest.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,uBAA4B,iBA2BtC"}
@@ -2,7 +2,10 @@ import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { loadApiModule } from '../utils/load-api.js';
4
4
  import { logger } from '../utils/logger.js';
5
+ import { updateCommandTelemetry } from '../utils/telemetry/command-context.js';
6
+ import { manifestMetrics } from '../utils/telemetry/generation-metrics.js';
5
7
  export async function generateManifestCommand(apiPath, options = {}) {
8
+ updateCommandTelemetry('generate:manifest', { custom_output: options.output !== undefined });
6
9
  if (!apiPath) {
7
10
  throw new Error('Missing API module path.\n\n' +
8
11
  'Usage: hypequery generate:manifest analytics/api.ts --output analytics/hypequery-manifest.json');
@@ -15,6 +18,7 @@ export async function generateManifestCommand(apiPath, options = {}) {
15
18
  `Export the value returned by createAPI() or initServe(...).serve(...).`);
16
19
  }
17
20
  const manifest = api.manifest();
21
+ updateCommandTelemetry('generate:manifest', manifestMetrics(api, manifest));
18
22
  await mkdir(path.dirname(outputPath), { recursive: true });
19
23
  await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
20
24
  logger.success(`Manifest written to ${outputPath}`);
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAK/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBA6JlE"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAQ/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBAqKlE"}
@@ -1,3 +1,5 @@
1
+ import { exitWith } from '../utils/command-exit.js';
2
+ import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
1
3
  import path from 'node:path';
2
4
  import ora from 'ora';
3
5
  import { logger } from '../utils/logger.js';
@@ -6,7 +8,13 @@ import { detectDatabase, getTableCount } from '../utils/detect-database.js';
6
8
  import { ensureChdbInstalled } from '../utils/chdb-client.js';
7
9
  import { getTypeGenerator } from '../generators/index.js';
8
10
  import { redactConnectionUrl } from '../utils/redact-connection-url.js';
11
+ import { updateCommandTelemetry } from '../utils/telemetry/command-context.js';
12
+ import { countBucket } from '../utils/telemetry/buckets.js';
13
+ import { TypeGenerationMetrics } from '../utils/telemetry/generation-metrics.js';
9
14
  export async function generateCommand(options = {}) {
15
+ const command = options.commandName === 'hypequery generate:types' ? 'generate:types' : 'generate';
16
+ const metrics = new TypeGenerationMetrics();
17
+ updateCommandTelemetry(command, { custom_output: !!(options.output || options.path), tables_filter_used: !!options.tables, chdb_path_given: !!options.chdbPath });
10
18
  // Determine output path
11
19
  let outputPath;
12
20
  if (options.output) {
@@ -35,6 +43,7 @@ export async function generateCommand(options = {}) {
35
43
  const requestedDbType = options.database;
36
44
  const dbType = requestedDbType ?? (await detectDatabase());
37
45
  const chdbPath = dbType === 'chdb' ? options.chdbPath : undefined;
46
+ updateCommandTelemetry(command, { database: dbType === 'clickhouse' || dbType === 'chdb' || dbType === 'bigquery' ? dbType : 'unknown', chdb_path_given: !!chdbPath });
38
47
  if (dbType === 'chdb' && !requestedDbType) {
39
48
  throw new Error('chDB generation must be explicit. Re-run with --database chdb and add --chdb-path <dir> for persistent storage.');
40
49
  }
@@ -68,10 +77,13 @@ export async function generateCommand(options = {}) {
68
77
  outputPath,
69
78
  includeTables: parsedTables,
70
79
  chdbPath,
80
+ onColumn: () => metrics.column(),
81
+ onUnsupportedType: type => metrics.fallback(type),
71
82
  });
72
83
  // The generator returns what it actually wrote after applying filters.
73
84
  // Requested names may contain duplicates or tables that do not exist.
74
85
  const generatedCount = generatedTables.length;
86
+ updateCommandTelemetry(command, { ...metrics.snapshot(), table_count_bucket: countBucket(generatedCount) });
75
87
  typeSpinner.succeed(`Generated types for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
76
88
  logger.success(`Updated ${path.relative(process.cwd(), outputPath)}`);
77
89
  logger.newline();
@@ -79,6 +91,7 @@ export async function generateCommand(options = {}) {
79
91
  logger.newline();
80
92
  }
81
93
  catch (error) {
94
+ updateCommandTelemetry(command, metrics.snapshot());
82
95
  activeSpinner.fail(failureMessage);
83
96
  logger.newline();
84
97
  if (error instanceof Error) {
@@ -140,6 +153,6 @@ export async function generateCommand(options = {}) {
140
153
  logger.error(String(error));
141
154
  }
142
155
  logger.newline();
143
- process.exit(1);
156
+ exitWith(1, 'failure', telemetryErrorCode(error));
144
157
  }
145
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAcL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAYzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAqKD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAsb1D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAUA,OAAO,EAcL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAc7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAYzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AA6GD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAY1D"}