@hypequery/cli 1.19.1 → 1.20.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;AAiCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAqRD,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;AAiCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA6QD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -170,13 +170,6 @@ program
170
170
  .command('deployment:build <api>')
171
171
  .description('Build a verified deployment bundle')
172
172
  .option('--bundle-output <directory>', 'Bundle directory (default: analytics/hypequery-deployment)')
173
- .option('-o, --output <path>', 'Output JSON file (default: analytics/hypequery-deployment.json)')
174
- .option('--runtime <runtime>', 'Runtime for non-portable handlers: node or python (default: node)')
175
- .option('--runtime-artifact <sha256>', 'Use a prebuilt runtime artifact with this SHA-256 identity')
176
- .option('--runtime-file <path>', 'Include the bytes for a prebuilt runtime artifact')
177
- .option('--runtime-output <path>', 'Bundled Node runtime path (default: beside deployment JSON)')
178
- .option('--entrypoint-prefix <prefix>', 'Runtime entrypoint prefix (default: queries)')
179
- .option('--hash-output <path>', 'Deployment identity sidecar path (default: <output>.sha256)')
180
173
  .option('--no-source', 'Exclude project source files from the deployment bundle')
181
174
  .option('--allow-unsupported-config', 'Deploy even though some Serve config will not be honoured in Cloud')
182
175
  .action(runCommand(async (api, options) => {
@@ -184,7 +177,7 @@ program
184
177
  }));
185
178
  program
186
179
  .command('deployment:validate <artifact>')
187
- .description('Verify a deployment bundle or validate a legacy deployment JSON file')
180
+ .description('Verify a deployment bundle or validate a deployment contract JSON file')
188
181
  .action(runCommand(async (artifact) => {
189
182
  await validateDeploymentCommand(artifact);
190
183
  }));
@@ -213,7 +206,6 @@ program
213
206
  .option('--release-output <path>', 'Release JSON path (default: beside the bundle)')
214
207
  .option('--project <project>', 'Target project identifier (advanced override)')
215
208
  .option('--environment <environment>', 'Target environment identifier (advanced override)')
216
- .option('--release <path>', 'Submit a prebuilt bundle with this release (legacy)')
217
209
  .option('--no-source', 'Exclude project source files from the deployment bundle')
218
210
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
219
211
  .option('--replace-restored', 'Intentionally replace a restored live release')
@@ -7,7 +7,9 @@ export interface SubmitDeploymentOptions {
7
7
  endpoint?: string;
8
8
  replaceRestored?: boolean;
9
9
  }
