@hypequery/cli 1.14.0 → 1.15.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
@@ -25,13 +25,6 @@ Or install it once:
25
25
  npm install -D @hypequery/cli
26
26
  ```
27
27
 
28
- Ship it to Hypequery Cloud:
29
-
30
- ```bash
31
- npx @hypequery/cli login
32
- npx @hypequery/cli deploy analytics/api.ts
33
- ```
34
-
35
28
  ## Commands
36
29
 
37
30
  ### `hypequery init`
@@ -156,9 +149,9 @@ semantic keys such as `dataset:orders`.
156
149
 
157
150
  Builds a closed deployment bundle for an exported HypeQuery API. The bundle
158
151
  contains canonical deployment metadata, every referenced runtime artifact, and
159
- a manifest that binds their exact bytes and identities. `hypequery deploy` runs
160
- this step for you; reach for it directly in CI pipelines that stage artifacts,
161
- or when you need the runtime options below.
152
+ a manifest that binds their exact bytes and identities. It is the first step of
153
+ a deployment pipeline, and produces the input `deployment:release` binds to a
154
+ target.
162
155
 
163
156
  ```bash
164
157
  npx hypequery deployment:build analytics/api.ts
@@ -200,112 +193,16 @@ are still accepted for metadata-only validation.
200
193
  npx hypequery deployment:validate analytics/hypequery-deployment
201
194
  ```
202
195
 
203
- ### `hypequery login`
204
-
205
- Authorizes the local CLI through your existing Hypequery Cloud browser
206
- session. The command uses an S256 PKCE loopback flow, then stores the
207
- target-scoped deployment token in the operating-system credential vault.
208
- Tokens expire after 12 hours.
209
-
210
- ```bash
211
- npx hypequery login
212
- ```
213
-
214
- Use `--cloud-url <origin>` or `HYPEQUERY_CLOUD_URL` for a self-hosted or local
215
- Cloud instance. HTTPS is required except for loopback development origins.
216
-
217
- Once logged in, `hypequery deploy` automatically uses the stored endpoint and
218
- token, and resolves the project and environment from the same stored profile.
219
- The deployment endpoint is returned by Cloud during the token exchange; it is
220
- the authenticated upload API, not the browser login endpoint. For
221
- non-interactive CI usage, set both `HYPEQUERY_API_TOKEN` and
222
- `HYPEQUERY_DEPLOYMENT_ENDPOINT` (or pass `--endpoint`).
223
-
224
- Because the CLI also loads a project `.env`, a `HYPEQUERY_API_TOKEN` left there
225
- takes precedence over the login flow and is rejected unless it is paired with an
226
- endpoint. Unset it to use `hypequery login`.
227
-
228
- Token storage requires an operating-system credential vault (Keychain,
229
- Credential Manager, or a Secret Service implementation such as
230
- gnome-keyring/KWallet). Headless environments without one should use
231
- `HYPEQUERY_API_TOKEN` instead of `hypequery login`.
232
-
233
- ### `hypequery logout`
234
-
235
- Revokes the current Cloud token and removes it from the operating-system
236
- credential vault:
237
-
238
- ```bash
239
- npx hypequery logout
240
- ```
241
-
242
- If the stored profile is unreadable, `logout` still removes the local state and
243
- reports that the token was not revoked; it expires on its own.
244
-
245
- ### `hypequery deploy`
246
-
247
- Builds a deployment bundle from an API module, prepares a target-bound release,
248
- and submits both. This is the primary Cloud workflow: sign in once, then deploy
249
- with one command.
250
-
251
- ```bash
252
- npx hypequery login
253
- npx hypequery deploy analytics/api.ts
254
- ```
255
-
256
- The bundle is written to `analytics/hypequery-deployment` and the release to
257
- `analytics/hypequery-deployment.release.json`. The target comes from the stored
258
- Cloud profile unless you override it:
259
-
260
- ```bash
261
- npx hypequery deploy analytics/api.ts \
262
- --project my-project \
263
- --environment production
264
- ```
265
-
266
- The deployment endpoint and token are resolved before the build, so a missing
267
- or expired login fails immediately rather than after bundling. The credential
268
- rules are the same as `hypequery deployment:submit` below.
269
-
270
- The three steps remain available individually as `deployment:build`,
271
- `deployment:release`, and `deployment:submit` for CI pipelines that stage
272
- artifacts, and for the cases `deploy` does not cover: `deploy` always builds the
273
- runtime artifact itself, so Python deployments and prebuilt runtime artifacts
274
- (`--runtime`, `--runtime-artifact`, `--runtime-file`) need `deployment:build`.
275
-
276
- Options:
277
-
278
- - `--project <project>`: target project identifier; defaults to the logged-in
279
- target and must be paired with `--environment`
280
- - `--environment <environment>`: target environment identifier; defaults to the
281
- logged-in target and must be paired with `--project`
282
- - `--bundle-output <directory>`: bundle directory, default
283
- `analytics/hypequery-deployment`
284
- - `--release-output <path>`: release JSON path, default beside the bundle
285
- - `--endpoint <url>`: HTTPS submission endpoint; requires `HYPEQUERY_API_TOKEN`
286
-
287
- > **Deprecated:** `hypequery deploy <bundle> --release <file>` still submits a
288
- > prebuilt bundle and prints a deprecation warning. Use
289
- > `hypequery deployment:submit` instead. `--release` cannot be combined with
290
- > `--project`, `--environment`, `--bundle-output`, or `--release-output`.
291
-
292
196
  ### `hypequery deployment:release`
293
197
 
294
198
  Prepares a deterministic release request from a verified deployment bundle and
295
199
  a project/environment target. This command does not upload, authorize, or
296
200
  execute the release.
297
201
 
298
- After `hypequery login` the target is read from the stored Cloud profile, and
299
- the resolved target is printed alongside the release identity:
300
-
301
- ```bash
302
- npx hypequery deployment:release analytics/hypequery-deployment
303
- ```
304
-
305
- To target something other than the logged-in project and environment, pass both
306
- flags. A half-specified override is rejected rather than completed from the
307
- stored profile, so an unset shell variable fails loudly instead of silently
308
- retargeting the release:
202
+ Pass the target as both flags. A half-specified target is rejected rather than
203
+ completed from anywhere else, so an unset shell variable fails loudly instead of
204
+ silently retargeting the release. The resolved target is printed alongside the
205
+ release identity:
309
206
 
310
207
  ```bash
