scripts/resolve-review-thread.mjs
scripts/resolve-review-thread.mjsBrowse 7 files
1,292 tokens
4,982 bytes
Token encoding: o200k_base
Snapshot fac8604
← Back to SKILL.md
1#!/usr/bin/env node2 3import { spawnSync } from 'node:child_process';4import { realpathSync } from 'node:fs';5import { resolve } from 'node:path';6import { fileURLToPath } from 'node:url';7 8const EXIT_SUCCESS = 0;9const EXIT_OPERATIONAL = 1;10const EXIT_CLI = 2;11const SUBPROCESS_TIMEOUT_MS = 30_000;12 13function parseCliArgs(argv) {14 const args = argv.slice(2);15 const result = { help: false, threadNodeId: null };16 if (args.includes('--help')) {17 result.help = true;18 return result;19 }20 for (let index = 0; index < args.length; index += 1) {21 const arg = args[index];22 if (arg !== '--thread-node-id') {23 throw { code: EXIT_CLI, message: `error: unknown flag "${arg}"` };24 }25 index += 1;26 if (index >= args.length) {27 throw { code: EXIT_CLI, message: 'error: --thread-node-id requires a value' };28 }29 result.threadNodeId = args[index];30 }31 if (!result.threadNodeId) {32 throw { code: EXIT_CLI, message: 'error: --thread-node-id is required' };33 }34 return result;35}36 37function getHelpText() {38 return [39 'Usage:',40 ' resolve-review-thread.mjs --thread-node-id <NODE_ID>',41 '',42 'Purpose:',43 ' Resolve a pull request review thread by node ID via GitHub GraphQL API.',44 ].join('\n');45}46 47function run(command, args, input = null) {48 const result = spawnSync(command, args, {49 encoding: 'utf8',50 input: input ?? undefined,51 timeout: SUBPROCESS_TIMEOUT_MS,52 });53 if (result.error) {54 if (result.error.code === 'ETIMEDOUT') {55 throw new Error(`error: ${command} timed out after ${SUBPROCESS_TIMEOUT_MS / 1000} seconds`);56 }57 throw new Error(`error: failed to execute ${command}: ${result.error.message}`);58 }59 if (result.signal) {60 throw new Error(`error: ${command} was terminated by signal ${result.signal}`);61 }62 if (result.status !== 0) {63 throw new Error(64 `error: ${command} ${args.join(' ')} failed: ${result.stderr || result.stdout}`.trim(),65 );66 }67 return result.stdout;68}69 70function assertCommandAvailable(command, installHint) {71 const probe = spawnSync(command, ['--version'], {72 encoding: 'utf8',73 timeout: SUBPROCESS_TIMEOUT_MS,74 });75 if (probe.error || probe.status !== 0) {76 if (probe.error?.code === 'ETIMEDOUT') {77 throw new Error(78 `error: required dependency "${command}" timed out after ${SUBPROCESS_TIMEOUT_MS / 1000} seconds.`,79 );80 }81 if (probe.signal) {82 throw new Error(83 `error: required dependency "${command}" was terminated by signal ${probe.signal}.`,84 );85 }86 throw new Error(87 `error: required dependency "${command}" is not available. Install ${installHint} and retry.`,88 );89 }90}91 92function resolveThread(threadNodeId) {93 const mutation = [94 'mutation($threadId:ID!){',95 ' resolveReviewThread(input:{threadId:$threadId}){',96 ' thread {',97 ' id',98 ' isResolved',99 ' }',100 ' }',101 '}',102 ].join('\n');103 104 const response = run('gh', [105 'api',106 'graphql',107 '-f',108 `query=${mutation}`,109 '-F',110 `threadId=${threadNodeId}`,111 ]);112 113 let parsed;114 try {115 parsed = JSON.parse(response);116 } catch (parseError) {117 throw new Error(`error: failed to parse GraphQL response: ${parseError.message}`);118 }119 120 if (Array.isArray(parsed?.errors) && parsed.errors.length > 0) {121 const messages = parsed.errors122 .map((err) =>123 typeof err?.message === 'string' && err.message.length > 0124 ? err.message125 : JSON.stringify(err),126 )127 .join('; ');128 throw new Error(`error: ${messages}`);129 }130 131 const thread = parsed?.data?.resolveReviewThread?.thread;132 if (thread?.isResolved !== true) {133 throw new Error(134 `error: thread was not resolved successfully (isResolved=${thread?.isResolved === undefined ? 'null' : String(thread.isResolved)})`,135 );136 }137 138 return { resolvedThreadId: thread.id, isResolved: true };139}140 141async function main() {142 const args = parseCliArgs(process.argv);143 if (args.help) {144 process.stdout.write(`${getHelpText()}\n`);145 process.exit(EXIT_SUCCESS);146 }147 148 assertCommandAvailable('gh', 'GitHub CLI (`gh`)');149 150 const result = resolveThread(args.threadNodeId);151 process.stdout.write(152 `${JSON.stringify({153 ok: true,154 threadNodeId: args.threadNodeId,155 resolvedThreadId: result.resolvedThreadId,156 isResolved: true,157 })}\n`,158 );159}160 161const isMain = (() => {162 try {163 const invokedScriptPath = process.argv[1] ? realpathSync(resolve(process.argv[1])) : null;164 const currentModulePath = realpathSync(fileURLToPath(import.meta.url));165 return invokedScriptPath !== null && invokedScriptPath === currentModulePath;166 } catch {167 return false;168 }169})();170 171if (isMain) {172 main().catch((error) => {173 const code = typeof error?.code === 'number' ? error.code : EXIT_OPERATIONAL;174 const message = error?.message ? String(error.message) : String(error);175 process.stderr.write(`${message}\n`);176 process.exit(code);177 });178}179