@hypequery/cli 1.13.0 → 1.14.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.
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;AA8ND,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
  }
@@ -135,6 +142,7 @@ program
135
142
  .option('--runtime-output <path>', 'Bundled Node runtime path (default: beside deployment JSON)')
136
143
  .option('--entrypoint-prefix <prefix>', 'Runtime entrypoint prefix (default: queries)')
137
144
  .option('--hash-output <path>', 'Deployment identity sidecar path (default: <output>.sha256)')
145
+ .option('--no-source', 'Exclude project source files from the deployment bundle')
138
146
  .action(runCommand(async (api, options) => {
139
147
  await buildDeploymentCommand(api, options);
140
148
  }));
@@ -169,6 +177,7 @@ program
169
177
  .option('--project <project>', 'Target project identifier (advanced override)')
170
178
  .option('--environment <environment>', 'Target environment identifier (advanced override)')
171
179
  .option('--release <path>', 'Submit a prebuilt bundle with this release (legacy)')
180
+ .option('--no-source', 'Exclude project source files from the deployment bundle')
172
181
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
173
182
  .action(runCommand(async (source, options) => {
174
183
  await deployCommand(source, options);
@@ -10,6 +10,7 @@ export interface DeployOptions extends SubmitDeploymentOptions {
10
10
  environment?: string;
11
11
  bundleOutput?: string;
12
12
  releaseOutput?: string;
13
+ source?: boolean;
13
14
  }
14
15
  export interface SubmitDeploymentDependencies {
15
16
  readonly env?: Readonly<Record<string, string | undefined>>;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA8GD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAwDvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAmDvC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA8GD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAwDvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAmDvC"}
@@ -204,7 +204,7 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
204
204
  // a missing or expired login only surfaces after a full bundle build.
205
205
  await resolveDeploymentCredential(options.endpoint, dependencies);
206
206
  try {
207
- await build(sourcePath, { bundleOutput: bundlePath });
207
+ await build(sourcePath, { bundleOutput: bundlePath, source: options.source });
208
208
  }
209
209
  catch (error) {
210
210
  throw withRuntimeFlagHint(error);
@@ -9,6 +9,7 @@ export interface BuildDeploymentOptions {
9
9
  runtimeOutput?: string;
10
10
  entrypointPrefix?: string;
11
11
  hashOutput?: string;
12
+ source?: boolean;
12
13
  }
13
14
  export interface PrepareDeploymentReleaseOptions {
14
15
  project?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAc7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAI5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAwIrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA2ErC;AAiED,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,EAC7C,YAAY,GAAE,oCAAyC,GACtD,OAAO,CAAC,iCAAiC,CAAC,CAkE5C"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAI5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAsJrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA2ErC;AAiED,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,EAC7C,YAAY,GAAE,oCAAyC,GACtD,OAAO,CAAC,iCAAiC,CAAC,CAkE5C"}
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { prepareProtocolDeploymentReleaseEnvelope, prepareProtocolDeploymentContract, } from '@hypequery/protocol';
5
5
  import { writeDeploymentBundle, verifyDeploymentBundle, readDeploymentRuntimeFile, } from '../utils/deployment-bundle.js';
6
6
  import { buildNodeRuntimeArtifact, getDeploymentRuntimeEntrypoints, } from '../utils/deployment-runtime-artifact.js';
7
+ import { captureDeploymentSourceSnapshot } from '../utils/deployment-source-snapshot.js';
7
8
  import { loadApiModule } from '../utils/load-api.js';
8
9
  import { logger } from '../utils/logger.js';
9
10
  import { loadCloudCredential, } from '../utils/cloud-credential-store.js';
@@ -123,8 +124,15 @@ export async function buildDeploymentCommand(apiPath, options = {}) {
123
124
  }
124
125
  runtimeFiles.push({ runtime, sha256: actualSha256, bytes });
125
126
  }
126
- const bundle = await writeDeploymentBundle(bundleOutput, prepared, runtimeFiles);
127
+ const sourceSnapshot = options.source === false
128
+ ? undefined
129
+ : await captureDeploymentSourceSnapshot(apiPath);
130
+ const bundle = await writeDeploymentBundle(bundleOutput, prepared, runtimeFiles, sourceSnapshot);
127
131
  logger.success(`Deployment bundle written to ${bundle.directory}`);
132
+ if (bundle.manifest.source) {
133
+ logger.info(`Captured ${bundle.manifest.source.files.length} source `
134
+ + `${bundle.manifest.source.files.length === 1 ? 'file' : 'files'}`);
135
+ }
128
136
  logger.info(`Bundle identity: ${bundle.identity}`);
129
137
  logger.info(`Deployment identity: ${digest}`);
130
138
  return validated;
@@ -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
  }
@@ -8,12 +8,25 @@ export interface DeploymentBundleRuntimeFile {
8
8
  readonly sha256: string;
9
9
  readonly bytes: Uint8Array;
10
10
  }
11
+ export interface DeploymentBundleSourceFile {
12
+ readonly path: string;
13
+ readonly bytes: Uint8Array;
14
+ }
15
+ export interface DeploymentBundleSourceSnapshot {
16
+ readonly entrypoint: string;
17
+ readonly files: readonly DeploymentBundleSourceFile[];
18
+ readonly revision?: {
19
+ readonly kind: 'git';
20
+ readonly commit: string;
21
+ readonly dirty: boolean;
22
+ };
23
+ }
11
24
  export interface WrittenDeploymentBundle {
12
25
  readonly directory: string;
13
26
  readonly manifest: ProtocolDeploymentBundleManifest;
14
27
  readonly identity: string;
15
28
  readonly contract: ProtocolDeploymentContract;
16
29
  }
17
- export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, runtimeFiles: readonly DeploymentBundleRuntimeFile[]): Promise<WrittenDeploymentBundle>;
30
+ export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, runtimeFiles: readonly DeploymentBundleRuntimeFile[], sourceSnapshot?: DeploymentBundleSourceSnapshot): Promise<WrittenDeploymentBundle>;
18
31
  export declare function readDeploymentRuntimeFile(filePath: string): Promise<Uint8Array>;
19
32
  //# sourceMappingURL=deployment-bundle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-bundle.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,CAAC;AACF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC/C;AA+FD,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,kCAAkC,EAC5C,YAAY,EAAE,SAAS,2BAA2B,EAAE,GACnD,OAAO,CAAC,uBAAuB,CAAC,CA6ElC;AAoCD,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAMrF"}
1
+ {"version":3,"file":"deployment-bundle.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,CAAC;AACF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC/C;AA0ID,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,kCAAkC,EAC5C,YAAY,EAAE,SAAS,2BAA2B,EAAE,EACpD,cAAc,CAAC,EAAE,8BAA8B,GAC9C,OAAO,CAAC,uBAAuB,CAAC,CA+FlC;AAoCD,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAMrF"}
@@ -12,6 +12,7 @@ function sha256(bytes) {
12
12
  function runtimeArtifactPath(runtime, digest) {
13
13
  return `artifacts/${digest}.${runtime === 'node' ? 'mjs' : 'pyz'}`;
14
14
  }
15
+ const SOURCE_ROOT = 'source';
15
16
  function errorCode(error) {
16
17
  return typeof error === 'object' && error !== null && 'code' in error
17
18
  ? String(error.code)
@@ -83,12 +84,51 @@ function requireClosedContractArtifactSet(contract) {
83
84
  }
84
85
  }
85
86
  }
86
- export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFiles) {
87
+ function validateSourceSnapshot(source) {
88
+ if (!source)
89
+ return undefined;
90
+ const files = [...source.files].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
91
+ if (files.length < 1
92
+ || files.length > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFiles) {
93
+ throw new Error('Deployment source snapshot exceeds the bundle file limits.');
94
+ }
95
+ let totalBytes = 0;
96
+ const seen = new Set();
97
+ for (const file of files) {
98
+ const portable = file.path.split(path.sep).join('/');
99
+ if (portable !== file.path || path.isAbsolute(file.path)
100
+ || file.path.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
101
+ throw new Error(`Deployment source path must be project-relative: ${file.path}`);
102
+ }
103
+ const caseFolded = file.path.toLowerCase();
104
+ if (seen.has(caseFolded)) {
105
+ throw new Error(`Duplicate deployment source path: ${file.path}`);
106
+ }
107
+ seen.add(caseFolded);
108
+ if (file.bytes.byteLength > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFileBytes) {
109
+ throw new Error(`Deployment source file exceeds its byte limit: ${file.path}`);
110
+ }
111
+ totalBytes += file.bytes.byteLength;
112
+ }
113
+ if (totalBytes > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceBytes) {
114
+ throw new Error('Deployment source snapshot exceeds its total byte limit.');
115
+ }
116
+ if (!files.some(file => file.path === source.entrypoint)) {
117
+ throw new Error('Deployment source snapshot does not contain its API entrypoint.');
118
+ }
119
+ return Object.freeze({
120
+ entrypoint: source.entrypoint,
121
+ files: Object.freeze(files),
122
+ ...(source.revision ? { revision: Object.freeze({ ...source.revision }) } : {}),
123
+ });
124
+ }
125
+ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFiles, sourceSnapshot) {
87
126
  const destination = path.resolve(outputDirectory);
88
127
  if (destination === path.parse(destination).root) {
89
128
  throw new Error('The deployment bundle output cannot be a filesystem root.');
90
129
  }
91
130
  const files = validateRuntimeFiles(prepared, runtimeFiles);
131
+ const source = validateSourceSnapshot(sourceSnapshot);
92
132
  const deploymentBytes = utf8Encoder.encode(`${prepared.canonical}\n`);
93
133
  const manifestInput = {
94
134
  kind: 'hypequery-deployment-bundle',
@@ -105,6 +145,18 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
105
145
  sha256: file.sha256,
106
146
  byteLength: file.bytes.byteLength,
107
147
  })),