10
- export interface DeployOptions extends SubmitDeploymentOptions {
10
+ export interface DeployOptions {
11
+ endpoint?: string;
12
+ replaceRestored?: boolean;
11
13
  project?: string;
12
14
  environment?: string;
13
15
  bundleOutput?: string;
@@ -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,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,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;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;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;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;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;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACjD;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;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA0EvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAyDvC"}
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,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,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;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,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;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;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;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACjD;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;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA0EvC;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAwCvC"}
@@ -140,43 +140,11 @@ async function rejectBundleDirectorySource(sourcePath) {
140
140
  + 'To upload this bundle, use `hypequery deployment:submit '
141
141
  + `${sourcePath} --release <file>\`.`);
142
142
  }
143
- /**
144
- * `deploy` exposes no runtime flags, so a runtime error from the shared build
145
- * command would otherwise point at flags this command does not accept.
146
- */
147
- function withRuntimeFlagHint(error) {
148
- if (!(error instanceof Error) || !error.message.includes('--runtime'))
149
- return error;
150
- return new Error(`${error.message}\n\n`
151
- + 'Runtime overrides are not available on `hypequery deploy`. Build with '
152
- + '`hypequery deployment:build` (which accepts --runtime, --runtime-artifact, '
153
- + 'and --runtime-file), then upload with `hypequery deployment:submit`.');
154
- }
155
- function rejectLegacyOrchestrationOptions(options) {
156
- if (options.project !== undefined
157
- || options.environment !== undefined
158
- || options.bundleOutput !== undefined
159
- || options.releaseOutput !== undefined) {
160
- throw new Error('--release selects prebuilt submission mode and cannot be combined with '
161
- + '--project, --environment, --bundle-output, or --release-output. '
162
- + 'Use `hypequery deployment:submit` for explicit prebuilt uploads.');
163
- }
164
- }
165
143
  export async function deployCommand(sourcePath, options = {}, dependencies = {}) {
166
144
  if (!sourcePath) {
167
145
  throw new Error('Missing API module path.\n\n'
168
146
  + 'Usage: hypequery deploy analytics/api.ts');
169
147
  }
170
- if (options.release !== undefined) {
171
- rejectLegacyOrchestrationOptions(options);
172
- logger.warn('`hypequery deploy <bundle> --release <file>` is deprecated. '
173
- + 'Use `hypequery deployment:submit <bundle> --release <file>` instead.');
174
- return submitDeploymentCommand(sourcePath, {
175
- release: options.release,
176
- endpoint: options.endpoint,
177
- replaceRestored: options.replaceRestored,
178
- }, dependencies);
179
- }
180
148
  await rejectBundleDirectorySource(sourcePath);
181
149
  const bundlePath = options.bundleOutput ?? DEFAULT_BUNDLE_PATH;
182
150
  const releasePath = options.releaseOutput
@@ -188,16 +156,11 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
188
156
  // Resolve credentials up front. Submission is the last step, so without this
189
157
  // a missing or expired login only surfaces after a full bundle build.
190
158
  await resolveDeploymentCredential(options.endpoint, dependencies);
191
- try {
192
- await build(sourcePath, {
193
- bundleOutput: bundlePath,
194
- source: options.source,
195
- allowUnsupportedConfig: options.allowUnsupportedConfig,
196
- });
197
- }
198
- catch (error) {
199
- throw withRuntimeFlagHint(error);
200
- }
159
+ await build(sourcePath, {
160
+ bundleOutput: bundlePath,
161
+ source: options.source,
162
+ allowUnsupportedConfig: options.allowUnsupportedConfig,
163
+ });
201
164
  await prepareRelease(bundlePath, {
202
165
  project: options.project,
203
166
  environment: options.environment,
@@ -2,13 +2,6 @@ import { type ProtocolDeploymentContract, type ProtocolDeploymentReleaseEnvelope
2
2
  import { type StoredCloudCredential } from '../utils/cloud-credential-store.js';
3
3
  export interface BuildDeploymentOptions {
4
4
  bundleOutput?: string;
5
- output?: string;
6
- runtime?: 'node' | 'python';
7
- runtimeArtifact?: string;
8
- runtimeFile?: string;
9
- runtimeOutput?: string;
10
- entrypointPrefix?: string;
11
- hashOutput?: string;
12
5
  source?: boolean;
13
6
  allowUnsupportedConfig?: boolean;
14
7
  }
@@ -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;AAgB7B,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;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;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;AAsDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CA0JrC;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":"AAEA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAS7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;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;AAYD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAyCrC;AAUD,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CAqErC;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,177 +1,49 @@
1
- import { createHash } from 'node:crypto';
2
1
  import { lstat, mkdir, readFile, realpath, stat, writeFile } from 'node:fs/promises';
3
2
  import path from 'node:path';
4
3
  import { prepareProtocolDeploymentReleaseEnvelope, prepareProtocolDeploymentContract, } from '@hypequery/protocol';
5
- import { writeDeploymentBundle, verifyDeploymentBundle, readDeploymentRuntimeFile, } from '../utils/deployment-bundle.js';
6
- import { buildNodeRuntimeArtifact, getDeploymentRuntimeEntrypoints, } from '../utils/deployment-runtime-artifact.js';
4
+ import { writeDeploymentBundle, verifyDeploymentBundle, } from '../utils/deployment-bundle.js';
7
5
  import { captureDeploymentSourceSnapshot } from '../utils/deployment-source-snapshot.js';
8
6
  import { loadApiModule } from '../utils/load-api.js';
9
7
  import { logger } from '../utils/logger.js';
10
8
  import { reportCloudDiagnostic } from '../utils/cloud-diagnostic.js';
11
9
  import { loadCloudCredential, } from '../utils/cloud-credential-store.js';
12
- const SHA256_PATTERN = /^[0-9a-f]{64}$/;
13
10
  const DEFAULT_BUNDLE_OUTPUT = 'analytics/hypequery-deployment';
14
- function runtimeName(options) {
15
- const runtime = options.runtime ?? 'node';
16
- if (runtime !== 'node' && runtime !== 'python') {
17
- throw new Error('--runtime must be either node or python.');
18
- }
19
- return runtime;
20
- }
21
- function configuredRuntimeArtifact(options, runtime) {
22
- if (options.runtimeArtifact === undefined)
23
- return undefined;
24
- if (!SHA256_PATTERN.test(options.runtimeArtifact)) {
25
- throw new Error('--runtime-artifact must be a lowercase 64-character SHA-256 digest.');
26
- }
27
- return {
28
- runtime,
29
- artifactSha256: options.runtimeArtifact,
30
- ...(options.entrypointPrefix !== undefined
31
- ? { entrypointPrefix: options.entrypointPrefix }
32
- : {}),
33
- };
34
- }
35
- function assertDistinctOutputPaths(paths) {
36
- const seen = new Map();
37
- for (const [label, value] of Object.entries(paths)) {
38
- if (value === undefined)
39
- continue;
40
- const resolved = path.resolve(value);
41
- const existing = seen.get(resolved);
42
- if (existing) {
43
- throw new Error(`${label} must use a different path from ${existing}.`);
44
- }
45
- seen.set(resolved, label);
46
- }
47
- }
48
11
  export async function buildDeploymentCommand(apiPath, options = {}) {
49
12
  if (!apiPath) {
50
13
  throw new Error('Missing API module path.\n\n'
51
14
  + 'Usage: hypequery deployment:build analytics/api.ts');
52
15
  }
53
- const runtime = runtimeName(options);
54
- const configuredArtifact = configuredRuntimeArtifact(options, runtime);
55
- const legacyOutputRequested = options.output !== undefined
56
- || options.hashOutput !== undefined
57
- || options.runtimeOutput !== undefined;
58
- if (options.bundleOutput !== undefined && legacyOutputRequested) {
59
- throw new Error('--bundle-output cannot be combined with --output, --hash-output, or --runtime-output.');
60
- }
61
- const bundleOutput = options.bundleOutput
62
- ?? (legacyOutputRequested ? undefined : DEFAULT_BUNDLE_OUTPUT);
63
- if (options.runtimeFile !== undefined && configuredArtifact === undefined) {
64
- throw new Error('--runtime-file requires --runtime-artifact.');
65
- }
66
- if (options.runtimeFile !== undefined && bundleOutput === undefined) {
67
- throw new Error('--runtime-file is only supported when building a deployment bundle.');
68
- }
69
- if (configuredArtifact && bundleOutput !== undefined && options.runtimeFile === undefined) {
70
- throw new Error('A complete deployment bundle requires --runtime-file with a prebuilt --runtime-artifact.');
71
- }
72
- if (configuredArtifact && options.runtimeOutput !== undefined) {
73
- throw new Error('--runtime-output cannot be used with a prebuilt --runtime-artifact.');
74
- }
75
- const outputPath = options.output ?? 'analytics/hypequery-deployment.json';
76
- const hashOutputPath = options.hashOutput ?? `${outputPath}.sha256`;
77
- if (options.runtimeOutput !== undefined) {
78
- assertDistinctOutputPaths({
79
- '--output': outputPath,
80
- '--hash-output': hashOutputPath,
81
- '--runtime-output': options.runtimeOutput,
82
- });
83
- }
16
+ const bundleOutput = options.bundleOutput ?? DEFAULT_BUNDLE_OUTPUT;
84
17
  const api = await loadApiModule(apiPath);
85
18
  if (typeof api.deploymentContract !== 'function') {
86
19
  throw new Error(`Invalid API module: ${apiPath}\n\n`
87
20
  + 'The exported API must provide deploymentContract(). '
88
21
  + 'Upgrade @hypequery/serve and export the value returned by createAPI() or serve().');
89
22
  }
90
- let builtArtifact;
91
- let artifact = configuredArtifact;
92
- if (!artifact) {
93
- const runtimeEntrypoints = getDeploymentRuntimeEntrypoints(api);
94
- if (runtimeEntrypoints.length > 0) {
95
- if (runtime !== 'node') {
96
- throw new Error('Automatic runtime artifact builds currently support Node only. '
97
- + 'Provide --runtime-artifact for Python deployments.');
98
- }
99
- builtArtifact = await buildNodeRuntimeArtifact(apiPath, runtimeEntrypoints, options.entrypointPrefix);
100
- artifact = {
101
- runtime: 'node',
102
- artifactSha256: builtArtifact.artifactSha256,
103
- entrypointPrefix: builtArtifact.entrypointPrefix,
104
- };
105
- }
106
- }
107
23
  const contract = api.deploymentContract({
108
- ...(artifact ? { runtimeArtifact: artifact } : {}),
109
24
  ...(options.allowUnsupportedConfig ? { allowUnsupportedConfig: true } : {}),
110
25
  onCloudDiagnostic: diagnostic => reportCloudDiagnostic(diagnostic, options.allowUnsupportedConfig === true),
111
26
  });
112
27
  const prepared = prepareProtocolDeploymentContract(contract);
113
- const { canonical, contract: validated, identity: digest } = prepared;
114
- if (bundleOutput !== undefined) {
115
- const runtimeFiles = [];
116
- if (builtArtifact) {
117
- runtimeFiles.push({
118
- runtime: 'node',
119
- sha256: builtArtifact.artifactSha256,
120
- bytes: builtArtifact.bytes,
121
- });
122
- }
123
- else if (configuredArtifact && options.runtimeFile) {
124
- const bytes = await readDeploymentRuntimeFile(options.runtimeFile);
125
- const actualSha256 = createHash('sha256').update(bytes).digest('hex');
126
- if (actualSha256 !== configuredArtifact.artifactSha256) {
127
- throw new Error(`Prebuilt runtime artifact SHA-256 mismatch: ${options.runtimeFile}\n\n`
128
- + `Expected: ${configuredArtifact.artifactSha256}\nActual: ${actualSha256}`);
129
- }
130
- runtimeFiles.push({ runtime, sha256: actualSha256, bytes });
131
- }
132
- const sourceSnapshot = options.source === false
133
- ? undefined
134
- : await captureDeploymentSourceSnapshot(apiPath);
135
- const bundle = await writeDeploymentBundle(bundleOutput, prepared, runtimeFiles, sourceSnapshot);
136
- logger.success(`Deployment bundle written to ${bundle.directory}`);
137
- if (bundle.manifest.source) {
138
- logger.info(`Captured ${bundle.manifest.source.files.length} source `
139
- + `${bundle.manifest.source.files.length === 1 ? 'file' : 'files'}`);
140
- }
141
- logger.info(`Bundle identity: ${bundle.identity}`);
142
- logger.info(`Deployment identity: ${digest}`);
143
- return validated;
144
- }
145
- const runtimeOutputPath = builtArtifact
146
- ? options.runtimeOutput ?? path.join(path.dirname(outputPath), 'hypequery-runtime.mjs')
147
- : undefined;
148
- assertDistinctOutputPaths({
149
- '--output': outputPath,
150
- '--hash-output': hashOutputPath,
151
- '--runtime-output': runtimeOutputPath,
152
- });
153
- const identitySidecar = [
154
- '# Hypequery deployment identity v1; not a file checksum or sha256sum input.',
155
- '# SHA-256(UTF-8("hypequery:deployment:v1") || 0x00 || RFC 8785 canonical bytes); '
156
- + 'the output newline is excluded.',
157
- `${digest} ${path.basename(outputPath)}`,
158
- '',
159
- ].join('\n');
160
- await mkdir(path.dirname(outputPath), { recursive: true });
161
- await mkdir(path.dirname(hashOutputPath), { recursive: true });
162
- if (runtimeOutputPath && builtArtifact) {
163
- await mkdir(path.dirname(runtimeOutputPath), { recursive: true });
164
- await writeFile(runtimeOutputPath, builtArtifact.bytes);
165
- }
166
- await writeFile(outputPath, `${canonical}\n`, 'utf8');
167
- await writeFile(hashOutputPath, identitySidecar, 'utf8');
168
- if (runtimeOutputPath && builtArtifact) {
169
- logger.success(`Runtime artifact written to ${runtimeOutputPath}`);
170
- logger.info(`Runtime artifact SHA-256: ${builtArtifact.artifactSha256}`);
171
- }
172
- logger.success(`Deployment contract written to ${outputPath}`);
173
- logger.info(`Identity: ${digest}`);
174
- return validated;
28
+ const sourceSnapshot = options.source === false
29
+ ? undefined
30
+ : await captureDeploymentSourceSnapshot(apiPath);
31
+ const bundle = await writeDeploymentBundle(bundleOutput, prepared, sourceSnapshot);
32
+ logger.success(`Deployment bundle written to ${bundle.directory}`);
33
+ if (bundle.manifest.source) {
34
+ logger.info(`Captured ${bundle.manifest.source.files.length} source `
35
+ + `${bundle.manifest.source.files.length === 1 ? 'file' : 'files'}`);
36
+ }
37
+ logger.info(`${prepared.contract.datasets.length} `
38
+ + `${prepared.contract.datasets.length === 1 ? 'dataset' : 'datasets'}, 0 runtime artifacts`);
39
+ logger.info(`Bundle identity: ${bundle.identity}`);
40
+ logger.info(`Deployment identity: ${prepared.identity}`);
41
+ return prepared.contract;
42
+ }
43
+ function describeContract(contract) {
44
+ const datasets = `${contract.datasets.length} `
45
+ + `${contract.datasets.length === 1 ? 'dataset' : 'datasets'}`;
46
+ return datasets;
175
47
  }
176
48
  export async function validateDeploymentCommand(artifactPath) {
177
49
  if (!artifactPath) {
@@ -194,8 +66,7 @@ export async function validateDeploymentCommand(artifactPath) {
194
66
  const bundle = await verifyDeploymentBundle(artifactPath);
195
67
  const contract = bundle.contract;
196
68
  logger.success(`Valid deployment bundle: ${artifactPath}`);
197
- logger.info(`${contract.datasets.length} datasets, ${contract.queries.length} queries, `
198
- + `${contract.artifacts.length} runtime artifacts`);
69
+ logger.info(describeContract(contract));
199
70
  logger.info(`Bundle identity: ${bundle.identity}`);
200
71
  logger.info(`Deployment identity: ${bundle.manifest.deployment.identity}`);
201
72
  return contract;
@@ -226,8 +97,7 @@ export async function validateDeploymentCommand(artifactPath) {
226
97
  }
227
98
  const { contract, identity: digest } = prepared;
228
99
  logger.success(`Valid deployment contract: ${artifactPath}`);
229
- logger.info(`${contract.datasets.length} datasets, ${contract.queries.length} queries, `
230
- + `${contract.artifacts.length} runtime artifacts`);
100
+ logger.info(describeContract(contract));
231
101
  logger.info(`Identity: ${digest}`);
232
102
  return contract;
233
103
  }
@@ -1,13 +1,8 @@
1
- import { type PreparedProtocolDeploymentContract, type ProtocolDeploymentBundleManifest, type ProtocolDeploymentContract } from '@hypequery/protocol';
1
+ import { type PreparedProtocolDeploymentContract, type ProtocolDeploymentContract, type ProtocolDeploymentBundleManifest } from '@hypequery/protocol';
2
2
  import { DEPLOYMENT_BUNDLE_CONTRACT, DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle } from '@hypequery/deployment';
3
3
  import type { VerifiedDeploymentBundle } from '@hypequery/deployment';
4
4
  export { DEPLOYMENT_BUNDLE_CONTRACT, DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle, };
5
5
  export type { VerifiedDeploymentBundle };
6
- export interface DeploymentBundleRuntimeFile {
7
- readonly runtime: 'node' | 'python';
8
- readonly sha256: string;
9
- readonly bytes: Uint8Array;
10
- }
11
6
  export interface DeploymentBundleSourceFile {
12
7
  readonly path: string;
13
8
  readonly bytes: Uint8Array;
@@ -28,6 +23,5 @@ export interface WrittenDeploymentBundle {
28
23
  readonly identity: string;
29
24
  readonly contract: ProtocolDeploymentContract;
30
25
  }
31
- export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, runtimeFiles: readonly DeploymentBundleRuntimeFile[], sourceSnapshot?: DeploymentBundleSourceSnapshot): Promise<WrittenDeploymentBundle>;
32
- export declare function readDeploymentRuntimeFile(filePath: string): Promise<Uint8Array>;
26
+ export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, sourceSnapshot?: DeploymentBundleSourceSnapshot): Promise<WrittenDeploymentBundle>;
33
27
  //# 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,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;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,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"}
1
+ {"version":3,"file":"deployment-bundle.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-bundle.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,kCAAkC,EACvC,KAAK,0BAA0B,EAC/B,KAAK,gCAAgC,EACtC,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,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;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,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;AA4ED,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,kCAAkC,EAC5C,cAAc,CAAC,EAAE,8BAA8B,GAC9C,OAAO,CAAC,uBAAuB,CAAC,CAkFlC"}
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { constants, lstat, mkdir, mkdtemp, open, rename, rm, writeFile, } from 'node:fs/promises';
2
+ import { lstat, mkdir, mkdtemp, rename, rm, writeFile, } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS, prepareProtocolDeploymentBundleManifest, } from '@hypequery/protocol';
5
5
  import { DEPLOYMENT_BUNDLE_CONTRACT, DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle, } from '@hypequery/deployment';
@@ -9,9 +9,6 @@ const utf8Encoder = new TextEncoder();
9
9
  function sha256(bytes) {
10
10
  return createHash('sha256').update(bytes).digest('hex');
11
11
  }
12
- function runtimeArtifactPath(runtime, digest) {
13
- return `artifacts/${digest}.${runtime === 'node' ? 'mjs' : 'pyz'}`;
14
- }
15
12
  const SOURCE_ROOT = 'source';
16
13
  function errorCode(error) {
17
14
  return typeof error === 'object' && error !== null && 'code' in error
@@ -37,53 +34,6 @@ async function existingVerifiedBundle(outputDirectory) {
37
34
  }
38
35
  return true;
39
36
  }
40
- function validateRuntimeFiles(prepared, files) {
41
- requireClosedContractArtifactSet(prepared.contract);
42
- const declared = new Map(prepared.contract.artifacts.map(artifact => [artifact.artifactSha256, artifact.runtime]));
43
- const supplied = new Map();
44
- for (const file of files) {
45
- if (sha256(file.bytes) !== file.sha256) {
46
- throw new Error(`Runtime artifact bytes do not match SHA-256 ${file.sha256}.`);
47
- }
48
- if (declared.get(file.sha256) !== file.runtime) {
49
- throw new Error(`Runtime artifact ${file.sha256} is not declared by the deployment contract as ${file.runtime}.`);
50
- }
51
- if (supplied.has(file.sha256)) {
52
- throw new Error(`Duplicate runtime artifact bytes supplied for ${file.sha256}.`);
53
- }
54
- if (file.bytes.byteLength < 1
55
- || file.bytes.byteLength > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxArtifactBytes) {
56
- throw new Error(`Runtime artifact ${file.sha256} exceeds the bundle byte limits.`);
57
- }
58
- supplied.set(file.sha256, file.runtime);
59
- }
60
- for (const [digest, runtime] of declared) {
61
- if (supplied.get(digest) !== runtime) {
62
- throw new Error(`Deployment bundle is missing ${runtime} runtime artifact ${digest}.`);
63
- }
64
- }
65
- return Object.freeze([...files].sort((left, right) => {
66
- const leftPath = runtimeArtifactPath(left.runtime, left.sha256);
67
- const rightPath = runtimeArtifactPath(right.runtime, right.sha256);
68
- return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
69
- }));
70
- }
71
- function requireClosedContractArtifactSet(contract) {
72
- const referenced = new Map();
73
- for (const query of contract.queries) {
74
- if (query.implementation.kind === 'runtime-reference') {
75
- referenced.set(query.implementation.artifactSha256, query.implementation.runtime);
76
- }
77
- }
78
- if (referenced.size !== contract.artifacts.length) {
79
- throw new Error('Deployment contract contains missing or unreferenced runtime artifacts.');
80
- }
81
- for (const artifact of contract.artifacts) {
82
- if (referenced.get(artifact.artifactSha256) !== artifact.runtime) {
83
- throw new Error(`Deployment runtime artifact ${artifact.artifactSha256} is not referenced by a named query.`);
84
- }
85
- }
86
- }
87
37
  function validateSourceSnapshot(source) {
88
38
  if (!source)
89
39
  return undefined;
@@ -122,12 +72,11 @@ function validateSourceSnapshot(source) {
122
72
  ...(source.revision ? { revision: Object.freeze({ ...source.revision }) } : {}),
123
73
  });
124
74
  }
125
- export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFiles, sourceSnapshot) {
75
+ export async function writeDeploymentBundle(outputDirectory, prepared, sourceSnapshot) {
126
76
  const destination = path.resolve(outputDirectory);
127
77
  if (destination === path.parse(destination).root) {
128
78
  throw new Error('The deployment bundle output cannot be a filesystem root.');
129
79
  }
130
- const files = validateRuntimeFiles(prepared, runtimeFiles);
131
80
  const source = validateSourceSnapshot(sourceSnapshot);
132
81
  const deploymentBytes = utf8Encoder.encode(`${prepared.canonical}\n`);
133
82
  const manifestInput = {
@@ -139,12 +88,8 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
139
88
  sha256: sha256(deploymentBytes),
140
89
  byteLength: deploymentBytes.byteLength,
141
90
  },
142
- artifacts: files.map(file => ({
143
- runtime: file.runtime,
144
- path: runtimeArtifactPath(file.runtime, file.sha256),
145
- sha256: file.sha256,
146
- byteLength: file.bytes.byteLength,
147
- })),
91
+ // A deployment carries datasets: there is nothing else to put here.
92
+ artifacts: [],
148
93
  ...(source ? {
149
94
  source: {
150
95
  root: SOURCE_ROOT,
@@ -164,12 +109,7 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
164
109
  await mkdir(parent, { recursive: true });
165
110
  const staging = await mkdtemp(path.join(parent, `.${path.basename(destination)}.tmp-`));
166
111
  try {
167
- if (files.length > 0)
168
- await mkdir(path.join(staging, 'artifacts'));
169
112
  await writeFile(path.join(staging, DEPLOYMENT_BUNDLE_CONTRACT), deploymentBytes, { flag: 'wx' });
170
- for (const file of files) {
171
- await writeFile(path.join(staging, runtimeArtifactPath(file.runtime, file.sha256)), file.bytes, { flag: 'wx' });
172
- }
173
113
  for (const file of source?.files ?? []) {
174
114
  const outputPath = path.join(staging, SOURCE_ROOT, ...file.path.split('/'));
175
115
  await mkdir(path.dirname(outputPath), { recursive: true });
@@ -209,38 +149,3 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
209
149
  contract: prepared.contract,
210
150
  });
211
151
  }
212
- async function readBoundedAbsoluteRegularFile(absolutePath, displayPath, maximum) {
213
- let handle;
214
- try {
215
- const initialStat = await lstat(absolutePath);
216
- if (initialStat.isSymbolicLink()) {
217
- throw new Error(`Bundle entry must not be a symbolic link: ${displayPath}`);
218
- }
219
- if (!initialStat.isFile()) {
220
- throw new Error(`Bundle entry is not a regular file: ${displayPath}`);
221
- }
222
- if (initialStat.size < 1 || initialStat.size > maximum) {
223
- throw new Error(`Bundle entry exceeds its byte limit: ${displayPath}`);
224
- }
225
- handle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
226
- const stat = await handle.stat();
227
- if (!stat.isFile())
228
- throw new Error(`Bundle entry is not a regular file: ${displayPath}`);
229
- if (stat.size < 1 || stat.size > maximum) {
230
- throw new Error(`Bundle entry exceeds its byte limit: ${displayPath}`);
231
- }
232
- return await handle.readFile();
233
- }
234
- catch (error) {
235
- if (errorCode(error) === 'ELOOP') {
236
- throw new Error(`Bundle entry must not be a symbolic link: ${displayPath}`);
237
- }
238
- throw error;
239
- }
240
- finally {
241
- await handle?.close();
242
- }
243
- }
244
- export async function readDeploymentRuntimeFile(filePath) {
245
- return readBoundedAbsoluteRegularFile(path.resolve(filePath), filePath, DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxArtifactBytes);
246
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.19.1",
3
+ "version": "1.20.0",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -30,8 +30,8 @@
30
30
  "open": "^10.0.0",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/deployment": "0.8.0",
34
- "@hypequery/protocol": "0.13.0"
33
+ "@hypequery/deployment": "0.9.0",
34
+ "@hypequery/protocol": "0.14.0"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "1.3.0"
@@ -51,8 +51,8 @@
51
51
  "@vitest/coverage-v8": "^3.2.6",
52
52
  "typescript": "^5.7.3",
53
53
  "vitest": "^3.2.6",
54
- "@hypequery/mcp": "0.7.0",
55
- "@hypequery/serve": "0.17.1"
54
+ "@hypequery/mcp": "0.8.1",
55
+ "@hypequery/serve": "0.18.0"
56
56
  },
57
57
  "repository": {
58
58
  "type": "git",
@@ -1,9 +0,0 @@
1
- export interface NodeRuntimeArtifact {
2
- readonly bytes: Uint8Array;
3
- readonly artifactSha256: string;
4
- readonly entrypointPrefix: string;
5
- readonly runtimeEntrypoints: readonly string[];
6
- }
7
- export declare function getDeploymentRuntimeEntrypoints(input: unknown): readonly string[];
8
- export declare function buildNodeRuntimeArtifact(apiPath: string, runtimeEntrypoints: readonly string[], entrypointPrefix?: string): Promise<NodeRuntimeArtifact>;
9
- //# sourceMappingURL=deployment-runtime-artifact.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deployment-runtime-artifact.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-runtime-artifact.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CAChD;AASD,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CA6BjF;AAiED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,SAAS,MAAM,EAAE,EACrC,gBAAgB,SAAY,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAmD9B"}
@@ -1,162 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import path from 'node:path';
3
- import { parseProtocolIdentifier, parseProtocolQualifiedIdentifier, } from '@hypequery/protocol';
4
- import { build } from 'esbuild';
5
- import { findNearestTsconfig } from './load-api.js';
6
- const deploymentBuildSourceSymbol = Symbol.for('hypequery.deployment-build-source.v1');
7
- function executableQuery(query) {
8
- return typeof query === 'function'
9
- || (typeof query === 'object'
10
- && query !== null
11
- && typeof query.run === 'function');
12
- }
13
- export function getDeploymentRuntimeEntrypoints(input) {
14
- if (typeof input !== 'object' || input === null) {
15
- throw new Error('Invalid API module: deployment build metadata is unavailable.');
16
- }
17
- const api = input;
18
- const source = api[deploymentBuildSourceSymbol];
19
- if (source?.version !== 1 || !Array.isArray(source.runtimeEntrypoints)) {
20
- throw new Error('The exported API does not expose deployment build metadata. '
21
- + 'Upgrade @hypequery/serve before building runtime artifacts.');
22
- }
23
- const names = source.runtimeEntrypoints.map((name) => {
24
- try {
25
- return parseProtocolIdentifier(name);
26
- }
27
- catch {
28
- throw new Error(`Invalid runtime entrypoint name: ${String(name)}`);
29
- }
30
- });
31
- if (new Set(names).size !== names.length) {
32
- throw new Error('Deployment build metadata contains duplicate runtime entrypoints.');
33
- }
34
- for (const name of names) {
35
- if (!executableQuery(api.queries?.[name]?.query)) {
36
- throw new Error(`Serve query "${name}" does not expose an executable runtime handler.`);
37
- }
38
- }
39
- return Object.freeze([...names].sort());
40
- }
41
- function projectRelativeImport(apiPath) {
42
- const resolved = path.resolve(process.cwd(), apiPath);
43
- const relative = path.relative(process.cwd(), resolved);
44
- if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
45
- throw new Error('The API module must be inside the current project for deterministic bundling.');
46
- }
47
- const portable = relative.split(path.sep).join('/');
48
- return {
49
- resolved,
50
- specifier: portable.startsWith('.') ? portable : `./${portable}`,
51
- };
52
- }
53
- function runtimeEntrySource(apiSpecifier, runtimeEntrypoints, entrypointPrefix) {
54
- const prefix = parseProtocolQualifiedIdentifier(entrypointPrefix).split('.');
55
- const names = JSON.stringify(runtimeEntrypoints);
56
- let registryExpression = 'runtimeEntries';
57
- for (let index = prefix.length - 1; index >= 1; index -= 1) {
58
- registryExpression = `Object.freeze({ ${prefix[index]}: ${registryExpression} })`;
59
- }
60
- return [
61
- `import * as apiModule from ${JSON.stringify(apiSpecifier)};`,
62
- 'const api = apiModule.api ?? Reflect.get(apiModule, "default");',
63
- 'if (!api || typeof api !== "object") throw new Error("Runtime API export is unavailable.");',
64
- 'const resolveQuery = (name) => {',
65
- ' if (typeof api.execute === "function") {',
66
- ' // Serve entrypoint names are validated against api.queries before bundling.',
67
- ' return (argument = {}) => api.execute(name, {',
68
- ' input: argument.input,',
69
- ' ...(argument.context === undefined ? {} : { context: argument.context }),',
70
- ' ...(argument.request === undefined ? {} : { request: argument.request }),',
71
- ' ...(argument.requestId === undefined ? {} : { requestId: argument.requestId }),',
72
- // A null principal means "no trusted principal"; forwarding it would skip the
73
- // API's own auth strategies instead of falling through to them.
74
- ' ...(argument.trustedAuth == null ? {} : { trustedAuth: argument.trustedAuth }),',
75
- ' });',
76
- ' }',
77
- ' const entry = api.queries?.[name];',
78
- ' const query = entry?.query;',
79
- ' let run;',
80
- ' if (typeof query === "function") run = query;',
81
- ' else if (query && typeof query === "object" && typeof query.run === "function") run = query.run.bind(query);',
82
- ' if (!run) throw new Error(`Runtime entrypoint ${name} is unavailable.`);',
83
- ' // Without a Serve execute() pipeline there is nothing to enforce roles, scopes,',
84
- ' // or tenancy, so refuse a trusted principal instead of silently ignoring it.',
85
- ' return (argument = {}) => {',
86
- ' if (argument?.trustedAuth != null) {',
87
- ' throw new Error(`Runtime entrypoint ${name} cannot enforce a trusted principal without a Serve execute() pipeline.`);',
88
- ' }',
89
- ' return run(argument);',
90
- ' };',
91
- '};',
92
- `const runtimeEntries = Object.freeze(Object.fromEntries(${names}.map(name => [name, resolveQuery(name)])));`,
93
- `export const ${prefix[0]} = ${registryExpression};`,
94
- '',
95
- ].join('\n');
96
- }
97
- export async function buildNodeRuntimeArtifact(apiPath, runtimeEntrypoints, entrypointPrefix = 'queries') {
98
- if (runtimeEntrypoints.length === 0) {
99
- throw new Error('Cannot build a runtime artifact without Serve query entrypoints.');
100
- }
101
- const names = getValidatedEntrypointNames(runtimeEntrypoints);
102
- const { resolved, specifier } = projectRelativeImport(apiPath);
103
- let source;
104
- try {
105
- source = runtimeEntrySource(specifier, names, entrypointPrefix);
106
- }
107
- catch {
108
- throw new Error(`Invalid runtime entrypoint prefix: ${entrypointPrefix}`);
109
- }
110
- try {
111
- const result = await build({
112
- stdin: {
113
- contents: source,
114
- loader: 'ts',
115
- resolveDir: process.cwd(),
116
- sourcefile: 'hypequery-runtime-entry.ts',
117
- },
118
- absWorkingDir: process.cwd(),
119
- bundle: true,
120
- charset: 'utf8',
121
- format: 'esm',
122
- legalComments: 'none',
123
- logLevel: 'warning',
124
- minify: true,
125
- platform: 'node',
126
- sourcemap: false,
127
- target: ['node18'],
128
- treeShaking: true,
129
- tsconfig: await findNearestTsconfig(resolved) ?? undefined,
130
- write: false,
131
- });
132
- const output = result.outputFiles?.find(file => file.path.endsWith('.js'))
133
- ?? result.outputFiles?.[0];
134
- if (!output)
135
- throw new Error('esbuild produced no output');
136
- const bytes = output.contents;
137
- return Object.freeze({
138
- bytes,
139
- artifactSha256: createHash('sha256').update(bytes).digest('hex'),
140
- entrypointPrefix,
141
- runtimeEntrypoints: names,
142
- });
143
- }
144
- catch (error) {
145
- throw new Error(`Failed to build the Node runtime artifact from ${apiPath}.\n`
146
- + `Original error: ${error instanceof Error ? error.message : String(error)}`);
147
- }
148
- }
149
- function getValidatedEntrypointNames(input) {
150
- const names = input.map((name) => {
151
- try {
152
- return parseProtocolIdentifier(name);
153
- }
154
- catch {
155
- throw new Error(`Invalid runtime entrypoint name: ${name}`);
156
- }
157
- });
158
- if (new Set(names).size !== names.length) {
159
- throw new Error('Runtime entrypoint names must be unique.');
160
- }
161
- return Object.freeze([...names].sort());
162
- }