@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 +22 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -1
- package/dist/commands/deployment.d.ts +7 -1
- package/dist/commands/deployment.d.ts.map +1 -1
- package/dist/commands/deployment.js +99 -2
- package/package.json +3 -3
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;
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
40
|
+
"@hypequery/serve": "0.13.2"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|