@hypequery/cli 1.4.0 → 1.5.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/README.md CHANGED
@@ -7,6 +7,7 @@ Use it to:
7
7
  - generate schema types from ClickHouse or embedded chDB
8
8
  - scaffold `analytics/` files
9
9
  - run the local dev server with docs
10
+ - build and validate portable deployment contracts
10
11
 
11
12
  ## Quick Start
12
13
 
@@ -135,6 +136,39 @@ npx hypequery generate:manifest analytics/api.ts --output analytics/hypequery-ma
135
136
  The output is the exact serializable JSON returned by `api.manifest()`, including
136
137
  semantic keys such as `dataset:orders`.
137
138
 
139
+ ### `hypequery deployment:build`
140
+
141
+ Builds deployment metadata for an exported HypeQuery API and writes the
142
+ artifact with its identity sidecar.
143
+
144
+ ```bash
145
+ npx hypequery deployment:build analytics/api.ts \
146
+ --runtime-artifact 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
147
+ ```
148
+
149
+ The runtime artifact digest is required when named Serve handlers cannot be
150
+ lowered to a portable implementation. Dataset-only APIs can omit it.
151
+
152
+ Options:
153
+
154
+ - `--output <path>`: default `analytics/hypequery-deployment.json`
155
+ - `--runtime <runtime>`: `node` (default) or `python`
156
+ - `--runtime-artifact <sha256>`: lowercase SHA-256 of the built runtime artifact
157
+ - `--entrypoint-prefix <prefix>`: default `queries`
158
+ - `--hash-output <path>`: default `<output>.sha256`
159
+
160
+ The identity sidecar is not compatible with `sha256sum -c`. Use
161
+ `deployment:validate` to validate the artifact and report its identity.
162
+
163
+ ### `hypequery deployment:validate`
164
+
165
+ Validates an existing deployment artifact and reports its datasets, queries,
166
+ runtime artifacts, and identity.
167
+
168
+ ```bash
169
+ npx hypequery deployment:validate analytics/hypequery-deployment.json
170
+ ```
171
+
138
172
  ## Non-interactive Setup
139
173
 
140
174
  For ClickHouse, `hypequery init --no-interactive` reads:
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;AAOpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAmID,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;AAYpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAwJD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { devCommand } from './commands/dev.js';
6
6
  import { generateCommand } from './commands/generate.js';
7
7
  import { generateDatasetsCommand } from './commands/generate-datasets.js';
8
8
  import { generateManifestCommand } from './commands/generate-manifest.js';
9
+ import { buildDeploymentCommand, validateDeploymentCommand, } from './commands/deployment.js';
9
10
  const program = new Command();