311
208
  npx hypequery deployment:release analytics/hypequery-deployment \
@@ -319,36 +216,30 @@ invalidate bundle verification.
319
216
 
320
217
  Options:
321
218
 
322
- - `--project <project>`: target project identifier; defaults to the logged-in
323
- target and must be paired with `--environment`
324
- - `--environment <environment>`: target environment identifier; defaults to the
325
- logged-in target and must be paired with `--project`
219
+ - `--project <project>`: target project identifier; must be paired with
220
+ `--environment`
221
+ - `--environment <environment>`: target environment identifier; must be paired
222
+ with `--project`
326
223
  - `--output <path>`: release JSON path, default beside the bundle
327
224
 
328
225
  ### `hypequery deployment:submit`
329
226
 
330
227
  Submits a verified deployment bundle with an already-prepared target-bound
331
228
  release. The command verifies both inputs again, requires their bundle
332
- identities to match, and streams only the files declared by the bundle. Use it
333
- when the bundle and release were produced by an earlier pipeline step; for the
334
- ordinary path, use `hypequery deploy`.
229
+ identities to match, and streams only the files declared by the bundle.
335
230
 
336
231
  ```bash
337
232
  npx hypequery deployment:submit analytics/hypequery-deployment \
338
233
  --release analytics/hypequery-deployment.release.json
339
234
  ```
340
235
 
