🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

git-stack-cli

Package Overview
Dependencies
Maintainers
1
Versions
127
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

git-stack-cli - npm Package Compare versions

Comparing version
2.9.3
to
2.9.4
+40
src/app/DebugOutput.tsx
import * as React from "react";
import * as Ink from "ink-cjs";
import { DateTime } from "luxon";
type Props = {
node: React.ReactNode;
};
export function DebugOutput(props: Props) {
const { stdout } = Ink.useStdout();
const available_width = stdout.columns;
const timestamp = DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss.SSS");
const content_width = available_width - timestamp.length - 2;
const content = (function () {
switch (typeof props.node) {
case "boolean":
case "number":
case "string": {
return <Ink.Text dimColor>{String(props.node)}</Ink.Text>;
}
default:
return props.node;
}
})();
return (
<Ink.Box flexDirection="column">
<Ink.Box flexDirection="row" gap={1} width={available_width}>
<Ink.Box width={timestamp.length} flexDirection="column">
<Ink.Text dimColor>{timestamp}</Ink.Text>
</Ink.Box>
<Ink.Box width={content_width}>{content}</Ink.Box>
</Ink.Box>
</Ink.Box>
);
}
+1
-1
{
"name": "git-stack-cli",
"version": "2.9.3",
"version": "2.9.4",
"description": "",

@@ -5,0 +5,0 @@ "author": "magus",

@@ -7,2 +7,3 @@ import * as React from "react";

import { Brackets } from "~/app/Brackets";
import { FormatText } from "~/app/FormatText";
import { Parens } from "~/app/Parens";

@@ -55,30 +56,32 @@ import { Store } from "~/app/Store";

actions.output(
<Ink.Text>
<Ink.Text>{"Github "}</Ink.Text>
<Brackets>graphql</Brackets>
<Ink.Text>{" API rate limit "}</Ink.Text>
<Brackets>
<Ink.Text>{used}</Ink.Text>
<Ink.Text>/</Ink.Text>
<Ink.Text>{limit}</Ink.Text>
</Brackets>
<Ink.Text>{" will reset at "}</Ink.Text>
<Ink.Text bold color={colors.yellow}>
{reset_time}
</Ink.Text>
<Ink.Text> </Ink.Text>
<Parens>
<Ink.Text>{"in "}</Ink.Text>
<Ink.Text bold color={colors.yellow}>
{time_until}
</Ink.Text>
</Parens>
</Ink.Text>,
<FormatText
message="Github {graphql} API rate limit {ratio} will reset at {reset_time} {time_until}"
values={{
graphql: <Brackets>graphql</Brackets>,
ratio: (
<Brackets>
<FormatText message="{used}/{limit}" values={{ used, limit }} />
</Brackets>
),
reset_time: (
<Ink.Text bold color={colors.yellow}>
{reset_time}
</Ink.Text>
),
time_until: (
<Parens>
<FormatText
message="in {time_until}"
values={{
time_until: (
<Ink.Text bold color={colors.yellow}>
{time_until}
</Ink.Text>
),
}}
/>
</Parens>
),
}}
/>,
);

@@ -85,0 +88,0 @@

@@ -50,2 +50,7 @@ import * as React from "react";

// ensure merge_base is updated
actions.set((state) => {
state.merge_base = merge_base;
});
// immediately paint all commit to preserve selected commit ranges

@@ -120,3 +125,3 @@ let commit_range = await CommitMetadata.range(commit_map);

state.step = "sync-github";
state.sync_github = { commit_range, rebase_group_index };
state.sync_github = { commit_range };
});