148
+ ...(source ? {
149
+ source: {
150
+ root: SOURCE_ROOT,
151
+ entrypoint: source.entrypoint,
152
+ files: source.files.map(file => ({
153
+ path: file.path,
154
+ sha256: sha256(file.bytes),
155
+ byteLength: file.bytes.byteLength,
156
+ })),
157
+ ...(source.revision ? { revision: source.revision } : {}),
158
+ },
159
+ } : {}),
108
160
  };
109
161
  const preparedManifest = prepareProtocolDeploymentBundleManifest(manifestInput);
110
162
  const replaceExisting = await existingVerifiedBundle(destination);
@@ -118,6 +170,11 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
118
170
  for (const file of files) {
119
171
  await writeFile(path.join(staging, runtimeArtifactPath(file.runtime, file.sha256)), file.bytes, { flag: 'wx' });
120
172
  }
173
+ for (const file of source?.files ?? []) {
174
+ const outputPath = path.join(staging, SOURCE_ROOT, ...file.path.split('/'));
175
+ await mkdir(path.dirname(outputPath), { recursive: true });
176
+ await writeFile(outputPath, file.bytes, { flag: 'wx' });
177
+ }
121
178
  await writeFile(path.join(staging, DEPLOYMENT_BUNDLE_MANIFEST), `${preparedManifest.canonical}\n`, { encoding: 'utf8', flag: 'wx' });