341
- For local development, run `hypequery login` first; the CLI reads the endpoint
342
- and token from its secure Cloud profile. For CI and manual credentials, set
343
- `HYPEQUERY_API_TOKEN` together with either `--endpoint` or
344
- `HYPEQUERY_DEPLOYMENT_ENDPOINT`. The CLI never combines one explicit value with
345
- the other value from the stored profile. Tokens are never accepted as
346
- command-line arguments, keeping them out of shell history. The submission
347
- endpoint must not contain credentials or a URL fragment, and must use HTTPS
348
- except for `127.0.0.1`/`localhost`, which is permitted for local Cloud
349
- development and warns that the token is sent in cleartext. The release identity
350
- is sent as the idempotency key, so an unchanged release can be submitted safely
351
- again.
236
+ Set `HYPEQUERY_API_TOKEN` together with either `--endpoint` or
237
+ `HYPEQUERY_DEPLOYMENT_ENDPOINT`. Tokens are never accepted as command-line
238
+ arguments, keeping them out of shell history. The submission endpoint must not
239
+ contain credentials or a URL fragment, and must use HTTPS except for
240
+ `127.0.0.1`/`localhost`, which is permitted for local development and warns that
241
+ the token is sent in cleartext. The release identity is sent as the idempotency
242
+ key, so an unchanged release can be submitted safely again.
352
243
 
353
244
  This command submits immutable deployment inputs. Activation, status changes,
354
245
  promotion, and rollback remain control-plane operations.
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;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA8ND,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;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA+ND,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -34,6 +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('--no-branch', 'Do not send the current Git branch (or HYPEQUERY_CLI_SEND_BRANCH=0)')
37
38
  .action(runCommand(async (options) => {
38
39
  await loginCommand(options);
39
40
  }));
@@ -1,9 +1,12 @@
1
1
  import { deleteCloudCredential, loadCloudCredential, saveCloudCredential } from '../utils/cloud-credential-store.js';
2
2
  export interface LoginOptions {
3
3
  readonly cloudUrl?: string;
4
+ /** Commander sets this to false for `--no-branch`. */
5
+ readonly branch?: boolean;
4
6
  }