@@ -123,0 +128,0 @@ } else {

@@ -5,2 +5,3 @@ import * as React from "react";

import { DebugOutput } from "~/app/DebugOutput";
import { Store } from "~/app/Store";

@@ -11,3 +12,2 @@

const pending_output = Store.useState((state) => state.pending_output);
const pending_output_items = Object.values(pending_output);

@@ -17,21 +17,12 @@ return (

<Ink.Static items={output}>
{(node, i) => {
return <Ink.Box key={i}>{node}</Ink.Box>;
{(entry) => {
const [id, node] = entry;
return <Ink.Box key={id}>{node}</Ink.Box>;
}}
</Ink.Static>
{pending_output_items.map((node_list, i) => {
return (
<Ink.Box key={i}>
<Ink.Text>
{node_list.map((text, j) => {
return (
<React.Fragment key={j}>
<Ink.Text>{text}</Ink.Text>
</React.Fragment>
);
})}
</Ink.Text>
</Ink.Box>
);
{Object.entries(pending_output).map((entry) => {
const [id, content_list] = entry;
const content = content_list.join("");
return <DebugOutput key={id} node={content} />;
})}

@@ -38,0 +29,0 @@ </React.Fragment>

import * as React from "react";
import crypto from "node:crypto";
import * as Ink from "ink-cjs";

@@ -7,4 +9,4 @@ import { createStore, useStore } from "zustand";

import { DebugOutput } from "~/app/DebugOutput";
import { Exit } from "~/app/Exit";
import { LogTimestamp } from "~/app/LogTimestamp";
import { colors } from "~/core/colors";

@@ -25,4 +27,2 @@ import { pretty_json } from "~/core/pretty_json";

id?: string;
debug?: boolean;
withoutTimestamp?: boolean;
};

@@ -32,3 +32,2 @@

commit_range: CommitMetadata.CommitRange;
rebase_group_index: number;
};

@@ -79,8 +78,8 @@

output: Array<React.ReactNode>;
pending_output: Record<string, Array<React.ReactNode>>;
output: Array<[string, React.ReactNode]>;
pending_output: Record<string, Array<string>>;
// cache
pr: { [branch: string]: PullRequest };
cache_pr_diff: { [id: number]: string };
cache_gh_cli_by_branch: { [branch: string]: { [command: string]: string } };

@@ -95,3 +94,5 @@ actions: {

output(node: React.ReactNode): void;
debug(node: React.ReactNode, id?: string): void;
debug(node: React.ReactNode): void;
debug_pending(id: string, content: string): void;
debug_pending_end(id: string): void;

@@ -108,4 +109,2 @@ isDebug(): boolean;

output(state: State, args: MutateOutputArgs): void;
pending_output(state: State, args: MutateOutputArgs): void;
end_pending_output(state: State, id: string): void;
};

@@ -148,3 +147,3 @@

pr: {},
cache_pr_diff: {},
cache_gh_cli_by_branch: {},

@@ -221,12 +220,18 @@ actions: {

debug(node, id) {
debug(node) {
if (get().actions.isDebug()) {
const debug = true;
set((state) => {
state.mutate.output(state, { node: <DebugOutput node={node} /> });
});
}
},
debug_pending(id, content) {
if (get().actions.isDebug()) {
set((state) => {
if (id) {
state.mutate.pending_output(state, { id, node, debug });
} else {
state.mutate.output(state, { node, debug });
if (!state.pending_output[id]) {
state.pending_output[id] = [];
}
state.pending_output[id].push(content);
});

@@ -236,2 +241,8 @@ }

debug_pending_end(id) {
set((state) => {
delete state.pending_output[id];
});
},
isDebug() {

@@ -263,35 +274,5 @@ const state = get();

output(state, args) {
const renderOutput = renderOutputArgs(args);
state.output.push(renderOutput);
const id = crypto.randomUUID();
state.output.push([id, args.node]);
},
pending_output(state, args) {
const { id } = args;
if (!id) {
return;
}
// set `withoutTimestamp` to skip <LogTimestamp> for all subsequent pending outputs
// we only want to timestamp for the first part (when we initialize the [])
// if we have many incremental outputs on the same line we do not want multiple timestamps
//
// await Promise.all([
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
// ]);
//
let withoutTimestamp = true;
if (!state.pending_output[id]) {
withoutTimestamp = false;
state.pending_output[id] = [];
}
const renderOutput = renderOutputArgs({ ...args, withoutTimestamp });
state.pending_output[id].push(renderOutput);
},
end_pending_output(state, id) {
delete state.pending_output[id];
},
},

@@ -307,24 +288,2 @@

function renderOutputArgs(args: MutateOutputArgs) {
let output = args.node;
switch (typeof args.node) {
case "boolean":
case "number":
case "string":
output = <Ink.Text dimColor={args.debug}>{String(args.node)}</Ink.Text>;
}
if (args.debug) {
return (
<React.Fragment>
{args.withoutTimestamp ? null : <LogTimestamp />}
{output}
</React.Fragment>
);
}
return output;
}
function useState<R>(selector: (state: State) => R): R {

@@ -331,0 +290,0 @@ return useStore(BaseStore, selector);

@@ -29,5 +29,6 @@ import * as React from "react";

const branch_name = state.branch_name;
const merge_base = state.merge_base;
const commit_map = state.commit_map;
const master_branch = state.master_branch;
const repo_root = state.repo_root;
const repo_path = state.repo_path;
const sync_github = state.sync_github;

@@ -37,7 +38,6 @@

invariant(commit_map, "commit_map must exist");
invariant(repo_root, "repo_root must exist");
invariant(repo_path, "repo_path must exist");
invariant(sync_github, "sync_github must exist");
const commit_range = sync_github.commit_range;
const rebase_group_index = sync_github.rebase_group_index;

@@ -51,5 +51,2 @@ let DEFAULT_PR_BODY = "";

// console.debug({ push_group_list });
// throw new Error("STOP");
// for all push targets in push_group_list

@@ -171,3 +168,3 @@ // things that can be done in parallel are grouped by numbers

delete state.pr[group.pr.headRefName];
delete state.cache_pr_diff[group.pr.number];
delete state.cache_gh_cli_by_branch[group.pr.headRefName];
}

@@ -199,20 +196,10 @@ }

function get_push_group_list() {
// start from HEAD and work backward to rebase_group_index
const push_group_list = [];
for (let i = 0; i < commit_range.group_list.length; i++) {
const index = commit_range.group_list.length - 1 - i;
// do not go past rebase_group_index
if (index < rebase_group_index) {
break;
}
const group = commit_range.group_list[index];
for (let group of commit_range.group_list) {
// skip the unassigned commits group
if (group.id === commit_range.UNASSIGNED) continue;
// if not --force, skip non-dirty master_base groups
if (group.master_base && !group.dirty && !argv.force) continue;
// if not --force, skip non-dirty groups
if (!group.dirty && !argv.force) continue;

@@ -248,3 +235,3 @@ push_group_list.unshift(group);

//
if (!is_master_base(group)) {
if (!is_pr_master_base(group)) {
await github.pr_edit({

@@ -267,3 +254,11 @@ branch: group.id,

if (group.pr) {
if (!is_master_base(group)) {
// there are two scenarios where we should restore the base after push
// 1. if we aren't master base and pr is master base we should fix it
const base_mismatch = !group.master_base && is_pr_master_base(group);
// 2. if group pr was not master before the push we set it to master before pushing
// now we need to restore it back to how it was before the before_push
const was_modified_before_push = !is_pr_master_base(group);
const needs_base_fix = base_mismatch || was_modified_before_push;
if (needs_base_fix) {
// ensure base matches pr in github

@@ -323,3 +318,3 @@ await github.pr_edit({ branch: group.id, base: group.base });

function is_master_base(group: CommitMetadataGroup) {
function is_pr_master_base(group: CommitMetadataGroup) {
if (!group.pr) {

@@ -329,10 +324,16 @@ return false;

return group.master_base || `origin/${group.pr.baseRefName}` === master_branch;
return `origin/${group.pr.baseRefName}` === master_branch;
}
async function push_master_group(group: CommitMetadataGroup) {
invariant(repo_root, "repo_root must exist");
invariant(repo_path, "repo_path must exist");
const repo_rel_worktree_path = `.git/git-stack-worktrees/push_master_group`;
const worktree_path = path.join(repo_root, repo_rel_worktree_path);
const worktree_path = path.join(
process.env.HOME,
".cache",
"git-stack",
"worktrees",
repo_path,
"push_master_group",
);

@@ -343,3 +344,3 @@ // ensure worktree for pushing master groups

<Ink.Text color={colors.white}>
Creating <Ink.Text color={colors.yellow}>{repo_rel_worktree_path}</Ink.Text>
Creating <Ink.Text color={colors.yellow}>{worktree_path}</Ink.Text>
</Ink.Text>,

@@ -355,3 +356,3 @@ );

// - abort any in-progress cherry-pick/rebase
// - drop local changes/untracked files (including ignored) for a truly fresh state
// - drop local changes/untracked files to fresh state
// - reset to the desired base

@@ -362,3 +363,3 @@ await cli(`git -C ${worktree_path} cherry-pick --abort`, { ignoreExitCode: true });

await cli(`git -C ${worktree_path} checkout -f ${master_branch}`);
await cli(`git -C ${worktree_path} reset --hard ${master_branch}`);
await cli(`git -C ${worktree_path} reset --hard ${merge_base}`);
await cli(`git -C ${worktree_path} clean -fd`);

@@ -365,0 +366,0 @@

import * as React from "react";
import path from "node:path";
import * as Ink from "ink-cjs";

@@ -9,2 +11,3 @@

import { colors } from "~/core/colors";
import { pretty_json } from "~/core/pretty_json";

@@ -33,2 +36,7 @@ type Props = {

await cli(`echo GIT_AUTHOR_EMAIL=$GIT_AUTHOR_EMAIL`);
const PATH = process.env["PATH"];
const PATH_LIST = pretty_json(PATH.split(path.delimiter));
actions.debug(`process.env.PATH ${PATH_LIST}`);
await cli(`git config --list --show-origin`);

@@ -35,0 +43,0 @@ } catch (err) {

@@ -11,2 +11,3 @@ import * as child from "node:child_process";

onOutput?: (data: string) => void;
quiet?: boolean;
};

@@ -49,3 +50,3 @@

state.actions.debug(log.start(command));
state.actions.debug(log.pending(command), id);
state.actions.debug_pending(id, log.pending(command));

@@ -56,3 +57,5 @@ const timer = Timer();

output += value;
state.actions.debug(value, id);
if (!options.quiet) {
state.actions.debug_pending(id, value);
}
options.onOutput?.(value);

@@ -85,5 +88,7 @@ }

state.actions.set((state) => state.mutate.end_pending_output(state, id));
state.actions.debug_pending_end(id);
state.actions.debug(log.end(result));
state.actions.debug(log.output(result));
if (!options.quiet) {
state.actions.debug(log.output(result));
}

@@ -140,3 +145,5 @@ if (!options.ignoreExitCode && result.code !== 0) {

state.actions.debug(log.end(result));
state.actions.debug(log.output(result));
if (!options.quiet) {
state.actions.debug(log.output(result));
}

@@ -143,0 +150,0 @@ if (!options.ignoreExitCode && result.code !== 0) {

@@ -6,2 +6,3 @@ /* eslint-disable no-console */

import * as github from "~/core/github";
import { invariant } from "~/core/invariant";

@@ -32,6 +33,6 @@ export type CommitRange = Awaited<ReturnType<typeof range>>;

export async function range(commit_group_map?: CommitGroupMap) {
const DEBUG = process.env.DEV && false;
const state = Store.getState();
const actions = state.actions;
const argv = state.argv;
const merge_base = state.merge_base;
const master_branch = state.master_branch;

@@ -41,2 +42,4 @@ const master_branch_name = master_branch.replace(/^origin\//, "");

invariant(merge_base, "merge_base must exist");
const pr_lookup: Record<string, void | PullRequest> = {};

@@ -131,2 +134,11 @@

// actions.json({ group });
actions.debug(`title=${group.title}`);
actions.debug(` id=${group.id}`);
actions.debug(` master_base=${group.master_base}`);
// special case
// boundary between normal commits and master commits
const MASTER_BASE_BOUNDARY = !group.master_base && previous_group && previous_group.master_base;
if (group.id !== UNASSIGNED) {

@@ -152,5 +164,4 @@ let pr_result = pr_lookup[group.id];

} else {
const last_group = group_value_list[i - 1];
// console.debug(" ", "last_group", last_group.pr?.title.substring(0, 40));
// console.debug(" ", "last_group.id", last_group.id);
// console.debug(" ", "previous_group", previous_group.pr?.title.substring(0, 40));
// console.debug(" ", "previous_group.id", previous_group.id);

@@ -163,69 +174,89 @@ if (group.master_base) {

group.base = null;
} else if (last_group.base === null) {
} else if (MASTER_BASE_BOUNDARY) {
// ensure we set its base to `master`
actions.debug(` MASTER_BASE_BOUNDARY set group.base = ${master_branch_name}`);
group.base = master_branch_name;
} else if (previous_group.base === null) {
// null out base when last group base is null
group.base = null;
} else {
group.base = last_group.id;
group.base = previous_group.id;
}
// console.debug(" ", "group.base", group.base);
}
actions.json({ group });
actions.debug(` base=${group.base}`);
if (!group.pr) {
actions.debug(` group.pr=${group.pr}`);
group.dirty = true;
} else {
if (group.pr.baseRefName !== group.base) {
actions.debug("PR_BASEREF_MISMATCH");
// actions.json(group.pr);
actions.debug(` group.pr.state=${group.pr.state}`);
actions.debug(` group.pr.baseRefName=${group.pr.baseRefName}`);
if (group.pr.state === "MERGED" || group.pr.state === "CLOSED") {
group.dirty = true;
} else if (group.pr.baseRefName !== group.base) {
actions.debug(" PR_BASEREF_MISMATCH");
group.dirty = true;
} else if (group.master_base) {
actions.debug("MASTER_BASE_DIFF_COMPARE");
// first check if merge base has changed
let branch_compare = await github.pr_compare(group.pr.headRefName);
if (!(branch_compare instanceof Error)) {
if (branch_compare.merge_base_commit.sha !== merge_base) {
actions.debug(" MASTER_BASE_MERGE_BASE_MISMATCH");
group.dirty = true;
}
}
// special case
// master_base groups cannot be compared by commit sha
// instead compare the literal diff local against origin
// gh pr diff --color=never 110
// git --no-pager diff --color=never 00c8fe0~1..00c8fe0
let diff_github = await github.pr_diff(group.pr.number);
diff_github = normalize_diff(diff_github);
// if still not dirty, check diffs
if (!group.dirty) {
actions.debug(" MASTER_BASE_DIFF_COMPARE");
let diff_local = await git.get_diff(group.commits);
diff_local = normalize_diff(diff_local);
// special case
// master_base groups cannot be compared by commit sha
// instead compare the literal diff local against origin
// gh pr diff --color=never 110
// git --no-pager diff --color=never 00c8fe0~1..00c8fe0
let diff_github = await github.pr_diff(group.pr.headRefName);
diff_github = normalize_diff(diff_github);
actions.json({ diff_local, diff_github });
let diff_local = await git.get_diff(group.commits);
diff_local = normalize_diff(diff_local);
// find the first differing character index
let compare_length = Math.max(diff_github.length, diff_local.length);
let diff_index = -1;
for (let c_i = 0; c_i < compare_length; c_i++) {
if (diff_github[c_i] !== diff_local[c_i]) {
diff_index = c_i;
break;
// find the first differing character index
let compare_length = Math.max(diff_github.length, diff_local.length);
let diff_index = -1;
for (let c_i = 0; c_i < compare_length; c_i++) {
if (diff_github[c_i] !== diff_local[c_i]) {
diff_index = c_i;
break;
}
}
}
if (diff_index > -1) {
group.dirty = true;
if (diff_index > -1) {
actions.debug(" MASTER_BASE_DIFF_MISMATCH");
group.dirty = true;
if (DEBUG) {
// print preview at diff_index for both strings
const preview_radius = 30;
const start_index = Math.max(0, diff_index - preview_radius);
const end_index = Math.min(compare_length, diff_index + preview_radius);
if (argv.verbose) {
// print preview at diff_index for both strings
const preview_radius = 30;
const start_index = Math.max(0, diff_index - preview_radius);
const end_index = Math.min(compare_length, diff_index + preview_radius);
diff_github = diff_github.substring(start_index, end_index);
diff_github = JSON.stringify(diff_github).slice(1, -1);
diff_github = diff_github.substring(start_index, end_index);
diff_github = JSON.stringify(diff_github).slice(1, -1);
diff_local = diff_local.substring(start_index, end_index);
diff_local = JSON.stringify(diff_local).slice(1, -1);
diff_local = diff_local.substring(start_index, end_index);
diff_local = JSON.stringify(diff_local).slice(1, -1);
let pointer_indent = " ".repeat(diff_index - start_index + 1);
actions.debug(`⚠️ git diff mismatch`);
actions.debug(` ${pointer_indent}⌄`);
actions.debug(`diff_github …${diff_github}…`);
actions.debug(`diff_local …${diff_local}…`);
actions.debug(` ${pointer_indent}⌃`);
let pointer_indent = " ".repeat(diff_index - start_index + 1);
actions.debug(` ⚠️ git diff mismatch`);
actions.debug(` ${pointer_indent}⌄`);
actions.debug(` diff_github …${diff_github}…`);
actions.debug(` diff_local …${diff_local}…`);
actions.debug(` ${pointer_indent}⌃`);
}
}
}
} else if (!group.master_base && previous_group && previous_group.master_base) {
} else if (MASTER_BASE_BOUNDARY) {
// special case

@@ -248,6 +279,5 @@ // boundary between normal commits and master commits

if (group.pr.commits.length !== all_commits.length) {
actions.debug("BOUNDARY_COMMIT_LENGTH_MISMATCH");
actions.debug(" BOUNDARY_COMMIT_LENGTH_MISMATCH");
group.dirty = true;
} else {
actions.debug("BOUNDARY_COMMIT_SHA_COMPARISON");
for (let i = 0; i < group.pr.commits.length; i++) {

@@ -258,2 +288,3 @@ const pr_commit = group.pr.commits[i];

if (pr_commit.oid !== local_commit.sha) {
actions.debug(" BOUNDARY_COMMIT_SHA_MISMATCH");
group.dirty = true;

@@ -264,6 +295,5 @@ }

} else if (group.pr.commits.length !== group.commits.length) {
actions.debug("COMMIT_LENGTH_MISMATCH");
actions.debug(" COMMIT_LENGTH_MISMATCH");
group.dirty = true;
} else {
actions.debug("COMMIT_SHA_COMPARISON");
// if we still haven't marked this dirty, check each commit

@@ -276,3 +306,3 @@ // comapre literal commit shas in group

if (pr_commit.oid !== local_commit.sha) {
actions.json({ pr_commit, local_commit });
actions.debug(" COMMIT_SHA_MISMATCH");
group.dirty = true;

@@ -284,3 +314,3 @@ }

// console.debug(" ", "group.dirty", group.dirty);
actions.debug(` group.dirty=${group.dirty}`);
}

@@ -287,0 +317,0 @@

@@ -64,3 +64,3 @@ import { Store } from "~/app/Store";

const sha_range = `${first_commit.sha}~1..${last_commit.sha}`;
const diff_result = await cli(`git --no-pager diff --color=never ${sha_range}`);
const diff_result = await cli(`git --no-pager diff --color=never ${sha_range}`, { quiet: true });
return diff_result.stdout;

@@ -67,0 +67,0 @@ }

@@ -43,3 +43,2 @@ import * as React from "react";

<FormatText
wrapper={<Ink.Text dimColor />}
message="Github cache {count} open PRs from {repo_path} authored by {username}"

@@ -105,3 +104,4 @@ values={{

const pr = await gh_json<PullRequest>(`pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`);
const commmand = `pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`;
const pr = await gh_json<PullRequest>(commmand, { branch });

@@ -246,44 +246,33 @@ if (pr instanceof Error) {

export async function pr_diff(number: number) {
const state = Store.getState();
const actions = state.actions;
export async function pr_diff(branch: string) {
// https://cli.github.com/manual/gh_pr_diff
const result = await gh(`pr diff --color=never ${branch}`, { branch });
const maybe_diff = state.cache_pr_diff[number];
if (result instanceof Error) {
handle_error(result.message);
}
if (maybe_diff) {
if (actions.isDebug()) {
actions.debug(
cache_message({
hit: true,
message: "Github pr_diff cache",
extra: number,
}),
);
}
return result;
}
return maybe_diff;
}
export async function pr_compare(branch: string) {
const state = Store.getState();
const master_branch = state.master_branch;
const repo_path = state.repo_path;
invariant(master_branch, "master_branch must exist");
invariant(repo_path, "repo_path must exist");
if (actions.isDebug()) {
actions.debug(
cache_message({
hit: false,
message: "Github pr_diff cache",
extra: number,
}),
);
}
const master_branch_name = master_branch.replace(/^origin\//, "");
// https://cli.github.com/manual/gh_pr_diff
const cli_result = await cli(`gh pr diff --color=never ${number}`);
// gh api repos/openai/openai/compare/master...chrome/publish/vine-1211---4h2xmw0o3ndvnt
const result = await gh_json<BranchCompare>(
`api repos/${repo_path}/compare/${master_branch_name}...${branch}`,
{ branch },
);
if (cli_result.code !== 0) {
handle_error(cli_result.output);
if (result instanceof Error) {
handle_error(result.message);
}
actions.set((state) => {
state.cache_pr_diff[number] = cli_result.output;
});
return cli_result.stdout;
return result;
}

@@ -296,12 +285,74 @@

type GhCmdOptions = {
branch?: string;
};
// consistent handle gh cli commands returning json
// redirect to tmp file to avoid scrollback overflow causing scrollback to be cleared
async function gh_json<T>(command: string): Promise<T | Error> {
async function gh_json<T>(command: string, gh_options?: GhCmdOptions): Promise<T | Error> {
const gh_result = await gh(command, gh_options);
if (gh_result instanceof Error) {
return gh_result;
}
try {
const json = JSON.parse(gh_result);
return json as T;
} catch (error) {
return new Error(`gh_json JSON.parse: ${error}`);
}
}
// consistent handle gh cli commands
// redirect to tmp file to avoid scrollback overflow causing scrollback to be cleared
async function gh(command: string, gh_options?: GhCmdOptions): Promise<string | Error> {
const state = Store.getState();
const actions = state.actions;
if (gh_options?.branch) {
const branch = gh_options.branch;
type CacheEntryByHeadRefName = (typeof state.cache_gh_cli_by_branch)[string][string];
let cache: undefined | CacheEntryByHeadRefName = undefined;
if (branch) {
if (state.cache_gh_cli_by_branch[branch]) {
cache = state.cache_gh_cli_by_branch[branch][command];
}
}
if (cache) {
if (actions.isDebug()) {
actions.debug(
cache_message({
hit: true,
message: "gh cache",
extra: command,
}),
);
}
return cache;
}
if (actions.isDebug()) {
actions.debug(
cache_message({
hit: false,
message: "gh cache",
extra: command,
}),
);
}
}
// hash command for unique short string
let hash = crypto.createHash("md5").update(command).digest("hex");
let tmp_filename = safe_filename(`gh_json-${hash}`);
const tmp_pr_json = path.join(await get_tmp_dir(), `${tmp_filename}.json`);
let tmp_filename = safe_filename(`gh-${hash}`);
const tmp_filepath = path.join(await get_tmp_dir(), `${tmp_filename}`);
const options = { ignoreExitCode: true };
const cli_result = await cli(`gh ${command} > ${tmp_pr_json}`, options);
const cli_result = await cli(`gh ${command} > ${tmp_filepath}`, options);

@@ -313,9 +364,17 @@ if (cli_result.code !== 0) {

// read from file
const json_str = String(await fs.readFile(tmp_pr_json));
try {
const json = JSON.parse(json_str);
return json;
} catch (error) {
return new Error(`gh_json JSON.parse: ${error}`);
let content = String(await fs.readFile(tmp_filepath));
content = content.trim();
if (gh_options?.branch) {
const branch = gh_options.branch;
actions.set((state) => {
if (!state.cache_gh_cli_by_branch[branch]) {
state.cache_gh_cli_by_branch[branch] = {};
}
state.cache_gh_cli_by_branch[branch][command] = content;
});
}
return content;
}

@@ -416,4 +475,34 @@

type MergeBaseCommit = {
author: unknown;
comments_url: string;
commit: unknown;
committer: unknown;
html_url: string;
node_id: string;
parents: unknown;
sha: string;
url: string;
};
export type BranchCompare = {
ahead_by: number;
base_commit: unknown;
behind_by: number;
commits: unknown;
diff_url: string;
files: unknown;
html_url: string;
merge_base_commit: MergeBaseCommit;
patch_url: string;
permalink_url: string;
status: unknown;
total_commits: number;
url: string;
};
const RE = {
non_alphanumeric_dash: /[^a-zA-Z0-9_-]+/g,
};

@@ -8,3 +8,2 @@ #!/usr/bin/env node

import fs from "node:fs/promises";
import path from "node:path";

@@ -65,6 +64,2 @@ import * as Ink from "ink-cjs";

const PATH = process.env["PATH"];
const PATH_LIST = pretty_json(PATH.split(path.delimiter));
actions.debug(`process.env.PATH ${PATH_LIST}`);
await ink.waitUntilExit();

@@ -71,0 +66,0 @@

declare namespace NodeJS {
interface ProcessEnv {
PATH: string;
HOME: string;
DEV?: "true" | "false";

@@ -5,0 +6,0 @@ CLI_VERSION?: string;

import * as React from "react";
import * as Ink from "ink-cjs";
import { DateTime } from "luxon";
export function LogTimestamp() {
return <Ink.Text dimColor>{DateTime.now().toFormat("[yyyy-MM-dd HH:mm:ss.SSS] ")}</Ink.Text>;
}

Sorry, the diff of this file is too big to display