10
11
  function getCliVersion() {
11
12
  try {
@@ -108,6 +109,23 @@ program
108
109
  .action(runCommand(async (api, options) => {
109
110
  await generateManifestCommand(api, options);
110
111
  }));
112
+ program
113
+ .command('deployment:build <api>')
114
+ .description('Build a validated, canonical deployment contract and identity')
115
+ .option('-o, --output <path>', 'Output JSON file (default: analytics/hypequery-deployment.json)')
116
+ .option('--runtime <runtime>', 'Runtime for non-portable handlers: node or python (default: node)')
117
+ .option('--runtime-artifact <sha256>', 'SHA-256 identity of the built runtime artifact')
118
+ .option('--entrypoint-prefix <prefix>', 'Runtime entrypoint prefix (default: queries)')
119
+ .option('--hash-output <path>', 'Deployment identity sidecar path (default: <output>.sha256)')
120
+ .action(runCommand(async (api, options) => {
121
+ await buildDeploymentCommand(api, options);
122
+ }));
123
+ program
124
+ .command('deployment:validate <artifact>')
125
+ .description('Validate a deployment contract and report its identity')
126
+ .action(runCommand(async (artifact) => {
127
+ await validateDeploymentCommand(artifact);
128
+ }));
111
129
  // Help command
112
130
  program
113
131
  .command('help [command]')
@@ -138,6 +156,8 @@ program.on('--help', () => {
138
156
  console.log(' hypequery generate:types --output analytics/schema.ts');
139
157
  console.log(' hypequery generate:datasets');
140
158
  console.log(' hypequery generate:manifest analytics/api.ts --output analytics/hypequery-manifest.json');
159
+ console.log(' hypequery deployment:build analytics/api.ts --runtime-artifact <sha256>');
160
+ console.log(' hypequery deployment:validate analytics/hypequery-deployment.json');
141
161
  console.log('');
142
162
  console.log('Docs: https://hypequery.com/docs');
143
163
  });
@@ -0,0 +1,11 @@
1
+ import { type ProtocolDeploymentContract } from '@hypequery/protocol';
2
+ export interface BuildDeploymentOptions {
3
+ output?: string;
4
+ runtime?: 'node' | 'python';
5
+ runtimeArtifact?: string;
6
+ entrypointPrefix?: string;
7
+ hashOutput?: string;
8
+ }
9
+ export declare function buildDeploymentCommand(apiPath: string | undefined, options?: BuildDeploymentOptions): Promise<ProtocolDeploymentContract>;
10
+ export declare function validateDeploymentCommand(artifactPath: string | undefined): Promise<ProtocolDeploymentContract>;
11
+ //# sourceMappingURL=deployment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAM7B,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8BD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAuCrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CAoCrC"}
@@ -0,0 +1,84 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { prepareProtocolDeploymentContract, } from '@hypequery/protocol';
4
+ import { loadApiModule } from '../utils/load-api.js';
5
+ import { logger } from '../utils/logger.js';
6
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
7
+ function runtimeArtifact(options) {
8
+ if (options.runtimeArtifact === undefined)
9
+ return undefined;
10
+ if (!SHA256_PATTERN.test(options.runtimeArtifact)) {
11
+ throw new Error('--runtime-artifact must be a lowercase 64-character SHA-256 digest.');
12
+ }
13
+ const runtime = options.runtime ?? 'node';
14
+ if (runtime !== 'node' && runtime !== 'python') {
15
+ throw new Error('--runtime must be either node or python.');
16
+ }
17
+ return {
18
+ runtime,
19
+ artifactSha256: options.runtimeArtifact,
20
+ ...(options.entrypointPrefix !== undefined
21
+ ? { entrypointPrefix: options.entrypointPrefix }
22
+ : {}),
23
+ };
24
+ }
25
+ export async function buildDeploymentCommand(apiPath, options = {}) {
26
+ if (!apiPath) {
27
+ throw new Error('Missing API module path.\n\n'
28
+ + 'Usage: hypequery deployment:build analytics/api.ts');
29
+ }
30
+ const artifact = runtimeArtifact(options);
31
+ const api = await loadApiModule(apiPath);
32
+ if (typeof api.deploymentContract !== 'function') {
33
+ throw new Error(`Invalid API module: ${apiPath}\n\n`
34
+ + 'The exported API must provide deploymentContract(). '
35
+ + 'Upgrade @hypequery/serve and export the value returned by createAPI() or serve().');
36
+ }
37
+ const contract = api.deploymentContract(artifact ? { runtimeArtifact: artifact } : {});
38
+ const prepared = prepareProtocolDeploymentContract(contract);
39
+ const { canonical, contract: validated, identity: digest } = prepared;
40
+ const outputPath = options.output ?? 'analytics/hypequery-deployment.json';
41
+ const hashOutputPath = options.hashOutput ?? `${outputPath}.sha256`;
42
+ const identitySidecar = [
43
+ '# Hypequery deployment identity v1; not a file checksum or sha256sum input.',
44
+ '# SHA-256(UTF-8("hypequery:deployment:v1") || 0x00 || RFC 8785 canonical bytes); '
45
+ + 'the output newline is excluded.',
46
+ `${digest} ${path.basename(outputPath)}`,
47
+ '',
48
+ ].join('\n');
49
+ await mkdir(path.dirname(outputPath), { recursive: true });
50
+ await mkdir(path.dirname(hashOutputPath), { recursive: true });
51
+ await writeFile(outputPath, `${canonical}\n`, 'utf8');
52
+ await writeFile(hashOutputPath, identitySidecar, 'utf8');
53
+ logger.success(`Deployment contract written to ${outputPath}`);
54
+ logger.info(`Identity: ${digest}`);
55
+ return validated;
56
+ }
57
+ export async function validateDeploymentCommand(artifactPath) {
58
+ if (!artifactPath) {
59
+ throw new Error('Missing deployment artifact path.\n\n'
60
+ + 'Usage: hypequery deployment:validate analytics/hypequery-deployment.json');
61
+ }
62
+ let input;
63
+ try {
64
+ input = JSON.parse(await readFile(artifactPath, 'utf8'));
65
+ }
66
+ catch (error) {
67
+ throw new Error(`Invalid deployment JSON: ${artifactPath}\n\n`
68
+ + (error instanceof Error ? error.message : String(error)));
69
+ }
70
+ let prepared;
71
+ try {
72
+ prepared = prepareProtocolDeploymentContract(input);
73
+ }
74
+ catch (error) {
75
+ throw new Error(`Invalid deployment contract: ${artifactPath}\n\n`
76
+ + (error instanceof Error ? error.message : String(error)));
77
+ }
78
+ const { contract, identity: digest } = prepared;
79
+ logger.success(`Valid deployment contract: ${artifactPath}`);
80
+ logger.info(`${contract.datasets.length} datasets, ${contract.queries.length} queries, `
81
+ + `${contract.artifacts.length} runtime artifacts`);
82
+ logger.info(`Identity: ${digest}`);
83
+ return contract;
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -18,7 +18,8 @@
18
18
  "esbuild": "^0.25.0",
19
19
  "open": "^10.0.0",
20
20
  "ora": "^8.0.1",
21
- "prompts": "^2.4.2"
21
+ "prompts": "^2.4.2",
22
+ "@hypequery/protocol": "0.3.0"
22
23
  },
23
24
  "peerDependencies": {
24
25
  "@hypequery/clickhouse": "*",
@@ -36,7 +37,7 @@
36
37
  "typescript": "^5.7.3",
37
38
  "vitest": "^3.2.6",
38
39
  "@hypequery/clickhouse": "2.4.0",
39
- "@hypequery/serve": "0.11.0"
40
+ "@hypequery/serve": "0.12.0"
40
41
  },
41
42
  "repository": {
42
43
  "type": "git",