@hypequery/cli 1.7.0 → 1.8.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
@@ -182,6 +182,28 @@ are still accepted for metadata-only validation.
182
182
  npx hypequery deployment:validate analytics/hypequery-deployment
183
183
  ```
184
184
 
185
+ ### `hypequery deployment:release`
186
+
187
+ Prepares a deterministic release request from a verified deployment bundle and
188
+ an explicit project/environment target. This command does not upload, authorize,
189
+ or execute the release.
190
+
191
+ ```bash
192
+ npx hypequery deployment:release analytics/hypequery-deployment \
193
+ --project my-project \
194
+ --environment production
195
+ ```
196
+
197
+ The default output is `analytics/hypequery-deployment.release.json`. It is
198
+ written beside the bundle because adding it inside the closed bundle would
199
+ invalidate bundle verification.
200
+
201
+ Options:
202
+
203
+ - `--project <project>`: required target project identifier
204
+ - `--environment <environment>`: required target environment identifier
205
+ - `--output <path>`: release JSON path, default beside the bundle
206
+
185
207
  ## Non-interactive Setup
186
208
 
187
209
  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;AAYpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA2JD,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;AAcpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAsKD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -6,7 +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
+ import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
10
10
  const program = new Command();
11
11
  function getCliVersion() {
12
12
  try {
@@ -129,6 +129,15 @@ program
129
129
  .action(runCommand(async (artifact) => {
130
130
  await validateDeploymentCommand(artifact);
131
131
  }));
132
+ program
133
+ .command('deployment:release <bundle>')
134
+ .description('Prepare a target-bound release from a verified deployment bundle')
135
+ .requiredOption('--project <project>', 'Target project identifier')
136
+ .requiredOption('--environment <environment>', 'Target environment identifier')
137
+ .option('-o, --output <path>', 'Release JSON path (default: beside the bundle)')
138
+ .action(runCommand(async (bundle, options) => {
139
+ await prepareDeploymentReleaseCommand(bundle, options);
140
+ }));
132
141
  // Help command
133
142
  program
134
143
  .command('help [command]')
@@ -161,6 +170,7 @@ program.on('--help', () => {
161
170
  console.log(' hypequery generate:manifest analytics/api.ts --output analytics/hypequery-manifest.json');
162
171
  console.log(' hypequery deployment:build analytics/api.ts');
163
172
  console.log(' hypequery deployment:validate analytics/hypequery-deployment');
173
+ console.log(' hypequery deployment:release analytics/hypequery-deployment --project my-project --environment production');
164
174
  console.log('');
165
175
  console.log('Docs: https://hypequery.com/docs');
166
176
  });
@@ -1,4 +1,4 @@
1
- import { type ProtocolDeploymentContract } from '@hypequery/protocol';
1
+ import { type ProtocolDeploymentContract, type ProtocolDeploymentReleaseEnvelope } from '@hypequery/protocol';
2
2
  export interface BuildDeploymentOptions {
3
3
  bundleOutput?: string;
4
4
  output?: string;
@@ -9,6 +9,12 @@ export interface BuildDeploymentOptions {
9
9
  entrypointPrefix?: string;
10
10
  hashOutput?: string;
11
11
  }
12
+ export interface PrepareDeploymentReleaseOptions {
13
+ project?: string;
14
+ environment?: string;
15
+ output?: string;
16
+ }
12
17
  export declare function buildDeploymentCommand(apiPath: string | undefined, options?: BuildDeploymentOptions): Promise<ProtocolDeploymentContract>;
13
18
  export declare function validateDeploymentCommand(artifactPath: string | undefined): Promise<ProtocolDeploymentContract>;
19
+ export declare function prepareDeploymentReleaseCommand(bundlePath: string | undefined, options?: PrepareDeploymentReleaseOptions): Promise<ProtocolDeploymentReleaseEnvelope>;
14
20
  //# sourceMappingURL=deployment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAiB7B,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;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"}
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;AAiB7B,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;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;AA0DD,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,iCAAiC,CAAC,CA8C5C"}
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
2
+ import { lstat, mkdir, readFile, realpath, stat, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import { prepareProtocolDeploymentContract, } from '@hypequery/protocol';
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
7
  import { loadApiModule } from '../utils/load-api.js';
@@ -217,3 +217,100 @@ export async function validateDeploymentCommand(artifactPath) {
217
217
  logger.info(`Identity: ${digest}`);
218
218
  return contract;
219
219
  }
220
+ function assertOutputOutsideBundle(bundlePath, outputPath) {
221
+ const bundle = path.resolve(bundlePath);
222
+ const output = path.resolve(outputPath);
223
+ const relative = path.relative(bundle, output);
224
+ const outside = relative === '..'
225
+ || relative.startsWith(`..${path.sep}`)
226
+ || path.isAbsolute(relative);
227
+ if (relative === '' || !outside) {
228
+ throw new Error('--output must be outside the closed deployment bundle directory.');
229
+ }
230
+ }
231
+ async function prospectiveRealPath(inputPath) {
232
+ let current = path.resolve(inputPath);
233
+ const missingSegments = [];
234
+ for (;;) {
235
+ try {
236
+ return path.join(await realpath(current), ...missingSegments);
237
+ }
238
+ catch (error) {
239
+ if (!(typeof error === 'object' && error !== null && 'code' in error
240
+ && error.code === 'ENOENT')) {
241
+ throw error;
242
+ }
243
+ try {
244
+ const stat = await lstat(current);
245
+ if (stat.isSymbolicLink()) {
246
+ throw new Error('--output must not traverse a dangling symbolic link.');
247
+ }
248
+ }
249
+ catch (lstatError) {
250
+ if (!(typeof lstatError === 'object' && lstatError !== null && 'code' in lstatError
251
+ && lstatError.code === 'ENOENT')) {
252
+ throw lstatError;
253
+ }
254
+ }
255
+ const parent = path.dirname(current);
256
+ if (parent === current)
257
+ throw error;
258
+ missingSegments.unshift(path.basename(current));
259
+ current = parent;
260
+ }
261
+ }
262
+ }
263
+ async function assertOutputIsNotSymbolicLink(outputPath) {
264
+ try {
265
+ const outputStat = await lstat(outputPath);
266
+ if (outputStat.isSymbolicLink()) {
267
+ throw new Error('--output must not be a symbolic link.');
268
+ }
269
+ }
270
+ catch (error) {
271
+ if (!(typeof error === 'object' && error !== null && 'code' in error
272
+ && error.code === 'ENOENT')) {
273
+ throw error;
274
+ }
275
+ }
276
+ }
277
+ export async function prepareDeploymentReleaseCommand(bundlePath, options = {}) {
278
+ if (!bundlePath) {
279
+ throw new Error('Missing deployment bundle path.\n\n'
280
+ + 'Usage: hypequery deployment:release analytics/hypequery-deployment '
281
+ + '--project <project> --environment <environment>');
282
+ }
283
+ if (options.project === undefined) {
284
+ throw new Error('Missing required --project <project>.');
285
+ }
286
+ if (options.environment === undefined) {
287
+ throw new Error('Missing required --environment <environment>.');
288
+ }
289
+ const outputPath = options.output ?? `${bundlePath.replace(/[\\/]+$/, '')}.release.json`;
290
+ assertOutputOutsideBundle(bundlePath, outputPath);
291
+ let bundle;
292
+ try {
293
+ bundle = await verifyDeploymentBundle(bundlePath);
294
+ }
295
+ catch (error) {
296
+ throw new Error(`Cannot prepare a release from an invalid deployment bundle: ${bundlePath}\n\n`
297
+ + (error instanceof Error ? error.message : String(error)));
298
+ }
299
+ assertOutputOutsideBundle(bundle.directory, await prospectiveRealPath(outputPath));
300
+ const prepared = prepareProtocolDeploymentReleaseEnvelope({
301
+ kind: 'hypequery-deployment-release',
302
+ version: 1,
303
+ bundleIdentity: bundle.identity,
304
+ target: {
305
+ project: options.project,
306
+ environment: options.environment,
307
+ },
308
+ });
309
+ await mkdir(path.dirname(outputPath), { recursive: true });
310
+ await assertOutputIsNotSymbolicLink(outputPath);
311
+ await writeFile(outputPath, `${prepared.canonical}\n`, 'utf8');
312
+ logger.success(`Deployment release written to ${outputPath}`);
313
+ logger.info(`Release identity: ${prepared.identity}`);
314
+ logger.info(`Bundle identity: ${bundle.identity}`);
315
+ return prepared.release;
316
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "open": "^10.0.0",
20
20
  "ora": "^8.0.1",
21
21
  "prompts": "^2.4.2",
22
- "@hypequery/protocol": "0.4.0"
22
+ "@hypequery/protocol": "0.5.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "@hypequery/clickhouse": "*",
@@ -37,7 +37,7 @@
37
37
  "typescript": "^5.7.3",
38
38
  "vitest": "^3.2.6",
39
39
  "@hypequery/clickhouse": "2.4.0",
40
- "@hypequery/serve": "0.13.1"
40
+ "@hypequery/serve": "0.13.2"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",