git-stack-cli 2.1.0-beta → 2.1.2

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/dist/js/index.js CHANGED
@@ -28099,6 +28099,7 @@ var require_last = __commonJS((exports, module) => {
28099
28099
 
28100
28100
  // src/index.tsx
28101
28101
  var React55 = __toESM(require_react(), 1);
28102
+ import fs14 from "node:fs/promises";
28102
28103
 
28103
28104
  // node_modules/.pnpm/ink-cjs@4.4.1_@types+react@18.2.33_react-devtools-core@4.19.1_react@18.2.0/node_modules/ink-cjs/build/render.js
28104
28105
  import { Stream } from "node:stream";
@@ -37089,11 +37090,24 @@ var BaseStore = createStore()(immer2((set2, get) => ({
37089
37090
  state.mutate.output(state, { node });
37090
37091
  });
37091
37092
  },
37092
- error(message) {
37093
- set2((state) => {
37094
- const node = /* @__PURE__ */ React16.createElement(Text, {
37093
+ error(error) {
37094
+ let node;
37095
+ if (typeof error === "string") {
37096
+ node = /* @__PURE__ */ React16.createElement(Text, {
37097
+ color: colors.red
37098
+ }, error);
37099
+ } else if (error instanceof Error) {
37100
+ node = /* @__PURE__ */ React16.createElement(Box_default, {
37101
+ flexDirection: "column"
37102
+ }, /* @__PURE__ */ React16.createElement(Text, {
37103
+ color: colors.red
37104
+ }, error.stack));
37105
+ } else {
37106
+ node = /* @__PURE__ */ React16.createElement(Text, {
37095
37107
  color: colors.red
37096
- }, message);
37108
+ }, `Unhandled error: ${JSON.stringify(error)}`);
37109
+ }
37110
+ set2((state) => {
37097
37111
  state.mutate.output(state, { node });
37098
37112
  });
37099
37113
  },
@@ -37279,9 +37293,7 @@ async function cli(unsafe_command, unsafe_options) {
37279
37293
  };
37280
37294
  state.actions.set((state2) => state2.mutate.end_pending_output(state2, id));
37281
37295
  state.actions.debug(log.end(result));
37282
- state.actions.debug(result.output);
37283
- state.actions.debug(`
37284
- `);
37296
+ state.actions.debug(log.output(result));
37285
37297
  if (!options.ignoreExitCode && result.code !== 0) {
37286
37298
  reject(new Error(log.error(result)));
37287
37299
  } else {
@@ -37317,7 +37329,7 @@ cli.sync = function cli_sync(unsafe_command, unsafe_options) {
37317
37329
  duration
37318
37330
  };
37319
37331
  state.actions.debug(log.end(result));
37320
- state.actions.debug(result.output);
37332
+ state.actions.debug(log.output(result));
37321
37333
  if (!options.ignoreExitCode && result.code !== 0) {
37322
37334
  throw new Error(log.error(result));
37323
37335
  }
@@ -37335,6 +37347,10 @@ var log = {
37335
37347
  const { command, code, duration } = result;
37336
37348
  return `[end] ${command} (exit_code=${code} duration=${duration})`;
37337
37349
  },
37350
+ output(result) {
37351
+ return `${result.output}
37352
+ `;
37353
+ },
37338
37354
  error(result) {
37339
37355
  const { command, code, duration } = result;
37340
37356
  return `${command} (exit_code=${code} duration=${duration})`;
@@ -38015,9 +38031,21 @@ var RE3 = {
38015
38031
 
38016
38032
  // src/core/github.tsx
38017
38033
  var React24 = __toESM(require_react(), 1);
38034
+ import crypto from "node:crypto";
38035
+ import fs9 from "node:fs/promises";
38036
+ import path6 from "node:path";
38037
+
38038
+ // src/core/get_tmp_dir.ts
38018
38039
  import fs8 from "node:fs/promises";
38019
38040
  import os2 from "node:os";
38020
38041
  import path5 from "node:path";
38042
+ async function get_tmp_dir() {
38043
+ const dir = path5.join(os2.tmpdir(), "git-stack-cli");
38044
+ await fs8.mkdir(dir, { recursive: true });
38045
+ return dir;
38046
+ }
38047
+
38048
+ // src/core/github.tsx
38021
38049
  async function pr_list() {
38022
38050
  const state = Store.getState();
38023
38051
  const actions = state.actions;
@@ -38146,15 +38174,21 @@ async function pr_draft(args) {
38146
38174
  }
38147
38175
  var JSON_FIELDS = "--json id,number,state,baseRefName,headRefName,commits,title,body,url,isDraft";
38148
38176
  async function gh_json(command) {
38149
- const tmp_pr_json = path5.join(os2.tmpdir(), "git-stack-gh.json");
38177
+ let hash = crypto.createHash("md5").update(command).digest("hex");
38178
+ let tmp_filename = safe_filename(`gh_json-${hash}`);
38179
+ const tmp_pr_json = path6.join(await get_tmp_dir(), `${tmp_filename}.json`);
38150
38180
  const options = { ignoreExitCode: true };
38151
38181
  const cli_result = await cli(`gh ${command} > ${tmp_pr_json}`, options);
38152
38182
  if (cli_result.code !== 0) {
38153
38183
  return new Error(cli_result.output);
38154
38184
  }
38155
- const json_str = await fs8.readFile(tmp_pr_json, "utf-8");
38156
- const json = JSON.parse(json_str);
38157
- return json;
38185
+ const json_str = String(await fs9.readFile(tmp_pr_json));
38186
+ try {
38187
+ const json = JSON.parse(json_str);
38188
+ return json;
38189
+ } catch (error) {
38190
+ return new Error(`gh_json JSON.parse: ${error}`);
38191
+ }
38158
38192
  }
38159
38193
  function handle_error(output) {
38160
38194
  const state = Store.getState();
@@ -38166,14 +38200,15 @@ function handle_error(output) {
38166
38200
  }
38167
38201
  async function write_body_file(args) {
38168
38202
  invariant(args.body, "args.body must exist");
38169
- const temp_dir = os2.tmpdir();
38170
- let temp_filename = `git-stack-body-${args.base}`;
38171
- temp_filename = temp_filename.replace(RE4.non_alphanumeric_dash, "-");
38172
- const temp_path = path5.join(temp_dir, temp_filename);
38203
+ let tmp_filename = safe_filename(`git-stack-body-${args.base}`);
38204
+ const temp_path = path6.join(await get_tmp_dir(), tmp_filename);
38173
38205
  await safe_rm(temp_path);
38174
- await fs8.writeFile(temp_path, args.body);
38206
+ await fs9.writeFile(temp_path, args.body);
38175
38207
  return temp_path;
38176
38208
  }
38209
+ function safe_filename(value) {
38210
+ return value.replace(RE4.non_alphanumeric_dash, "-");
38211
+ }
38177
38212
  var RE4 = {
38178
38213
  non_alphanumeric_dash: /[^a-zA-Z0-9_-]+/g
38179
38214
  };
@@ -38327,9 +38362,8 @@ function lines(value) {
38327
38362
  var UNASSIGNED = "unassigned";
38328
38363
 
38329
38364
  // src/core/GitReviseTodo.ts
38330
- import fs9 from "node:fs/promises";
38331
- import os3 from "node:os";
38332
- import path6 from "node:path";
38365
+ import fs10 from "node:fs/promises";
38366
+ import path7 from "node:path";
38333
38367
  function GitReviseTodo(args) {
38334
38368
  const commit_list = [];
38335
38369
  const group_list = args.commit_range.group_list;
@@ -38365,7 +38399,7 @@ GitReviseTodo.todo = function todo(args) {
38365
38399
  return todo2;
38366
38400
  };
38367
38401
  GitReviseTodo.execute = async function grt_execute(args) {
38368
- const tmp_git_sequence_editor_path = path6.join(os3.tmpdir(), "git-sequence-editor.sh");
38402
+ const tmp_git_sequence_editor_path = path7.join(await get_tmp_dir(), "git-sequence-editor.sh");
38369
38403
  const GIT_SEQUENCE_EDITOR_SCRIPT = `#!/bin/sh
38370
38404
 
38371
38405
  # Example
@@ -38403,8 +38437,8 @@ echo "------ END ------"
38403
38437
  echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
38404
38438
  `;
38405
38439
  invariant(GIT_SEQUENCE_EDITOR_SCRIPT, "GIT_SEQUENCE_EDITOR_SCRIPT must exist");
38406
- await fs9.writeFile(tmp_git_sequence_editor_path, GIT_SEQUENCE_EDITOR_SCRIPT);
38407
- await fs9.chmod(tmp_git_sequence_editor_path, "755");
38440
+ await fs10.writeFile(tmp_git_sequence_editor_path, GIT_SEQUENCE_EDITOR_SCRIPT);
38441
+ await fs10.chmod(tmp_git_sequence_editor_path, "755");
38408
38442
  const git_revise_todo = GitReviseTodo(args);
38409
38443
  const command = [
38410
38444
  `GIT_EDITOR="${tmp_git_sequence_editor_path}"`,
@@ -38481,7 +38515,7 @@ function DetectInitialPR(props) {
38481
38515
  const branch_name2 = Store.getState().branch_name;
38482
38516
  const commit_range = Store.getState().commit_range;
38483
38517
  invariant(branch_name2, "branch_name must exist");
38484
- invariant(commit_range, "branch_name must exist");
38518
+ invariant(commit_range, "commit_range must exist");
38485
38519
  try {
38486
38520
  let has_existing_metadata = false;
38487
38521
  for (const commit2 of commit_range.commit_list) {
@@ -38513,7 +38547,7 @@ function DetectInitialPR(props) {
38513
38547
  const branch_name2 = Store.getState().branch_name;
38514
38548
  const commit_range = import_cloneDeep.default(Store.getState().commit_range);
38515
38549
  invariant(branch_name2, "branch_name must exist");
38516
- invariant(commit_range, "branch_name must exist");
38550
+ invariant(commit_range, "commit_range must exist");
38517
38551
  for (const group of commit_range.group_list) {
38518
38552
  group.id = branch_name2;
38519
38553
  group.title = state.pr?.title || "-";
@@ -38845,16 +38879,16 @@ var React32 = __toESM(require_react(), 1);
38845
38879
 
38846
38880
  // src/commands/Rebase.tsx
38847
38881
  var React31 = __toESM(require_react(), 1);
38848
- import fs10 from "node:fs";
38882
+ import fs11 from "node:fs";
38849
38883
 
38850
38884
  // src/core/short_id.ts
38851
- import crypto from "node:crypto";
38885
+ import crypto2 from "node:crypto";
38852
38886
  function short_id() {
38853
38887
  const timestamp = Date.now();
38854
38888
  const js_max_bits = 53;
38855
38889
  const timestamp_bits = Math.floor(Math.log2(timestamp)) + 1;
38856
38890
  const padding_bits = js_max_bits - timestamp_bits;
38857
- const random = crypto.randomInt(0, Math.pow(2, padding_bits));
38891
+ const random = crypto2.randomInt(0, Math.pow(2, padding_bits));
38858
38892
  const combined = interleave_bits(timestamp, random);
38859
38893
  return encode(combined);
38860
38894
  }
@@ -38998,7 +39032,7 @@ Rebase.run = async function run4() {
38998
39032
  cli.sync(`git clean -df`, spawn_options);
38999
39033
  cli.sync(`git checkout ${branch_name}`, spawn_options);
39000
39034
  cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
39001
- if (fs10.existsSync(cwd2)) {
39035
+ if (fs11.existsSync(cwd2)) {
39002
39036
  process.chdir(cwd2);
39003
39037
  }
39004
39038
  cli.sync(`pwd`, spawn_options);
@@ -39021,7 +39055,7 @@ function LocalMergeRebase() {
39021
39055
 
39022
39056
  // src/app/ManualRebase.tsx
39023
39057
  var React33 = __toESM(require_react(), 1);
39024
- import fs11 from "node:fs";
39058
+ import fs12 from "node:fs";
39025
39059
  function ManualRebase() {
39026
39060
  return /* @__PURE__ */ React33.createElement(Await, {
39027
39061
  fallback: /* @__PURE__ */ React33.createElement(Text, {
@@ -39121,7 +39155,7 @@ async function run5() {
39121
39155
  cli.sync(`git clean -df`, spawn_options);
39122
39156
  cli.sync(`git checkout ${branch_name}`, spawn_options);
39123
39157
  cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
39124
- if (fs11.existsSync(cwd2)) {
39158
+ if (fs12.existsSync(cwd2)) {
39125
39159
  process.chdir(cwd2);
39126
39160
  }
39127
39161
  cli.sync(`pwd`, spawn_options);
@@ -39380,8 +39414,8 @@ function PreLocalMergeRebase() {
39380
39414
 
39381
39415
  // src/app/PreManualRebase.tsx
39382
39416
  var React38 = __toESM(require_react(), 1);
39383
- import fs12 from "node:fs/promises";
39384
- import path7 from "node:path";
39417
+ import fs13 from "node:fs/promises";
39418
+ import path8 from "node:path";
39385
39419
  function PreManualRebase() {
39386
39420
  return /* @__PURE__ */ React38.createElement(Await, {
39387
39421
  fallback: null,
@@ -39403,7 +39437,7 @@ async function run7() {
39403
39437
  for (const key of PR_TEMPLATE_KEY_LIST) {
39404
39438
  const pr_template_fn = PR_TEMPLATE[key];
39405
39439
  if (await safe_exists(pr_template_fn(repo_root))) {
39406
- pr_template_body = await fs12.readFile(pr_template_fn(repo_root), "utf-8");
39440
+ pr_template_body = await fs13.readFile(pr_template_fn(repo_root), "utf-8");
39407
39441
  actions.output(/* @__PURE__ */ React38.createElement(FormatText, {
39408
39442
  wrapper: /* @__PURE__ */ React38.createElement(Text, {
39409
39443
  color: colors.yellow
@@ -39418,7 +39452,7 @@ async function run7() {
39418
39452
  }
39419
39453
  let pr_templates = [];
39420
39454
  if (await safe_exists(PR_TEMPLATE.TemplateDir(repo_root))) {
39421
- pr_templates = await fs12.readdir(PR_TEMPLATE.TemplateDir(repo_root));
39455
+ pr_templates = await fs13.readdir(PR_TEMPLATE.TemplateDir(repo_root));
39422
39456
  }
39423
39457
  actions.set((state2) => {
39424
39458
  state2.pr_template_body = pr_template_body;
@@ -39441,10 +39475,10 @@ async function run7() {
39441
39475
  });
39442
39476
  }
39443
39477
  var PR_TEMPLATE = Object.freeze({
39444
- Github: (root) => path7.join(root, ".github", "pull_request_template.md"),
39445
- Root: (root) => path7.join(root, "pull_request_template.md"),
39446
- Docs: (root) => path7.join(root, "docs", "pull_request_template.md"),
39447
- TemplateDir: (root) => path7.join(root, ".github", "PULL_REQUEST_TEMPLATE")
39478
+ Github: (root) => path8.join(root, ".github", "pull_request_template.md"),
39479
+ Root: (root) => path8.join(root, "pull_request_template.md"),
39480
+ Docs: (root) => path8.join(root, "docs", "pull_request_template.md"),
39481
+ TemplateDir: (root) => path8.join(root, ".github", "PULL_REQUEST_TEMPLATE")
39448
39482
  });
39449
39483
  var PR_TEMPLATE_KEY_LIST = Object.keys(PR_TEMPLATE);
39450
39484
 
@@ -40426,7 +40460,7 @@ function Providers(props) {
40426
40460
 
40427
40461
  // src/app/RebaseCheck.tsx
40428
40462
  var React48 = __toESM(require_react(), 1);
40429
- import path8 from "node:path";
40463
+ import path9 from "node:path";
40430
40464
  function reducer5(state, patch) {
40431
40465
  return { ...state, ...patch };
40432
40466
  }
@@ -40464,8 +40498,8 @@ function RebaseCheck(props) {
40464
40498
  try {
40465
40499
  const git_dir = (await cli(`git rev-parse --absolute-git-dir`)).stdout;
40466
40500
  let is_rebase = false;
40467
- is_rebase ||= await safe_exists(path8.join(git_dir, "rebase-apply"));
40468
- is_rebase ||= await safe_exists(path8.join(git_dir, "rebase-merge"));
40501
+ is_rebase ||= await safe_exists(path9.join(git_dir, "rebase-apply"));
40502
+ is_rebase ||= await safe_exists(path9.join(git_dir, "rebase-merge"));
40469
40503
  const status = is_rebase ? "prompt" : "done";
40470
40504
  patch({ status });
40471
40505
  } catch (err) {
@@ -42021,9 +42055,9 @@ var parser = new YargsParser({
42021
42055
  format,
42022
42056
  normalize,
42023
42057
  resolve: resolve2,
42024
- require: (path9) => {
42058
+ require: (path10) => {
42025
42059
  if (true) {
42026
- return __require(path9);
42060
+ return __require(path10);
42027
42061
  } else
42028
42062
  ;
42029
42063
  }
@@ -45606,7 +45640,7 @@ var yargs_default = Yargs;
45606
45640
 
45607
45641
  // src/command.ts
45608
45642
  async function command2() {
45609
- return yargs_default(hideBin(process.argv)).usage("Usage: git stack [command] [options]").command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions)).command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit)).command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false)).command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.1.0-beta").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`").argv;
45643
+ return yargs_default(hideBin(process.argv)).usage("Usage: git stack [command] [options]").command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions)).command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit)).command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false)).command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.1.2").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`").argv;
45610
45644
  }
45611
45645
  var GlobalOptions = {
45612
45646
  verbose: {
@@ -45715,6 +45749,8 @@ var FixupOptions = {
45715
45749
  maybe_verbose_help();
45716
45750
  process.exit(238);
45717
45751
  });
45752
+ const tmp_dir = await get_tmp_dir();
45753
+ await fs14.rm(tmp_dir, { recursive: true });
45718
45754
  const ink = render_default(/* @__PURE__ */ React55.createElement(App2, null), {
45719
45755
  exitOnCtrlC: false
45720
45756
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "2.1.0-beta",
3
+ "version": "2.1.2",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -52,6 +52,7 @@
52
52
  "zustand": "^4.4.4"
53
53
  },
54
54
  "devDependencies": {
55
+ "@oven/bun-darwin-aarch64": "1.1.42",
55
56
  "@types/chalk": "^2.2.0",
56
57
  "@types/lodash": "^4.17.7",
57
58
  "@types/luxon": "^3.4.2",
@@ -108,7 +108,7 @@ export function DetectInitialPR(props: Props) {
108
108
  const commit_range = Store.getState().commit_range;
109
109
 
110
110
  invariant(branch_name, "branch_name must exist");
111
- invariant(commit_range, "branch_name must exist");
111
+ invariant(commit_range, "commit_range must exist");
112
112
 
113
113
  try {
114
114
  let has_existing_metadata = false;
@@ -149,7 +149,7 @@ export function DetectInitialPR(props: Props) {
149
149
  const commit_range = cloneDeep(Store.getState().commit_range);
150
150
 
151
151
  invariant(branch_name, "branch_name must exist");
152
- invariant(commit_range, "branch_name must exist");
152
+ invariant(commit_range, "commit_range must exist");
153
153
 
154
154
  for (const group of commit_range.group_list) {
155
155
  group.id = branch_name;
package/src/app/Store.tsx CHANGED
@@ -84,7 +84,7 @@ export type State = {
84
84
  unmount(): void;
85
85
  newline(): void;
86
86
  json(value: pretty_json.JSONValue): void;
87
- error(message: string): void;
87
+ error(error: unknown): void;
88
88
  output(node: React.ReactNode): void;
89
89
  debug(node: React.ReactNode, id?: string): void;
90
90
 
@@ -176,9 +176,24 @@ const BaseStore = createStore<State>()(
176
176
  });
177
177
  },
178
178
 
179
- error(message) {
179
+ error(error) {
180
+ let node: React.ReactNode;
181
+
182
+ if (typeof error === "string") {
183
+ node = <Ink.Text color={colors.red}>{error}</Ink.Text>;
184
+ } else if (error instanceof Error) {
185
+ node = (
186
+ <Ink.Box flexDirection="column">
187
+ <Ink.Text color={colors.red}>{error.stack}</Ink.Text>
188
+ </Ink.Box>
189
+ );
190
+ } else {
191
+ node = (
192
+ <Ink.Text color={colors.red}>{`Unhandled error: ${JSON.stringify(error)}`}</Ink.Text>
193
+ );
194
+ }
195
+
180
196
  set((state) => {
181
- const node = <Ink.Text color={colors.red}>{message}</Ink.Text>;
182
197
  state.mutate.output(state, { node });
183
198
  });
184
199
  },
@@ -1,9 +1,9 @@
1
1
  import fs from "node:fs/promises";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
 
5
4
  import * as Metadata from "~/core/Metadata";
6
5
  import { cli } from "~/core/cli";
6
+ import { get_tmp_dir } from "~/core/get_tmp_dir";
7
7
  import { invariant } from "~/core/invariant";
8
8
  import { safe_rm } from "~/core/safe_rm";
9
9
 
@@ -109,7 +109,7 @@ GitReviseTodo.todo = function todo(args: CommitListArgs) {
109
109
 
110
110
  GitReviseTodo.execute = async function grt_execute(args: ExecuteArgs) {
111
111
  // generate temporary directory and drop sequence editor script
112
- const tmp_git_sequence_editor_path = path.join(os.tmpdir(), "git-sequence-editor.sh");
112
+ const tmp_git_sequence_editor_path = path.join(await get_tmp_dir(), "git-sequence-editor.sh");
113
113
 
114
114
  // replaced at build time with literal contents of `scripts/git-sequence-editor.sh`
115
115
  const GIT_SEQUENCE_EDITOR_SCRIPT = process.env.GIT_SEQUENCE_EDITOR_SCRIPT;
package/src/core/cli.ts CHANGED
@@ -79,8 +79,7 @@ export async function cli(
79
79
 
80
80
  state.actions.set((state) => state.mutate.end_pending_output(state, id));
81
81
  state.actions.debug(log.end(result));
82
- state.actions.debug(result.output);
83
- state.actions.debug("\n");
82
+ state.actions.debug(log.output(result));
84
83
 
85
84
  if (!options.ignoreExitCode && result.code !== 0) {
86
85
  reject(new Error(log.error(result)));
@@ -131,7 +130,7 @@ cli.sync = function cli_sync(
131
130
  };
132
131
 
133
132
  state.actions.debug(log.end(result));
134
- state.actions.debug(result.output);
133
+ state.actions.debug(log.output(result));
135
134
 
136
135
  if (!options.ignoreExitCode && result.code !== 0) {
137
136
  throw new Error(log.error(result));
@@ -154,6 +153,10 @@ const log = {
154
153
  return `[end] ${command} (exit_code=${code} duration=${duration})`;
155
154
  },
156
155
 
156
+ output(result: Return) {
157
+ return `${result.output}\n`;
158
+ },
159
+
157
160
  error(result: Return) {
158
161
  const { command, code, duration } = result;
159
162
  return `${command} (exit_code=${code} duration=${duration})`;
@@ -0,0 +1,12 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ export async function get_tmp_dir(): Promise<string> {
6
+ const dir = path.join(os.tmpdir(), "git-stack-cli");
7
+
8
+ // ensure tmp directory exists
9
+ await fs.mkdir(dir, { recursive: true });
10
+
11
+ return dir;
12
+ }
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
+ import crypto from "node:crypto";
3
4
  import fs from "node:fs/promises";
4
- import os from "node:os";
5
5
  import path from "node:path";
6
6
 
7
7
  import * as Ink from "ink-cjs";
@@ -10,6 +10,7 @@ import { Brackets } from "~/app/Brackets";
10
10
  import { Store } from "~/app/Store";
11
11
  import { cli } from "~/core/cli";
12
12
  import { colors } from "~/core/colors";
13
+ import { get_tmp_dir } from "~/core/get_tmp_dir";
13
14
  import { invariant } from "~/core/invariant";
14
15
  import { safe_quote } from "~/core/safe_quote";
15
16
  import { safe_rm } from "~/core/safe_rm";
@@ -228,7 +229,10 @@ const JSON_FIELDS = "--json id,number,state,baseRefName,headRefName,commits,titl
228
229
  // consistent handle gh cli commands returning json
229
230
  // redirect to tmp file to avoid scrollback overflow causing scrollback to be cleared
230
231
  async function gh_json<T>(command: string): Promise<T | Error> {
231
- const tmp_pr_json = path.join(os.tmpdir(), "git-stack-gh.json");
232
+ // hash command for unique short string
233
+ let hash = crypto.createHash("md5").update(command).digest("hex");
234
+ let tmp_filename = safe_filename(`gh_json-${hash}`);
235
+ const tmp_pr_json = path.join(await get_tmp_dir(), `${tmp_filename}.json`);
232
236
 
233
237
  const options = { ignoreExitCode: true };
234
238
  const cli_result = await cli(`gh ${command} > ${tmp_pr_json}`, options);
@@ -238,9 +242,13 @@ async function gh_json<T>(command: string): Promise<T | Error> {
238
242
  }
239
243
 
240
244
  // read from file
241
- const json_str = await fs.readFile(tmp_pr_json, "utf-8");
242
- const json = JSON.parse(json_str);
243
- return json;
245
+ const json_str = String(await fs.readFile(tmp_pr_json));
246
+ try {
247
+ const json = JSON.parse(json_str);
248
+ return json;
249
+ } catch (error) {
250
+ return new Error(`gh_json JSON.parse: ${error}`);
251
+ }
244
252
  }
245
253
 
246
254
  function handle_error(output: string): never {
@@ -258,15 +266,12 @@ function handle_error(output: string): never {
258
266
  async function write_body_file(args: EditPullRequestArgs) {
259
267
  invariant(args.body, "args.body must exist");
260
268
 
261
- const temp_dir = os.tmpdir();
262
-
263
269
  // ensure unique filename is safe for filesystem
264
270
  // base (group id) might contain slashes, e.g. dev/magus/gs-3cmrMBSUj
265
271
  // the flashes would mess up the filesystem path to this file
266
- let temp_filename = `git-stack-body-${args.base}`;
267
- temp_filename = temp_filename.replace(RE.non_alphanumeric_dash, "-");
272
+ let tmp_filename = safe_filename(`git-stack-body-${args.base}`);
268
273
 
269
- const temp_path = path.join(temp_dir, temp_filename);
274
+ const temp_path = path.join(await get_tmp_dir(), tmp_filename);
270
275
 
271
276
  await safe_rm(temp_path);
272
277
 
@@ -275,6 +280,10 @@ async function write_body_file(args: EditPullRequestArgs) {
275
280
  return temp_path;
276
281
  }
277
282
 
283
+ function safe_filename(value: string): string {
284
+ return value.replace(RE.non_alphanumeric_dash, "-");
285
+ }
286
+
278
287
  type Commit = {
279
288
  authoredDate: string; // "2023-10-22T23:13:35Z"
280
289
  authors: [
package/src/index.tsx CHANGED
@@ -4,11 +4,14 @@
4
4
 
5
5
  import * as React from "react";
6
6
 
7
+ import fs from "node:fs/promises";
8
+
7
9
  import * as Ink from "ink-cjs";
8
10
 
9
11
  import { App } from "~/app/App";
10
12
  import { Store } from "~/app/Store";
11
13
  import { command } from "~/command";
14
+ import { get_tmp_dir } from "~/core/get_tmp_dir";
12
15
  import { pretty_json } from "~/core/pretty_json";
13
16
 
14
17
  (async function main() {
@@ -33,6 +36,10 @@ import { pretty_json } from "~/core/pretty_json";
33
36
  process.exit(238);
34
37
  });
35
38
 
39
+ // cleanup leftover temporary files from previous run
40
+ const tmp_dir = await get_tmp_dir();
41
+ await fs.rm(tmp_dir, { recursive: true });
42
+
36
43
  const ink = Ink.render(<App />, {
37
44
  // If true, each update will be rendered as a separate output, without replacing the previous one.
38
45
  // debug: true,