@hypequery/cli 1.13.0 → 1.13.1

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.
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;AAyBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAsND,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA4ND,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import { generateManifestCommand } from './commands/generate-manifest.js';
9
9
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
10
10
  import { deployCommand, submitDeploymentCommand, } from './commands/deploy.js';
11
11
  import { loginCommand, logoutCommand, } from './commands/login.js';
12
+ import { isPromptCancelled } from './utils/prompts.js';
12
13
  const program = new Command();
13
14
  function getCliVersion() {
14
15
  try {
@@ -48,6 +49,12 @@ function runCommand(action) {
48
49
  await action(...args);
49
50
  }
50
51
  catch (error) {
52
+ // Ctrl+C at a prompt is a deliberate abort, not a failure: report it as
53
+ // the interrupt it is instead of dumping an error and exiting 1.
54
+ if (isPromptCancelled(error)) {
55
+ console.log('\nCancelled.');
56
+ process.exit(130);
57
+ }
51
58
  console.error(error instanceof Error ? error.message : error);
52
59
  process.exit(1);
53
60
  }
@@ -1,5 +1,6 @@
1
1
  type ScaffoldStyle = 'queries' | 'datasets';
2
2
  type ScaffoldDatabase = 'clickhouse' | 'chdb';
3
+ export declare function isCanaryVersion(version: string | undefined): boolean;
3
4
  export declare function resolveScaffoldPackages(cliVersion: string | undefined, style?: ScaffoldStyle, database?: ScaffoldDatabase): string[];
4
5
  export declare function installScaffoldDependencies(style?: ScaffoldStyle, database?: ScaffoldDatabase): Promise<void>;
5
6
  export declare const installServeDependencies: typeof installScaffoldDependencies;
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AA0F9C,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AAsBD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAwD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
1
+ {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AAsBD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAyD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
@@ -68,26 +68,44 @@ function detectPackageManager(pkgJson) {
68
68
  return 'bun';
69
69
  return 'npm';
70
70
  }
71
- function getInstallArgs(manager, packages) {
71
+ export function isCanaryVersion(version) {
72
+ return version?.includes('canary') === true;
73
+ }
74
+ /**
75
+ * Canary builds pin every hypequery package to a `0.0.0-canary-*` version, which
76
+ * sorts below every published semver range. Any third-party peer range on a
77
+ * hypequery package is therefore unsatisfiable — `chdb` declares
78
+ * `peerOptional @hypequery/clickhouse@">=2.1.2"` — and npm aborts the whole
79
+ * install with ERESOLVE, leaving the scaffold without its dependencies.
80
+ *
81
+ * Only npm needs this: pnpm (strict-peer-dependencies defaults to false), yarn
82
+ * and bun all install through peer conflicts rather than failing. Stable
83
+ * installs keep strict peer checking on every manager.
84
+ */
85
+ function getPeerRelaxFlags(manager, canary) {
86
+ return canary && manager === 'npm' ? ['--legacy-peer-deps'] : [];
87
+ }
88
+ function getInstallArgs(manager, packages, canary = false) {
89
+ const relaxFlags = getPeerRelaxFlags(manager, canary);
72
90
  switch (manager) {
73
91
  case 'pnpm':
74
- return ['add', ...packages];
92
+ return ['add', ...relaxFlags, ...packages];
75
93
  case 'yarn':
76
- return ['add', ...packages];
94
+ return ['add', ...relaxFlags, ...packages];
77
95
  case 'bun':
78
- return ['add', ...packages];
96
+ return ['add', ...relaxFlags, ...packages];
79
97
  case 'npm':
80
98
  default:
81
- return ['install', ...packages];
99
+ return ['install', ...relaxFlags, ...packages];
82
100
  }
83
101
  }
84
- function formatManualCommand(manager, packages) {
85
- return `${MANUAL_COMMANDS[manager]} ${packages.join(' ')}`;
102
+ function formatManualCommand(manager, packages, canary = false) {
103
+ return [MANUAL_COMMANDS[manager], ...getPeerRelaxFlags(manager, canary), ...packages].join(' ');
86
104
  }
87
105
  export function resolveScaffoldPackages(cliVersion, style = 'queries', database = 'clickhouse') {
88
106
  const includeDatasets = style === 'datasets';
89
107
  const includeChdb = database === 'chdb';
90
- if (cliVersion?.includes('canary')) {
108
+ if (isCanaryVersion(cliVersion)) {
91
109
  return [
92
110
  `@hypequery/clickhouse@${cliVersion}`,
93
111
  `@hypequery/serve@${cliVersion}`,
@@ -141,9 +159,10 @@ export async function installScaffoldDependencies(style = 'queries', database =
141
159
  if (missing.length === 0) {
142
160
  return;
143
161
  }
162
+ const canary = isCanaryVersion(cliPkgJson?.version);
144
163
  const manager = detectPackageManager(pkgJson);
145
164
  const command = manager === 'npm' ? 'npm' : manager;
146
- const args = getInstallArgs(manager, missing);
165
+ const args = getInstallArgs(manager, missing, canary);
147
166
  logger.info(`Installing ${missing.join(', ')} with ${manager}...`);
148
167
  try {
149
168
  await new Promise((resolve, reject) => {
@@ -165,7 +184,7 @@ export async function installScaffoldDependencies(style = 'queries', database =
165
184
  }
166
185
  catch (error) {
167
186
  logger.warn('Failed to install hypequery packages automatically.');
168
- logger.info(`Run manually: ${formatManualCommand(manager, missing)}`);
187
+ logger.info(`Run manually: ${formatManualCommand(manager, missing, canary)}`);
169
188
  if (error instanceof Error && error.message) {
170
189
  logger.info(error.message);
171
190
  }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Raised when the user aborts a prompt with Ctrl+C or Esc.
3
+ */
4
+ export declare class PromptCancelledError extends Error {
5
+ constructor();
6
+ }
7
+ export declare function isPromptCancelled(error: unknown): error is PromptCancelledError;
1
8
  /**
2
9
  * Prompt for ClickHouse connection details
3
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAAC,CAyCR;AAED,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,CAAC;AAEjD;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4BrE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B7D;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAa1D;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9C;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BnF;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,GAAE,MAAM,EAAO,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnE;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAShE"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAE/E;AA8BD;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAAC,CAyCR;AAED,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,CAAC;AAEjD;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4BrE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B7D;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAa1D;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9C;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BnF;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,GAAE,MAAM,EAAO,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnE;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAShE"}
@@ -1,13 +1,44 @@
1
1
  import prompts from 'prompts';
2
2
  import { logger } from './logger.js';
3
- const noop = () => undefined;
4
- // Configure prompts to not exit on cancel
5
- prompts.override({ onCancel: noop });
3
+ /**
4
+ * Raised when the user aborts a prompt with Ctrl+C or Esc.
5
+ */
6
+ export class PromptCancelledError extends Error {
7
+ constructor() {
8
+ super('Prompt cancelled');
9
+ this.name = 'PromptCancelledError';
10
+ }
11
+ }
12
+ export function isPromptCancelled(error) {
13
+ return error instanceof PromptCancelledError;
14
+ }
15
+ /**
16
+ * Ask a question and turn an aborted prompt into a thrown PromptCancelledError.
17
+ *
18
+ * `prompts` only reports cancellation through the `onCancel` callback passed as
19
+ * its second argument — the resolved answers simply omit the cancelled key. So
20
+ * without this wrapper Ctrl+C is indistinguishable from an unanswered question,
21
+ * and each caller's `?? default` silently answers on the user's behalf.
22
+ */
23
+ async function ask(questions) {
24
+ let cancelled = false;
25
+ const answers = await prompts(questions, {
26
+ onCancel: () => {
27
+ cancelled = true;
28
+ // Returning false stops any remaining questions in the same chain.
29
+ return false;
30
+ },
31
+ });
32
+ if (cancelled) {
33
+ throw new PromptCancelledError();
34
+ }
35
+ return answers;
36
+ }
6
37
  /**
7
38
  * Prompt for ClickHouse connection details
8
39
  */
9
40
  export async function promptClickHouseConnection() {
10
- const hostResponse = await prompts({
41
+ const hostResponse = await ask({
11
42
  type: 'text',
12
43
  name: 'host',
13
44
  message: 'ClickHouse URL (or skip to configure later):',
@@ -18,7 +49,7 @@ export async function promptClickHouseConnection() {
18
49
  if (!hostResponse.host) {
19
50
  return null;
20
51
  }
21
- const credentials = await prompts([
52
+ const credentials = await ask([
22
53
  {
23
54
  type: 'text',
24
55
  name: 'database',
@@ -49,7 +80,7 @@ export async function promptClickHouseConnection() {
49
80
  * Choose between a remote ClickHouse server and embedded chDB.
50
81
  */
51
82
  export async function promptInitDatabase() {
52
- const response = await prompts({
83
+ const response = await ask({
53
84
  type: 'select',
54
85
  name: 'database',
55
86
  message: 'Choose your database',
@@ -67,7 +98,7 @@ export async function promptInitDatabase() {
67
98
  * or undefined for in-memory.
68
99
  */
69
100
  export async function promptChdbStorage() {
70
- const response = await prompts({
101
+ const response = await ask({
71
102
  type: 'select',
72
103
  name: 'storage',
73
104
  message: 'Where should embedded chDB store data?',
@@ -82,7 +113,7 @@ export async function promptChdbStorage() {
82
113
  return undefined;
83
114
  }
84
115
  if (response.storage === 'custom') {
85
- const customResponse = await prompts({
116
+ const customResponse = await ask({
86
117
  type: 'text',
87
118
  name: 'path',
88
119
  message: 'Enter chDB data directory:',
@@ -96,7 +127,7 @@ export async function promptChdbStorage() {
96
127
  * Prompt for output directory
97
128
  */
98
129
  export async function promptOutputDirectory() {
99
- const response = await prompts({
130
+ const response = await ask({
100
131
  type: 'select',
101
132
  name: 'directory',
102
133
  message: 'Where should we create your analytics files?',
@@ -111,7 +142,7 @@ export async function promptOutputDirectory() {
111
142
  return 'analytics'; // Default fallback
112
143
  }
113
144
  if (response.directory === 'custom') {
114
- const customResponse = await prompts({
145
+ const customResponse = await ask({
115
146
  type: 'text',
116
147
  name: 'path',
117
148
  message: 'Enter custom path:',
@@ -122,7 +153,7 @@ export async function promptOutputDirectory() {
122
153
  return response.directory;
123
154
  }
124
155
  export async function promptInitStyle() {
125
- const response = await prompts({
156
+ const response = await ask({
126
157
  type: 'select',
127
158
  name: 'style',
128
159
  message: 'Choose your dev API style',
@@ -138,7 +169,7 @@ export async function promptInitStyle() {
138
169
  * Choose whether generated routes include request-context auth scaffolding.
139
170
  */
140
171
  export async function promptInitAuthMode() {
141
- const response = await prompts({
172
+ const response = await ask({
142
173
  type: 'select',
143
174
  name: 'auth',
144
175
  message: 'Choose authentication scaffolding',
@@ -154,7 +185,7 @@ export async function promptInitAuthMode() {
154
185
  * Confirm scaffolding when init is run outside a Node project.
155
186
  */
156
187
  export async function confirmWithoutPackageJson(directory) {
157
- const response = await prompts({
188
+ const response = await ask({
158
189
  type: 'confirm',
159
190
  name: 'continue',
160
191
  message: `package.json was not found in ${directory}. Continue scaffolding here?`,
@@ -166,7 +197,7 @@ export async function confirmWithoutPackageJson(directory) {
166
197
  * Prompt for example query generation
167
198
  */
168
199
  export async function promptGenerateExample() {
169
- const response = await prompts({
200
+ const response = await ask({
170
201
  type: 'confirm',
171
202
  name: 'generate',
172
203
  message: 'Generate an example query?',
@@ -191,7 +222,7 @@ export async function promptTableSelection(tables) {
191
222
  ...tables.slice(0, 10).map(table => ({ title: table, value: table })),
192
223
  { title: 'Skip example', value: null },
193
224
  ];
194
- const response = await prompts({
225
+ const response = await ask({
195
226
  type: 'select',
196
227
  name: 'table',
197
228
  message: 'Which table should we use for the example?',
@@ -214,7 +245,7 @@ export async function promptDatasetTableSelection(tables, defaultTables = []) {
214
245
  logger.newline();
215
246
  }
216
247
  const defaults = new Set(defaultTables);
217
- const response = await prompts({
248
+ const response = await ask({
218
249
  type: 'multiselect',
219
250
  name: 'tables',
220
251
  message: 'Which tables should we scaffold as datasets?',
@@ -232,7 +263,7 @@ export async function promptDatasetTableSelection(tables, defaultTables = []) {
232
263
  * Confirm overwrite of existing files
233
264
  */
234
265
  export async function confirmOverwrite(files) {
235
- const response = await prompts({
266
+ const response = await ask({
236
267
  type: 'confirm',
237
268
  name: 'overwrite',
238
269
  message: `The following files will be overwritten:\n${files.map(f => ` • ${f}`).join('\n')}\n\nContinue?`,
@@ -244,7 +275,7 @@ export async function confirmOverwrite(files) {
244
275
  * Retry prompt for failed operations
245
276
  */
246
277
  export async function promptRetry(message) {
247
- const response = await prompts({
278
+ const response = await ask({
248
279
  type: 'confirm',
249
280
  name: 'retry',
250
281
  message,
@@ -256,7 +287,7 @@ export async function promptRetry(message) {
256
287
  * Ask if user wants to continue without DB connection
257
288
  */
258
289
  export async function promptContinueWithoutDb() {
259
- const response = await prompts({
290
+ const response = await ask({
260
291
  type: 'confirm',
261
292
  name: 'continue',
262
293
  message: 'Continue setup without database connection?',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "open": "^10.0.0",
20
20
  "ora": "^8.0.1",
21
21
  "prompts": "^2.4.2",
22
- "@hypequery/deployment": "0.6.0",
23
- "@hypequery/protocol": "0.9.0"
22
+ "@hypequery/protocol": "0.9.0",
23
+ "@hypequery/deployment": "0.6.0"
24
24
  },
25
25
  "optionalDependencies": {
26
26
  "@napi-rs/keyring": "1.3.0"