122
179
  if (!replaceExisting) {
123
180
  await rename(staging, destination);
@@ -0,0 +1,3 @@
1
+ import type { DeploymentBundleSourceSnapshot } from './deployment-bundle.js';
2
+ export declare function captureDeploymentSourceSnapshot(apiPath: string): Promise<DeploymentBundleSourceSnapshot>;
3
+ //# sourceMappingURL=deployment-source-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-source-snapshot.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-source-snapshot.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,8BAA8B,EAC/B,MAAM,wBAAwB,CAAC;AAqEhC,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,8BAA8B,CAAC,CAkDzC"}
@@ -0,0 +1,110 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { constants, lstat, open, realpath } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS, } from '@hypequery/protocol';
5
+ import { build } from 'esbuild';
6
+ import { findNearestTsconfig } from './load-api.js';
7
+ function portableProjectPath(projectRoot, absolutePath) {
8
+ const relative = path.relative(projectRoot, absolutePath);
9
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`)
10
+ || path.isAbsolute(relative)) {
11
+ throw new Error('Deployment source files must remain inside the current project.');
12
+ }
13
+ return relative.split(path.sep).join('/');
14
+ }
15
+ async function readSourceFile(projectRoot, inputPath) {
16
+ const absolutePath = path.resolve(projectRoot, inputPath);
17
+ const displayPath = portableProjectPath(projectRoot, absolutePath);
18
+ const resolvedPath = await realpath(absolutePath);
19
+ if (resolvedPath !== absolutePath) {
20
+ throw new Error(`Deployment source files must not be symbolic links: ${displayPath}`);
21
+ }
22
+ const initial = await lstat(absolutePath);
23
+ if (!initial.isFile() || initial.isSymbolicLink()) {
24
+ throw new Error(`Deployment source entry is not a regular file: ${displayPath}`);
25
+ }
26
+ if (initial.size > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFileBytes) {
27
+ throw new Error(`Deployment source file exceeds its byte limit: ${displayPath}`);
28
+ }
29
+ const handle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
30
+ try {
31
+ const stat = await handle.stat();
32
+ if (!stat.isFile() || stat.size !== initial.size) {
33
+ throw new Error(`Deployment source file changed while being read: ${displayPath}`);
34
+ }
35
+ return Object.freeze({
36
+ path: displayPath,
37
+ bytes: new Uint8Array(await handle.readFile()),
38
+ });
39
+ }
40
+ finally {
41
+ await handle.close();
42
+ }
43
+ }
44
+ function gitOutput(projectRoot, arguments_) {
45
+ return new Promise((resolve) => {
46
+ execFile('git', [...arguments_], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024 }, (error, stdout) => resolve(error ? undefined : stdout.trim()));
47
+ });
48
+ }
49
+ async function sourceRevision(projectRoot) {
50
+ const commit = await gitOutput(projectRoot, ['rev-parse', 'HEAD']);
51
+ if (!commit || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
52
+ return undefined;
53
+ const status = await gitOutput(projectRoot, ['status', '--porcelain', '--untracked-files=all']);
54
+ if (status === undefined)
55
+ return undefined;
56
+ return Object.freeze({
57
+ kind: 'git',
58
+ commit,
59
+ dirty: status.length > 0,
60
+ });
61
+ }
62
+ export async function captureDeploymentSourceSnapshot(apiPath) {
63
+ const projectRoot = await realpath(process.cwd());
64
+ const entrypointAbsolute = await realpath(path.resolve(projectRoot, apiPath));
65
+ const entrypoint = portableProjectPath(projectRoot, entrypointAbsolute);
66
+ let inputs;
67
+ try {
68
+ const result = await build({
69
+ absWorkingDir: projectRoot,
70
+ bundle: true,
71
+ entryPoints: [entrypointAbsolute],
72
+ logLevel: 'silent',
73
+ metafile: true,
74
+ packages: 'external',
75
+ platform: 'node',
76
+ sourcemap: false,
77
+ tsconfig: await findNearestTsconfig(entrypointAbsolute) ?? undefined,
78
+ write: false,
79
+ });
80
+ inputs = Object.keys(result.metafile.inputs);
81
+ }
82
+ catch (error) {
83
+ throw new Error(`Could not resolve the deployment source graph from ${apiPath}.\n`
84
+ + `Original error: ${error instanceof Error ? error.message : String(error)}`);
85
+ }
86
+ const paths = [...new Set(inputs.map(input => {
87
+ const absolute = path.resolve(projectRoot, input);
88
+ const relative = portableProjectPath(projectRoot, absolute);
89
+ if (relative.split('/').includes('node_modules')) {
90
+ throw new Error(`Deployment source graph unexpectedly included a dependency: ${relative}`);
91
+ }
92
+ return relative;
93
+ }))].sort();
94
+ if (!paths.includes(entrypoint))
95
+ paths.unshift(entrypoint);
96
+ if (paths.length > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFiles) {
97
+ throw new Error('Deployment source graph exceeds the bundle file limit.');
98
+ }
99
+ const files = await Promise.all(paths.map(file => readSourceFile(projectRoot, file)));
100
+ const totalBytes = files.reduce((total, file) => total + file.bytes.byteLength, 0);
101
+ if (totalBytes > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceBytes) {
102
+ throw new Error('Deployment source graph exceeds the bundle byte limit.');
103
+ }
104
+ const revision = await sourceRevision(projectRoot);
105
+ return Object.freeze({
106
+ entrypoint,
107
+ files: Object.freeze(files),
108
+ ...(revision ? { revision } : {}),
109
+ });
110
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAE1E,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAQ1E,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AA+VD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CAsF3B"}
1
+ {"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAE1E,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAQ1E,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AA+WD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CAsF3B"}
@@ -78,6 +78,9 @@ function validateMultipartBundlePaths(bundle) {
78
78
  multipartBundlePath(bundle.manifest.deployment.path);
79
79
  for (const artifact of bundle.manifest.artifacts)
80
80
  multipartBundlePath(artifact.path);
81
+ for (const source of bundle.manifest.source?.files ?? []) {
82
+ multipartBundlePath(`${bundle.manifest.source.root}/${source.path}`);
83
+ }
81
84
  }
82
85
  function partHeader(boundary, part) {
83
86
  const headers = [
@@ -130,6 +133,19 @@ function uploadParts(bundle, release, manifest) {
130
133
  byteLength: artifact.byteLength,
131
134
  sha256: artifact.sha256,
132
135
  })),
136
+ ...(manifest.manifest.source?.files.map(source => {
137
+ const bundlePath = `${manifest.manifest.source.root}/${source.path}`;
138
+ return {
139
+ kind: 'file',
140
+ name: 'bundle',
141
+ filename: path.basename(source.path),
142
+ contentType: 'application/octet-stream',
143
+ absolutePath: path.join(root, ...bundlePath.split('/')),
144
+ bundlePath,
145
+ byteLength: source.byteLength,
146
+ sha256: source.sha256,
147
+ };
148
+ }) ?? []),
133
149
  ]);
134
150
  }
135
151
  async function* streamVerifiedFile(part) {
@@ -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.14.0",
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/deployment": "0.7.0",
23
+ "@hypequery/protocol": "0.10.0"
24
24
  },
25
25
  "optionalDependencies": {
26
26
  "@napi-rs/keyring": "1.3.0"
@@ -34,7 +34,7 @@
34
34
  "@vitest/coverage-v8": "^3.2.6",
35
35
  "typescript": "^5.7.3",
36
36
  "vitest": "^3.2.6",
37
- "@hypequery/serve": "0.14.0"
37
+ "@hypequery/serve": "0.14.1"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",