@hypequery/cli 1.15.0 → 1.16.1
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 +28 -0
- package/dist/cli.js +1 -1
- package/dist/commands/login.d.ts +2 -3
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +16 -22
- package/dist/utils/deployment-bundle.d.ts +1 -0
- package/dist/utils/deployment-bundle.d.ts.map +1 -1
- package/dist/utils/deployment-source-snapshot.d.ts.map +1 -1
- package/dist/utils/deployment-source-snapshot.js +6 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -251,6 +251,34 @@ Options:
|
|
|
251
251
|
|
|
252
252
|
## Non-interactive Setup
|
|
253
253
|
|
|
254
|
+
### Cloud deployment targets in CI
|
|
255
|
+
|
|
256
|
+
Login selects a stable Cloud deployment target. The deployment itself captures
|
|
257
|
+
the checked-out Git branch, commit, and dirty state as source provenance; none
|
|
258
|
+
of those values select or create an environment. Select the destination
|
|
259
|
+
explicitly when the same branch or commit deploys to more than one environment:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npx hypequery login --environment development
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
For CI, create a target-scoped API key in Cloud for each environment and store
|
|
266
|
+
it as a separate secret. The explicit release target and its matching key make
|
|
267
|
+
the destination independent of the source branch:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
HYPEQUERY_API_TOKEN="$HYPEQUERY_DEV_TOKEN" npx hypequery deploy analytics/api.ts \
|
|
271
|
+
--project acme:analytics \
|
|
272
|
+
--environment development
|
|
273
|
+
|
|
274
|
+
HYPEQUERY_API_TOKEN="$HYPEQUERY_PROD_TOKEN" npx hypequery deploy analytics/api.ts \
|
|
275
|
+
--project acme:analytics \
|
|
276
|
+
--environment production
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Set `HYPEQUERY_DEPLOYMENT_ENDPOINT` for both jobs. A key is scoped to one target,
|
|
280
|
+
so a production key cannot submit a development release or vice versa.
|
|
281
|
+
|
|
254
282
|
For ClickHouse, `hypequery init --no-interactive` reads:
|
|
255
283
|
|
|
256
284
|
- `CLICKHOUSE_URL` or deprecated `CLICKHOUSE_HOST`
|
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ program
|
|
|
34
34
|
.command('login')
|
|
35
35
|
.description('Authorize this machine with Hypequery Cloud')
|
|
36
36
|
.option('--cloud-url <url>', 'Cloud origin (or HYPEQUERY_CLOUD_URL)')
|
|
37
|
-
.option('--
|
|
37
|
+
.option('--environment <environment>', 'Stable deployment target')
|
|
38
38
|
.action(runCommand(async (options) => {
|
|
39
39
|
await loginCommand(options);
|
|
40
40
|
}));
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { deleteCloudCredential, loadCloudCredential, saveCloudCredential } from '../utils/cloud-credential-store.js';
|
|
2
2
|
export interface LoginOptions {
|
|
3
3
|
readonly cloudUrl?: string;
|
|
4
|
-
/**
|
|
5
|
-
readonly
|
|
4
|
+
/** Stable Cloud deployment target. */
|
|
5
|
+
readonly environment?: string;
|
|
6
6
|
}
|
|
7
7
|
export interface LoginDependencies {
|
|
8
8
|
readonly fetch?: typeof fetch;
|
|
9
|
-
readonly getGitBranch?: () => Promise<string | null>;
|
|
10
9
|
readonly openBrowser?: (url: string) => Promise<unknown>;
|
|
11
10
|
readonly now?: () => number;
|
|
12
11
|
readonly requestTimeoutMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,qBAAqB,EACrB,mBAAmB,EAGnB,mBAAmB,EAEpB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,qBAAqB,EACrB,mBAAmB,EAGnB,mBAAmB,EAEpB,MAAM,oCAAoC,CAAC;AAgB5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACrD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,qBAAqB,CAAC;IACzD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAsJD,wBAAsB,YAAY,CAChC,OAAO,GAAE,YAAiB,EAC1B,YAAY,GAAE,iBAAsB,iBAyErC;AAED,wBAAsB,aAAa,CAAC,YAAY,GAAE,kBAAuB,iBAoCxE"}
|
package/dist/commands/login.js
CHANGED
|
@@ -3,7 +3,6 @@ import { createServer } from 'node:http';
|
|
|
3
3
|
import open from 'open';
|
|
4
4
|
import { validateProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
5
5
|
import { CLOUD_DEPLOYMENT_SCOPE, deleteCloudCredential, loadCloudCredential, normalizeCloudDeploymentEndpoint, normalizeCloudOrigin, saveCloudCredential, } from '../utils/cloud-credential-store.js';
|
|
6
|
-
import { currentGitBranch } from '../utils/git-branch.js';
|
|
7
6
|
import { logger } from '../utils/logger.js';
|
|
8
7
|
const DEFAULT_CLOUD_URL = 'https://cloud.hypequery.com';
|
|
9
8
|
const LOGIN_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -17,17 +16,6 @@ const MAX_TOKEN_LIFETIME_MS = 13 * 60 * 60_000;
|
|
|
17
16
|
// CLI the day Cloud rotates its token format.
|
|
18
17
|
const TOKEN_PATTERN = /^hqdp_[A-Za-z0-9_-]{16,512}$/;
|
|
19
18
|
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
20
|
-
/**
|
|
21
|
-
* Branch names carry internal detail (customer names, embargoed identifiers),
|
|
22
|
-
* so both an explicit `--no-branch` and the environment variable suppress the
|
|
23
|
-
* parameter. The flag wins because it is the more specific signal.
|
|
24
|
-
*/
|
|
25
|
-
function branchContextEnabled(options) {
|
|
26
|
-
if (options.branch === false)
|
|
27
|
-
return false;
|
|
28
|
-
const configured = process.env.HYPEQUERY_CLI_SEND_BRANCH?.trim().toLowerCase();
|
|
29
|
-
return configured !== '0' && configured !== 'false' && configured !== 'no';
|
|
30
|
-
}
|
|
31
19
|
function callbackHtml(success) {
|
|
32
20
|
const title = success ? 'CLI authorized' : 'Authorization failed';
|
|
33
21
|
const message = success
|
|
@@ -174,20 +162,26 @@ export async function loginCommand(options = {}, dependencies = {}) {
|
|
|
174
162
|
const state = randomBytes(24).toString('base64url');
|
|
175
163
|
const verifier = randomBytes(32).toString('base64url');
|
|
176
164
|
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
165
|
+
let environment;
|
|
166
|
+
if (options.environment !== undefined) {
|
|
167
|
+
try {
|
|
168
|
+
environment = validateProtocolDeploymentReleaseTarget({
|
|
169
|
+
project: 'target',
|
|
170
|
+
environment: options.environment,
|
|
171
|
+
}).environment;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
throw new Error('Invalid deployment environment. Use letters, numbers, dots, underscores, colons, or hyphens.');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
183
177
|
const callback = await callbackServer(state, dependencies.timeoutMs ?? LOGIN_TIMEOUT_MS);
|
|
184
178
|
const authorizeUrl = new URL('/cli/authorize', origin);
|
|
185
179
|
authorizeUrl.searchParams.set('redirect_uri', callback.redirectUri);
|
|
186
180
|
authorizeUrl.searchParams.set('code_challenge', challenge);
|
|
187
181
|
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
188
182
|
authorizeUrl.searchParams.set('state', state);
|
|
189
|
-
if (
|
|
190
|
-
authorizeUrl.searchParams.set('
|
|
183
|
+
if (environment)
|
|
184
|
+
authorizeUrl.searchParams.set('environment', environment);
|
|
191
185
|
try {
|
|
192
186
|
logger.info('Opening your browser to authorize Hypequery CLI…');
|
|
193
187
|
await (dependencies.openBrowser ?? open)(authorizeUrl.toString());
|
|
@@ -215,8 +209,8 @@ export async function loginCommand(options = {}, dependencies = {}) {
|
|
|
215
209
|
const credential = tokenResponse(body, origin, (dependencies.now ?? Date.now)());
|
|
216
210
|
await (dependencies.saveCredential ?? saveCloudCredential)(credential);
|
|
217
211
|
logger.success('Logged in to Hypequery Cloud');
|
|
218
|
-
// Cloud resolves the target,
|
|
219
|
-
//
|
|
212
|
+
// Cloud resolves the target, so print what was actually issued rather than
|
|
213
|
+
// what was requested.
|
|
220
214
|
if (credential.target) {
|
|
221
215
|
logger.info(`Deployments target ${credential.target.project} / ${credential.target.environment}`);
|
|
222
216
|
}
|
|
@@ -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;
|
|
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 +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;
|
|
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;AA0EhC,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,8BAA8B,CAAC,CAkDzC"}
|
|
@@ -3,6 +3,7 @@ import { constants, lstat, open, realpath } from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS, } from '@hypequery/protocol';
|
|
5
5
|
import { build } from 'esbuild';
|
|
6
|
+
import { currentGitBranch } from './git-branch.js';
|
|
6
7
|
import { findNearestTsconfig } from './load-api.js';
|
|
7
8
|
function portableProjectPath(projectRoot, absolutePath) {
|
|
8
9
|
const relative = path.relative(projectRoot, absolutePath);
|
|
@@ -47,7 +48,10 @@ function gitOutput(projectRoot, arguments_) {
|
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
async function sourceRevision(projectRoot) {
|
|
50
|
-
const commit = await
|
|
51
|
+
const [commit, branch] = await Promise.all([
|
|
52
|
+
gitOutput(projectRoot, ['rev-parse', 'HEAD']),
|
|
53
|
+
currentGitBranch(projectRoot),
|
|
54
|
+
]);
|
|
51
55
|
if (!commit || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
|
|
52
56
|
return undefined;
|
|
53
57
|
const status = await gitOutput(projectRoot, ['status', '--porcelain', '--untracked-files=all']);
|
|
@@ -57,6 +61,7 @@ async function sourceRevision(projectRoot) {
|
|
|
57
61
|
kind: 'git',
|
|
58
62
|
commit,
|
|
59
63
|
dirty: status.length > 0,
|
|
64
|
+
...(branch ? { branch } : {}),
|
|
60
65
|
});
|
|
61
66
|
}
|
|
62
67
|
export async function captureDeploymentSourceSnapshot(apiPath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
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.7.
|
|
23
|
-
"@hypequery/protocol": "0.10.
|
|
22
|
+
"@hypequery/deployment": "0.7.1",
|
|
23
|
+
"@hypequery/protocol": "0.10.1"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
26
|
"@napi-rs/keyring": "1.3.0"
|