git-stack-cli 0.7.5 → 0.8.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/dist/app/App.js CHANGED
@@ -8,6 +8,7 @@ import { LocalCommitStatus } from "./LocalCommitStatus.js";
8
8
  import { Main } from "./Main.js";
9
9
  import { Output } from "./Output.js";
10
10
  import { Providers } from "./Providers.js";
11
+ import { RebaseCheck } from "./RebaseCheck.js";
11
12
  import { Store } from "./Store.js";
12
13
  export function App() {
13
14
  const actions = Store.useActions();
@@ -27,10 +28,15 @@ export function App() {
27
28
  return (React.createElement(Providers, null,
28
29
  React.createElement(Debug, null),
29
30
  React.createElement(Output, null),
30
- !argv.verbose ? null : React.createElement(GithubApiError, null),
31
- React.createElement(DependencyCheck, null,
32
- React.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose, onOutput: actions.output },
33
- React.createElement(GatherMetadata, null,
34
- React.createElement(LocalCommitStatus, null,
35
- React.createElement(Main, null)))))));
31
+ React.createElement(AutoUpdate, { name: "git-stack-cli", verbose: argv.verbose || argv.update, timeoutMs: argv.update ? 30 * 1000 : 2 * 1000, onOutput: actions.output, onDone: () => {
32
+ if (argv.update) {
33
+ actions.exit(0);
34
+ }
35
+ } },
36
+ React.createElement(RebaseCheck, null,
37
+ React.createElement(DependencyCheck, null,
38
+ !argv.verbose ? null : React.createElement(GithubApiError, null),
39
+ React.createElement(GatherMetadata, null,
40
+ React.createElement(LocalCommitStatus, null,
41
+ React.createElement(Main, null))))))));
36
42
  }
@@ -5,12 +5,12 @@ import * as Ink from "ink";
5
5
  import { cli } from "../core/cli.js";
6
6
  import { colors } from "../core/colors.js";
7
7
  import { fetch_json } from "../core/fetch_json.js";
8
+ import { is_finite_value } from "../core/is_finite_value.js";
8
9
  import { read_json } from "../core/read_json.js";
9
10
  import { semver_compare } from "../core/semver_compare.js";
10
11
  import { sleep } from "../core/sleep.js";
11
12
  import { Brackets } from "./Brackets.js";
12
13
  import { FormatText } from "./FormatText.js";
13
- import { Parens } from "./Parens.js";
14
14
  import { YesNoPrompt } from "./YesNoPrompt.js";
