@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ import { COMMANDS, COMMAND_FLAGS, COUNT_BUCKETS, DATABASES, DURATION_BUCKETS, ERROR_CODES, GLOBAL_FLAGS, HYPEQUERY_PACKAGES, OUTCOMES, TYPE_FAMILIES, WARNING_CODES } from './domains.js';
2
+ import { boolean, enumeration, enumList, fieldsFor, formatted, optional, record } from './schema.js';
3
+ const count = (description) => enumeration(description, COUNT_BUCKETS);
4
+ export const EVENT_SCHEMA_VERSION = 1;
5
+ const cache = enumeration('Which cache providers are adopted?', ['none', 'memory', 'redis', 'unknown']);
6
+ const entry = enumeration('Which entrypoint convention is used? Never the path.', ['hypequery.ts', 'api.ts', 'queries.ts', 'explicit_file', 'unknown']);
7
+ const output = boolean('Was a custom output location supplied? Never the path.');
8
+ const stages = ['started', 'database_selected', 'connection_tested', 'style_selected', 'files_written', 'dependencies_installed', 'completed'];
9
+ export const COMMON_PROPERTIES = {
10
+ schema_version: enumeration('Which event contract does this payload use?', [EVENT_SCHEMA_VERSION]),
11
+ cli_version: formatted('Which CLI releases are in use?', 'version'),
12
+ node_version: formatted('Which Node major.minor releases need support?', 'node_version'),
13
+ os: enumeration('Which operating systems need support?', ['darwin', 'linux', 'win32', 'aix', 'freebsd', 'openbsd', 'sunos', 'android', 'unknown']),
14
+ arch: enumeration('Which CPU architectures need support?', ['arm', 'arm64', 'ia32', 'x64', 'loong64', 'mips', 'mipsel', 'ppc', 'ppc64', 'riscv64', 's390', 's390x', 'unknown']),
15
+ is_ci: boolean('How much usage happens in CI?'),
16
+ ci_name: enumeration('Which CI providers need support?', ['none', 'github_actions', 'gitlab', 'circleci', 'buildkite', 'jenkins', 'vercel', 'netlify', 'azure', 'bitbucket', 'teamcity', 'travis', 'codebuild', 'unknown']),
17
+ is_tty: boolean('How much usage is interactive?'),
18
+ is_docker: boolean('How much usage runs in containers?'),
19
+ is_wsl: boolean('How much usage runs in WSL?'),
20
+ package_manager: enumeration('Which package managers need support?', ['npm', 'pnpm', 'yarn', 'bun', 'unknown']),
21
+ package_manager_major: optional(formatted('Which package-manager major releases need support?', 'major_version')),
22
+ invoked_via: enumeration('How is the CLI invoked?', ['npx', 'pnpm_dlx', 'bunx', 'global', 'local_bin', 'unknown']),
23
+ install_id: formatted('Count active installations without hardware or account identifiers.', 'uuid'),
24
+ project_id: optional(formatted('Count active projects across installations; omit when no signal exists.', 'hash')),
25
+ session_id: formatted('Group events from the same invocation.', 'uuid'),
26
+ is_first_run: boolean('How many installations are new?'),
27
+ hypequery_packages: optional(record('Which toolkit packages and exact versions are adopted? No project dependencies or names.', fieldsFor(HYPEQUERY_PACKAGES, optional(formatted('Installed exact release version, or unknown.', 'version'))))),
28
+ database: enumeration('Which database backends merit investment?', DATABASES),
29
+ };
30
+ const generation = {
31
+ chdb_path_given: optional(boolean('Is persistent embedded ClickHouse adopted?')),
32
+ tables_filter_used: optional(boolean('Are targeted type-generation workflows used?')),
33
+ table_count_bucket: optional(count('How large are introspected schemas?')),
34
+ column_count_bucket: optional(count('How many columns need type generation?')),
35
+ unsupported_type_count_bucket: optional(count('How often does type mapping need improvement?')),
36
+ unsupported_type_families: optional(enumList('Which built-in type families need mapping? Never full type expressions.', TYPE_FAMILIES)),
37
+ custom_output: optional(output),
38
+ };
39
+ const INSTRUMENTED = ['init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets', 'generate:manifest', 'help'];
40
+ /** Command-specific completion fields; absence means that stage was not reached. */
41
+ export const COMMAND_PROPERTIES = {
42
+ init: {
43
+ stage_reached: optional(enumeration('Where does onboarding stop?', stages)),
44
+ failed_stage: optional(enumeration('Which onboarding stage needs reliability work?', stages)),
45
+ style: optional(enumeration('Which scaffold style is adopted?', ['queries', 'datasets'])),
46
+ auth: optional(enumeration('Is context authentication adopted?', ['none', 'context'])),
47
+ interactive: optional(boolean('How much onboarding is interactive?')),
48
+ force: optional(boolean('How often are scaffold files overwritten?')),
49
+ example: optional(boolean('Are example queries requested?')),
50
+ skip_connection: optional(boolean('Are offline scaffolds used?')),
51
+ connection_result: optional(enumeration('Where does connection setup fail?', ['ok', 'failed', 'skipped'])),
52
+ chdb_failure_reason: optional(enumeration('Does embedded onboarding fail at installation or execution?', ['not_installed', 'engine_error'])),
53
+ table_count_bucket: optional(count('How large are onboarding schemas?')),
54
+ datasets_generated_bucket: optional(count('How many datasets are scaffolded?')),
55
+ table_selection: optional(enumeration('Which table-selection workflows are used?', ['all', 'list', 'exclude', 'prompt'])),
56
+ env_file: optional(enumeration('Does onboarding create or update environment config?', ['created', 'updated', 'unchanged', 'skipped'])),
57
+ gitignore: optional(enumeration('Does onboarding create or update ignore config?', ['created', 'updated', 'unchanged', 'skipped'])),
58
+ package_json_present: optional(boolean('Does onboarding start in an existing project?')),
59
+ analytics_directory_present: optional(boolean('Does onboarding reuse an analytics directory?')),
60
+ },
61
+ dev: {},
62
+ mcp: {
63
+ mode: optional(enumeration('Is MCP used for validation or serving?', ['self_test', 'serve'])),
64
+ hosted: optional(boolean('Is MCP self-test targeting a hosted endpoint?')),
65
+ dataset_count_bucket: optional(count('How many datasets are exposed to agents?')),
66
+ tenant_used: optional(boolean('Is a trusted tenant supplied? Never its value.')),
67
+ tenant_dataset_count_bucket: optional(count('How many datasets require tenant scope?')),
68
+ },
69
+ generate: generation,
70
+ 'generate:types': generation,
71
+ 'generate:datasets': {
72
+ mode: optional(enumeration('Are datasets generated locally or checked in CI?', ['write', 'check', 'diff'])),
73
+ check_result: optional(enumeration('Does CI find stale dataset definitions?', ['up_to_date', 'out_of_date', 'missing'])),
74
+ force: optional(boolean('Are existing definitions replaced?')),
75
+ tenant_column_used: optional(boolean('Is tenant isolation explicitly scaffolded? Never the column.')),
76
+ tables_filter_used: optional(boolean('Is include filtering adopted?')),
77
+ exclude_filter_used: optional(boolean('Is exclude filtering adopted?')),
78
+ datasets_generated_bucket: optional(count('How many dataset definitions are generated?')),
79
+ warning_counts: optional(record('Which tenant-scaffolding warnings need improvement?', fieldsFor(WARNING_CODES, optional(count('Bucketed warning occurrences.'))))),
80
+ },
81
+ 'generate:manifest': {
82
+ query_count_bucket: optional(count('How much query-based React integration is adopted?')),
83
+ endpoint_count_bucket: optional(count('How much endpoint-based React integration is adopted?')),
84
+ custom_output: optional(output),
85
+ },
86
+ // Other commands report only the common completion fields until they are
87
+ // instrumented; each instrumentation change adds its command's fields here.
88
+ ...Object.fromEntries(COMMANDS.filter(command => !INSTRUMENTED.includes(command)).map(command => [command, {}])),
89
+ help: { help_topic: optional(enumeration('Which command help needs improvement? Unknown input becomes unknown.', ['root', ...COMMANDS])) },
90
+ };
91
+ export const SESSION_START_PROPERTIES = {
92
+ dev: {
93
+ entry_type: entry,
94
+ watch: boolean('Is file watching used?'),
95
+ cache_provider: cache,
96
+ cors: boolean('Is cross-origin development adopted?'),
97
+ open: boolean('Is the browser opened automatically?'),
98
+ custom_port: boolean('Is a non-default port selected? Never its number or hostname.'),
99
+ quiet: boolean('Is quiet startup adopted?'),
100
+ },
101
+ mcp: {
102
+ entry_type: entry,
103
+ mode: enumeration('Is MCP serving or validating?', ['self_test', 'serve']),
104
+ dataset_count_bucket: count('How many datasets are exposed to agents?'),
105
+ tenant_used: boolean('Is tenant scope supplied? Never its value.'),
106
+ tenant_dataset_count_bucket: count('How many exposed datasets require tenant scope?'),
107
+ },
108
+ };
109
+ export const SESSION_END_PROPERTIES = {
110
+ dev: {
111
+ reload_count_bucket: count('How heavily is hot reload used?'),
112
+ reload_error_count_bucket: count('How often does hot reload fail?'),
113
+ load_failures: record('Which load failures need reliability work?', fieldsFor(ERROR_CODES, optional(count('Bucketed failures by stable code.')))),
114
+ },
115
+ mcp: {
116
+ tool_call_counts: record('Which MCP tool kinds are adopted? No names or arguments.', fieldsFor(['list', 'describe', 'query'], count('Bucketed calls by built-in tool kind.'))),
117
+ error_count_bucket: count('How often do MCP sessions encounter errors?'),
118
+ },
119
+ };
120
+ export const EVENT_CATALOG = {
121
+ cli_command_completed: {
122
+ purpose: 'Measure command adoption, onboarding drop-off and release reliability. Exactly once per invocation; never emit for telemetry disable.',
123
+ properties: {
124
+ command: enumeration('Which known command was requested? Raw unknown input is never retained.', COMMANDS),
125
+ flags_used: enumList('Which documented flags are adopted? Canonical names only, never values.', [...new Set([...Object.values(COMMAND_FLAGS).flat(), ...GLOBAL_FLAGS])]),
126
+ outcome: enumeration('Which commands fail, succeed or are aborted?', OUTCOMES),
127
+ duration_bucket: enumeration('Which commands need performance work?', DURATION_BUCKETS),
128
+ error_code: optional(enumeration('Which known failures need reliability work?', ERROR_CODES)),
129
+ },
130
+ variants: COMMAND_PROPERTIES,
131
+ },
132
+ cli_session_started: {
133
+ purpose: 'Measure development-server and MCP session configuration; never emit on every reload or request.',
134
+ properties: { command: enumeration('Which long-running command starts?', ['dev', 'mcp']) },
135
+ variants: SESSION_START_PROPERTIES,
136
+ },
137
+ cli_session_ended: {
138
+ purpose: 'Measure session duration and aggregated usage on shutdown, without individual requests or tool arguments.',
139
+ properties: {
140
+ command: enumeration('Which long-running command ends?', ['dev', 'mcp']),
141
+ outcome: enumeration('How did the session end?', OUTCOMES),
142
+ duration_bucket: enumeration('How long are development and agent sessions?', DURATION_BUCKETS),
143
+ },
144
+ variants: SESSION_END_PROPERTIES,
145
+ },
146
+ cli_crash: {
147
+ purpose: 'Measure uncaught failures by stable class and code, with no messages or stacks.',
148
+ properties: {
149
+ command: enumeration('Which command crashed?', COMMANDS),
150
+ exception_class: enumeration('Which built-in exception classes need investigation?', ['Error', 'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError', 'URIError', 'EvalError', 'AggregateError', 'unknown']),
151
+ error_code: enumeration('Which known failure caused the crash?', ERROR_CODES),
152
+ },
153
+ },
154
+ };
@@ -0,0 +1,17 @@
1
+ import type { COMMAND_PROPERTIES, SESSION_START_PROPERTIES, SESSION_END_PROPERTIES } from './catalog.js';
2
+ import type { Properties } from './schema.js';
3
+ import type { CommandLifecycle } from './lifecycle.js';
4
+ import type { DATABASES, ERROR_CODES } from './domains.js';
5
+ export type CommandMetrics<C extends keyof typeof COMMAND_PROPERTIES> = Properties<typeof COMMAND_PROPERTIES[C]> & {
6
+ database?: typeof DATABASES[number];
7
+ };
8
+ export type SessionCommand = keyof typeof SESSION_START_PROPERTIES;
9
+ export type SessionStart<C extends SessionCommand> = Properties<typeof SESSION_START_PROPERTIES[C]>;
10
+ export type SessionEnd<C extends SessionCommand> = Properties<typeof SESSION_END_PROPERTIES[C]>;
11
+ /** Command callbacks and their asynchronous work share only this invocation. */
12
+ export declare function withCommandTelemetry<T>(lifecycle: CommandLifecycle, action: () => T): T;
13
+ export declare function updateCommandTelemetry<C extends keyof typeof COMMAND_PROPERTIES>(command: C, metrics: CommandMetrics<C>): void;
14
+ export declare function cancelCommandTelemetry(): void;
15
+ export declare function startCommandSession<C extends SessionCommand>(command: C, start: SessionStart<C>, end: () => SessionEnd<C>): void;
16
+ export declare function setCommandTelemetryError(code: typeof ERROR_CODES[number]): void;
17
+ //# sourceMappingURL=command-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-context.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/command-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACzG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,OAAO,kBAAkB,IAAI,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC;AAC3J,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,wBAAwB,CAAC;AACnE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,cAAc,IAAI,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;AACpG,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,cAAc,IAAI,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;AAGhG,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvF;AAED,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,OAAO,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAE9H;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAEhI;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAE/E"}
@@ -0,0 +1,30 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const context = new AsyncLocalStorage();
3
+ /** Command callbacks and their asynchronous work share only this invocation. */
4
+ export function withCommandTelemetry(lifecycle, action) {
5
+ return context.run(lifecycle, action);
6
+ }
7
+ export function updateCommandTelemetry(command, metrics) {
8
+ try {
9
+ context.getStore()?.update(command, metrics);
10
+ }
11
+ catch { /* Metrics never affect commands. */ }
12
+ }
13
+ export function cancelCommandTelemetry() {
14
+ try {
15
+ context.getStore()?.cancel();
16
+ }
17
+ catch { /* Preserve cancellation behavior. */ }
18
+ }
19
+ export function startCommandSession(command, start, end) {
20
+ try {
21
+ context.getStore()?.startSession(command, start, end);
22
+ }
23
+ catch { /* Sessions never affect commands. */ }
24
+ }
25
+ export function setCommandTelemetryError(code) {
26
+ try {
27
+ context.getStore()?.setError(code);
28
+ }
29
+ catch { /* Preserve command errors. */ }
30
+ }
@@ -0,0 +1,12 @@
1
+ export declare const TELEMETRY_SCHEMA_VERSION = 1;
2
+ export declare const TELEMETRY_DOCS_URL = "https://hypequery.com/docs/telemetry";
3
+ export interface TelemetryConfig {
4
+ readonly schema_version: typeof TELEMETRY_SCHEMA_VERSION;
5
+ readonly enabled: boolean;
6
+ readonly install_id: string;
7
+ readonly notice_shown_at?: string;
8
+ readonly disabled_cli_versions?: readonly string[];
9
+ }
10
+ /** Reject malformed or future settings rather than silently enabling telemetry. */
11
+ export declare function parseTelemetryConfig(input: string): TelemetryConfig | null;
12
+ //# sourceMappingURL=config-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/config-schema.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,kBAAkB,yCAAyC,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,cAAc,EAAE,OAAO,wBAAwB,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAyB1E"}
@@ -0,0 +1,33 @@
1
+ import { matchesTelemetryFormat } from './value-formats.js';
2
+ export const TELEMETRY_SCHEMA_VERSION = 1;
3
+ export const TELEMETRY_DOCS_URL = 'https://hypequery.com/docs/telemetry';
4
+ /** Reject malformed or future settings rather than silently enabling telemetry. */
5
+ export function parseTelemetryConfig(input) {
6
+ try {
7
+ const value = JSON.parse(input);
8
+ if (!value || typeof value !== 'object' || Array.isArray(value))
9
+ return null;
10
+ const config = value;
11
+ if (config.schema_version !== TELEMETRY_SCHEMA_VERSION
12
+ || typeof config.enabled !== 'boolean'
13
+ || typeof config.install_id !== 'string'
14
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(config.install_id)
15
+ || (config.notice_shown_at !== undefined
16
+ && (typeof config.notice_shown_at !== 'string'
17
+ || !Number.isFinite(Date.parse(config.notice_shown_at))))
18
+ || (config.disabled_cli_versions !== undefined
19
+ && (!Array.isArray(config.disabled_cli_versions) || config.disabled_cli_versions.length > 20
20
+ || !config.disabled_cli_versions.every(version => matchesTelemetryFormat('version', version)))))
21
+ return null;
22
+ return {
23
+ schema_version: TELEMETRY_SCHEMA_VERSION,
24
+ enabled: config.enabled,
25
+ install_id: config.install_id,
26
+ ...(config.notice_shown_at ? { notice_shown_at: config.notice_shown_at } : {}),
27
+ ...(config.disabled_cli_versions ? { disabled_cli_versions: config.disabled_cli_versions } : {}),
28
+ };
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
@@ -0,0 +1,35 @@
1
+ import { type TelemetryConfig } from './config-schema.js';
2
+ import { type SettingsLockIdentity } from './settings-lock.js';
3
+ export interface TelemetryConfigDependencies {
4
+ readonly env?: Readonly<Record<string, string | undefined>>;
5
+ readonly platform?: NodeJS.Platform;
6
+ readonly configDirectory?: string;
7
+ /** Lock owner identity; tests use it to model separate processes. */
8
+ readonly lockIdentity?: SettingsLockIdentity;
9
+ }
10
+ /**
11
+ * Waits, bounded, for in-progress settings writes to release their lock. The
12
+ * executable calls this before exiting, including on signals: process.exit()
13
+ * skips `finally` blocks, and a stranded lock would make every later
14
+ * preference change fail until someone removed it by hand.
15
+ */
16
+ export declare function settleTelemetrySettings(timeoutMs?: number): Promise<void>;
17
+ export declare class TelemetryConfigStore {
18
+ private readonly dependencies;
19
+ private readonly env;
20
+ private readonly platform;
21
+ constructor(dependencies?: TelemetryConfigDependencies);
22
+ private directory;
23
+ /** Read/create settings only when collection is allowed. All failures fail closed. */
24
+ load(): Promise<TelemetryConfig | null>;
25
+ /** Inspect without creating an identity or changing a disabled installation. */
26
+ peek(): Promise<TelemetryConfig | null>;
27
+ setEnabled(enabled: boolean): Promise<TelemetryConfig | null>;
28
+ /** Serialize display and persistence so concurrent interactive runs show one notice. */
29
+ showNotice(write: () => Promise<void>, now?: Date): Promise<TelemetryConfig | null>;
30
+ /** Kill switch from an HTTP 410. One immediate lock attempt; never waits for another writer. */
31
+ disableVersion(version: string): Promise<TelemetryConfig | null>;
32
+ private update;
33
+ private locked;
34
+ }
35
+ //# sourceMappingURL=config-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/config-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkD,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1G,OAAO,EAA0C,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEvG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;CAC9C;AAKD;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5E;AAED,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAHzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAEd,YAAY,GAAE,2BAAgC;YAK7D,SAAS;IAUvB,sFAAsF;IAChF,IAAI,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAI7C,gFAAgF;IAC1E,IAAI,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAWvC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAInE,wFAAwF;IAClF,UAAU,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAa,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQ/F,gGAAgG;IAC1F,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQtE,OAAO,CAAC,MAAM;YAYA,MAAM;CAkDrB"}
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { configDirectory } from '../config-directory.js';
5
+ import { parseTelemetryConfig, TELEMETRY_SCHEMA_VERSION } from './config-schema.js';
6
+ import { matchesTelemetryFormat } from './value-formats.js';
7
+ import { acquireSettingsLock } from './settings-lock.js';
8
+ /** Settings writes in progress; each holds the cross-process lock until it settles. */
9
+ const activeWrites = new Set();
10
+ /**
11
+ * Waits, bounded, for in-progress settings writes to release their lock. The
12
+ * executable calls this before exiting, including on signals: process.exit()
13
+ * skips `finally` blocks, and a stranded lock would make every later
14
+ * preference change fail until someone removed it by hand.
15
+ */
16
+ export async function settleTelemetrySettings(timeoutMs = 250) {
17
+ if (activeWrites.size === 0)
18
+ return;
19
+ let timer;
20
+ await Promise.race([
21
+ Promise.allSettled([...activeWrites]),
22
+ new Promise(resolve => { timer = setTimeout(resolve, timeoutMs); timer.unref(); }),
23
+ ]);
24
+ clearTimeout(timer);
25
+ }
26
+ export class TelemetryConfigStore {
27
+ dependencies;
28
+ env;
29
+ platform;
30
+ constructor(dependencies = {}) {
31
+ this.dependencies = dependencies;
32
+ this.env = dependencies.env ?? process.env;
33
+ this.platform = dependencies.platform ?? process.platform;
34
+ }
35
+ async directory() {
36
+ if (this.dependencies.configDirectory)
37
+ return this.dependencies.configDirectory;
38
+ const home = this.platform === 'win32' ? this.env.USERPROFILE : this.env.HOME;
39
+ const explicit = this.env.HYPEQUERY_CONFIG_DIR
40
+ || (this.platform === 'win32' ? this.env.APPDATA || this.env.LOCALAPPDATA
41
+ : this.platform !== 'darwin' ? this.env.XDG_CONFIG_HOME : undefined);
42
+ if (!explicit && (!home || !(await stat(home)).isDirectory()))
43
+ return null;
44
+ return configDirectory(this.env, this.platform, home ?? '');
45
+ }
46
+ /** Read/create settings only when collection is allowed. All failures fail closed. */
47
+ async load() {
48
+ return this.update();
49
+ }
50
+ /** Inspect without creating an identity or changing a disabled installation. */
51
+ async peek() {
52
+ try {
53
+ const directory = await this.directory();
54
+ return directory
55
+ ? parseTelemetryConfig(await readFile(path.join(directory, 'telemetry.json'), 'utf8'))
56
+ : null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ async setEnabled(enabled) {
63
+ return this.update(enabled);
64
+ }
65
+ /** Serialize display and persistence so concurrent interactive runs show one notice. */
66
+ async showNotice(write, now = new Date()) {
67
+ return this.update(undefined, async (config) => {
68
+ if (!config.enabled || config.notice_shown_at)
69
+ return config;
70
+ await write();
71
+ return { ...config, notice_shown_at: now.toISOString() };
72
+ });
73
+ }
74
+ /** Kill switch from an HTTP 410. One immediate lock attempt; never waits for another writer. */
75
+ async disableVersion(version) {
76
+ if (!matchesTelemetryFormat('version', version))
77
+ return null;
78
+ return this.update(undefined, async (config) => ({
79
+ ...config,
80
+ disabled_cli_versions: [...new Set([...(config.disabled_cli_versions ?? []), version])].slice(-20),
81
+ }), 1, true);
82
+ }
83
+ update(enabled, mutate, lockAttempts = 20, requireExisting = false) {
84
+ const write = this.locked(enabled, mutate, lockAttempts, requireExisting);
85
+ activeWrites.add(write);
86
+ void write.finally(() => activeWrites.delete(write));
87
+ return write;
88
+ }
89
+ async locked(enabled, mutate, lockAttempts, requireExisting) {
90
+ let lock = null;
91
+ let temporary;
92
+ try {
93
+ const directory = await this.directory();
94
+ if (!directory)
95
+ return null;
96
+ await mkdir(directory, { recursive: true, mode: 0o700 });
97
+ // Serialize read-modify-write across processes, including first identity
98
+ // creation. A live writer's lock fails closed however long it is held; a
99
+ // lock whose owner process has died is taken over (see settings-lock.ts).
100
+ lock = await acquireSettingsLock(directory, lockAttempts, this.dependencies.lockIdentity);
101
+ if (!lock)
102
+ return null;
103
+ const file = path.join(directory, 'telemetry.json');
104
+ let previous = null;
105
+ try {
106
+ previous = parseTelemetryConfig(await readFile(file, 'utf8'));
107
+ // Explicit enable/disable can repair settings; ordinary commands cannot.
108
+ if (!previous && enabled === undefined)
109
+ return null;
110
+ }
111
+ catch (error) {
112
+ if (error.code !== 'ENOENT')
113
+ throw error;
114
+ }
115
+ // The kill switch only amends settings that collection already created.
116
+ if (!previous && requireExisting)
117
+ return null;
118
+ let config = {
119
+ schema_version: TELEMETRY_SCHEMA_VERSION,
120
+ install_id: previous?.install_id ?? randomUUID(),
121
+ enabled: enabled ?? previous?.enabled ?? true,
122
+ ...(previous?.notice_shown_at ? { notice_shown_at: previous.notice_shown_at } : {}),
123
+ ...(previous?.disabled_cli_versions ? { disabled_cli_versions: previous.disabled_cli_versions } : {}),
124
+ };
125
+ if (previous && enabled === undefined)
126
+ config = previous;
127
+ if (mutate)
128
+ config = await mutate(config);
129
+ if (previous && enabled === undefined && (!mutate || config === previous))
130
+ return previous;
131
+ temporary = `${file}.${randomUUID()}.tmp`;
132
+ await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
133
+ await rename(temporary, file);
134
+ return config;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ finally {
140
+ if (temporary)
141
+ await unlink(temporary).catch(() => undefined);
142
+ // Releases only this writer's ownership, never a lock taken over by another.
143
+ if (lock)
144
+ await lock.release();
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,13 @@
1
+ import { type PostJson } from './post-json.js';
2
+ /** Runs in the detached sender, so this budget never delays the CLI's own exit. */
3
+ export declare const DELIVERY_TIMEOUT_MS = 5000;
4
+ export interface DeliveryOptions {
5
+ /** Injectable for tests; defaults to an exit-safe node:http(s) POST. */
6
+ readonly post?: PostJson;
7
+ readonly timeoutMs?: number;
8
+ /** The proxy owns the real PostHog API key. 410 disables this CLI version. */
9
+ readonly onDisabled?: () => Promise<unknown>;
10
+ }
11
+ /** One bounded attempt with no retries; every failure is swallowed. */
12
+ export declare function deliverBatch(url: URL, body: string, options?: DeliveryOptions): Promise<void>;
13
+ //# sourceMappingURL=delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEzD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,OAAQ,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAevG"}
@@ -0,0 +1,26 @@
1
+ import { postJson } from './post-json.js';
2
+ /** Runs in the detached sender, so this budget never delays the CLI's own exit. */
3
+ export const DELIVERY_TIMEOUT_MS = 5_000;
4
+ /** One bounded attempt with no retries; every failure is swallowed. */
5
+ export async function deliverBatch(url, body, options = {}) {
6
+ let timer;
7
+ const controller = new AbortController();
8
+ try {
9
+ const timeout = new Promise(resolve => {
10
+ timer = setTimeout(() => { controller.abort(); resolve(); }, options.timeoutMs ?? DELIVERY_TIMEOUT_MS);
11
+ timer.unref();
12
+ });
13
+ const request = (async () => {
14
+ const status = await (options.post ?? postJson)(url, body, controller.signal);
15
+ if (status === 410 && !controller.signal.aborted)
16
+ await options.onDisabled?.();
17
+ })().catch(() => undefined);
18
+ await Promise.race([request, timeout]);
19
+ }
20
+ catch { /* DNS/TLS/HTTP/abort/synchronous failures are dropped. */ }
21
+ finally {
22
+ if (timer)
23
+ clearTimeout(timer);
24
+ controller.abort();
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ /** Hands one serialized batch to delivery; must return without waiting on the network. */
2
+ export type DispatchBatch = (url: URL, body: string) => void;
3
+ export declare const BATCH_DIRECTORY_PREFIX = "hypequery-telemetry-";
4
+ export declare const BATCH_FILE = "batch.json";
5
+ /**
6
+ * The CLI's exit budget is too short for a TLS handshake plus a serverless cold
7
+ * start, so a detached child delivers the batch and the command exits at once.
8
+ * The batch travels through a private temporary file: argv and env are capped
9
+ * near 32 KiB on Windows, and pipe writes still buffered at exit are lost.
10
+ */
11
+ export declare const dispatchDetached: DispatchBatch;
12
+ //# sourceMappingURL=detached-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detached-dispatch.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/detached-dispatch.ts"],"names":[],"mappings":"AAMA,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE7D,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAC7D,eAAO,MAAM,UAAU,eAAe,CAAC;AAOvC;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAW9B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ export const BATCH_DIRECTORY_PREFIX = 'hypequery-telemetry-';
7
+ export const BATCH_FILE = 'batch.json';
8
+ const SENDER = fileURLToPath(new URL('./detached-sender.js', import.meta.url));
9
+ function removeQuietly(directory) {
10
+ try {
11
+ rmSync(directory, { recursive: true, force: true });
12
+ }
13
+ catch { /* A stray temporary file is harmless. */ }
14
+ }
15
+ /**
16
+ * The CLI's exit budget is too short for a TLS handshake plus a serverless cold
17
+ * start, so a detached child delivers the batch and the command exits at once.
18
+ * The batch travels through a private temporary file: argv and env are capped
19
+ * near 32 KiB on Windows, and pipe writes still buffered at exit are lost.
20
+ */
21
+ export const dispatchDetached = (url, body) => {
22
+ const directory = mkdtempSync(path.join(tmpdir(), BATCH_DIRECTORY_PREFIX));
23
+ try {
24
+ writeFileSync(path.join(directory, BATCH_FILE), JSON.stringify({ endpoint: url.href, body }), { mode: 0o600, flag: 'wx' });
25
+ const child = spawn(process.execPath, [SENDER, directory], { detached: true, stdio: 'ignore', windowsHide: true });
26
+ child.on('error', () => { removeQuietly(directory); });
27
+ child.unref();
28
+ }
29
+ catch (error) {
30
+ removeQuietly(directory);
31
+ throw error;
32
+ }
33
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=detached-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detached-sender.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/detached-sender.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ import { readFile, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getCliVersion } from '../cli-version.js';
4
+ import { deliverBatch } from './delivery.js';
5
+ import { BATCH_DIRECTORY_PREFIX, BATCH_FILE } from './detached-dispatch.js';
6
+ import { telemetryEndpoint } from './endpoint.js';
7
+ /** Entry point of the child started by dispatchDetached; the CLI never imports it. */
8
+ async function send(directory) {
9
+ if (!directory || !path.basename(directory).startsWith(BATCH_DIRECTORY_PREFIX))
10
+ return;
11
+ let batch;
12
+ try {
13
+ batch = JSON.parse(await readFile(path.join(directory, BATCH_FILE), 'utf8'));
14
+ }
15
+ finally {
16
+ await rm(directory, { recursive: true, force: true });
17
+ }
18
+ if (!batch || typeof batch !== 'object')
19
+ return;
20
+ const { endpoint, body } = batch;
21
+ const url = telemetryEndpoint(typeof endpoint === 'string' ? endpoint : undefined);
22
+ if (!url || typeof body !== 'string')
23
+ return;
24
+ await deliverBatch(url, body, {
25
+ onDisabled: async () => {
26
+ const { TelemetryConfigStore } = await import('./config-store.js');
27
+ return new TelemetryConfigStore().disableVersion(getCliVersion());
28
+ },
29
+ });
30
+ }
31
+ await send(process.argv[2]).catch(() => undefined);
@@ -0,0 +1,15 @@
1
+ import type { DevOptions } from '../../commands/dev.js';
2
+ import { type SessionEnd } from './command-context.js';
3
+ import type { ERROR_CODES } from './domains.js';
4
+ /** Counters only: no retained API, reload paths, errors, queries, or requests. */
5
+ export declare class DevSession {
6
+ private reloads;
7
+ private reloadErrors;
8
+ private failures;
9
+ start(resolved: string, explicit: string | undefined, options: DevOptions): void;
10
+ reload(): void;
11
+ reloadFailed(): void;
12
+ loadFailed(error: unknown): typeof ERROR_CODES[number];
13
+ snapshot(): SessionEnd<'dev'>;
14
+ }
15
+ //# sourceMappingURL=dev-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-session.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/dev-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAG5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,kFAAkF;AAClF,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAA2D;IAE3E,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIhF,MAAM,IAAI,IAAI;IACd,YAAY,IAAI,IAAI;IAEpB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,WAAW,CAAC,MAAM,CAAC;IAOtD,QAAQ,IAAI,UAAU,CAAC,KAAK,CAAC;CAI9B"}