hivectl 0.1.0 → 0.2.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
@@ -1,6 +1,6 @@
1
1
  # hivectl
2
2
 
3
- `hivectl` is a Bun-first CLI for a small set of local and GitHub workflows.
3
+ `hivectl` is a Bun-first CLI for a small set of local Git and GitHub workflows.
4
4
 
5
5
  ## Features
6
6
 
@@ -18,6 +18,7 @@
18
18
  ### Requirements
19
19
 
20
20
  * [Bun](https://bun.sh/)
21
+ * [Git](https://git-scm.com/)
21
22
  * [GitHub CLI](https://cli.github.com/) installed and authenticated for GitHub-backed commands
22
23
  * macOS or Linux
23
24
 
@@ -92,6 +93,28 @@ Exit codes:
92
93
  * `1`: Unresolved review threads found or an operational error occurred
93
94
  * `2`: No pull request found for the current branch
94
95
 
96
+ ### `sync-upstream`
97
+
98
+ Syncs any of `dev`, `develop`, `main`, and `master` that exist on a source remote to a destination remote, then restores your original checkout.
99
+
100
+ By default, `sync-upstream` reads from `upstream` and pushes to `origin`. Use `--destination` and `--source` to override either remote name.
101
+
102
+ ```bash
103
+ hivectl sync-upstream
104
+ ```
105
+
106
+ Sync from `source` to `fork` instead:
107
+
108
+ ```bash
109
+ hivectl sync-upstream --destination fork --source source
110
+ ```
111
+
112
+ Exit codes:
113
+
114
+ * `0`: At least one conventional branch was synced successfully
115
+ * `1`: A git or operational error occurred
116
+ * `2`: No syncable `dev`, `develop`, `main`, or `master` branches were found on the source remote
117
+
95
118
  ### Publishing
96
119
 
97
120
  This project uses Changesets for versioning and publishing.
@@ -122,7 +145,7 @@ This project uses Changesets for versioning and publishing.
122
145
  │ ├── cli.ts # CLI entry point
123
146
  │ └── index.ts # Main library entry point
124
147
  ├── test/ # Unit tests
125
- │ ├── cli.test.ts # CLI tests with a fake gh executable
148
+ │ ├── cli.test.ts # CLI tests with fake gh and git executables
126
149
  │ └── index.test.ts # Library tests
127
150
  ├── tsdown.config.ts # Configuration for tsdown (bundling)
128
151
  ├── biome.json # Biome linter/formatter configuration
package/dist/cli.cjs CHANGED
@@ -4,9 +4,18 @@ let commander = require("commander");
4
4
 
5
5
  //#region src/cli.ts
6
6
  const NO_PR_MESSAGE = "No pull request found for current branch";
7
+ const NO_SYNCABLE_BRANCHES_EXIT_CODE = 2;
7
8
  const ANSI_ESCAPE_SEQUENCES = new RegExp(String.raw`\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001b\\))`, "gu");
8
9
  const CONTROL_CHARACTERS = new RegExp(String.raw`[\u0000-\u001f\u007f]`, "gu");
9
10
  const MAX_PREVIEW_LENGTH = 120;
11
+ const SYNC_UPSTREAM_BRANCHES = [
12
+ "dev",
13
+ "develop",
14
+ "main",
15
+ "master"
16
+ ];
17
+ const SYNC_UPSTREAM_DEFAULT_DESTINATION = "origin";
18
+ const SYNC_UPSTREAM_DEFAULT_SOURCE = "upstream";
10
19
  const REVIEW_THREADS_QUERY = `
11
20
  query($id: ID!, $after: String) {
12
21
  node(id: $id) {
@@ -101,6 +110,21 @@ function runGh(args) {
101
110
  stdout: result.stdout ?? ""
102
111
  };
103
112
  }
113
+ function runGit(args) {
114
+ const result = (0, node_child_process.spawnSync)("git", args, {
115
+ encoding: "utf8",
116
+ env: process.env
117
+ });
118
+ if (result.error) {
119
+ if ("code" in result.error && result.error.code === "ENOENT") throw new Error("Failed to run git: git is not installed or not available on PATH");
120
+ throw new Error(`Failed to run git: ${result.error.message}`);
121
+ }
122
+ return {
123
+ status: result.status ?? 1,
124
+ stderr: result.stderr ?? "",
125
+ stdout: result.stdout ?? ""
126
+ };
127
+ }
104
128
  function isNoPullRequestFailure(result) {
105
129
  const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase();
106
130
  return detail.includes("could not determine current branch") || detail.includes("no pull requests found for branch") || detail.includes("not on any branch");
@@ -222,12 +246,121 @@ function runGhPrUnresolved(options) {
222
246
  else printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose));
223
247
  return unresolvedThreads.length > 0 ? 1 : 0;
224
248
  }
249
+ function getAvailableRemotesLabel(remotes) {
250
+ return remotes.length > 0 ? remotes.join(", ") : "(none)";
251
+ }
252
+ function getCurrentCheckoutState() {
253
+ const branchResult = runGit(["branch", "--show-current"]);
254
+ if (branchResult.status !== 0) throw formatOperationalError("Failed to resolve current checkout", branchResult);
255
+ const branch = normalizeOutput(branchResult.stdout);
256
+ if (branch.length > 0) return {
257
+ kind: "branch",
258
+ ref: branch
259
+ };
260
+ const detachedHeadResult = runGit([
261
+ "rev-parse",
262
+ "--verify",
263
+ "HEAD"
264
+ ]);
265
+ if (detachedHeadResult.status !== 0) throw formatOperationalError("Failed to resolve current checkout", detachedHeadResult);
266
+ const commit = normalizeOutput(detachedHeadResult.stdout);
267
+ if (commit.length === 0) throw new Error("Failed to resolve current checkout: HEAD did not resolve to a commit");
268
+ return {
269
+ kind: "detached",
270
+ ref: commit
271
+ };
272
+ }
273
+ function getGitRemotes() {
274
+ const result = runGit(["remote"]);
275
+ if (result.status !== 0) throw formatOperationalError("Failed to list git remotes", result);
276
+ return normalizeOutput(result.stdout).split(/\r?\n/u).map((remote) => remote.trim()).filter((remote) => remote.length > 0).sort((left, right) => left.localeCompare(right));
277
+ }
278
+ function getSyncRemoteLabel(role) {
279
+ return role === "destination" ? "Destination" : "Source";
280
+ }
281
+ function getSyncableBranches(source) {
282
+ return SYNC_UPSTREAM_BRANCHES.filter((branch) => hasFetchedRemoteBranch(source, branch));
283
+ }
284
+ function hasFetchedRemoteBranch(source, branch) {
285
+ const result = runGit([
286
+ "show-ref",
287
+ "--verify",
288
+ "--quiet",
289
+ `refs/remotes/${source}/${branch}`
290
+ ]);
291
+ if (result.status === 0) return true;
292
+ if (result.status === 1) return false;
293
+ throw formatOperationalError(`Failed to resolve ${source}/${branch}`, result);
294
+ }
295
+ function ensureSyncRemoteExists(remote, remotes, role) {
296
+ if (remotes.includes(remote)) return;
297
+ throw new Error(`${getSyncRemoteLabel(role)} remote "${remote}" not found. Available remotes: ${getAvailableRemotesLabel(remotes)}`);
298
+ }
299
+ function fetchRemote(remote) {
300
+ const result = runGit(["fetch", remote]);
301
+ if (result.status !== 0) throw formatOperationalError(`Failed to fetch ${remote}`, result);
302
+ }
303
+ function restoreOriginalCheckout(checkoutState) {
304
+ const result = checkoutState.kind === "branch" ? runGit(["checkout", checkoutState.ref]) : runGit([
305
+ "checkout",
306
+ "--detach",
307
+ checkoutState.ref
308
+ ]);
309
+ if (result.status === 0) return null;
310
+ return formatOperationalError(`Failed to restore original checkout to ${checkoutState.kind === "branch" ? `branch "${checkoutState.ref}"` : `detached HEAD at ${checkoutState.ref}`}`, result);
311
+ }
312
+ function syncBranch(branch, destination, source) {
313
+ const checkoutResult = runGit([
314
+ "checkout",
315
+ "-B",
316
+ branch,
317
+ `refs/remotes/${source}/${branch}`
318
+ ]);
319
+ if (checkoutResult.status !== 0) throw formatOperationalError(`Failed to check out ${branch} from ${source}/${branch}`, checkoutResult);
320
+ const pushResult = runGit([
321
+ "push",
322
+ destination,
323
+ `${branch}:${branch}`
324
+ ]);
325
+ if (pushResult.status !== 0) throw formatOperationalError(`Failed to push ${branch} to ${destination}`, pushResult);
326
+ }
327
+ function runSyncUpstream(destinationOption, sourceOption) {
328
+ const destination = normalizeOutput(destinationOption) || SYNC_UPSTREAM_DEFAULT_DESTINATION;
329
+ const source = normalizeOutput(sourceOption) || SYNC_UPSTREAM_DEFAULT_SOURCE;
330
+ const remotes = getGitRemotes();
331
+ ensureSyncRemoteExists(destination, remotes, "destination");
332
+ ensureSyncRemoteExists(source, remotes, "source");
333
+ fetchRemote(source);
334
+ const branches = getSyncableBranches(source);
335
+ if (branches.length === 0) {
336
+ console.log(`No syncable branches found on ${source}. Checked: ${SYNC_UPSTREAM_BRANCHES.join(", ")}`);
337
+ return NO_SYNCABLE_BRANCHES_EXIT_CODE;
338
+ }
339
+ const originalCheckout = getCurrentCheckoutState();
340
+ let syncError = null;
341
+ console.log(`Syncing ${branches.join(", ")} from ${source} to ${destination}`);
342
+ for (const branch of branches) try {
343
+ syncBranch(branch, destination, source);
344
+ console.log(`Synced ${branch} to ${destination}`);
345
+ } catch (error) {
346
+ syncError = error instanceof Error ? error : new Error(String(error));
347
+ break;
348
+ }
349
+ const restoreError = restoreOriginalCheckout(originalCheckout);
350
+ if (syncError && restoreError) throw new Error(`${syncError.message}\n${restoreError.message}`);
351
+ if (syncError) throw syncError;
352
+ if (restoreError) throw restoreError;
353
+ return 0;
354
+ }
225
355
  function createProgram() {
226
356
  const program = new commander.Command();
227
357
  program.name("hivectl").description("Common local and GitHub workflow helpers").exitOverride();
228
358
  program.command("gh-pr-unresolved").description("Show unresolved review threads on the pull request for the current branch").option("--json", "show unresolved review threads as JSON").option("-v, --verbose", "show unresolved review threads in detail").action((options) => {
229
359
  process.exitCode = runGhPrUnresolved(options);
230
360
  });
361
+ program.command("sync-upstream").description("Sync dev, develop, main, and master from a source remote to a destination remote").option("--destination <remote>", "destination remote name", SYNC_UPSTREAM_DEFAULT_DESTINATION).option("--source <remote>", "source remote name", SYNC_UPSTREAM_DEFAULT_SOURCE).action((options) => {
362
+ process.exitCode = runSyncUpstream(options.destination, options.source);
363
+ });
231
364
  return program;
232
365
  }
233
366
  async function main(argv = process.argv) {
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["Command","CommanderError"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawnSync } from 'node:child_process'\nimport { Command, CommanderError } from 'commander'\n\ntype ReviewComment = {\n author?: {\n login?: string | null\n } | null\n body?: string | null\n outdated?: boolean | null\n path?: string | null\n url?: string | null\n}\n\ntype ReviewThreadNode = {\n comments?: {\n nodes?: Array<ReviewComment | null> | null\n } | null\n isOutdated?: boolean | null\n isResolved?: boolean | null\n}\n\ntype ReviewThreadsResponse = {\n data?: {\n node?: {\n reviewThreads?: {\n nodes?: Array<ReviewThreadNode | null> | null\n pageInfo?: {\n endCursor?: string | null\n hasNextPage?: boolean | null\n } | null\n } | null\n } | null\n } | null\n errors?: Array<{\n message?: string | null\n } | null> | null\n}\n\ntype PullRequestState = 'closed' | 'merged' | 'open'\n\ntype PullRequestResponse = {\n id: string\n number: number\n state: PullRequestState\n title: string\n url: string\n}\n\ntype PullRequestThread = {\n author: string\n outdated: boolean\n path: string\n preview: string\n url: string\n}\n\ntype CommandOptions = {\n json?: boolean\n verbose?: boolean\n}\n\ntype GhResult = {\n status: number\n stderr: string\n stdout: string\n}\n\ntype JsonOutput = {\n pullRequest: {\n number: number\n state: PullRequestState\n title: string\n url: string\n } | null\n status: 'clean' | 'no_pr' | 'unresolved'\n threads: PullRequestThread[]\n unresolvedCount: number\n}\n\nconst NO_PR_MESSAGE = 'No pull request found for current branch'\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst ANSI_ESCAPE_SEQUENCES = new RegExp(\n String.raw`\\u001b(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~]|\\][^\\u0007]*(?:\\u0007|\\u001b\\\\))`,\n 'gu',\n)\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst CONTROL_CHARACTERS = new RegExp(String.raw`[\\u0000-\\u001f\\u007f]`, 'gu')\nconst MAX_PREVIEW_LENGTH = 120\nconst REVIEW_THREADS_QUERY = `\n query($id: ID!, $after: String) {\n node(id: $id) {\n ... on PullRequest {\n reviewThreads(first: 100, after: $after) {\n nodes {\n isOutdated\n isResolved\n comments(first: 1) {\n nodes {\n author {\n login\n }\n body\n outdated\n path\n url\n }\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n`\n\nfunction normalizeOutput(value: string | null | undefined): string {\n return value?.trim() ?? ''\n}\n\nfunction formatOperationalError(prefix: string, result: GhResult): Error {\n const detail = normalizeOutput(result.stderr) || normalizeOutput(result.stdout)\n\n return new Error(detail ? `${prefix}: ${detail}` : prefix)\n}\n\nfunction parseJson<T>(value: string, context: string): T {\n try {\n return JSON.parse(value) as T\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`${context}: ${message}`)\n }\n}\n\nfunction parsePullRequestState(value: unknown): PullRequestState | null {\n if (typeof value !== 'string') {\n return null\n }\n\n switch (value.toLowerCase()) {\n case 'closed':\n return 'closed'\n case 'merged':\n return 'merged'\n case 'open':\n return 'open'\n default:\n return null\n }\n}\n\nfunction toPullRequestResponse(value: unknown): PullRequestResponse | null {\n const pullRequest = value as\n | {\n id?: unknown\n number?: unknown\n state?: unknown\n title?: unknown\n url?: unknown\n }\n | null\n | undefined\n const state = parsePullRequestState(pullRequest?.state)\n\n if (\n !pullRequest ||\n typeof pullRequest !== 'object' ||\n typeof pullRequest.id !== 'string' ||\n pullRequest.id.length === 0 ||\n typeof pullRequest.number !== 'number' ||\n !state ||\n typeof pullRequest.title !== 'string' ||\n typeof pullRequest.url !== 'string'\n ) {\n return null\n }\n\n return {\n id: pullRequest.id,\n number: pullRequest.number,\n state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n}\n\nfunction parsePullRequestResponse(value: string): PullRequestResponse {\n const pullRequest = toPullRequestResponse(parseJson<unknown>(value, 'Failed to parse pull request response'))\n\n if (!pullRequest) {\n throw new Error('Failed to parse pull request response: Response is missing required pull request fields')\n }\n\n return pullRequest\n}\n\nfunction getPreview(body: string): string {\n const firstLine = body\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .find((line) => line.length > 0)\n\n if (!firstLine) {\n return '(no preview available)'\n }\n\n if (firstLine.length <= MAX_PREVIEW_LENGTH) {\n return firstLine\n }\n\n return `${firstLine.slice(0, MAX_PREVIEW_LENGTH - 3)}...`\n}\n\nfunction sanitizeTerminalText(value: string): string {\n return value.replace(ANSI_ESCAPE_SEQUENCES, '').replace(CONTROL_CHARACTERS, '')\n}\n\nfunction runGh(args: string[]): GhResult {\n const result = spawnSync('gh', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run gh: gh is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run gh: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction isNoPullRequestFailure(result: GhResult): boolean {\n const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase()\n\n return (\n detail.includes('could not determine current branch') ||\n detail.includes('no pull requests found for branch') ||\n detail.includes('not on any branch')\n )\n}\n\nfunction getCurrentPullRequest(): PullRequestResponse | null {\n const result = runGh(['pr', 'view', '--json', 'id,number,state,title,url'])\n\n if (result.status === 0) {\n return parsePullRequestResponse(result.stdout)\n }\n\n if (isNoPullRequestFailure(result)) {\n return null\n }\n\n throw formatOperationalError('Failed to resolve pull request for current branch', result)\n}\n\nfunction getPullRequestHostname(url: string): string | null {\n try {\n const hostname = new URL(url).hostname\n\n return hostname === 'github.com' ? null : hostname\n } catch {\n return null\n }\n}\n\nfunction getReviewThreadsPage(id: string, hostname: string | null, after: string | null): ReviewThreadsResponse {\n const args = ['api', 'graphql']\n\n if (hostname) {\n args.push('--hostname', hostname)\n }\n\n args.push('-f', `query=${REVIEW_THREADS_QUERY}`, '-F', `id=${id}`)\n\n if (after) {\n args.push('-F', `after=${after}`)\n }\n\n const result = runGh(args)\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to fetch review threads', result)\n }\n\n return parseJson<ReviewThreadsResponse>(result.stdout, 'Failed to parse review threads response')\n}\n\nfunction getUnresolvedThreads(id: string, hostname: string | null): PullRequestThread[] {\n const unresolvedThreads: PullRequestThread[] = []\n let after: string | null = null\n\n do {\n const response = getReviewThreadsPage(id, hostname, after)\n const errorMessage =\n response.errors\n ?.map((error) => normalizeOutput(error?.message))\n .filter((message) => message.length > 0)\n .join('; ') ?? ''\n\n if (errorMessage.length > 0) {\n throw new Error(`Failed to fetch review threads: ${errorMessage}`)\n }\n\n const reviewThreads = response.data?.node?.reviewThreads\n const nodes = reviewThreads?.nodes\n const hasNextPage = reviewThreads?.pageInfo?.hasNextPage\n\n if (!Array.isArray(nodes) || typeof hasNextPage !== 'boolean') {\n throw new Error('Failed to fetch review threads: Pull request review threads were not returned')\n }\n\n for (const thread of nodes) {\n if (!thread || thread.isResolved === true) {\n continue\n }\n\n const comments = Array.isArray(thread.comments?.nodes) ? thread.comments.nodes : []\n const reviewComment = comments[0] ?? null\n\n unresolvedThreads.push({\n author: normalizeOutput(reviewComment?.author?.login) || '(unknown author)',\n outdated: thread.isOutdated === true || reviewComment?.outdated === true,\n path: normalizeOutput(reviewComment?.path) || '(unknown file)',\n preview: getPreview(reviewComment?.body ?? ''),\n url: normalizeOutput(reviewComment?.url) || '(missing comment url)',\n })\n }\n\n const endCursor = normalizeOutput(reviewThreads?.pageInfo?.endCursor)\n after = hasNextPage ? endCursor || null : null\n } while (after)\n\n return unresolvedThreads.sort((left, right) => {\n const pathComparison = left.path.localeCompare(right.path)\n\n if (pathComparison !== 0) {\n return pathComparison\n }\n\n return left.url.localeCompare(right.url)\n })\n}\n\nfunction printSummaryThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n console.log(thread.url)\n }\n}\n\nfunction printVerboseThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n const author = sanitizeTerminalText(thread.author)\n const outdatedMarker = thread.outdated ? ' (outdated)' : ''\n const path = sanitizeTerminalText(thread.path)\n const preview = sanitizeTerminalText(thread.preview)\n\n console.log(`${thread.url} | ${author} | ${path}${outdatedMarker} | ${preview}`)\n }\n}\n\nfunction printSummary(pullRequest: PullRequestResponse, unresolvedCount: number): void {\n const stateLabel = pullRequest.state === 'open' ? '' : ` (${pullRequest.state})`\n console.log(\n `PR #${pullRequest.number}${stateLabel} has ${unresolvedCount} unresolved review thread(s): ${pullRequest.url}`,\n )\n}\n\nfunction getJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): JsonOutput {\n return {\n pullRequest: pullRequest\n ? {\n number: pullRequest.number,\n state: pullRequest.state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n : null,\n status,\n threads: unresolvedThreads,\n unresolvedCount: unresolvedThreads.length,\n }\n}\n\nfunction printJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): void {\n console.log(JSON.stringify(getJsonOutput(pullRequest, unresolvedThreads, status), null, 2))\n}\n\nfunction printThreads(verbose: boolean, unresolvedThreads: PullRequestThread[]): void {\n if (verbose) {\n printVerboseThreads(unresolvedThreads)\n return\n }\n\n printSummaryThreads(unresolvedThreads)\n}\n\nfunction printOutput(pullRequest: PullRequestResponse, unresolvedThreads: PullRequestThread[], verbose: boolean): void {\n printSummary(pullRequest, unresolvedThreads.length)\n\n if (unresolvedThreads.length > 0) {\n printThreads(verbose, unresolvedThreads)\n }\n}\n\nfunction runGhPrUnresolved(options: CommandOptions): number {\n const pullRequest = getCurrentPullRequest()\n\n if (!pullRequest) {\n if (options.json) {\n printJsonOutput(null, [], 'no_pr')\n return 2\n }\n\n console.log(NO_PR_MESSAGE)\n return 2\n }\n\n const unresolvedThreads = getUnresolvedThreads(pullRequest.id, getPullRequestHostname(pullRequest.url))\n\n if (options.json) {\n printJsonOutput(pullRequest, unresolvedThreads, unresolvedThreads.length === 0 ? 'clean' : 'unresolved')\n } else {\n printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose))\n }\n\n return unresolvedThreads.length > 0 ? 1 : 0\n}\n\nfunction createProgram(): Command {\n const program = new Command()\n\n program.name('hivectl').description('Common local and GitHub workflow helpers').exitOverride()\n\n program\n .command('gh-pr-unresolved')\n .description('Show unresolved review threads on the pull request for the current branch')\n .option('--json', 'show unresolved review threads as JSON')\n .option('-v, --verbose', 'show unresolved review threads in detail')\n .action((options: CommandOptions) => {\n process.exitCode = runGhPrUnresolved(options)\n })\n\n return program\n}\n\nasync function main(argv = process.argv): Promise<void> {\n const program = createProgram()\n\n try {\n await program.parseAsync(argv)\n\n if (typeof process.exitCode !== 'number') {\n process.exitCode = 0\n }\n } catch (error) {\n if (error instanceof CommanderError) {\n process.exitCode = error.code === 'commander.helpDisplayed' ? 0 : error.exitCode\n return\n }\n\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n }\n}\n\nvoid main()\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","names":["Command","CommanderError"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawnSync } from 'node:child_process'\nimport { Command, CommanderError } from 'commander'\n\ntype ReviewComment = {\n author?: {\n login?: string | null\n } | null\n body?: string | null\n outdated?: boolean | null\n path?: string | null\n url?: string | null\n}\n\ntype ReviewThreadNode = {\n comments?: {\n nodes?: Array<ReviewComment | null> | null\n } | null\n isOutdated?: boolean | null\n isResolved?: boolean | null\n}\n\ntype ReviewThreadsResponse = {\n data?: {\n node?: {\n reviewThreads?: {\n nodes?: Array<ReviewThreadNode | null> | null\n pageInfo?: {\n endCursor?: string | null\n hasNextPage?: boolean | null\n } | null\n } | null\n } | null\n } | null\n errors?: Array<{\n message?: string | null\n } | null> | null\n}\n\ntype PullRequestState = 'closed' | 'merged' | 'open'\n\ntype PullRequestResponse = {\n id: string\n number: number\n state: PullRequestState\n title: string\n url: string\n}\n\ntype PullRequestThread = {\n author: string\n outdated: boolean\n path: string\n preview: string\n url: string\n}\n\ntype CheckoutState =\n | {\n kind: 'branch'\n ref: string\n }\n | {\n kind: 'detached'\n ref: string\n }\n\ntype CommandOptions = {\n json?: boolean\n verbose?: boolean\n}\n\ntype CommandResult = {\n status: number\n stderr: string\n stdout: string\n}\n\ntype JsonOutput = {\n pullRequest: {\n number: number\n state: PullRequestState\n title: string\n url: string\n } | null\n status: 'clean' | 'no_pr' | 'unresolved'\n threads: PullRequestThread[]\n unresolvedCount: number\n}\n\nconst NO_PR_MESSAGE = 'No pull request found for current branch'\nconst NO_SYNCABLE_BRANCHES_EXIT_CODE = 2\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst ANSI_ESCAPE_SEQUENCES = new RegExp(\n String.raw`\\u001b(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~]|\\][^\\u0007]*(?:\\u0007|\\u001b\\\\))`,\n 'gu',\n)\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst CONTROL_CHARACTERS = new RegExp(String.raw`[\\u0000-\\u001f\\u007f]`, 'gu')\nconst MAX_PREVIEW_LENGTH = 120\nconst SYNC_UPSTREAM_BRANCHES = ['dev', 'develop', 'main', 'master'] as const\nconst SYNC_UPSTREAM_DEFAULT_DESTINATION = 'origin'\nconst SYNC_UPSTREAM_DEFAULT_SOURCE = 'upstream'\nconst REVIEW_THREADS_QUERY = `\n query($id: ID!, $after: String) {\n node(id: $id) {\n ... on PullRequest {\n reviewThreads(first: 100, after: $after) {\n nodes {\n isOutdated\n isResolved\n comments(first: 1) {\n nodes {\n author {\n login\n }\n body\n outdated\n path\n url\n }\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n`\n\nfunction normalizeOutput(value: string | null | undefined): string {\n return value?.trim() ?? ''\n}\n\nfunction formatOperationalError(prefix: string, result: CommandResult): Error {\n const detail = normalizeOutput(result.stderr) || normalizeOutput(result.stdout)\n\n return new Error(detail ? `${prefix}: ${detail}` : prefix)\n}\n\nfunction parseJson<T>(value: string, context: string): T {\n try {\n return JSON.parse(value) as T\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`${context}: ${message}`)\n }\n}\n\nfunction parsePullRequestState(value: unknown): PullRequestState | null {\n if (typeof value !== 'string') {\n return null\n }\n\n switch (value.toLowerCase()) {\n case 'closed':\n return 'closed'\n case 'merged':\n return 'merged'\n case 'open':\n return 'open'\n default:\n return null\n }\n}\n\nfunction toPullRequestResponse(value: unknown): PullRequestResponse | null {\n const pullRequest = value as\n | {\n id?: unknown\n number?: unknown\n state?: unknown\n title?: unknown\n url?: unknown\n }\n | null\n | undefined\n const state = parsePullRequestState(pullRequest?.state)\n\n if (\n !pullRequest ||\n typeof pullRequest !== 'object' ||\n typeof pullRequest.id !== 'string' ||\n pullRequest.id.length === 0 ||\n typeof pullRequest.number !== 'number' ||\n !state ||\n typeof pullRequest.title !== 'string' ||\n typeof pullRequest.url !== 'string'\n ) {\n return null\n }\n\n return {\n id: pullRequest.id,\n number: pullRequest.number,\n state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n}\n\nfunction parsePullRequestResponse(value: string): PullRequestResponse {\n const pullRequest = toPullRequestResponse(parseJson<unknown>(value, 'Failed to parse pull request response'))\n\n if (!pullRequest) {\n throw new Error('Failed to parse pull request response: Response is missing required pull request fields')\n }\n\n return pullRequest\n}\n\nfunction getPreview(body: string): string {\n const firstLine = body\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .find((line) => line.length > 0)\n\n if (!firstLine) {\n return '(no preview available)'\n }\n\n if (firstLine.length <= MAX_PREVIEW_LENGTH) {\n return firstLine\n }\n\n return `${firstLine.slice(0, MAX_PREVIEW_LENGTH - 3)}...`\n}\n\nfunction sanitizeTerminalText(value: string): string {\n return value.replace(ANSI_ESCAPE_SEQUENCES, '').replace(CONTROL_CHARACTERS, '')\n}\n\nfunction runGh(args: string[]): CommandResult {\n const result = spawnSync('gh', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run gh: gh is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run gh: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction runGit(args: string[]): CommandResult {\n const result = spawnSync('git', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run git: git is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run git: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction isNoPullRequestFailure(result: CommandResult): boolean {\n const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase()\n\n return (\n detail.includes('could not determine current branch') ||\n detail.includes('no pull requests found for branch') ||\n detail.includes('not on any branch')\n )\n}\n\nfunction getCurrentPullRequest(): PullRequestResponse | null {\n const result = runGh(['pr', 'view', '--json', 'id,number,state,title,url'])\n\n if (result.status === 0) {\n return parsePullRequestResponse(result.stdout)\n }\n\n if (isNoPullRequestFailure(result)) {\n return null\n }\n\n throw formatOperationalError('Failed to resolve pull request for current branch', result)\n}\n\nfunction getPullRequestHostname(url: string): string | null {\n try {\n const hostname = new URL(url).hostname\n\n return hostname === 'github.com' ? null : hostname\n } catch {\n return null\n }\n}\n\nfunction getReviewThreadsPage(id: string, hostname: string | null, after: string | null): ReviewThreadsResponse {\n const args = ['api', 'graphql']\n\n if (hostname) {\n args.push('--hostname', hostname)\n }\n\n args.push('-f', `query=${REVIEW_THREADS_QUERY}`, '-F', `id=${id}`)\n\n if (after) {\n args.push('-F', `after=${after}`)\n }\n\n const result = runGh(args)\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to fetch review threads', result)\n }\n\n return parseJson<ReviewThreadsResponse>(result.stdout, 'Failed to parse review threads response')\n}\n\nfunction getUnresolvedThreads(id: string, hostname: string | null): PullRequestThread[] {\n const unresolvedThreads: PullRequestThread[] = []\n let after: string | null = null\n\n do {\n const response = getReviewThreadsPage(id, hostname, after)\n const errorMessage =\n response.errors\n ?.map((error) => normalizeOutput(error?.message))\n .filter((message) => message.length > 0)\n .join('; ') ?? ''\n\n if (errorMessage.length > 0) {\n throw new Error(`Failed to fetch review threads: ${errorMessage}`)\n }\n\n const reviewThreads = response.data?.node?.reviewThreads\n const nodes = reviewThreads?.nodes\n const hasNextPage = reviewThreads?.pageInfo?.hasNextPage\n\n if (!Array.isArray(nodes) || typeof hasNextPage !== 'boolean') {\n throw new Error('Failed to fetch review threads: Pull request review threads were not returned')\n }\n\n for (const thread of nodes) {\n if (!thread || thread.isResolved === true) {\n continue\n }\n\n const comments = Array.isArray(thread.comments?.nodes) ? thread.comments.nodes : []\n const reviewComment = comments[0] ?? null\n\n unresolvedThreads.push({\n author: normalizeOutput(reviewComment?.author?.login) || '(unknown author)',\n outdated: thread.isOutdated === true || reviewComment?.outdated === true,\n path: normalizeOutput(reviewComment?.path) || '(unknown file)',\n preview: getPreview(reviewComment?.body ?? ''),\n url: normalizeOutput(reviewComment?.url) || '(missing comment url)',\n })\n }\n\n const endCursor = normalizeOutput(reviewThreads?.pageInfo?.endCursor)\n after = hasNextPage ? endCursor || null : null\n } while (after)\n\n return unresolvedThreads.sort((left, right) => {\n const pathComparison = left.path.localeCompare(right.path)\n\n if (pathComparison !== 0) {\n return pathComparison\n }\n\n return left.url.localeCompare(right.url)\n })\n}\n\nfunction printSummaryThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n console.log(thread.url)\n }\n}\n\nfunction printVerboseThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n const author = sanitizeTerminalText(thread.author)\n const outdatedMarker = thread.outdated ? ' (outdated)' : ''\n const path = sanitizeTerminalText(thread.path)\n const preview = sanitizeTerminalText(thread.preview)\n\n console.log(`${thread.url} | ${author} | ${path}${outdatedMarker} | ${preview}`)\n }\n}\n\nfunction printSummary(pullRequest: PullRequestResponse, unresolvedCount: number): void {\n const stateLabel = pullRequest.state === 'open' ? '' : ` (${pullRequest.state})`\n console.log(\n `PR #${pullRequest.number}${stateLabel} has ${unresolvedCount} unresolved review thread(s): ${pullRequest.url}`,\n )\n}\n\nfunction getJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): JsonOutput {\n return {\n pullRequest: pullRequest\n ? {\n number: pullRequest.number,\n state: pullRequest.state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n : null,\n status,\n threads: unresolvedThreads,\n unresolvedCount: unresolvedThreads.length,\n }\n}\n\nfunction printJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): void {\n console.log(JSON.stringify(getJsonOutput(pullRequest, unresolvedThreads, status), null, 2))\n}\n\nfunction printThreads(verbose: boolean, unresolvedThreads: PullRequestThread[]): void {\n if (verbose) {\n printVerboseThreads(unresolvedThreads)\n return\n }\n\n printSummaryThreads(unresolvedThreads)\n}\n\nfunction printOutput(pullRequest: PullRequestResponse, unresolvedThreads: PullRequestThread[], verbose: boolean): void {\n printSummary(pullRequest, unresolvedThreads.length)\n\n if (unresolvedThreads.length > 0) {\n printThreads(verbose, unresolvedThreads)\n }\n}\n\nfunction runGhPrUnresolved(options: CommandOptions): number {\n const pullRequest = getCurrentPullRequest()\n\n if (!pullRequest) {\n if (options.json) {\n printJsonOutput(null, [], 'no_pr')\n return 2\n }\n\n console.log(NO_PR_MESSAGE)\n return 2\n }\n\n const unresolvedThreads = getUnresolvedThreads(pullRequest.id, getPullRequestHostname(pullRequest.url))\n\n if (options.json) {\n printJsonOutput(pullRequest, unresolvedThreads, unresolvedThreads.length === 0 ? 'clean' : 'unresolved')\n } else {\n printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose))\n }\n\n return unresolvedThreads.length > 0 ? 1 : 0\n}\n\nfunction getAvailableRemotesLabel(remotes: string[]): string {\n return remotes.length > 0 ? remotes.join(', ') : '(none)'\n}\n\nfunction getCurrentCheckoutState(): CheckoutState {\n const branchResult = runGit(['branch', '--show-current'])\n\n if (branchResult.status !== 0) {\n throw formatOperationalError('Failed to resolve current checkout', branchResult)\n }\n\n const branch = normalizeOutput(branchResult.stdout)\n\n if (branch.length > 0) {\n return {\n kind: 'branch',\n ref: branch,\n }\n }\n\n const detachedHeadResult = runGit(['rev-parse', '--verify', 'HEAD'])\n\n if (detachedHeadResult.status !== 0) {\n throw formatOperationalError('Failed to resolve current checkout', detachedHeadResult)\n }\n\n const commit = normalizeOutput(detachedHeadResult.stdout)\n\n if (commit.length === 0) {\n throw new Error('Failed to resolve current checkout: HEAD did not resolve to a commit')\n }\n\n return {\n kind: 'detached',\n ref: commit,\n }\n}\n\nfunction getGitRemotes(): string[] {\n const result = runGit(['remote'])\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to list git remotes', result)\n }\n\n return normalizeOutput(result.stdout)\n .split(/\\r?\\n/u)\n .map((remote) => remote.trim())\n .filter((remote) => remote.length > 0)\n .sort((left, right) => left.localeCompare(right))\n}\n\nfunction getSyncRemoteLabel(role: 'destination' | 'source'): string {\n return role === 'destination' ? 'Destination' : 'Source'\n}\n\nfunction getSyncableBranches(source: string): string[] {\n return SYNC_UPSTREAM_BRANCHES.filter((branch) => hasFetchedRemoteBranch(source, branch))\n}\n\nfunction hasFetchedRemoteBranch(source: string, branch: string): boolean {\n const ref = `refs/remotes/${source}/${branch}`\n const result = runGit(['show-ref', '--verify', '--quiet', ref])\n\n if (result.status === 0) {\n return true\n }\n\n if (result.status === 1) {\n return false\n }\n\n throw formatOperationalError(`Failed to resolve ${source}/${branch}`, result)\n}\n\nfunction ensureSyncRemoteExists(remote: string, remotes: string[], role: 'destination' | 'source'): void {\n if (remotes.includes(remote)) {\n return\n }\n\n throw new Error(\n `${getSyncRemoteLabel(role)} remote \"${remote}\" not found. Available remotes: ${getAvailableRemotesLabel(remotes)}`,\n )\n}\n\nfunction fetchRemote(remote: string): void {\n const result = runGit(['fetch', remote])\n\n if (result.status !== 0) {\n throw formatOperationalError(`Failed to fetch ${remote}`, result)\n }\n}\n\nfunction restoreOriginalCheckout(checkoutState: CheckoutState): Error | null {\n const result =\n checkoutState.kind === 'branch'\n ? runGit(['checkout', checkoutState.ref])\n : runGit(['checkout', '--detach', checkoutState.ref])\n\n if (result.status === 0) {\n return null\n }\n\n const destination =\n checkoutState.kind === 'branch' ? `branch \"${checkoutState.ref}\"` : `detached HEAD at ${checkoutState.ref}`\n\n return formatOperationalError(`Failed to restore original checkout to ${destination}`, result)\n}\n\nfunction syncBranch(branch: string, destination: string, source: string): void {\n const checkoutResult = runGit(['checkout', '-B', branch, `refs/remotes/${source}/${branch}`])\n\n if (checkoutResult.status !== 0) {\n throw formatOperationalError(`Failed to check out ${branch} from ${source}/${branch}`, checkoutResult)\n }\n\n const pushResult = runGit(['push', destination, `${branch}:${branch}`])\n\n if (pushResult.status !== 0) {\n throw formatOperationalError(`Failed to push ${branch} to ${destination}`, pushResult)\n }\n}\n\nfunction runSyncUpstream(destinationOption: string | undefined, sourceOption: string | undefined): number {\n const destination = normalizeOutput(destinationOption) || SYNC_UPSTREAM_DEFAULT_DESTINATION\n const source = normalizeOutput(sourceOption) || SYNC_UPSTREAM_DEFAULT_SOURCE\n const remotes = getGitRemotes()\n\n ensureSyncRemoteExists(destination, remotes, 'destination')\n ensureSyncRemoteExists(source, remotes, 'source')\n\n fetchRemote(source)\n\n const branches = getSyncableBranches(source)\n\n if (branches.length === 0) {\n console.log(`No syncable branches found on ${source}. Checked: ${SYNC_UPSTREAM_BRANCHES.join(', ')}`)\n return NO_SYNCABLE_BRANCHES_EXIT_CODE\n }\n\n const originalCheckout = getCurrentCheckoutState()\n let syncError: Error | null = null\n\n console.log(`Syncing ${branches.join(', ')} from ${source} to ${destination}`)\n\n for (const branch of branches) {\n try {\n syncBranch(branch, destination, source)\n console.log(`Synced ${branch} to ${destination}`)\n } catch (error) {\n syncError = error instanceof Error ? error : new Error(String(error))\n break\n }\n }\n\n const restoreError = restoreOriginalCheckout(originalCheckout)\n\n if (syncError && restoreError) {\n throw new Error(`${syncError.message}\\n${restoreError.message}`)\n }\n\n if (syncError) {\n throw syncError\n }\n\n if (restoreError) {\n throw restoreError\n }\n\n return 0\n}\n\nfunction createProgram(): Command {\n const program = new Command()\n\n program.name('hivectl').description('Common local and GitHub workflow helpers').exitOverride()\n\n program\n .command('gh-pr-unresolved')\n .description('Show unresolved review threads on the pull request for the current branch')\n .option('--json', 'show unresolved review threads as JSON')\n .option('-v, --verbose', 'show unresolved review threads in detail')\n .action((options: CommandOptions) => {\n process.exitCode = runGhPrUnresolved(options)\n })\n\n program\n .command('sync-upstream')\n .description('Sync dev, develop, main, and master from a source remote to a destination remote')\n .option('--destination <remote>', 'destination remote name', SYNC_UPSTREAM_DEFAULT_DESTINATION)\n .option('--source <remote>', 'source remote name', SYNC_UPSTREAM_DEFAULT_SOURCE)\n .action((options: { destination?: string; source?: string }) => {\n process.exitCode = runSyncUpstream(options.destination, options.source)\n })\n\n return program\n}\n\nasync function main(argv = process.argv): Promise<void> {\n const program = createProgram()\n\n try {\n await program.parseAsync(argv)\n\n if (typeof process.exitCode !== 'number') {\n process.exitCode = 0\n }\n } catch (error) {\n if (error instanceof CommanderError) {\n process.exitCode = error.code === 'commander.helpDisplayed' ? 0 : error.exitCode\n return\n }\n\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n }\n}\n\nvoid main()\n"],"mappings":"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"}
package/dist/cli.mjs CHANGED
@@ -4,9 +4,18 @@ import { Command, CommanderError } from "commander";
4
4
 
5
5
  //#region src/cli.ts
6
6
  const NO_PR_MESSAGE = "No pull request found for current branch";
7
+ const NO_SYNCABLE_BRANCHES_EXIT_CODE = 2;
7
8
  const ANSI_ESCAPE_SEQUENCES = new RegExp(String.raw`\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001b\\))`, "gu");
8
9
  const CONTROL_CHARACTERS = new RegExp(String.raw`[\u0000-\u001f\u007f]`, "gu");
9
10
  const MAX_PREVIEW_LENGTH = 120;
11
+ const SYNC_UPSTREAM_BRANCHES = [
12
+ "dev",
13
+ "develop",
14
+ "main",
15
+ "master"
16
+ ];
17
+ const SYNC_UPSTREAM_DEFAULT_DESTINATION = "origin";
18
+ const SYNC_UPSTREAM_DEFAULT_SOURCE = "upstream";
10
19
  const REVIEW_THREADS_QUERY = `
11
20
  query($id: ID!, $after: String) {
12
21
  node(id: $id) {
@@ -101,6 +110,21 @@ function runGh(args) {
101
110
  stdout: result.stdout ?? ""
102
111
  };
103
112
  }
113
+ function runGit(args) {
114
+ const result = spawnSync("git", args, {
115
+ encoding: "utf8",
116
+ env: process.env
117
+ });
118
+ if (result.error) {
119
+ if ("code" in result.error && result.error.code === "ENOENT") throw new Error("Failed to run git: git is not installed or not available on PATH");
120
+ throw new Error(`Failed to run git: ${result.error.message}`);
121
+ }
122
+ return {
123
+ status: result.status ?? 1,
124
+ stderr: result.stderr ?? "",
125
+ stdout: result.stdout ?? ""
126
+ };
127
+ }
104
128
  function isNoPullRequestFailure(result) {
105
129
  const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase();
106
130
  return detail.includes("could not determine current branch") || detail.includes("no pull requests found for branch") || detail.includes("not on any branch");
@@ -222,12 +246,121 @@ function runGhPrUnresolved(options) {
222
246
  else printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose));
223
247
  return unresolvedThreads.length > 0 ? 1 : 0;
224
248
  }
249
+ function getAvailableRemotesLabel(remotes) {
250
+ return remotes.length > 0 ? remotes.join(", ") : "(none)";
251
+ }
252
+ function getCurrentCheckoutState() {
253
+ const branchResult = runGit(["branch", "--show-current"]);
254
+ if (branchResult.status !== 0) throw formatOperationalError("Failed to resolve current checkout", branchResult);
255
+ const branch = normalizeOutput(branchResult.stdout);
256
+ if (branch.length > 0) return {
257
+ kind: "branch",
258
+ ref: branch
259
+ };
260
+ const detachedHeadResult = runGit([
261
+ "rev-parse",
262
+ "--verify",
263
+ "HEAD"
264
+ ]);
265
+ if (detachedHeadResult.status !== 0) throw formatOperationalError("Failed to resolve current checkout", detachedHeadResult);
266
+ const commit = normalizeOutput(detachedHeadResult.stdout);
267
+ if (commit.length === 0) throw new Error("Failed to resolve current checkout: HEAD did not resolve to a commit");
268
+ return {
269
+ kind: "detached",
270
+ ref: commit
271
+ };
272
+ }
273
+ function getGitRemotes() {
274
+ const result = runGit(["remote"]);
275
+ if (result.status !== 0) throw formatOperationalError("Failed to list git remotes", result);
276
+ return normalizeOutput(result.stdout).split(/\r?\n/u).map((remote) => remote.trim()).filter((remote) => remote.length > 0).sort((left, right) => left.localeCompare(right));
277
+ }
278
+ function getSyncRemoteLabel(role) {
279
+ return role === "destination" ? "Destination" : "Source";
280
+ }
281
+ function getSyncableBranches(source) {
282
+ return SYNC_UPSTREAM_BRANCHES.filter((branch) => hasFetchedRemoteBranch(source, branch));
283
+ }
284
+ function hasFetchedRemoteBranch(source, branch) {
285
+ const result = runGit([
286
+ "show-ref",
287
+ "--verify",
288
+ "--quiet",
289
+ `refs/remotes/${source}/${branch}`
290
+ ]);
291
+ if (result.status === 0) return true;
292
+ if (result.status === 1) return false;
293
+ throw formatOperationalError(`Failed to resolve ${source}/${branch}`, result);
294
+ }
295
+ function ensureSyncRemoteExists(remote, remotes, role) {
296
+ if (remotes.includes(remote)) return;
297
+ throw new Error(`${getSyncRemoteLabel(role)} remote "${remote}" not found. Available remotes: ${getAvailableRemotesLabel(remotes)}`);
298
+ }
299
+ function fetchRemote(remote) {
300
+ const result = runGit(["fetch", remote]);
301
+ if (result.status !== 0) throw formatOperationalError(`Failed to fetch ${remote}`, result);
302
+ }
303
+ function restoreOriginalCheckout(checkoutState) {
304
+ const result = checkoutState.kind === "branch" ? runGit(["checkout", checkoutState.ref]) : runGit([
305
+ "checkout",
306
+ "--detach",
307
+ checkoutState.ref
308
+ ]);
309
+ if (result.status === 0) return null;
310
+ return formatOperationalError(`Failed to restore original checkout to ${checkoutState.kind === "branch" ? `branch "${checkoutState.ref}"` : `detached HEAD at ${checkoutState.ref}`}`, result);
311
+ }
312
+ function syncBranch(branch, destination, source) {
313
+ const checkoutResult = runGit([
314
+ "checkout",
315
+ "-B",
316
+ branch,
317
+ `refs/remotes/${source}/${branch}`
318
+ ]);
319
+ if (checkoutResult.status !== 0) throw formatOperationalError(`Failed to check out ${branch} from ${source}/${branch}`, checkoutResult);
320
+ const pushResult = runGit([
321
+ "push",
322
+ destination,
323
+ `${branch}:${branch}`
324
+ ]);
325
+ if (pushResult.status !== 0) throw formatOperationalError(`Failed to push ${branch} to ${destination}`, pushResult);
326
+ }
327
+ function runSyncUpstream(destinationOption, sourceOption) {
328
+ const destination = normalizeOutput(destinationOption) || SYNC_UPSTREAM_DEFAULT_DESTINATION;
329
+ const source = normalizeOutput(sourceOption) || SYNC_UPSTREAM_DEFAULT_SOURCE;
330
+ const remotes = getGitRemotes();
331
+ ensureSyncRemoteExists(destination, remotes, "destination");
332
+ ensureSyncRemoteExists(source, remotes, "source");
333
+ fetchRemote(source);
334
+ const branches = getSyncableBranches(source);
335
+ if (branches.length === 0) {
336
+ console.log(`No syncable branches found on ${source}. Checked: ${SYNC_UPSTREAM_BRANCHES.join(", ")}`);
337
+ return NO_SYNCABLE_BRANCHES_EXIT_CODE;
338
+ }
339
+ const originalCheckout = getCurrentCheckoutState();
340
+ let syncError = null;
341
+ console.log(`Syncing ${branches.join(", ")} from ${source} to ${destination}`);
342
+ for (const branch of branches) try {
343
+ syncBranch(branch, destination, source);
344
+ console.log(`Synced ${branch} to ${destination}`);
345
+ } catch (error) {
346
+ syncError = error instanceof Error ? error : new Error(String(error));
347
+ break;
348
+ }
349
+ const restoreError = restoreOriginalCheckout(originalCheckout);
350
+ if (syncError && restoreError) throw new Error(`${syncError.message}\n${restoreError.message}`);
351
+ if (syncError) throw syncError;
352
+ if (restoreError) throw restoreError;
353
+ return 0;
354
+ }
225
355
  function createProgram() {
226
356
  const program = new Command();
227
357
  program.name("hivectl").description("Common local and GitHub workflow helpers").exitOverride();
228
358
  program.command("gh-pr-unresolved").description("Show unresolved review threads on the pull request for the current branch").option("--json", "show unresolved review threads as JSON").option("-v, --verbose", "show unresolved review threads in detail").action((options) => {
229
359
  process.exitCode = runGhPrUnresolved(options);
230
360
  });
361
+ program.command("sync-upstream").description("Sync dev, develop, main, and master from a source remote to a destination remote").option("--destination <remote>", "destination remote name", SYNC_UPSTREAM_DEFAULT_DESTINATION).option("--source <remote>", "source remote name", SYNC_UPSTREAM_DEFAULT_SOURCE).action((options) => {
362
+ process.exitCode = runSyncUpstream(options.destination, options.source);
363
+ });
231
364
  return program;
232
365
  }
233
366
  async function main(argv = process.argv) {
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawnSync } from 'node:child_process'\nimport { Command, CommanderError } from 'commander'\n\ntype ReviewComment = {\n author?: {\n login?: string | null\n } | null\n body?: string | null\n outdated?: boolean | null\n path?: string | null\n url?: string | null\n}\n\ntype ReviewThreadNode = {\n comments?: {\n nodes?: Array<ReviewComment | null> | null\n } | null\n isOutdated?: boolean | null\n isResolved?: boolean | null\n}\n\ntype ReviewThreadsResponse = {\n data?: {\n node?: {\n reviewThreads?: {\n nodes?: Array<ReviewThreadNode | null> | null\n pageInfo?: {\n endCursor?: string | null\n hasNextPage?: boolean | null\n } | null\n } | null\n } | null\n } | null\n errors?: Array<{\n message?: string | null\n } | null> | null\n}\n\ntype PullRequestState = 'closed' | 'merged' | 'open'\n\ntype PullRequestResponse = {\n id: string\n number: number\n state: PullRequestState\n title: string\n url: string\n}\n\ntype PullRequestThread = {\n author: string\n outdated: boolean\n path: string\n preview: string\n url: string\n}\n\ntype CommandOptions = {\n json?: boolean\n verbose?: boolean\n}\n\ntype GhResult = {\n status: number\n stderr: string\n stdout: string\n}\n\ntype JsonOutput = {\n pullRequest: {\n number: number\n state: PullRequestState\n title: string\n url: string\n } | null\n status: 'clean' | 'no_pr' | 'unresolved'\n threads: PullRequestThread[]\n unresolvedCount: number\n}\n\nconst NO_PR_MESSAGE = 'No pull request found for current branch'\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst ANSI_ESCAPE_SEQUENCES = new RegExp(\n String.raw`\\u001b(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~]|\\][^\\u0007]*(?:\\u0007|\\u001b\\\\))`,\n 'gu',\n)\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst CONTROL_CHARACTERS = new RegExp(String.raw`[\\u0000-\\u001f\\u007f]`, 'gu')\nconst MAX_PREVIEW_LENGTH = 120\nconst REVIEW_THREADS_QUERY = `\n query($id: ID!, $after: String) {\n node(id: $id) {\n ... on PullRequest {\n reviewThreads(first: 100, after: $after) {\n nodes {\n isOutdated\n isResolved\n comments(first: 1) {\n nodes {\n author {\n login\n }\n body\n outdated\n path\n url\n }\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n`\n\nfunction normalizeOutput(value: string | null | undefined): string {\n return value?.trim() ?? ''\n}\n\nfunction formatOperationalError(prefix: string, result: GhResult): Error {\n const detail = normalizeOutput(result.stderr) || normalizeOutput(result.stdout)\n\n return new Error(detail ? `${prefix}: ${detail}` : prefix)\n}\n\nfunction parseJson<T>(value: string, context: string): T {\n try {\n return JSON.parse(value) as T\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`${context}: ${message}`)\n }\n}\n\nfunction parsePullRequestState(value: unknown): PullRequestState | null {\n if (typeof value !== 'string') {\n return null\n }\n\n switch (value.toLowerCase()) {\n case 'closed':\n return 'closed'\n case 'merged':\n return 'merged'\n case 'open':\n return 'open'\n default:\n return null\n }\n}\n\nfunction toPullRequestResponse(value: unknown): PullRequestResponse | null {\n const pullRequest = value as\n | {\n id?: unknown\n number?: unknown\n state?: unknown\n title?: unknown\n url?: unknown\n }\n | null\n | undefined\n const state = parsePullRequestState(pullRequest?.state)\n\n if (\n !pullRequest ||\n typeof pullRequest !== 'object' ||\n typeof pullRequest.id !== 'string' ||\n pullRequest.id.length === 0 ||\n typeof pullRequest.number !== 'number' ||\n !state ||\n typeof pullRequest.title !== 'string' ||\n typeof pullRequest.url !== 'string'\n ) {\n return null\n }\n\n return {\n id: pullRequest.id,\n number: pullRequest.number,\n state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n}\n\nfunction parsePullRequestResponse(value: string): PullRequestResponse {\n const pullRequest = toPullRequestResponse(parseJson<unknown>(value, 'Failed to parse pull request response'))\n\n if (!pullRequest) {\n throw new Error('Failed to parse pull request response: Response is missing required pull request fields')\n }\n\n return pullRequest\n}\n\nfunction getPreview(body: string): string {\n const firstLine = body\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .find((line) => line.length > 0)\n\n if (!firstLine) {\n return '(no preview available)'\n }\n\n if (firstLine.length <= MAX_PREVIEW_LENGTH) {\n return firstLine\n }\n\n return `${firstLine.slice(0, MAX_PREVIEW_LENGTH - 3)}...`\n}\n\nfunction sanitizeTerminalText(value: string): string {\n return value.replace(ANSI_ESCAPE_SEQUENCES, '').replace(CONTROL_CHARACTERS, '')\n}\n\nfunction runGh(args: string[]): GhResult {\n const result = spawnSync('gh', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run gh: gh is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run gh: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction isNoPullRequestFailure(result: GhResult): boolean {\n const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase()\n\n return (\n detail.includes('could not determine current branch') ||\n detail.includes('no pull requests found for branch') ||\n detail.includes('not on any branch')\n )\n}\n\nfunction getCurrentPullRequest(): PullRequestResponse | null {\n const result = runGh(['pr', 'view', '--json', 'id,number,state,title,url'])\n\n if (result.status === 0) {\n return parsePullRequestResponse(result.stdout)\n }\n\n if (isNoPullRequestFailure(result)) {\n return null\n }\n\n throw formatOperationalError('Failed to resolve pull request for current branch', result)\n}\n\nfunction getPullRequestHostname(url: string): string | null {\n try {\n const hostname = new URL(url).hostname\n\n return hostname === 'github.com' ? null : hostname\n } catch {\n return null\n }\n}\n\nfunction getReviewThreadsPage(id: string, hostname: string | null, after: string | null): ReviewThreadsResponse {\n const args = ['api', 'graphql']\n\n if (hostname) {\n args.push('--hostname', hostname)\n }\n\n args.push('-f', `query=${REVIEW_THREADS_QUERY}`, '-F', `id=${id}`)\n\n if (after) {\n args.push('-F', `after=${after}`)\n }\n\n const result = runGh(args)\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to fetch review threads', result)\n }\n\n return parseJson<ReviewThreadsResponse>(result.stdout, 'Failed to parse review threads response')\n}\n\nfunction getUnresolvedThreads(id: string, hostname: string | null): PullRequestThread[] {\n const unresolvedThreads: PullRequestThread[] = []\n let after: string | null = null\n\n do {\n const response = getReviewThreadsPage(id, hostname, after)\n const errorMessage =\n response.errors\n ?.map((error) => normalizeOutput(error?.message))\n .filter((message) => message.length > 0)\n .join('; ') ?? ''\n\n if (errorMessage.length > 0) {\n throw new Error(`Failed to fetch review threads: ${errorMessage}`)\n }\n\n const reviewThreads = response.data?.node?.reviewThreads\n const nodes = reviewThreads?.nodes\n const hasNextPage = reviewThreads?.pageInfo?.hasNextPage\n\n if (!Array.isArray(nodes) || typeof hasNextPage !== 'boolean') {\n throw new Error('Failed to fetch review threads: Pull request review threads were not returned')\n }\n\n for (const thread of nodes) {\n if (!thread || thread.isResolved === true) {\n continue\n }\n\n const comments = Array.isArray(thread.comments?.nodes) ? thread.comments.nodes : []\n const reviewComment = comments[0] ?? null\n\n unresolvedThreads.push({\n author: normalizeOutput(reviewComment?.author?.login) || '(unknown author)',\n outdated: thread.isOutdated === true || reviewComment?.outdated === true,\n path: normalizeOutput(reviewComment?.path) || '(unknown file)',\n preview: getPreview(reviewComment?.body ?? ''),\n url: normalizeOutput(reviewComment?.url) || '(missing comment url)',\n })\n }\n\n const endCursor = normalizeOutput(reviewThreads?.pageInfo?.endCursor)\n after = hasNextPage ? endCursor || null : null\n } while (after)\n\n return unresolvedThreads.sort((left, right) => {\n const pathComparison = left.path.localeCompare(right.path)\n\n if (pathComparison !== 0) {\n return pathComparison\n }\n\n return left.url.localeCompare(right.url)\n })\n}\n\nfunction printSummaryThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n console.log(thread.url)\n }\n}\n\nfunction printVerboseThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n const author = sanitizeTerminalText(thread.author)\n const outdatedMarker = thread.outdated ? ' (outdated)' : ''\n const path = sanitizeTerminalText(thread.path)\n const preview = sanitizeTerminalText(thread.preview)\n\n console.log(`${thread.url} | ${author} | ${path}${outdatedMarker} | ${preview}`)\n }\n}\n\nfunction printSummary(pullRequest: PullRequestResponse, unresolvedCount: number): void {\n const stateLabel = pullRequest.state === 'open' ? '' : ` (${pullRequest.state})`\n console.log(\n `PR #${pullRequest.number}${stateLabel} has ${unresolvedCount} unresolved review thread(s): ${pullRequest.url}`,\n )\n}\n\nfunction getJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): JsonOutput {\n return {\n pullRequest: pullRequest\n ? {\n number: pullRequest.number,\n state: pullRequest.state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n : null,\n status,\n threads: unresolvedThreads,\n unresolvedCount: unresolvedThreads.length,\n }\n}\n\nfunction printJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): void {\n console.log(JSON.stringify(getJsonOutput(pullRequest, unresolvedThreads, status), null, 2))\n}\n\nfunction printThreads(verbose: boolean, unresolvedThreads: PullRequestThread[]): void {\n if (verbose) {\n printVerboseThreads(unresolvedThreads)\n return\n }\n\n printSummaryThreads(unresolvedThreads)\n}\n\nfunction printOutput(pullRequest: PullRequestResponse, unresolvedThreads: PullRequestThread[], verbose: boolean): void {\n printSummary(pullRequest, unresolvedThreads.length)\n\n if (unresolvedThreads.length > 0) {\n printThreads(verbose, unresolvedThreads)\n }\n}\n\nfunction runGhPrUnresolved(options: CommandOptions): number {\n const pullRequest = getCurrentPullRequest()\n\n if (!pullRequest) {\n if (options.json) {\n printJsonOutput(null, [], 'no_pr')\n return 2\n }\n\n console.log(NO_PR_MESSAGE)\n return 2\n }\n\n const unresolvedThreads = getUnresolvedThreads(pullRequest.id, getPullRequestHostname(pullRequest.url))\n\n if (options.json) {\n printJsonOutput(pullRequest, unresolvedThreads, unresolvedThreads.length === 0 ? 'clean' : 'unresolved')\n } else {\n printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose))\n }\n\n return unresolvedThreads.length > 0 ? 1 : 0\n}\n\nfunction createProgram(): Command {\n const program = new Command()\n\n program.name('hivectl').description('Common local and GitHub workflow helpers').exitOverride()\n\n program\n .command('gh-pr-unresolved')\n .description('Show unresolved review threads on the pull request for the current branch')\n .option('--json', 'show unresolved review threads as JSON')\n .option('-v, --verbose', 'show unresolved review threads in detail')\n .action((options: CommandOptions) => {\n process.exitCode = runGhPrUnresolved(options)\n })\n\n return program\n}\n\nasync function main(argv = process.argv): Promise<void> {\n const program = createProgram()\n\n try {\n await program.parseAsync(argv)\n\n if (typeof process.exitCode !== 'number') {\n process.exitCode = 0\n }\n } catch (error) {\n if (error instanceof CommanderError) {\n process.exitCode = error.code === 'commander.helpDisplayed' ? 0 : error.exitCode\n return\n }\n\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n }\n}\n\nvoid main()\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawnSync } from 'node:child_process'\nimport { Command, CommanderError } from 'commander'\n\ntype ReviewComment = {\n author?: {\n login?: string | null\n } | null\n body?: string | null\n outdated?: boolean | null\n path?: string | null\n url?: string | null\n}\n\ntype ReviewThreadNode = {\n comments?: {\n nodes?: Array<ReviewComment | null> | null\n } | null\n isOutdated?: boolean | null\n isResolved?: boolean | null\n}\n\ntype ReviewThreadsResponse = {\n data?: {\n node?: {\n reviewThreads?: {\n nodes?: Array<ReviewThreadNode | null> | null\n pageInfo?: {\n endCursor?: string | null\n hasNextPage?: boolean | null\n } | null\n } | null\n } | null\n } | null\n errors?: Array<{\n message?: string | null\n } | null> | null\n}\n\ntype PullRequestState = 'closed' | 'merged' | 'open'\n\ntype PullRequestResponse = {\n id: string\n number: number\n state: PullRequestState\n title: string\n url: string\n}\n\ntype PullRequestThread = {\n author: string\n outdated: boolean\n path: string\n preview: string\n url: string\n}\n\ntype CheckoutState =\n | {\n kind: 'branch'\n ref: string\n }\n | {\n kind: 'detached'\n ref: string\n }\n\ntype CommandOptions = {\n json?: boolean\n verbose?: boolean\n}\n\ntype CommandResult = {\n status: number\n stderr: string\n stdout: string\n}\n\ntype JsonOutput = {\n pullRequest: {\n number: number\n state: PullRequestState\n title: string\n url: string\n } | null\n status: 'clean' | 'no_pr' | 'unresolved'\n threads: PullRequestThread[]\n unresolvedCount: number\n}\n\nconst NO_PR_MESSAGE = 'No pull request found for current branch'\nconst NO_SYNCABLE_BRANCHES_EXIT_CODE = 2\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst ANSI_ESCAPE_SEQUENCES = new RegExp(\n String.raw`\\u001b(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~]|\\][^\\u0007]*(?:\\u0007|\\u001b\\\\))`,\n 'gu',\n)\n// biome-ignore lint/complexity/useRegexLiterals: The constructor avoids embedding control characters in a regex literal.\nconst CONTROL_CHARACTERS = new RegExp(String.raw`[\\u0000-\\u001f\\u007f]`, 'gu')\nconst MAX_PREVIEW_LENGTH = 120\nconst SYNC_UPSTREAM_BRANCHES = ['dev', 'develop', 'main', 'master'] as const\nconst SYNC_UPSTREAM_DEFAULT_DESTINATION = 'origin'\nconst SYNC_UPSTREAM_DEFAULT_SOURCE = 'upstream'\nconst REVIEW_THREADS_QUERY = `\n query($id: ID!, $after: String) {\n node(id: $id) {\n ... on PullRequest {\n reviewThreads(first: 100, after: $after) {\n nodes {\n isOutdated\n isResolved\n comments(first: 1) {\n nodes {\n author {\n login\n }\n body\n outdated\n path\n url\n }\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n`\n\nfunction normalizeOutput(value: string | null | undefined): string {\n return value?.trim() ?? ''\n}\n\nfunction formatOperationalError(prefix: string, result: CommandResult): Error {\n const detail = normalizeOutput(result.stderr) || normalizeOutput(result.stdout)\n\n return new Error(detail ? `${prefix}: ${detail}` : prefix)\n}\n\nfunction parseJson<T>(value: string, context: string): T {\n try {\n return JSON.parse(value) as T\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`${context}: ${message}`)\n }\n}\n\nfunction parsePullRequestState(value: unknown): PullRequestState | null {\n if (typeof value !== 'string') {\n return null\n }\n\n switch (value.toLowerCase()) {\n case 'closed':\n return 'closed'\n case 'merged':\n return 'merged'\n case 'open':\n return 'open'\n default:\n return null\n }\n}\n\nfunction toPullRequestResponse(value: unknown): PullRequestResponse | null {\n const pullRequest = value as\n | {\n id?: unknown\n number?: unknown\n state?: unknown\n title?: unknown\n url?: unknown\n }\n | null\n | undefined\n const state = parsePullRequestState(pullRequest?.state)\n\n if (\n !pullRequest ||\n typeof pullRequest !== 'object' ||\n typeof pullRequest.id !== 'string' ||\n pullRequest.id.length === 0 ||\n typeof pullRequest.number !== 'number' ||\n !state ||\n typeof pullRequest.title !== 'string' ||\n typeof pullRequest.url !== 'string'\n ) {\n return null\n }\n\n return {\n id: pullRequest.id,\n number: pullRequest.number,\n state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n}\n\nfunction parsePullRequestResponse(value: string): PullRequestResponse {\n const pullRequest = toPullRequestResponse(parseJson<unknown>(value, 'Failed to parse pull request response'))\n\n if (!pullRequest) {\n throw new Error('Failed to parse pull request response: Response is missing required pull request fields')\n }\n\n return pullRequest\n}\n\nfunction getPreview(body: string): string {\n const firstLine = body\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .find((line) => line.length > 0)\n\n if (!firstLine) {\n return '(no preview available)'\n }\n\n if (firstLine.length <= MAX_PREVIEW_LENGTH) {\n return firstLine\n }\n\n return `${firstLine.slice(0, MAX_PREVIEW_LENGTH - 3)}...`\n}\n\nfunction sanitizeTerminalText(value: string): string {\n return value.replace(ANSI_ESCAPE_SEQUENCES, '').replace(CONTROL_CHARACTERS, '')\n}\n\nfunction runGh(args: string[]): CommandResult {\n const result = spawnSync('gh', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run gh: gh is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run gh: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction runGit(args: string[]): CommandResult {\n const result = spawnSync('git', args, {\n encoding: 'utf8',\n env: process.env,\n })\n\n if (result.error) {\n if ('code' in result.error && result.error.code === 'ENOENT') {\n throw new Error('Failed to run git: git is not installed or not available on PATH')\n }\n\n throw new Error(`Failed to run git: ${result.error.message}`)\n }\n\n return {\n status: result.status ?? 1,\n stderr: result.stderr ?? '',\n stdout: result.stdout ?? '',\n }\n}\n\nfunction isNoPullRequestFailure(result: CommandResult): boolean {\n const detail = `${normalizeOutput(result.stderr)} ${normalizeOutput(result.stdout)}`.toLowerCase()\n\n return (\n detail.includes('could not determine current branch') ||\n detail.includes('no pull requests found for branch') ||\n detail.includes('not on any branch')\n )\n}\n\nfunction getCurrentPullRequest(): PullRequestResponse | null {\n const result = runGh(['pr', 'view', '--json', 'id,number,state,title,url'])\n\n if (result.status === 0) {\n return parsePullRequestResponse(result.stdout)\n }\n\n if (isNoPullRequestFailure(result)) {\n return null\n }\n\n throw formatOperationalError('Failed to resolve pull request for current branch', result)\n}\n\nfunction getPullRequestHostname(url: string): string | null {\n try {\n const hostname = new URL(url).hostname\n\n return hostname === 'github.com' ? null : hostname\n } catch {\n return null\n }\n}\n\nfunction getReviewThreadsPage(id: string, hostname: string | null, after: string | null): ReviewThreadsResponse {\n const args = ['api', 'graphql']\n\n if (hostname) {\n args.push('--hostname', hostname)\n }\n\n args.push('-f', `query=${REVIEW_THREADS_QUERY}`, '-F', `id=${id}`)\n\n if (after) {\n args.push('-F', `after=${after}`)\n }\n\n const result = runGh(args)\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to fetch review threads', result)\n }\n\n return parseJson<ReviewThreadsResponse>(result.stdout, 'Failed to parse review threads response')\n}\n\nfunction getUnresolvedThreads(id: string, hostname: string | null): PullRequestThread[] {\n const unresolvedThreads: PullRequestThread[] = []\n let after: string | null = null\n\n do {\n const response = getReviewThreadsPage(id, hostname, after)\n const errorMessage =\n response.errors\n ?.map((error) => normalizeOutput(error?.message))\n .filter((message) => message.length > 0)\n .join('; ') ?? ''\n\n if (errorMessage.length > 0) {\n throw new Error(`Failed to fetch review threads: ${errorMessage}`)\n }\n\n const reviewThreads = response.data?.node?.reviewThreads\n const nodes = reviewThreads?.nodes\n const hasNextPage = reviewThreads?.pageInfo?.hasNextPage\n\n if (!Array.isArray(nodes) || typeof hasNextPage !== 'boolean') {\n throw new Error('Failed to fetch review threads: Pull request review threads were not returned')\n }\n\n for (const thread of nodes) {\n if (!thread || thread.isResolved === true) {\n continue\n }\n\n const comments = Array.isArray(thread.comments?.nodes) ? thread.comments.nodes : []\n const reviewComment = comments[0] ?? null\n\n unresolvedThreads.push({\n author: normalizeOutput(reviewComment?.author?.login) || '(unknown author)',\n outdated: thread.isOutdated === true || reviewComment?.outdated === true,\n path: normalizeOutput(reviewComment?.path) || '(unknown file)',\n preview: getPreview(reviewComment?.body ?? ''),\n url: normalizeOutput(reviewComment?.url) || '(missing comment url)',\n })\n }\n\n const endCursor = normalizeOutput(reviewThreads?.pageInfo?.endCursor)\n after = hasNextPage ? endCursor || null : null\n } while (after)\n\n return unresolvedThreads.sort((left, right) => {\n const pathComparison = left.path.localeCompare(right.path)\n\n if (pathComparison !== 0) {\n return pathComparison\n }\n\n return left.url.localeCompare(right.url)\n })\n}\n\nfunction printSummaryThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n console.log(thread.url)\n }\n}\n\nfunction printVerboseThreads(threads: PullRequestThread[]): void {\n for (const thread of threads) {\n const author = sanitizeTerminalText(thread.author)\n const outdatedMarker = thread.outdated ? ' (outdated)' : ''\n const path = sanitizeTerminalText(thread.path)\n const preview = sanitizeTerminalText(thread.preview)\n\n console.log(`${thread.url} | ${author} | ${path}${outdatedMarker} | ${preview}`)\n }\n}\n\nfunction printSummary(pullRequest: PullRequestResponse, unresolvedCount: number): void {\n const stateLabel = pullRequest.state === 'open' ? '' : ` (${pullRequest.state})`\n console.log(\n `PR #${pullRequest.number}${stateLabel} has ${unresolvedCount} unresolved review thread(s): ${pullRequest.url}`,\n )\n}\n\nfunction getJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): JsonOutput {\n return {\n pullRequest: pullRequest\n ? {\n number: pullRequest.number,\n state: pullRequest.state,\n title: pullRequest.title,\n url: pullRequest.url,\n }\n : null,\n status,\n threads: unresolvedThreads,\n unresolvedCount: unresolvedThreads.length,\n }\n}\n\nfunction printJsonOutput(\n pullRequest: PullRequestResponse | null,\n unresolvedThreads: PullRequestThread[],\n status: JsonOutput['status'],\n): void {\n console.log(JSON.stringify(getJsonOutput(pullRequest, unresolvedThreads, status), null, 2))\n}\n\nfunction printThreads(verbose: boolean, unresolvedThreads: PullRequestThread[]): void {\n if (verbose) {\n printVerboseThreads(unresolvedThreads)\n return\n }\n\n printSummaryThreads(unresolvedThreads)\n}\n\nfunction printOutput(pullRequest: PullRequestResponse, unresolvedThreads: PullRequestThread[], verbose: boolean): void {\n printSummary(pullRequest, unresolvedThreads.length)\n\n if (unresolvedThreads.length > 0) {\n printThreads(verbose, unresolvedThreads)\n }\n}\n\nfunction runGhPrUnresolved(options: CommandOptions): number {\n const pullRequest = getCurrentPullRequest()\n\n if (!pullRequest) {\n if (options.json) {\n printJsonOutput(null, [], 'no_pr')\n return 2\n }\n\n console.log(NO_PR_MESSAGE)\n return 2\n }\n\n const unresolvedThreads = getUnresolvedThreads(pullRequest.id, getPullRequestHostname(pullRequest.url))\n\n if (options.json) {\n printJsonOutput(pullRequest, unresolvedThreads, unresolvedThreads.length === 0 ? 'clean' : 'unresolved')\n } else {\n printOutput(pullRequest, unresolvedThreads, Boolean(options.verbose))\n }\n\n return unresolvedThreads.length > 0 ? 1 : 0\n}\n\nfunction getAvailableRemotesLabel(remotes: string[]): string {\n return remotes.length > 0 ? remotes.join(', ') : '(none)'\n}\n\nfunction getCurrentCheckoutState(): CheckoutState {\n const branchResult = runGit(['branch', '--show-current'])\n\n if (branchResult.status !== 0) {\n throw formatOperationalError('Failed to resolve current checkout', branchResult)\n }\n\n const branch = normalizeOutput(branchResult.stdout)\n\n if (branch.length > 0) {\n return {\n kind: 'branch',\n ref: branch,\n }\n }\n\n const detachedHeadResult = runGit(['rev-parse', '--verify', 'HEAD'])\n\n if (detachedHeadResult.status !== 0) {\n throw formatOperationalError('Failed to resolve current checkout', detachedHeadResult)\n }\n\n const commit = normalizeOutput(detachedHeadResult.stdout)\n\n if (commit.length === 0) {\n throw new Error('Failed to resolve current checkout: HEAD did not resolve to a commit')\n }\n\n return {\n kind: 'detached',\n ref: commit,\n }\n}\n\nfunction getGitRemotes(): string[] {\n const result = runGit(['remote'])\n\n if (result.status !== 0) {\n throw formatOperationalError('Failed to list git remotes', result)\n }\n\n return normalizeOutput(result.stdout)\n .split(/\\r?\\n/u)\n .map((remote) => remote.trim())\n .filter((remote) => remote.length > 0)\n .sort((left, right) => left.localeCompare(right))\n}\n\nfunction getSyncRemoteLabel(role: 'destination' | 'source'): string {\n return role === 'destination' ? 'Destination' : 'Source'\n}\n\nfunction getSyncableBranches(source: string): string[] {\n return SYNC_UPSTREAM_BRANCHES.filter((branch) => hasFetchedRemoteBranch(source, branch))\n}\n\nfunction hasFetchedRemoteBranch(source: string, branch: string): boolean {\n const ref = `refs/remotes/${source}/${branch}`\n const result = runGit(['show-ref', '--verify', '--quiet', ref])\n\n if (result.status === 0) {\n return true\n }\n\n if (result.status === 1) {\n return false\n }\n\n throw formatOperationalError(`Failed to resolve ${source}/${branch}`, result)\n}\n\nfunction ensureSyncRemoteExists(remote: string, remotes: string[], role: 'destination' | 'source'): void {\n if (remotes.includes(remote)) {\n return\n }\n\n throw new Error(\n `${getSyncRemoteLabel(role)} remote \"${remote}\" not found. Available remotes: ${getAvailableRemotesLabel(remotes)}`,\n )\n}\n\nfunction fetchRemote(remote: string): void {\n const result = runGit(['fetch', remote])\n\n if (result.status !== 0) {\n throw formatOperationalError(`Failed to fetch ${remote}`, result)\n }\n}\n\nfunction restoreOriginalCheckout(checkoutState: CheckoutState): Error | null {\n const result =\n checkoutState.kind === 'branch'\n ? runGit(['checkout', checkoutState.ref])\n : runGit(['checkout', '--detach', checkoutState.ref])\n\n if (result.status === 0) {\n return null\n }\n\n const destination =\n checkoutState.kind === 'branch' ? `branch \"${checkoutState.ref}\"` : `detached HEAD at ${checkoutState.ref}`\n\n return formatOperationalError(`Failed to restore original checkout to ${destination}`, result)\n}\n\nfunction syncBranch(branch: string, destination: string, source: string): void {\n const checkoutResult = runGit(['checkout', '-B', branch, `refs/remotes/${source}/${branch}`])\n\n if (checkoutResult.status !== 0) {\n throw formatOperationalError(`Failed to check out ${branch} from ${source}/${branch}`, checkoutResult)\n }\n\n const pushResult = runGit(['push', destination, `${branch}:${branch}`])\n\n if (pushResult.status !== 0) {\n throw formatOperationalError(`Failed to push ${branch} to ${destination}`, pushResult)\n }\n}\n\nfunction runSyncUpstream(destinationOption: string | undefined, sourceOption: string | undefined): number {\n const destination = normalizeOutput(destinationOption) || SYNC_UPSTREAM_DEFAULT_DESTINATION\n const source = normalizeOutput(sourceOption) || SYNC_UPSTREAM_DEFAULT_SOURCE\n const remotes = getGitRemotes()\n\n ensureSyncRemoteExists(destination, remotes, 'destination')\n ensureSyncRemoteExists(source, remotes, 'source')\n\n fetchRemote(source)\n\n const branches = getSyncableBranches(source)\n\n if (branches.length === 0) {\n console.log(`No syncable branches found on ${source}. Checked: ${SYNC_UPSTREAM_BRANCHES.join(', ')}`)\n return NO_SYNCABLE_BRANCHES_EXIT_CODE\n }\n\n const originalCheckout = getCurrentCheckoutState()\n let syncError: Error | null = null\n\n console.log(`Syncing ${branches.join(', ')} from ${source} to ${destination}`)\n\n for (const branch of branches) {\n try {\n syncBranch(branch, destination, source)\n console.log(`Synced ${branch} to ${destination}`)\n } catch (error) {\n syncError = error instanceof Error ? error : new Error(String(error))\n break\n }\n }\n\n const restoreError = restoreOriginalCheckout(originalCheckout)\n\n if (syncError && restoreError) {\n throw new Error(`${syncError.message}\\n${restoreError.message}`)\n }\n\n if (syncError) {\n throw syncError\n }\n\n if (restoreError) {\n throw restoreError\n }\n\n return 0\n}\n\nfunction createProgram(): Command {\n const program = new Command()\n\n program.name('hivectl').description('Common local and GitHub workflow helpers').exitOverride()\n\n program\n .command('gh-pr-unresolved')\n .description('Show unresolved review threads on the pull request for the current branch')\n .option('--json', 'show unresolved review threads as JSON')\n .option('-v, --verbose', 'show unresolved review threads in detail')\n .action((options: CommandOptions) => {\n process.exitCode = runGhPrUnresolved(options)\n })\n\n program\n .command('sync-upstream')\n .description('Sync dev, develop, main, and master from a source remote to a destination remote')\n .option('--destination <remote>', 'destination remote name', SYNC_UPSTREAM_DEFAULT_DESTINATION)\n .option('--source <remote>', 'source remote name', SYNC_UPSTREAM_DEFAULT_SOURCE)\n .action((options: { destination?: string; source?: string }) => {\n process.exitCode = runSyncUpstream(options.destination, options.source)\n })\n\n return program\n}\n\nasync function main(argv = process.argv): Promise<void> {\n const program = createProgram()\n\n try {\n await program.parseAsync(argv)\n\n if (typeof process.exitCode !== 'number') {\n process.exitCode = 0\n }\n } catch (error) {\n if (error instanceof CommanderError) {\n process.exitCode = error.code === 'commander.helpDisplayed' ? 0 : error.exitCode\n return\n }\n\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n }\n}\n\nvoid main()\n"],"mappings":"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"}
package/package.json CHANGED
@@ -55,6 +55,6 @@
55
55
  },
56
56
  "type": "module",
57
57
  "types": "./dist/index.d.mts",
58
- "version": "0.1.0",
58
+ "version": "0.2.0",
59
59
  "description": ""
60
60
  }