15
15
  function reducer(state, patch) {
16
16
  return { ...state, ...patch };
@@ -41,31 +41,33 @@ export function AutoUpdate(props) {
41
41
  let latest_version = null;
42
42
  async function auto_update() {
43
43
  if (props_ref.current.verbose) {
44
- handle_output(React.createElement(Ink.Text, { key: "init", dimColor: true }, "Checking for latest version..."));
44
+ handle_output(React.createElement(Ink.Text, { key: "init" }, "Checking for latest version..."));
45
45
  }
46
- const timeout_ms = 2 * 1000;
46
+ const timeout_ms = is_finite_value(props.timeoutMs)
47
+ ? props.timeoutMs
48
+ : 2 * 1000;
47
49
  const npm_json = await Promise.race([
48
50
  fetch_json(`https://registry.npmjs.org/${props.name}`),
49
51
  sleep(timeout_ms).then(() => {
50
- throw new Error("timeout");
52
+ throw new Error("Timeout");
51
53
  }),
52
54
  ]);
53
55
  latest_version = npm_json?.["dist-tags"]?.latest;
54
56
  if (!latest_version) {
55
- throw new Error("unable to retrieve latest version from npm");
57
+ throw new Error("Unable to retrieve latest version from npm");
56
58
  }
57
59
  const script_dir = path.dirname(fs.realpathSync(process.argv[1]));
58
60
  const package_json_path = path.join(script_dir, "..", "package.json");
59
61
  const package_json = read_json(package_json_path);
60
62
  if (!package_json) {
61
63
  // unable to find read package.json, skip auto update
62
- throw new Error(`unable to find read package.json [${package_json_path}]`);
64
+ throw new Error(`Unable to find read package.json [${package_json_path}]`);
63
65
  }
64
66
  local_version = package_json.version;
65
67
  if (props_ref.current.verbose) {
66
- handle_output(React.createElement(FormatText, { key: "versions", wrapper: React.createElement(Ink.Text, { dimColor: true }), message: "Auto update found latest version {latest_version} and current local version {local_version}", values: {
68
+ handle_output(React.createElement(FormatText, { key: "versions", wrapper: React.createElement(Ink.Text, null), message: "Auto update found latest version {latest_version} and current local version {local_version}", values: {
67
69
  latest_version: React.createElement(Brackets, null, latest_version),
68
- local_version: React.createElement(Parens, null, local_version),
70
+ local_version: React.createElement(Brackets, null, local_version),
69
71
  } }));
70
72
  }
71
73
  const semver_result = semver_compare(latest_version, local_version);
@@ -88,8 +90,11 @@ export function AutoUpdate(props) {
88
90
  patch({ status, error, local_version, latest_version });
89
91
  onError(error);
90
92
  if (props_ref.current.verbose) {
91
- handle_output(React.createElement(Ink.Text, { key: "error", dimColor: true, color: colors.red }, error?.message));
93
+ handle_output(React.createElement(Ink.Text, { key: "error", color: colors.red }, error?.message));
92
94
  }
95
+ })
96
+ .finally(() => {
97
+ props.onDone?.();
93
98
  });
94
99
  }, []);
95
100
  const status = (function render_status() {
@@ -105,7 +110,7 @@ export function AutoUpdate(props) {
105
110
  patch({ status: "install" });
106
111
  await cli(`npm install -g ${props.name}@latest`);
107
112
  patch({ status: "exit" });
108
- handle_output(React.createElement(Ink.Text, { key: "done", dimColor: true }, "Auto update done."));
113
+ handle_output(React.createElement(Ink.Text, { key: "done" }, "Auto update done."));
109
114
  }, onNo: () => {
110
115
  patch({ status: "done" });
111
116
  } }));
@@ -16,6 +16,10 @@ export function DependencyCheck(props) {
16
16
  "Checking ",
17
17
  React.createElement(Command, null, "git"),
18
18
  " install..."), function: async () => {
19
+ // await Promise.all([
20
+ // cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
21
+ // cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
22
+ // ]);
19
23
  if (is_command_available("git")) {
20
24
  return;
21
25
  }
@@ -1,33 +1,60 @@
1
1
  import * as React from "react";
2
2
  import * as Ink from "ink";
3
3
  import { cli } from "../core/cli.js";
4
+ import { colors } from "../core/colors.js";
4
5
  import { invariant } from "../core/invariant.js";
5
6
  import { match_group } from "../core/match_group.js";
6
7
  import { Await } from "./Await.js";
8
+ import { Brackets } from "./Brackets.js";
9
+ import { FormatText } from "./FormatText.js";
7
10
  import { Store } from "./Store.js";
8
11
  export function GatherMetadata(props) {
9
12
  const argv = Store.useState((state) => state.argv);
10
13
  invariant(argv, "argv must exist");
11
- const fallback = (React.createElement(Ink.Text, { color: "yellow" }, "Gathering local git information..."));
14
+ const fallback = (React.createElement(Ink.Text, { color: colors.yellow }, "Gathering local git information..."));
12
15
  return (React.createElement(Await, { fallback: fallback, function: gather_metadata }, props.children));
13
16
  }
14
17
  async function gather_metadata() {
15
18
  const actions = Store.getState().actions;
19
+ const argv = Store.getState().argv;
20
+ invariant(argv, "argv must exist");
16
21
  try {
22
+ // default to master branch, fallback to main
23
+ let master_branch;
24
+ if (argv.branch) {
25
+ actions.debug(React.createElement(FormatText, { message: "Setting master branch to {branch}", values: {
26
+ branch: React.createElement(Brackets, null, argv.branch),
27
+ } }));
28
+ master_branch = argv.branch;
29
+ }
30
+ else {
31
+ const detect_master = await cli(`git branch --list "${BRANCH.master}" --color=never`);
32
+ if (detect_master.stdout !== "") {
33
+ master_branch = BRANCH.master;
34
+ }
35
+ else {
36
+ actions.debug(React.createElement(FormatText, { message: "Could not find {master} branch, falling back to {main}", values: {
37
+ master: React.createElement(Brackets, null, BRANCH.master),
38
+ main: React.createElement(Brackets, null, BRANCH.main),
39
+ } }));
40
+ master_branch = BRANCH.main;
41
+ }
42
+ }
17
43
  const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
18
44
  // handle when there are no detected changes
19
- if (branch_name === "master") {
45
+ if (branch_name === master_branch) {
20
46
  actions.newline();
21
47
  actions.error("Must run within a branch.");
22
48
  actions.exit(0);
23
49
  return;
24
50
  }
25
51
  const head = (await cli("git rev-parse HEAD")).stdout;
26
- const merge_base = (await cli("git merge-base HEAD master")).stdout;
52
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
53
+ .stdout;
27
54
  // handle when there are no detected changes
28
55
  if (head === merge_base) {
29
56
  actions.newline();
30
- actions.output(React.createElement(Ink.Text, { color: "gray" }, "No changes detected."));
57
+ actions.output(React.createElement(Ink.Text, { color: colors.gray }, "No changes detected."));
31
58
  actions.exit(0);
32
59
  return;
33
60
  }
@@ -37,6 +64,7 @@ async function gather_metadata() {
37
64
  const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
38
65
  Store.setState((state) => {
39
66
  state.repo_path = repo_path;
67
+ state.master_branch = master_branch;
40
68
  state.head = head;
41
69
  state.merge_base = merge_base;
42
70
  state.branch_name = branch_name;
@@ -49,6 +77,7 @@ async function gather_metadata() {
49
77
  actions.error(err.message);
50
78
  }
51
79
  }
80
+ actions.exit(7);
52
81
  }
53
82
  }
54
83
  const RE = {
@@ -56,3 +85,7 @@ const RE = {
56
85
  // https://github.com/magus/git-multi-diff-playground.git
57
86
  repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
58
87
  };
88
+ const BRANCH = {
89
+ master: "master",
90
+ main: "main",
91
+ };
@@ -53,11 +53,22 @@ async function run() {
53
53
  commit_message: React.createElement(Brackets, null, commit.message),
54
54
  } }));
55
55
  }
56
- const git_cherry_pick_command = [`git cherry-pick ${commit.sha}`];
56
+ await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
57
+ await cli(`git apply ${PATCH_FILE}`);
58
+ await cli(`rm ${PATCH_FILE}`);
59
+ await cli(`git add --all`);
60
+ let new_message;
61
+ if (commit.branch_id) {
62
+ new_message = await Metadata.write(commit.message, commit.branch_id);
63
+ }
64
+ else {
65
+ new_message = commit.message;
66
+ }
67
+ const git_commit_comand = [`git commit -m "${new_message}"`];
57
68
  if (argv.verify === false) {
58
- git_cherry_pick_command.push("--no-verify");
69
+ git_commit_comand.push("--no-verify");
59
70
  }
60
- await cli(git_cherry_pick_command);
71
+ await cli(git_commit_comand);
61
72
  if (commit.branch_id && !commit_pr) {
62
73
  if (actions.isDebug()) {
63
74
  actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Cleaning up unused group {group}", values: {
@@ -95,6 +106,11 @@ async function run() {
95
106
  // trying to use `await cli(...)` here will silently fail since
96
107
  // all children processes receive the SIGINT signal
97
108
  const spawn_options = { ignoreExitCode: true };
109
+ // always clean up any patch files
110
+ cli.sync(`rm ${PATCH_FILE}`, spawn_options);
111
+ // always hard reset to allow subsequent checkout
112
+ // if there are files checkout will fail and cascade fail subsequent commands
113
+ cli.sync(`git reset --hard`, spawn_options);
98
114
  // always put self back in original branch
99
115
  cli.sync(`git checkout ${branch_name}`, spawn_options);
100
116
  // ...and cleanup temporary branch
@@ -119,3 +135,4 @@ async function run() {
119
135
  actions.exit(6);
120
136
  }
121
137
  }
138
+ const PATCH_FILE = "mypatch.patch";
@@ -57,15 +57,16 @@ async function run(props) {
57
57
  const selected_url = get_group_url(group);
58
58
  // cherry-pick and amend commits one by one
59
59
  for (const commit of group.commits) {
60
- const git_cherry_pick_command = [`git cherry-pick ${commit.sha}`];
60
+ await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
61
+ await cli(`git apply ${PATCH_FILE}`);
62
+ await cli(`rm ${PATCH_FILE}`);
63
+ await cli(`git add --all`);
64
+ const new_message = await Metadata.write(commit.message, group.id);
65
+ const git_commit_comand = [`git commit -m "${new_message}"`];
61
66
  if (argv.verify === false) {
62
- git_cherry_pick_command.push("--no-verify");
63
- }
64
- await cli(git_cherry_pick_command);
65
- if (commit.branch_id !== group.id) {
66
- const new_message = await Metadata.write(commit.message, group.id);
67
- await cli(`git commit --amend -m "${new_message}"`);
67
+ git_commit_comand.push("--no-verify");
68
68
  }
69
+ await cli(git_commit_comand);
69
70
  }
70
71
  actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
71
72
  group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
@@ -164,6 +165,11 @@ async function run(props) {
164
165
  // trying to use `await cli(...)` here will silently fail since
165
166
  // all children processes receive the SIGINT signal
166
167
  const spawn_options = { ignoreExitCode: true };
168
+ // always clean up any patch files
169
+ cli.sync(`rm ${PATCH_FILE}`, spawn_options);
170
+ // always hard reset to allow subsequent checkout
171
+ // if there are files checkout will fail and cascade fail subsequent commands
172
+ cli.sync(`git reset --hard`, spawn_options);
167
173
  // always put self back in original branch
168
174
  cli.sync(`git checkout ${branch_name}`, spawn_options);
169
175
  // ...and cleanup temporary branch
@@ -189,3 +195,4 @@ async function run(props) {
189
195
  }
190
196
  }
191
197
  const get_group_url = (group) => group.pr?.url || group.id;
198
+ const PATCH_FILE = "mypatch.patch";
@@ -3,7 +3,17 @@ import * as Ink from "ink";
3
3
  import { Store } from "./Store.js";
4
4
  export function Output() {
5
5
  const output = Store.useState((state) => state.output);
6
- return (React.createElement(Ink.Static, { items: output }, (node, i) => {
7
- return React.createElement(Ink.Box, { key: i }, node);
8
- }));
6
+ const pending_output = Store.useState((state) => state.pending_output);
7
+ const pending_output_items = Object.values(pending_output);
8
+ return (React.createElement(React.Fragment, null,
9
+ React.createElement(Ink.Static, { items: output }, (node, i) => {
10
+ return React.createElement(Ink.Box, { key: i }, node);
11
+ }),
12
+ pending_output_items.map((node_list, i) => {
13
+ return (React.createElement(Ink.Box, { key: i },
14
+ React.createElement(Ink.Text, null, node_list.map((text, j) => {
15
+ return (React.createElement(React.Fragment, { key: j },
16
+ React.createElement(Ink.Text, null, text)));
17
+ }))));
18
+ })));
9
19
  }
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import * as Ink from "ink";
5
+ import { cli } from "../core/cli.js";
6
+ import { colors } from "../core/colors.js";
7
+ import { invariant } from "../core/invariant.js";
8
+ import { Await } from "./Await.js";
9
+ import { Store } from "./Store.js";
10
+ import { YesNoPrompt } from "./YesNoPrompt.js";
11
+ function reducer(state, patch) {
12
+ return { ...state, ...patch };
13
+ }
14
+ export function RebaseCheck(props) {
15
+ const actions = Store.useActions();
16
+ const argv = Store.useState((state) => state.argv);
17
+ invariant(argv, "argv must exist");
18
+ const [state, patch] = React.useReducer(reducer, {
19
+ status: "init",
20
+ });
21
+ switch (state.status) {
22
+ case "done":
23
+ return props.children;
24
+ case "prompt":
25
+ return (React.createElement(YesNoPrompt, { message: React.createElement(Ink.Text, { color: colors.yellow }, "Rebase detected, would you like to abort it?"), onYes: async () => {
26
+ await cli(`git rebase --abort`);
27
+ patch({ status: "done" });
28
+ }, onNo: async () => {
29
+ actions.exit(0);
30
+ } }));
31
+ default:
32
+ return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow }, "Checking for rebase..."), function: rebase_check }));
33
+ }
34
+ async function rebase_check() {
35
+ const actions = Store.getState().actions;
36
+ const argv = Store.getState().argv;
37
+ invariant(argv, "argv must exist");
38
+ try {
39
+ const repo_root = (await cli(`git rev-parse --absolute-git-dir`)).stdout;
40
+ let is_rebase = false;
41
+ is_rebase ||= fs.existsSync(path.join(repo_root, "rebase-apply"));
42
+ is_rebase ||= fs.existsSync(path.join(repo_root, "rebase-merge"));
43
+ const status = is_rebase ? "prompt" : "done";
44
+ patch({ status });
45
+ }
46
+ catch (err) {
47
+ actions.error("Unable to check for rebase.");
48
+ if (err instanceof Error) {
49
+ if (actions.isDebug()) {
50
+ actions.error(err.message);
51
+ }
52
+ }
53
+ actions.exit(9);
54
+ }
55
+ }
56
+ }
package/dist/app/Store.js CHANGED
@@ -18,11 +18,13 @@ const BaseStore = createStore()(immer((set, get) => ({
18
18
  commit_map: null,
19
19
  step: "loading",
20
20
  output: [],
21
+ pending_output: {},
21
22
  pr: {},
22
23
  actions: {
23
24
  exit(code, clear = true) {
24
25
  set((state) => {
25
- state.mutate.output(state, React.createElement(Exit, { clear: clear, code: code }));
26
+ const node = React.createElement(Exit, { clear: clear, code: code });
27
+ state.mutate.output(state, { node });
26
28
  });
27
29
  },
28
30
  clear() {
@@ -33,28 +35,37 @@ const BaseStore = createStore()(immer((set, get) => ({
33
35
  },
34
36
  newline() {
35
37
  set((state) => {
36
- state.mutate.output(state, "‎");
38
+ const node = "‎";
39
+ state.mutate.output(state, { node });
37
40
  });
38
41
  },
39
42
  json(value) {
40
43
  set((state) => {
41
- state.mutate.output(state, JSON.stringify(value, null, 2));
44
+ const node = JSON.stringify(value, null, 2);
45
+ state.mutate.output(state, { node });
42
46
  });
43
47
  },
44
48
  error(message) {
45
49
  set((state) => {
46
- state.mutate.output(state, React.createElement(Ink.Text, { color: colors.red }, message));
50
+ const node = React.createElement(Ink.Text, { color: colors.red }, message);
51
+ state.mutate.output(state, { node });
47
52
  });
48
53
  },
49
54
  output(node) {
50
55
  set((state) => {
51
- state.mutate.output(state, node);
56
+ state.mutate.output(state, { node });
52
57
  });
53
58
  },
54
- debug(node) {
59
+ debug(node, id) {
55
60
  if (get().actions.isDebug()) {
61
+ const debug = true;
56
62
  set((state) => {
57
- state.mutate.output(state, React.createElement(Ink.Text, { dimColor: true }, node));
63
+ if (id) {
64
+ state.mutate.pending_output(state, { id, node, debug });
65
+ }
66
+ else {
67
+ state.mutate.output(state, { node, debug });
68
+ }
58
69
  });
59
70
  }
60
71
  },
@@ -74,15 +85,36 @@ const BaseStore = createStore()(immer((set, get) => ({
74
85
  },
75
86
  },
76
87
  mutate: {
77
- output(state, node) {
78
- switch (typeof node) {
88
+ output(state, args) {
89
+ switch (typeof args.node) {
79
90
  case "boolean":
80
91
  case "number":
81
92
  case "string":
82
- state.output.push(React.createElement(Ink.Text, null, String(node)));
93
+ state.output.push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
83
94
  return;
84
95
  }
85
- state.output.push(node);
96
+ state.output.push(args.node);
97
+ },
98
+ pending_output(state, args) {
99
+ const { id } = args;
100
+ if (!id) {
101
+ return;
102
+ }
103
+ if (!state.pending_output[id]) {
104
+ state.pending_output[id] = [];
105
+ }
106
+ switch (typeof args.node) {
107
+ case "boolean":
108
+ case "number":
109
+ case "string": {
110
+ state.pending_output[id].push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
111
+ return;
112
+ }
113
+ }
114
+ state.pending_output[id].push(args.node);
115
+ },
116
+ end_pending_output(state, id) {
117
+ delete state.pending_output[id];
86
118
  },
87
119
  },
88
120
  select: {
package/dist/command.js CHANGED
@@ -26,6 +26,12 @@ export async function command() {
26
26
  alias: ["v"],
27
27
  default: false,
28
28
  description: "Print more detailed logs for debugging internals",
29
+ })
30
+ .option("update", {
31
+ type: "boolean",
32
+ alias: ["u"],
33
+ default: false,
34
+ description: "Check for updates",
29
35
  })
30
36
  .option("branch", {
31
37
  type: "string",
@@ -1,12 +1,17 @@
1
1
  export function write(args) {
2
2
  const stack_list = [];
3
- for (const pr_url of args.pr_url_list) {
3
+ const digits = String(args.pr_url_list.length).length;
4
+ for (let i = 0; i < args.pr_url_list.length; i++) {
5
+ const pr_url = args.pr_url_list[i];
4
6
  if (pr_url) {
5
7
  const selected = args.selected_url === pr_url;
6
8
  const icon = selected ? "👉" : "⏳";
7
- stack_list.push(`- ${icon} ${pr_url}`);
9
+ const num = String(i + 1).padStart(digits, "0");
10
+ stack_list.push(`- ${icon} \`${num}\` ${pr_url}`);
8
11
  }
9
12
  }
13
+ // reverse order of pr list to match the order of git stack
14
+ stack_list.reverse();
10
15
  let stack_table;
11
16
  if (stack_list.length > 1) {
12
17
  stack_table = TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
package/dist/core/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as child from "node:child_process";
2
2
  import { Store } from "../app/Store.js";
3
+ let i = 0;
3
4
  export async function cli(unsafe_command, unsafe_options) {
4
- const state = Store.getState();
5
5
  const options = Object.assign({}, unsafe_options);
6
+ const state = Store.getState();
6
7
  let command;
7
8
  if (Array.isArray(unsafe_command)) {
8
9
  command = unsafe_command.join(" ");
@@ -15,28 +16,38 @@ export async function cli(unsafe_command, unsafe_options) {
15
16
  let stdout = "";
16
17
  let stderr = "";
17
18
  let output = "";
19
+ const id = `${++i}-${command}`;
20
+ state.actions.debug(`[start] ${command}`);
21
+ state.actions.debug(`[start] ${command}\n`, id);
22
+ function write_output(value) {
23
+ output += value;
24
+ state.actions.debug(value, id);
25
+ }
18
26
  childProcess.stdout?.on("data", (data) => {
19
- stdout += data.toString();
20
- output += data.toString();
27
+ const value = String(data);
28
+ stdout += value;
29
+ write_output(value);
21
30
  });
22
31
  childProcess.stderr?.on("data", (data) => {
23
- stderr += data.toString();
24
- output += data.toString();
32
+ const value = String(data);
33
+ stderr += value;
34
+ write_output(value);
25
35
  });
26
- childProcess.on("close", (code) => {
27
- if (!options.ignoreExitCode && code !== 0) {
28
- reject(new Error(`[${command}] (${code})`));
36
+ childProcess.on("close", (unsafe_code) => {
37
+ const result = {
38
+ command,
39
+ code: unsafe_code || 0,
40
+ stdout: stdout.trimEnd(),
41
+ stderr: stderr.trimEnd(),
42
+ output: output.trimEnd(),
43
+ };
44
+ state.actions.set((state) => state.mutate.end_pending_output(state, id));
45
+ state.actions.debug(`[end] ${command} (${result.code})`);
46
+ state.actions.debug(result.output);
47
+ if (!options.ignoreExitCode && result.code !== 0) {
48
+ reject(new Error(`[${command}] (${result.code})`));
29
49
  }
30
50
  else {
31
- const result = {
32
- command,
33
- code: code || 0,
34
- stdout: stdout.trimEnd(),
35
- stderr: stderr.trimEnd(),
36
- output: output.trimEnd(),
37
- };
38
- state.actions.debug(`$ ${command}`);
39
- state.actions.debug(result.output);
40
51
  resolve(result);
41
52
  }
42
53
  });
@@ -45,12 +56,31 @@ export async function cli(unsafe_command, unsafe_options) {
45
56
  });
46
57
  });
47
58
  }
48
- cli.sync = function cli_sync(command, unsafe_options) {
59
+ cli.sync = function cli_sync(unsafe_command, unsafe_options) {
49
60
  const options = Object.assign({}, unsafe_options);
61
+ const state = Store.getState();
62
+ let command;
63
+ if (Array.isArray(unsafe_command)) {
64
+ command = unsafe_command.join(" ");
65
+ }
66
+ else {
67
+ command = unsafe_command;
68
+ }
69
+ state.actions.debug(`[start] ${command}`);
50
70
  const spawn_return = child.spawnSync("sh", ["-c", command], options);
51
71
  const stdout = String(spawn_return.stdout);
52
72
  const stderr = String(spawn_return.stderr);
53
- const output = String(spawn_return.output);
54
- const code = spawn_return.status || 0;
55
- return { command, code, stdout, stderr, output };
73
+ const result = {
74
+ command,
75
+ code: spawn_return.status || 0,
76
+ stdout,
77
+ stderr,
78
+ output: [stdout, stderr].join(""),
79
+ };
80
+ state.actions.debug(`[end] ${command} (${result.code})`);
81
+ state.actions.debug(result.output);
82
+ if (!options.ignoreExitCode && result.code !== 0) {
83
+ throw new Error(`[${command}] (${result.code})`);
84
+ }
85
+ return result;
56
86
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",