5
7
  export interface LoginDependencies {
6
8
  readonly fetch?: typeof fetch;
9
+ readonly getGitBranch?: () => Promise<string | null>;
7
10
  readonly openBrowser?: (url: string) => Promise<unknown>;
8
11
  readonly now?: () => number;
9
12
  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;AAgB5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;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,iBAoDrC;AAED,wBAAsB,aAAa,CAAC,YAAY,GAAE,kBAAuB,iBAoCxE"}
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;AAiB5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrD,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;AAaD,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,iBAkErC;AAED,wBAAsB,aAAa,CAAC,YAAY,GAAE,kBAAuB,iBAoCxE"}
@@ -3,6 +3,7 @@ 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';
6
7
  import { logger } from '../utils/logger.js';
7
8
  const DEFAULT_CLOUD_URL = 'https://cloud.hypequery.com';
8
9
  const LOGIN_TIMEOUT_MS = 5 * 60_000;
@@ -16,6 +17,17 @@ const MAX_TOKEN_LIFETIME_MS = 13 * 60 * 60_000;
16
17
  // CLI the day Cloud rotates its token format.
17
18
  const TOKEN_PATTERN = /^hqdp_[A-Za-z0-9_-]{16,512}$/;
18
19
  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
+ }
19
31
  function callbackHtml(success) {
20
32
  const title = success ? 'CLI authorized' : 'Authorization failed';
21
33
  const message = success
@@ -162,12 +174,20 @@ export async function loginCommand(options = {}, dependencies = {}) {
162
174
  const state = randomBytes(24).toString('base64url');
163
175
  const verifier = randomBytes(32).toString('base64url');
164
176
  const challenge = createHash('sha256').update(verifier).digest('base64url');
177
+ // Resolve branch context before the loopback listener opens: shelling out to
178
+ // git should not hold a server socket, and a caller-supplied resolver that
179
+ // rejects must not leak one.
180
+ const branch = branchContextEnabled(options)
181
+ ? await (dependencies.getGitBranch ?? currentGitBranch)()
182
+ : null;
165
183
  const callback = await callbackServer(state, dependencies.timeoutMs ?? LOGIN_TIMEOUT_MS);
166
184
  const authorizeUrl = new URL('/cli/authorize', origin);
167
185
  authorizeUrl.searchParams.set('redirect_uri', callback.redirectUri);
168
186
  authorizeUrl.searchParams.set('code_challenge', challenge);
169
187
  authorizeUrl.searchParams.set('code_challenge_method', 'S256');
170
188
  authorizeUrl.searchParams.set('state', state);
189
+ if (branch)
190
+ authorizeUrl.searchParams.set('branch', branch);
171
191
  try {
172
192
  logger.info('Opening your browser to authorize Hypequery CLI…');
173
193
  await (dependencies.openBrowser ?? open)(authorizeUrl.toString());
@@ -195,6 +215,11 @@ export async function loginCommand(options = {}, dependencies = {}) {
195
215
  const credential = tokenResponse(body, origin, (dependencies.now ?? Date.now)());
196
216
  await (dependencies.saveCredential ?? saveCloudCredential)(credential);
197
217
  logger.success('Logged in to Hypequery Cloud');
218
+ // Cloud resolves the target, and branch context only expresses intent, so
219
+ // print what was actually issued rather than what was requested.
220
+ if (credential.target) {
221
+ logger.info(`Deployments target ${credential.target.project} / ${credential.target.environment}`);
222
+ }
198
223
  logger.info(`Credential expires ${new Date(credential.expiresAt).toLocaleString()}`);
199
224
  }
200
225
  finally {
@@ -1 +1 @@
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;AAuDD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,SAAS,MAAM,EAAE,EACrC,gBAAgB,SAAY,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAmD9B"}
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"}
@@ -69,15 +69,25 @@ function runtimeEntrySource(apiSpecifier, runtimeEntrypoints, entrypointPrefix)
69
69
  ' ...(argument.context === undefined ? {} : { context: argument.context }),',
70
70
  ' ...(argument.request === undefined ? {} : { request: argument.request }),',
71
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 }),',
72
75
  ' });',
73
76
  ' }',
74
77
  ' const entry = api.queries?.[name];',
75
78
  ' const query = entry?.query;',
76
- ' if (typeof query === "function") return query;',
77
- ' if (query && typeof query === "object" && typeof query.run === "function") {',
78
- ' return query.run.bind(query);',
79
- ' }',
80
- ' throw new Error(`Runtime entrypoint ${name} is unavailable.`);',
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
+ ' };',
81
91
  '};',
82
92
  `const runtimeEntries = Object.freeze(Object.fromEntries(${names}.map(name => [name, resolveQuery(name)])));`,
83
93
  `export const ${prefix[0]} = ${registryExpression};`,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolves the checked-out branch name, or null when there isn't one.
3
+ *
4
+ * `git symbolic-ref` exits non-zero on a detached HEAD and outside a
5
+ * repository, and the binary may be missing entirely, so every failure mode
6
+ * collapses to "no branch context" rather than an error the caller must handle.
7
+ */
8
+ export declare function currentGitBranch(cwd?: string): Promise<string | null>;
9
+ //# sourceMappingURL=git-branch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-branch.d.ts","sourceRoot":"","sources":["../../src/utils/git-branch.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBxB"}
@@ -0,0 +1,26 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ const GIT_TIMEOUT_MS = 5_000;
5
+ /**
6
+ * Resolves the checked-out branch name, or null when there isn't one.
7
+ *
8
+ * `git symbolic-ref` exits non-zero on a detached HEAD and outside a
9
+ * repository, and the binary may be missing entirely, so every failure mode
10
+ * collapses to "no branch context" rather than an error the caller must handle.
11
+ */
12
+ export async function currentGitBranch(cwd = process.cwd()) {
13
+ try {
14
+ const { stdout } = await execFileAsync('git', ['symbolic-ref', '--quiet', '--short', 'HEAD'], {
15
+ cwd,
16
+ encoding: 'utf8',
17
+ timeout: GIT_TIMEOUT_MS,
18
+ windowsHide: true,
19
+ });
20
+ const branch = stdout.trim();
21
+ return branch.length > 0 ? branch : null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "@vitest/coverage-v8": "^3.2.6",
35
35
  "typescript": "^5.7.3",
36
36
  "vitest": "^3.2.6",
37
- "@hypequery/serve": "0.14.1"
37
+ "@hypequery/serve": "0.15.0"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",