cicd branch follower native closeout
This commit is contained in:
@@ -60,7 +60,7 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh pr comment edit <commentId> (--body-stdin|--body-file <file|->|--body <text>) [--repo owner/name] [--number N compat] [--dry-run] [compatibility alias for pr comment update]",
|
||||
"bun scripts/cli.ts gh pr comment delete <commentId> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr close|reopen <number> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr merge <number> [--repo owner/name] [--number N compat] [--merge|--squash|--rebase] [--delete-branch] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr merge <number> [--repo owner/name] [--number N compat] [--merge|--squash|--rebase] [--delete-branch|--keep-branch] [--sync-node NODE]... [--skip-local-closeout] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr delete <number> [unsupported: use close]",
|
||||
],
|
||||
defaults: { repo: DEFAULT_REPO },
|
||||
@@ -105,7 +105,7 @@ export function ghHelp(): unknown {
|
||||
"PR preflight/closeout accept the same owner/repo#number shorthand as PR view/read so merge readiness checks do not require repeating --repo after a PR URL has already been normalized.",
|
||||
"PR list does not fetch mergeability or statusCheckRollup; request those closeout fields with gh pr view <number> --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup.",
|
||||
"PR preflight is a low-noise read-only closeout helper for explicit diagnosis only. It combines redacted auth capability, PR branch/state metadata, mergeability, mergeStateStatus, compact status check counts, and an explicit read-only policy. It is not a required step before gh pr merge. Use --full or --raw to include all fetched status contexts.",
|
||||
"PR merge is the one-command guarded write path: it reads closeout metadata itself, retries GitHub UNKNOWN/null mergeability with YAML-configured exponential backoff, refuses non-open/draft/conflicting/non-clean/failed/pending PRs, then uses GitHub REST merge. Use --dry-run to see the exact merge plan without writing.",
|
||||
"PR merge is the one-command guarded write path: it reads closeout metadata itself, retries GitHub UNKNOWN/null mergeability with YAML-configured exponential backoff, refuses non-open/draft/conflicting/non-clean/failed/pending PRs, then uses GitHub REST merge. It defaults to deleting the merged same-repo head branch, cleaning the matching local .worktree when clean, and fast-forwarding the local main worktree on the PR base branch; --sync-node NODE additionally runs mapped node source-workspace sync. Use --dry-run to see the exact merge and closeout plan without writing.",
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -168,6 +168,7 @@ export function ghScopedHelpNotes(tokens: string[]): string[] {
|
||||
} else if (key === "pr merge") {
|
||||
notes.push("PR merge is one-command guarded: it performs the readiness check itself; `gh pr preflight` is optional read-only diagnosis, not a required first step.");
|
||||
notes.push("When GitHub reports mergeability as UNKNOWN/null, merge automatically retries with YAML-configured exponential backoff and shows retry attempts as N/M.");
|
||||
notes.push("After merge it deletes the same-repo head branch by default, removes a clean matching local `.worktree`, and fast-forwards the local main worktree; use `--keep-branch` or `--skip-local-closeout` only when intentionally preserving state.");
|
||||
} else if (key === "pr review-plan" || key === "pr diff") {
|
||||
notes.push("Use `pr review-plan` first for a bounded changed-file index with per-file drill-down commands.");
|
||||
notes.push("Use `pr diff <number> --file <path> [--hunk N]` for bounded patch review; full patch disclosure requires explicit --full or --raw.");
|
||||
|
||||
@@ -321,6 +321,9 @@ export function stdinAliasFileOption(args: string[], fileOption: string, stdinFl
|
||||
export function parseOptions(args: string[]): GitHubOptions {
|
||||
validateKnownOptions(args);
|
||||
const [top, sub] = args;
|
||||
if (hasFlag(args, "--delete-branch") && hasFlag(args, "--keep-branch")) {
|
||||
throw new Error("gh pr merge accepts either --delete-branch or --keep-branch, not both");
|
||||
}
|
||||
const requestedJsonFields = commaListOption(args, "--json");
|
||||
const limitMax = top === "pr" && (sub === "files" || sub === "diff")
|
||||
? MAX_PR_FILES_LIMIT
|
||||
@@ -372,7 +375,9 @@ export function parseOptions(args: string[]): GitHubOptions {
|
||||
boardGithubStatus: parseBoardGithubStatus(args),
|
||||
boardRowUpsertValues: parseBoardRowUpsertValues(args),
|
||||
mergeMethod: parsePullRequestMergeMethod(args),
|
||||
deleteBranch: hasFlag(args, "--delete-branch"),
|
||||
deleteBranch: top === "pr" && sub === "merge" ? !hasFlag(args, "--keep-branch") : hasFlag(args, "--delete-branch"),
|
||||
localCloseout: !hasFlag(args, "--skip-local-closeout"),
|
||||
syncNodes: optionValues(args, "--sync-node").map((value) => value.trim()).filter((value) => value.length > 0),
|
||||
attachmentSelector: optionValue(args, "--attachment"),
|
||||
outputPath: optionValue(args, "--output"),
|
||||
filePath: optionValue(args, "--file"),
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// SPEC: PJ2026-01060703 GitHub PR merge closeout.
|
||||
// Responsibility: guarded local/node worktree closeout after a successful PR merge.
|
||||
|
||||
import { relative, resolve, sep } from "node:path";
|
||||
import { repoRoot } from "../config";
|
||||
import { runCommand, type CommandResult } from "../command";
|
||||
import type { GitHubOptions, GitHubPullRequest } from "./types";
|
||||
|
||||
interface WorktreeEntry {
|
||||
path: string;
|
||||
branch: string | null;
|
||||
head: string | null;
|
||||
}
|
||||
|
||||
export function runPrMergeCloseout(repo: string, pr: GitHubPullRequest, options: GitHubOptions): Record<string, unknown> {
|
||||
const headRef = pr.head?.ref ?? null;
|
||||
const baseRef = pr.base?.ref ?? null;
|
||||
const localEnabled = options.localCloseout;
|
||||
return {
|
||||
ok: true,
|
||||
valuesPrinted: false,
|
||||
headRef,
|
||||
baseRef,
|
||||
localWorktree: localEnabled ? cleanupHeadWorktree(headRef, options.dryRun) : skipped("local-closeout-disabled"),
|
||||
mainWorktree: localEnabled ? syncLocalMainWorktree(repo, baseRef, options.dryRun) : skipped("local-closeout-disabled"),
|
||||
nodeSyncs: syncRequestedNodes(repo, baseRef, options.syncNodes, options.dryRun),
|
||||
};
|
||||
}
|
||||
|
||||
function cleanupHeadWorktree(headRef: string | null, dryRun: boolean): Record<string, unknown> {
|
||||
if (headRef === null || headRef.length === 0) return skipped("head-ref-missing");
|
||||
const list = git(["worktree", "list", "--porcelain"], 10_000);
|
||||
if (list.exitCode !== 0) return failed("worktree-list-failed", list);
|
||||
const entries = parseWorktreeList(list.stdout);
|
||||
const matches = entries.filter((entry) => entry.branch === `refs/heads/${headRef}` && isManagedTaskWorktree(entry.path));
|
||||
if (matches.length === 0) return skipped("local-head-worktree-not-found", { headRef });
|
||||
const removals = matches.map((entry) => removeWorktree(entry.path, dryRun));
|
||||
const branchDelete = deleteLocalBranch(headRef, dryRun);
|
||||
return {
|
||||
ok: removals.every((item) => item.ok === true || item.planned === true) && (branchDelete.ok === true || branchDelete.planned === true || branchDelete.skipped === true),
|
||||
headRef,
|
||||
worktrees: removals,
|
||||
branchDelete,
|
||||
};
|
||||
}
|
||||
|
||||
function removeWorktree(path: string, dryRun: boolean): Record<string, unknown> {
|
||||
const status = git(["-C", path, "status", "--porcelain"], 10_000);
|
||||
if (status.exitCode !== 0) return failed("worktree-status-failed", status, { path });
|
||||
if (status.stdout.trim().length > 0) return skipped("worktree-dirty", { path, porcelainLines: status.stdout.trim().split(/\r?\n/u).slice(0, 20) });
|
||||
if (dryRun) return { planned: true, action: "git-worktree-remove", path };
|
||||
const removed = git(["worktree", "remove", path], 30_000);
|
||||
return removed.exitCode === 0 ? { ok: true, action: "git-worktree-remove", path } : failed("worktree-remove-failed", removed, { path });
|
||||
}
|
||||
|
||||
function deleteLocalBranch(headRef: string, dryRun: boolean): Record<string, unknown> {
|
||||
const list = git(["branch", "--list", headRef], 10_000);
|
||||
if (list.exitCode !== 0) return failed("branch-list-failed", list, { headRef });
|
||||
if (list.stdout.trim().length === 0) return skipped("local-branch-not-found", { headRef });
|
||||
if (dryRun) return { planned: true, action: "git-branch-delete", branch: headRef };
|
||||
const deleted = git(["branch", "-d", headRef], 20_000);
|
||||
return deleted.exitCode === 0 ? { ok: true, action: "git-branch-delete", branch: headRef } : failed("branch-delete-failed", deleted, { branch: headRef });
|
||||
}
|
||||
|
||||
function syncLocalMainWorktree(repo: string, baseRef: string | null, dryRun: boolean): Record<string, unknown> {
|
||||
if (baseRef === null || baseRef.length === 0) return skipped("base-ref-missing");
|
||||
const remote = git(["remote", "get-url", "origin"], 10_000);
|
||||
if (remote.exitCode !== 0) return failed("remote-read-failed", remote);
|
||||
if (!remoteMatchesRepo(remote.stdout.trim(), repo)) return skipped("local-repo-mismatch", { repo, origin: redactRemote(remote.stdout.trim()) });
|
||||
const current = git(["rev-parse", "--abbrev-ref", "HEAD"], 10_000);
|
||||
if (current.exitCode !== 0) return failed("current-branch-read-failed", current);
|
||||
const currentBranch = current.stdout.trim();
|
||||
if (currentBranch !== baseRef) return skipped("main-worktree-on-different-branch", { currentBranch, baseRef });
|
||||
if (dryRun) return { planned: true, action: "git-fetch-merge-ff-only", path: repoRoot, branch: baseRef };
|
||||
|
||||
const status = git(["status", "--porcelain"], 10_000);
|
||||
if (status.exitCode !== 0) return failed("main-worktree-status-failed", status);
|
||||
const hadDirty = status.stdout.trim().length > 0;
|
||||
const stash = hadDirty ? git(["stash", "push", "-u", "-m", `unidesk-gh-pr-merge-closeout ${repo} ${baseRef}`], 30_000) : null;
|
||||
if (stash !== null && stash.exitCode !== 0) return failed("main-worktree-stash-failed", stash, { branch: baseRef });
|
||||
|
||||
const fetched = git(["fetch", "origin", baseRef], 60_000);
|
||||
const merged = fetched.exitCode === 0 ? git(["merge", "--ff-only", `origin/${baseRef}`], 60_000) : null;
|
||||
const applied = stash !== null ? git(["stash", "apply"], 60_000) : null;
|
||||
const ok = fetched.exitCode === 0 && merged?.exitCode === 0 && (applied === null || applied.exitCode === 0);
|
||||
return {
|
||||
ok,
|
||||
action: "git-fetch-merge-ff-only",
|
||||
path: repoRoot,
|
||||
branch: baseRef,
|
||||
dirtyPreservedByStash: hadDirty,
|
||||
fetch: commandSummary(fetched),
|
||||
merge: merged === null ? null : commandSummary(merged),
|
||||
stashApply: applied === null ? null : commandSummary(applied),
|
||||
};
|
||||
}
|
||||
|
||||
function syncRequestedNodes(repo: string, baseRef: string | null, nodes: string[], dryRun: boolean): Record<string, unknown>[] {
|
||||
return nodes.map((node) => syncRequestedNode(repo, baseRef, node, dryRun));
|
||||
}
|
||||
|
||||
function syncRequestedNode(repo: string, baseRef: string | null, node: string, dryRun: boolean): Record<string, unknown> {
|
||||
const command = nodeSyncCommand(repo, baseRef, node);
|
||||
if (command === null) return skipped("node-sync-unsupported", { repo, baseRef, node });
|
||||
if (dryRun) return { planned: true, node, command: command.join(" ") };
|
||||
const result = runCommand(command, repoRoot, { timeoutMs: 120_000 });
|
||||
return result.exitCode === 0 ? { ok: true, node, command: command.join(" ") } : failed("node-sync-failed", result, { node, command: command.join(" ") });
|
||||
}
|
||||
|
||||
function nodeSyncCommand(repo: string, baseRef: string | null, node: string): string[] | null {
|
||||
if (repo.toLowerCase() === "pikastech/hwlab" && baseRef === "v0.3") {
|
||||
return ["bun", "scripts/cli.ts", "hwlab", "nodes", "control-plane", "source-workspace", "sync", "--node", node.toUpperCase(), "--lane", "v03", "--confirm"];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseWorktreeList(text: string): WorktreeEntry[] {
|
||||
const entries: WorktreeEntry[] = [];
|
||||
let current: WorktreeEntry | null = null;
|
||||
for (const line of text.split(/\r?\n/u)) {
|
||||
if (line.startsWith("worktree ")) {
|
||||
if (current !== null) entries.push(current);
|
||||
current = { path: line.slice("worktree ".length), branch: null, head: null };
|
||||
} else if (line.startsWith("branch ") && current !== null) {
|
||||
current.branch = line.slice("branch ".length);
|
||||
} else if (line.startsWith("HEAD ") && current !== null) {
|
||||
current.head = line.slice("HEAD ".length);
|
||||
}
|
||||
}
|
||||
if (current !== null) entries.push(current);
|
||||
return entries;
|
||||
}
|
||||
|
||||
function isManagedTaskWorktree(path: string): boolean {
|
||||
const rel = relative(repoRoot, resolve(path));
|
||||
return rel === ".worktree" || rel.startsWith(`.worktree${sep}`);
|
||||
}
|
||||
|
||||
function git(args: string[], timeoutMs: number): CommandResult {
|
||||
return runCommand(["git", ...args], repoRoot, { timeoutMs });
|
||||
}
|
||||
|
||||
function remoteMatchesRepo(remote: string, repo: string): boolean {
|
||||
const normalized = remote
|
||||
.replace(/^git@github\.com:/u, "")
|
||||
.replace(/^https:\/\/github\.com\//u, "")
|
||||
.replace(/\.git$/u, "")
|
||||
.toLowerCase();
|
||||
return normalized === repo.toLowerCase();
|
||||
}
|
||||
|
||||
function redactRemote(remote: string): string {
|
||||
return remote.replace(/:\/\/[^/@]+@/u, "://<redacted>@");
|
||||
}
|
||||
|
||||
function skipped(reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return { ok: true, skipped: true, skippedReason: reason, ...details };
|
||||
}
|
||||
|
||||
function failed(reason: string, result: CommandResult, details: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return { ok: false, degradedReason: reason, ...details, command: result.command.join(" "), exitCode: result.exitCode, timedOut: result.timedOut, stderrTail: tail(result.stderr), stdoutTail: tail(result.stdout) };
|
||||
}
|
||||
|
||||
function commandSummary(result: CommandResult): Record<string, unknown> {
|
||||
return { exitCode: result.exitCode, timedOut: result.timedOut, stderrTail: tail(result.stderr), stdoutTail: tail(result.stdout) };
|
||||
}
|
||||
|
||||
function tail(text: string): string {
|
||||
return text.trim().split(/\r?\n/u).slice(-8).join("\n").slice(-1000);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { repoParts, resolveToken } from "./auth-and-safety";
|
||||
import { authStatus } from "./auth-pr-read";
|
||||
import { authRequired, commandError, errorPayload, githubRequest, isGitHubError, runnerDisposition, validationError } from "./client";
|
||||
import { isRecord } from "./notify-claudeqq";
|
||||
import { runPrMergeCloseout } from "./pr-merge-closeout";
|
||||
import { compactAuthCapability, deleteHeadBranchAfterMerge, loadPrMergeUnknownRetryConfig, mergeabilityHasUnknownPending, nextPrMergeRetryDelayMs, prCloseoutMetadata, prCloseoutSummary, preflightPullRequestSummary, prGraphqlMetadata, prMergeRetryCommand, prMetadataSummary, prPreflightPolicy, prSummary, sleepMs, statusRollupSummary } from "./pr-summary";
|
||||
import { ghShort, ghTable, ghText } from "./render";
|
||||
import { UNIDESK_CLI_CONFIG_PATH } from "./types";
|
||||
@@ -22,15 +23,19 @@ export async function prMerge(repo: string, token: string, number: number, optio
|
||||
if (isGitHubError(pr)) return commandError("pr merge", repo, pr, { number, phase: "fetch-pr" });
|
||||
const summary = prSummary(pr);
|
||||
if (summary.merged === true) {
|
||||
const branchDeletion = options.deleteBranch && !options.dryRun ? await deleteHeadBranchAfterMerge(repo, token, pr) : { attempted: false, skippedReason: options.deleteBranch ? "dry-run" : "keep-branch-requested" };
|
||||
const closeout = runPrMergeCloseout(repo, pr, options);
|
||||
return withPrMergeRendered({
|
||||
ok: true,
|
||||
command: "pr merge",
|
||||
repo,
|
||||
number,
|
||||
method: options.mergeMethod,
|
||||
deleteBranch: options.deleteBranch,
|
||||
alreadyMerged: true,
|
||||
pullRequest: summary,
|
||||
branchDeletion: { attempted: false, skippedReason: "already-merged" },
|
||||
branchDeletion,
|
||||
closeout,
|
||||
rest: true,
|
||||
});
|
||||
}
|
||||
@@ -97,6 +102,7 @@ export async function prMerge(repo: string, token: string, number: number, optio
|
||||
mergeability,
|
||||
statusChecks,
|
||||
retry,
|
||||
closeout: runPrMergeCloseout(repo, pr, options),
|
||||
});
|
||||
}
|
||||
const merged = await githubRequest<Record<string, unknown>>(token, "PUT", `/repos/${owner}/${name}/pulls/${number}/merge`, {
|
||||
@@ -105,19 +111,22 @@ export async function prMerge(repo: string, token: string, number: number, optio
|
||||
if (isGitHubError(merged)) return commandError("pr merge", repo, merged, { number, phase: "merge", method: options.mergeMethod, pullRequest: summary });
|
||||
const after = await githubRequest<GitHubPullRequest>(token, "GET", `/repos/${owner}/${name}/pulls/${number}`);
|
||||
if (isGitHubError(after)) return commandError("pr merge", repo, after, { number, phase: "fetch-after-merge", mergeResult: merged });
|
||||
const branchDeletion = options.deleteBranch ? await deleteHeadBranchAfterMerge(repo, token, after) : { attempted: false, skippedReason: "delete-branch-not-requested" };
|
||||
const branchDeletion = options.deleteBranch ? await deleteHeadBranchAfterMerge(repo, token, after) : { attempted: false, skippedReason: "keep-branch-requested" };
|
||||
const closeout = runPrMergeCloseout(repo, after, options);
|
||||
return withPrMergeRendered({
|
||||
ok: true,
|
||||
command: "pr merge",
|
||||
repo,
|
||||
number,
|
||||
method: options.mergeMethod,
|
||||
deleteBranch: options.deleteBranch,
|
||||
mergeResult: merged,
|
||||
pullRequest: prSummary(after),
|
||||
mergeability,
|
||||
statusChecks,
|
||||
retry,
|
||||
branchDeletion,
|
||||
closeout,
|
||||
rest: true,
|
||||
});
|
||||
}
|
||||
@@ -149,6 +158,10 @@ export function renderPrMergeTable(result: GitHubCommandResult, fallbackDetails?
|
||||
: {};
|
||||
const counts = isRecord(statusChecks.counts) ? statusChecks.counts : {};
|
||||
const branchDeletion = isRecord(result.branchDeletion) ? result.branchDeletion : {};
|
||||
const closeout = isRecord(result.closeout) ? result.closeout : {};
|
||||
const localWorktree = isRecord(closeout.localWorktree) ? closeout.localWorktree : {};
|
||||
const mainWorktree = isRecord(closeout.mainWorktree) ? closeout.mainWorktree : {};
|
||||
const nodeSyncs = Array.isArray(closeout.nodeSyncs) ? closeout.nodeSyncs.filter(isRecord) : [];
|
||||
const retry = isRecord(result.retry)
|
||||
? result.retry
|
||||
: isRecord(details.retry)
|
||||
@@ -186,6 +199,7 @@ export function renderPrMergeTable(result: GitHubCommandResult, fallbackDetails?
|
||||
` blockers=${blockers.length === 0 ? "-" : blockers.join(",")} pending=${pending.length === 0 ? "-" : pending.join(",")}`,
|
||||
` retry=${retry.attempts !== undefined && retry.maxAttempts !== undefined ? `${ghText(retry.attempts)}/${ghText(retry.maxAttempts)} exhausted=${ghText(retry.exhausted)}` : "-"}`,
|
||||
` branchDeletion=${ghText(branchDeletion.ok ?? branchDeletion.skippedReason ?? branchDeletion.attempted)}`,
|
||||
` closeout localWorktree=${closeoutCell(localWorktree)} mainWorktree=${closeoutCell(mainWorktree)} nodeSyncs=${nodeSyncs.length === 0 ? "-" : nodeSyncs.map(closeoutCell).join(",")}`,
|
||||
"",
|
||||
"Next:",
|
||||
];
|
||||
@@ -200,6 +214,14 @@ export function renderPrMergeTable(result: GitHubCommandResult, fallbackDetails?
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function closeoutCell(value: Record<string, unknown>): string {
|
||||
if (value.planned === true) return "planned";
|
||||
if (value.skipped === true) return `skipped:${ghText(value.skippedReason)}`;
|
||||
if (value.ok === true) return "ok";
|
||||
if (value.ok === false) return `failed:${ghText(value.degradedReason)}`;
|
||||
return "-";
|
||||
}
|
||||
|
||||
export async function prPreflight(repo: string, number: number, commandName: "preflight" | "pr preflight" | "pr closeout", includeRaw: boolean): Promise<GitHubCommandResult> {
|
||||
const auth = await authStatus(repo);
|
||||
const authCapability = compactAuthCapability(auth);
|
||||
|
||||
@@ -480,7 +480,7 @@ export function prMergeRetryCommand(repo: string, number: unknown, method: unkno
|
||||
"--repo",
|
||||
repo,
|
||||
prMergeMethodFlag(method),
|
||||
deleteBranch === true ? "--delete-branch" : "",
|
||||
deleteBranch === false ? "--keep-branch" : "--delete-branch",
|
||||
].filter((item) => item.length > 0).join(" ");
|
||||
}
|
||||
|
||||
|
||||
@@ -94,10 +94,10 @@ export const GH_VALUE_OPTIONS = new Set([
|
||||
"--value", "--section", "--to", "--status", "--row-file", "--category", "--branch",
|
||||
"--tasks", "--summary", "--focus", "--validation", "--progress", "--number", "--pr",
|
||||
"--search", "--title-prefix", "--inactive-hours", "--comment", "--comment-file", "--description",
|
||||
"--attachment", "--output", "--file", "--hunk",
|
||||
"--attachment", "--output", "--file", "--hunk", "--sync-node",
|
||||
]);
|
||||
|
||||
export const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--body-patch-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--private", "--public", "--auto-init"]);
|
||||
export const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--body-patch-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--keep-branch", "--skip-local-closeout", "--private", "--public", "--auto-init"]);
|
||||
|
||||
export const MIN_SAFE_BODY_SCAN_CHARS = MIN_SAFE_ISSUE_BODY_CHARS;
|
||||
|
||||
@@ -460,6 +460,8 @@ export interface GitHubOptions {
|
||||
boardRowUpsertValues: BoardRowUpsertValues;
|
||||
mergeMethod: PullRequestMergeMethod;
|
||||
deleteBranch: boolean;
|
||||
localCloseout: boolean;
|
||||
syncNodes: string[];
|
||||
attachmentSelector?: string;
|
||||
outputPath?: string;
|
||||
filePath?: string;
|
||||
|
||||
Reference in New Issue
Block a user