git-stack-cli 2.6.1 → 2.7.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/README.md CHANGED
@@ -58,6 +58,15 @@ git stack --no-verify # skip git hooks such as pre-commit and pre-push
58
58
  git stack help # print a table of all CLI arguments
59
59
  ```
60
60
 
61
+ ### Adding new commits and pull requests
62
+
63
+ Adding commits to a PR is as simple as running `git stack` and adding commits to a PR.
64
+
65
+ 1. Run `git stack`
66
+ 1. Press `c` to create a new PR
67
+ 1. Use `↑↓` and `space`/`enter` to navigate and assign commits to the PR
68
+ 1. Press `s` to sync created PRs and commits to Github
69
+
61
70
  ### Editing existing commits and pull requests
62
71
 
63
72
  Sometimes you want to add changes to an existing commit or pull request.
@@ -87,19 +96,29 @@ To update your local branch with the latest changes in the remote branch (e.g. `
87
96
  git stack rebase
88
97
  ```
89
98
 
90
- ### Customizing branch name
99
+ ### Branch names
100
+
101
+ By default `git stack` generates a unique identifier and appends it to your local branch name.
91
102
 
92
- By default `git stack` generates a unique branch name such as `gs-3cmrMBSUj`.
103
+ For example, if you are locally working in `dev/feature-a`, PRs branch names will look like `dev/feature-a-3cmrMBSUj`.
93
104
 
94
- You can specify a prefix for the generated branch name by using either the environment variable or the command line option.
95
- In the example below branches would be generated such as `dev/magus/gs-3cmrMBSUj`.
105
+ ### Overriding defaults
106
+
107
+ `git stack` has many optional flags to control its behavior.
108
+ You can view them by running `git stack help` and set them as needed.
109
+
110
+ If you prefer to set them once in your environment, you can use the command below to generate a one-time configuration.
96
111
 
97
112
  ```bash
98
- GIT_STACK_BRANCH_PREFIX="dev/magus/" git stack
113
+ git stack config --draft --force
99
114
 
100
- git stack --branch-prefix="dev/magus/"
115
+ Add the line below to your shell rc file (.zshrc, .bashrc, etc.)
116
+
117
+ export GIT_STACK_CONFIG="{\"draft\":true,\"force\":true}"
101
118
  ```
102
119
 
120
+ Copy this environment variable into your shell configuration, e.g. `.zshrc`, `.bashrc`, etc.
121
+
103
122
  ## Why?
104
123
 
105
124
  The goal of `git stack` is to combine the **simplicity of developing in a single branch** in order to **preserve your commit history** while also **grouping commits into pull requests for code review**.
package/dist/js/index.js CHANGED
@@ -28098,7 +28098,7 @@ var require_last = __commonJS((exports, module) => {
28098
28098
  });
28099
28099
 
28100
28100
  // src/index.tsx
28101
- var React56 = __toESM(require_react(), 1);
28101
+ var React57 = __toESM(require_react(), 1);
28102
28102
  import fs12 from "node:fs/promises";
28103
28103
  import path9 from "node:path";
28104
28104
 
@@ -31935,7 +31935,7 @@ var import_react20 = __toESM(require_react(), 1);
31935
31935
  // 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/hooks/use-focus-manager.js
31936
31936
  var import_react21 = __toESM(require_react(), 1);
31937
31937
  // src/app/App.tsx
31938
- var React55 = __toESM(require_react(), 1);
31938
+ var React56 = __toESM(require_react(), 1);
31939
31939
 
31940
31940
  // src/app/AutoUpdate.tsx
31941
31941
  var React18 = __toESM(require_react(), 1);
@@ -37467,7 +37467,7 @@ function AutoUpdate(props) {
37467
37467
  let status2 = "init";
37468
37468
  let latest_version = null;
37469
37469
  let is_brew_bun_standalone = false;
37470
- const local_version = "2.6.1";
37470
+ const local_version = "2.7.0";
37471
37471
  const is_output = props_ref.current.verbose || props_ref.current.force;
37472
37472
  async function auto_update() {
37473
37473
  if (!local_version) {
@@ -39844,11 +39844,6 @@ function get_value(props) {
39844
39844
  return props.value || props.defaultValue || "";
39845
39845
  }
39846
39846
 
39847
- // src/core/gs_short_id.ts
39848
- function gs_short_id() {
39849
- return `gs-${short_id()}`;
39850
- }
39851
-
39852
39847
  // src/app/SelectCommitRanges.tsx
39853
39848
  function SelectCommitRanges() {
39854
39849
  const commit_range = Store.useState((state) => state.commit_range);
@@ -39860,6 +39855,8 @@ function SelectCommitRanges() {
39860
39855
  function SelectCommitRangesInternal(props) {
39861
39856
  const actions = Store.useActions();
39862
39857
  const argv = Store.useState((state) => state.argv);
39858
+ const branch_name = Store.useState((state) => state.branch_name);
39859
+ invariant(branch_name, "branch_name must exist");
39863
39860
  const [selected_group_id, set_selected_group_id] = React42.useState(() => {
39864
39861
  const first_group = props.commit_range.group_list.find((g2) => g2.id !== props.commit_range.UNASSIGNED);
39865
39862
  if (first_group) {
@@ -40155,13 +40152,7 @@ function SelectCommitRangesInternal(props) {
40155
40152
  }
40156
40153
  })));
40157
40154
  function get_group_id() {
40158
- let branch_prefix = "";
40159
- if (argv["branch-prefix"]) {
40160
- branch_prefix = argv["branch-prefix"];
40161
- } else if (process.env.GIT_STACK_BRANCH_PREFIX) {
40162
- branch_prefix = process.env.GIT_STACK_BRANCH_PREFIX;
40163
- }
40164
- return `${branch_prefix}${gs_short_id()}`;
40155
+ return `${branch_name}-${short_id()}`;
40165
40156
  }
40166
40157
  function submit_group_input(title) {
40167
40158
  const id = get_group_id();
@@ -40674,260 +40665,8 @@ async function run11() {
40674
40665
  }
40675
40666
  }
40676
40667
 
40677
- // src/commands/Fixup.tsx
40668
+ // src/commands/Config.tsx
40678
40669
  var React50 = __toESM(require_react(), 1);
40679
- function Fixup() {
40680
- return /* @__PURE__ */ React50.createElement(Await, {
40681
- fallback: null,
40682
- function: run12
40683
- });
40684
- }
40685
- async function run12() {
40686
- const state = Store.getState();
40687
- const actions = state.actions;
40688
- const argv = state.argv;
40689
- const relative_number = argv.commit;
40690
- if (!relative_number) {
40691
- actions.output(/* @__PURE__ */ React50.createElement(Text, {
40692
- color: colors.red
40693
- }, "❗️ Usage: git fixup ", "<relative-commit-number>"));
40694
- actions.output("");
40695
- actions.output("This script automates the process of adding staged changes as a fixup commit");
40696
- actions.output("and the subsequent git rebase to flatten the commits based on relative commit number");
40697
- actions.output("You can use a `git log` like below to get the relative commit number");
40698
- actions.output("");
40699
- actions.output(" ❯ git stack log");
40700
- actions.output(" 1\te329794d5f881cbf0fc3f26d2108cf6f3fdebabe enable drop_error_subtask test param");
40701
- actions.output(" 2\t57f43b596e5c6b97bc47e2a591f82ccc81651156 test drop_error_subtask baseline");
40702
- actions.output(" 3\t838e878d483c6a2d5393063fc59baf2407225c6d ErrorSubtask test baseline");
40703
- actions.output("");
40704
- actions.output("To target `838e87` above, you would call `fixup 3`");
40705
- actions.exit(0);
40706
- }
40707
- const diff_staged_cmd = await cli("git diff --cached --quiet", {
40708
- ignoreExitCode: true
40709
- });
40710
- if (!diff_staged_cmd.code) {
40711
- actions.error("\uD83D\uDEA8 Stage changes before calling fixup");
40712
- actions.exit(1);
40713
- }
40714
- const adjusted_number = Number(relative_number) - 1;
40715
- const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
40716
- actions.output(/* @__PURE__ */ React50.createElement(FormatText, {
40717
- wrapper: /* @__PURE__ */ React50.createElement(Text, {
40718
- color: colors.yellow
40719
- }),
40720
- message: "\uD83D\uDEE0️ fixup {relative_number} {commit_sha}",
40721
- values: {
40722
- commit_sha: /* @__PURE__ */ React50.createElement(Parens, null, commit_sha),
40723
- relative_number
40724
- }
40725
- }));
40726
- await cli(`git commit --fixup ${commit_sha}`);
40727
- let save_stash = false;
40728
- const diff_cmd = await cli("git diff-index --quiet HEAD --", {
40729
- ignoreExitCode: true
40730
- });
40731
- if (diff_cmd.code) {
40732
- save_stash = true;
40733
- await cli("git stash --include-untracked");
40734
- actions.output(/* @__PURE__ */ React50.createElement(Text, {
40735
- color: colors.yellow
40736
- }, /* @__PURE__ */ React50.createElement(FormatText, {
40737
- message: "\uD83D\uDCE6 Changes saved to stash"
40738
- })));
40739
- }
40740
- try {
40741
- const rebase_target = Number(relative_number) + 1;
40742
- await cli(`git rebase -i --autosquash HEAD~${rebase_target}`, {
40743
- env: {
40744
- ...process.env,
40745
- GIT_EDITOR: "true"
40746
- }
40747
- });
40748
- } catch (error) {
40749
- actions.error("\uD83D\uDEA8 Fixup failed");
40750
- await cli("git rebase --abort");
40751
- await cli("git reset --soft HEAD~1");
40752
- } finally {
40753
- if (save_stash) {
40754
- await cli("git stash pop");
40755
- actions.output(/* @__PURE__ */ React50.createElement(Text, {
40756
- color: colors.green
40757
- }, "✅ Changes restored from stash"));
40758
- }
40759
- }
40760
- actions.exit(0);
40761
- }
40762
-
40763
- // src/commands/Log.tsx
40764
- var React51 = __toESM(require_react(), 1);
40765
- function Log() {
40766
- const { stdout } = use_stdout_default();
40767
- const available_width = stdout.columns || 80;
40768
- return /* @__PURE__ */ React51.createElement(Await, {
40769
- fallback: null,
40770
- function: () => run13({ available_width })
40771
- });
40772
- }
40773
- async function run13(args) {
40774
- const state = Store.getState();
40775
- const actions = state.actions;
40776
- const process_argv = state.process_argv;
40777
- invariant(actions, "actions must exist");
40778
- const color_buffer = 12 * 5;
40779
- const truncation_width = args.available_width + color_buffer;
40780
- const short_sha = (await cli(`git log -1 --format=%h`)).stdout.trim();
40781
- const short_sha_length = short_sha.length + 1;
40782
- const sha_format = `%C(green)%<(${short_sha_length},trunc)%h`;
40783
- const date_format = `%C(white)%<(15,trunc)%cr`;
40784
- const author_format = `%C(white)%<(8,trunc)%al`;
40785
- const decoration_format = `%C(auto)%d`;
40786
- const subject_format = `%<(60,trunc)%s`;
40787
- const format = [sha_format, date_format, author_format, decoration_format, subject_format].join(" ");
40788
- const rest_args = process_argv.slice(3).join(" ");
40789
- const command = [
40790
- `git log --pretty=format:"${format}" -n20 --graph --color ${rest_args}`,
40791
- `cut -c 1-"${truncation_width}"`,
40792
- `nl -w3 -s' '`
40793
- ].join(" | ");
40794
- const result = await cli(command);
40795
- actions.output(result.stdout);
40796
- actions.exit(0);
40797
- }
40798
-
40799
- // src/commands/Update.tsx
40800
- var React52 = __toESM(require_react(), 1);
40801
- function Update() {
40802
- return /* @__PURE__ */ React52.createElement(Await, {
40803
- fallback: null,
40804
- function: run14
40805
- });
40806
- }
40807
- async function run14() {
40808
- const state = Store.getState();
40809
- const actions = state.actions;
40810
- actions.exit(0);
40811
- }
40812
-
40813
- // src/components/ErrorBoundary.tsx
40814
- var React53 = __toESM(require_react(), 1);
40815
- class ErrorBoundary extends React53.Component {
40816
- constructor(props) {
40817
- super(props);
40818
- this.state = {
40819
- error: null,
40820
- component_stack: ""
40821
- };
40822
- }
40823
- static getDerivedStateFromError(error) {
40824
- return { error };
40825
- }
40826
- componentDidCatch(_error, error_info) {
40827
- let component_stack = error_info.componentStack;
40828
- if (component_stack) {
40829
- component_stack = component_stack.split(`
40830
- `).slice(1).join(`
40831
- `);
40832
- this.setState({ component_stack }, async () => {
40833
- await Exit.handle_exit({ code: 30, clear: true });
40834
- });
40835
- }
40836
- }
40837
- render() {
40838
- if (!this.state.error) {
40839
- return this.props.children;
40840
- }
40841
- const message = this.state.error.message;
40842
- return /* @__PURE__ */ React53.createElement(Box_default, {
40843
- flexDirection: "column",
40844
- gap: 0
40845
- }, /* @__PURE__ */ React53.createElement(Text, {
40846
- color: colors.red
40847
- }, /* @__PURE__ */ React53.createElement(FormatText, {
40848
- message: "\uD83D\uDEA8 Unhandled error {message}",
40849
- values: {
40850
- message: /* @__PURE__ */ React53.createElement(Text, {
40851
- color: colors.gray
40852
- }, message)
40853
- }
40854
- })), this._render_verbose());
40855
- }
40856
- _render_verbose() {
40857
- const store_state = Store.getState();
40858
- if (store_state.argv.verbose) {
40859
- return /* @__PURE__ */ React53.createElement(Text, {
40860
- color: colors.gray
40861
- }, this.state.component_stack);
40862
- }
40863
- return /* @__PURE__ */ React53.createElement(Text, {
40864
- color: colors.gray
40865
- }, /* @__PURE__ */ React53.createElement(FormatText, {
40866
- message: "Try again with `--verbose` to see more information."
40867
- }));
40868
- }
40869
- }
40870
-
40871
- // src/components/ExitingGate.tsx
40872
- var React54 = __toESM(require_react(), 1);
40873
- function ExitingGate(props) {
40874
- const exit_mode = Store.useState((state) => state.exit_mode);
40875
- if (!exit_mode) {
40876
- return props.children;
40877
- }
40878
- switch (exit_mode) {
40879
- case "quiet":
40880
- return null;
40881
- case "normal":
40882
- return /* @__PURE__ */ React54.createElement(Box_default, {
40883
- flexDirection: "column"
40884
- }, /* @__PURE__ */ React54.createElement(Text, {
40885
- color: colors.red
40886
- }, /* @__PURE__ */ React54.createElement(FormatText, {
40887
- message: "\uD83D\uDEA8 Exiting…"
40888
- })));
40889
- default:
40890
- return null;
40891
- }
40892
- }
40893
-
40894
- // src/app/App.tsx
40895
- function App2() {
40896
- const actions = Store.useActions();
40897
- const ink = Store.useState((state) => state.ink);
40898
- const argv = Store.useState((state) => state.argv);
40899
- if (!ink || !argv || !argv.$0) {
40900
- return null;
40901
- }
40902
- const positional_list = new Set(argv["_"]);
40903
- const is_update = positional_list.has("update") || positional_list.has("upgrade");
40904
- return /* @__PURE__ */ React55.createElement(Providers, null, /* @__PURE__ */ React55.createElement(ErrorBoundary, null, /* @__PURE__ */ React55.createElement(Debug, null), /* @__PURE__ */ React55.createElement(Output2, null), /* @__PURE__ */ React55.createElement(ExitingGate, null, /* @__PURE__ */ React55.createElement(AutoUpdate, {
40905
- name: "git-stack-cli",
40906
- verbose: argv.verbose,
40907
- force: is_update,
40908
- timeoutMs: is_update ? 30 * 1000 : 2 * 1000,
40909
- onOutput: actions.output,
40910
- onDone: () => {
40911
- if (is_update) {
40912
- actions.exit(0);
40913
- }
40914
- }
40915
- }, /* @__PURE__ */ React55.createElement(VerboseDebugInfo, null, /* @__PURE__ */ React55.createElement(RebaseCheck, null, /* @__PURE__ */ React55.createElement(CherryPickCheck, null, /* @__PURE__ */ React55.createElement(MaybeMain, null))))), /* @__PURE__ */ React55.createElement(HandleCtrlCSigint, null))));
40916
- }
40917
- function MaybeMain() {
40918
- const argv = Store.useState((state) => state.argv);
40919
- const positional_list = new Set(argv["_"]);
40920
- if (positional_list.has("fixup")) {
40921
- return /* @__PURE__ */ React55.createElement(Fixup, null);
40922
- } else if (positional_list.has("log")) {
40923
- return /* @__PURE__ */ React55.createElement(Log, null);
40924
- } else if (positional_list.has("update")) {
40925
- return /* @__PURE__ */ React55.createElement(Update, null);
40926
- } else if (positional_list.has("rebase")) {
40927
- return /* @__PURE__ */ React55.createElement(DependencyCheck, null, /* @__PURE__ */ React55.createElement(DirtyCheck, null, /* @__PURE__ */ React55.createElement(GatherMetadata, null, /* @__PURE__ */ React55.createElement(LocalCommitStatus, null, /* @__PURE__ */ React55.createElement(Rebase, null)))));
40928
- }
40929
- return /* @__PURE__ */ React55.createElement(React55.Fragment, null, !argv.verbose ? null : /* @__PURE__ */ React55.createElement(GithubApiError, null), /* @__PURE__ */ React55.createElement(DependencyCheck, null, /* @__PURE__ */ React55.createElement(DirtyCheck, null, /* @__PURE__ */ React55.createElement(GatherMetadata, null, /* @__PURE__ */ React55.createElement(RequireBranch, null, /* @__PURE__ */ React55.createElement(LocalCommitStatus, null, /* @__PURE__ */ React55.createElement(DetectInitialPR, null, /* @__PURE__ */ React55.createElement(Main, null))))))));
40930
- }
40931
40670
 
40932
40671
  // node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/lib/platform-shims/esm.mjs
40933
40672
  import { notStrictEqual, strictEqual } from "assert";
@@ -45790,8 +45529,17 @@ var Yargs = YargsFactory(esm_default);
45790
45529
  var yargs_default = Yargs;
45791
45530
 
45792
45531
  // src/command.ts
45793
- async function command2() {
45794
- return yargs_default(hideBin(process.argv)).scriptName("git stack").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).command(["update", "upgrade"], "Check and install the latest version of git stack", (yargs) => yargs).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.6.1").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`").argv;
45532
+ async function command2(argv, options = {}) {
45533
+ let builder = yargs_default(hideBin(argv));
45534
+ if (options.parserConfiguration) {
45535
+ builder = builder.parserConfiguration(options.parserConfiguration);
45536
+ }
45537
+ if (options.env_config) {
45538
+ builder = builder.config(options.env_config);
45539
+ }
45540
+ const parsed = await builder.scriptName("git stack").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).command(["update", "upgrade"], "Check and install the latest version of git stack", (yargs) => yargs).command("config", "Generate a one-time configuration json based on the passed arguments", (yargs) => yargs.options(DefaultOptions)).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.7.0").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`");
45541
+ const result = parsed.argv;
45542
+ return result;
45795
45543
  }
45796
45544
  var GlobalOptions = {
45797
45545
  verbose: {
@@ -45844,11 +45592,6 @@ var DefaultOptions = {
45844
45592
  default: false,
45845
45593
  description: "Open all PRs as drafts"
45846
45594
  },
45847
- "branch-prefix": {
45848
- type: "string",
45849
- default: "",
45850
- description: "Prefix for generated branch names, e.g. dev/magus/"
45851
- },
45852
45595
  "revise-sign": {
45853
45596
  type: "boolean",
45854
45597
  default: true,
@@ -45888,6 +45631,353 @@ var FixupOptions = {
45888
45631
  }
45889
45632
  };
45890
45633
 
45634
+ // src/commands/Config.tsx
45635
+ function Config() {
45636
+ return /* @__PURE__ */ React50.createElement(Await, {
45637
+ fallback: null,
45638
+ function: run12
45639
+ });
45640
+ async function run12() {
45641
+ const state = Store.getState();
45642
+ const actions = state.actions;
45643
+ const config = await get_explicit_args();
45644
+ const config_json = JSON.stringify(config).replace(/"/g, "\\\"");
45645
+ actions.output(/* @__PURE__ */ React50.createElement(Box_default, {
45646
+ flexDirection: "column",
45647
+ gap: 1,
45648
+ paddingTop: 1
45649
+ }, /* @__PURE__ */ React50.createElement(Text, null), /* @__PURE__ */ React50.createElement(FormatText, {
45650
+ message: "Add the line below to your shell rc file ({zshrc}, {bashrc}, etc.)",
45651
+ values: {
45652
+ zshrc: /* @__PURE__ */ React50.createElement(Text, {
45653
+ color: colors.gray
45654
+ }, ".zshrc"),
45655
+ bashrc: /* @__PURE__ */ React50.createElement(Text, {
45656
+ color: colors.gray
45657
+ }, ".bashrc")
45658
+ }
45659
+ }), /* @__PURE__ */ React50.createElement(FormatText, {
45660
+ message: `{export} {ENV_VAR}{e}{q}{config_json}{q}`,
45661
+ values: {
45662
+ export: /* @__PURE__ */ React50.createElement(Text, {
45663
+ color: colors.purple
45664
+ }, "export"),
45665
+ ENV_VAR: /* @__PURE__ */ React50.createElement(Text, {
45666
+ color: colors.yellow
45667
+ }, ENV_VAR),
45668
+ e: /* @__PURE__ */ React50.createElement(Text, {
45669
+ color: colors.purple
45670
+ }, "="),
45671
+ q: /* @__PURE__ */ React50.createElement(Text, {
45672
+ color: colors.white
45673
+ }, '"'),
45674
+ config_json: /* @__PURE__ */ React50.createElement(Text, {
45675
+ color: colors.green
45676
+ }, config_json)
45677
+ }
45678
+ })));
45679
+ actions.exit(0);
45680
+ }
45681
+ }
45682
+ async function argv_with_config_from_env() {
45683
+ if (!process.env.GIT_STACK_CONFIG) {
45684
+ return await command2(process.argv);
45685
+ }
45686
+ const env_config = parse_env_config();
45687
+ return await command2(process.argv, { env_config });
45688
+ }
45689
+ function parse_env_config() {
45690
+ const GIT_STACK_CONFIG = process.env.GIT_STACK_CONFIG;
45691
+ invariant(GIT_STACK_CONFIG, "GIT_STACK_CONFIG must exist");
45692
+ try {
45693
+ const env_config = JSON.parse(GIT_STACK_CONFIG);
45694
+ return env_config;
45695
+ } catch (error) {
45696
+ console.error(`ERROR GIT_STACK_CONFIG=${GIT_STACK_CONFIG}`);
45697
+ console.error("ERROR GIT_STACK_CONFIG environment variable is not valid JSON");
45698
+ process.exit(18);
45699
+ }
45700
+ }
45701
+ async function get_explicit_args() {
45702
+ const default_argv = await command2(["git", "stack"], COMMAND_OPTIONS);
45703
+ const state_argv = await command2(process.argv, COMMAND_OPTIONS);
45704
+ const config = {};
45705
+ for (const key of Object.keys(state_argv)) {
45706
+ if (key === "_" || key === "$0")
45707
+ continue;
45708
+ const state_value = state_argv[key];
45709
+ const default_value = default_argv[key];
45710
+ const is_set = default_value !== state_value;
45711
+ if (is_set) {
45712
+ config[key] = state_value;
45713
+ }
45714
+ }
45715
+ return config;
45716
+ }
45717
+ var ENV_VAR = "GIT_STACK_CONFIG";
45718
+ var COMMAND_OPTIONS = {
45719
+ parserConfiguration: {
45720
+ "strip-aliased": true
45721
+ }
45722
+ };
45723
+
45724
+ // src/commands/Fixup.tsx
45725
+ var React51 = __toESM(require_react(), 1);
45726
+ function Fixup() {
45727
+ return /* @__PURE__ */ React51.createElement(Await, {
45728
+ fallback: null,
45729
+ function: run12
45730
+ });
45731
+ }
45732
+ async function run12() {
45733
+ const state = Store.getState();
45734
+ const actions = state.actions;
45735
+ const argv = state.argv;
45736
+ const relative_number = argv.commit;
45737
+ if (!relative_number) {
45738
+ actions.output(/* @__PURE__ */ React51.createElement(Text, {
45739
+ color: colors.red
45740
+ }, "❗️ Usage: git fixup ", "<relative-commit-number>"));
45741
+ actions.output("");
45742
+ actions.output("This script automates the process of adding staged changes as a fixup commit");
45743
+ actions.output("and the subsequent git rebase to flatten the commits based on relative commit number");
45744
+ actions.output("You can use a `git log` like below to get the relative commit number");
45745
+ actions.output("");
45746
+ actions.output(" ❯ git stack log");
45747
+ actions.output(" 1\te329794d5f881cbf0fc3f26d2108cf6f3fdebabe enable drop_error_subtask test param");
45748
+ actions.output(" 2\t57f43b596e5c6b97bc47e2a591f82ccc81651156 test drop_error_subtask baseline");
45749
+ actions.output(" 3\t838e878d483c6a2d5393063fc59baf2407225c6d ErrorSubtask test baseline");
45750
+ actions.output("");
45751
+ actions.output("To target `838e87` above, you would call `fixup 3`");
45752
+ actions.exit(0);
45753
+ }
45754
+ const diff_staged_cmd = await cli("git diff --cached --quiet", {
45755
+ ignoreExitCode: true
45756
+ });
45757
+ if (!diff_staged_cmd.code) {
45758
+ actions.error("\uD83D\uDEA8 Stage changes before calling fixup");
45759
+ actions.exit(1);
45760
+ }
45761
+ const adjusted_number = Number(relative_number) - 1;
45762
+ const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
45763
+ actions.output(/* @__PURE__ */ React51.createElement(FormatText, {
45764
+ wrapper: /* @__PURE__ */ React51.createElement(Text, {
45765
+ color: colors.yellow
45766
+ }),
45767
+ message: "\uD83D\uDEE0️ fixup {relative_number} {commit_sha}",
45768
+ values: {
45769
+ commit_sha: /* @__PURE__ */ React51.createElement(Parens, null, commit_sha),
45770
+ relative_number
45771
+ }
45772
+ }));
45773
+ await cli(`git commit --fixup ${commit_sha}`);
45774
+ let save_stash = false;
45775
+ const diff_cmd = await cli("git diff-index --quiet HEAD --", {
45776
+ ignoreExitCode: true
45777
+ });
45778
+ if (diff_cmd.code) {
45779
+ save_stash = true;
45780
+ await cli("git stash --include-untracked");
45781
+ actions.output(/* @__PURE__ */ React51.createElement(Text, {
45782
+ color: colors.yellow
45783
+ }, /* @__PURE__ */ React51.createElement(FormatText, {
45784
+ message: "\uD83D\uDCE6 Changes saved to stash"
45785
+ })));
45786
+ }
45787
+ try {
45788
+ const rebase_target = Number(relative_number) + 1;
45789
+ await cli(`git rebase -i --autosquash HEAD~${rebase_target}`, {
45790
+ env: {
45791
+ ...process.env,
45792
+ GIT_EDITOR: "true"
45793
+ }
45794
+ });
45795
+ } catch (error) {
45796
+ actions.error("\uD83D\uDEA8 Fixup failed");
45797
+ await cli("git rebase --abort");
45798
+ await cli("git reset --soft HEAD~1");
45799
+ } finally {
45800
+ if (save_stash) {
45801
+ await cli("git stash pop");
45802
+ actions.output(/* @__PURE__ */ React51.createElement(Text, {
45803
+ color: colors.green
45804
+ }, "✅ Changes restored from stash"));
45805
+ }
45806
+ }
45807
+ actions.exit(0);
45808
+ }
45809
+
45810
+ // src/commands/Log.tsx
45811
+ var React52 = __toESM(require_react(), 1);
45812
+ function Log() {
45813
+ const { stdout } = use_stdout_default();
45814
+ const available_width = stdout.columns || 80;
45815
+ return /* @__PURE__ */ React52.createElement(Await, {
45816
+ fallback: null,
45817
+ function: () => run13({ available_width })
45818
+ });
45819
+ }
45820
+ async function run13(args) {
45821
+ const state = Store.getState();
45822
+ const actions = state.actions;
45823
+ const process_argv = state.process_argv;
45824
+ invariant(actions, "actions must exist");
45825
+ const color_buffer = 12 * 5;
45826
+ const truncation_width = args.available_width + color_buffer;
45827
+ const short_sha = (await cli(`git log -1 --format=%h`)).stdout.trim();
45828
+ const short_sha_length = short_sha.length + 1;
45829
+ const sha_format = `%C(green)%<(${short_sha_length},trunc)%h`;
45830
+ const date_format = `%C(white)%<(15,trunc)%cr`;
45831
+ const author_format = `%C(white)%<(8,trunc)%al`;
45832
+ const decoration_format = `%C(auto)%d`;
45833
+ const subject_format = `%<(60,trunc)%s`;
45834
+ const format3 = [sha_format, date_format, author_format, decoration_format, subject_format].join(" ");
45835
+ const rest_args = process_argv.slice(3).join(" ");
45836
+ const command3 = [
45837
+ `git log --pretty=format:"${format3}" -n20 --graph --color ${rest_args}`,
45838
+ `cut -c 1-"${truncation_width}"`,
45839
+ `nl -w3 -s' '`
45840
+ ].join(" | ");
45841
+ const result = await cli(command3);
45842
+ actions.output(result.stdout);
45843
+ actions.exit(0);
45844
+ }
45845
+
45846
+ // src/commands/Update.tsx
45847
+ var React53 = __toESM(require_react(), 1);
45848
+ function Update() {
45849
+ return /* @__PURE__ */ React53.createElement(Await, {
45850
+ fallback: null,
45851
+ function: run14
45852
+ });
45853
+ }
45854
+ async function run14() {
45855
+ const state = Store.getState();
45856
+ const actions = state.actions;
45857
+ actions.exit(0);
45858
+ }
45859
+
45860
+ // src/components/ErrorBoundary.tsx
45861
+ var React54 = __toESM(require_react(), 1);
45862
+ class ErrorBoundary extends React54.Component {
45863
+ constructor(props) {
45864
+ super(props);
45865
+ this.state = {
45866
+ error: null,
45867
+ component_stack: ""
45868
+ };
45869
+ }
45870
+ static getDerivedStateFromError(error) {
45871
+ return { error };
45872
+ }
45873
+ componentDidCatch(_error, error_info) {
45874
+ let component_stack = error_info.componentStack;
45875
+ if (component_stack) {
45876
+ component_stack = component_stack.split(`
45877
+ `).slice(1).join(`
45878
+ `);
45879
+ this.setState({ component_stack }, async () => {
45880
+ await Exit.handle_exit({ code: 30, clear: true });
45881
+ });
45882
+ }
45883
+ }
45884
+ render() {
45885
+ if (!this.state.error) {
45886
+ return this.props.children;
45887
+ }
45888
+ const message = this.state.error.message;
45889
+ return /* @__PURE__ */ React54.createElement(Box_default, {
45890
+ flexDirection: "column",
45891
+ gap: 0
45892
+ }, /* @__PURE__ */ React54.createElement(Text, {
45893
+ color: colors.red
45894
+ }, /* @__PURE__ */ React54.createElement(FormatText, {
45895
+ message: "\uD83D\uDEA8 Unhandled error {message}",
45896
+ values: {
45897
+ message: /* @__PURE__ */ React54.createElement(Text, {
45898
+ color: colors.gray
45899
+ }, message)
45900
+ }
45901
+ })), this._render_verbose());
45902
+ }
45903
+ _render_verbose() {
45904
+ const store_state = Store.getState();
45905
+ if (store_state.argv.verbose) {
45906
+ return /* @__PURE__ */ React54.createElement(Text, {
45907
+ color: colors.gray
45908
+ }, this.state.component_stack);
45909
+ }
45910
+ return /* @__PURE__ */ React54.createElement(Text, {
45911
+ color: colors.gray
45912
+ }, /* @__PURE__ */ React54.createElement(FormatText, {
45913
+ message: "Try again with `--verbose` to see more information."
45914
+ }));
45915
+ }
45916
+ }
45917
+
45918
+ // src/components/ExitingGate.tsx
45919
+ var React55 = __toESM(require_react(), 1);
45920
+ function ExitingGate(props) {
45921
+ const exit_mode = Store.useState((state) => state.exit_mode);
45922
+ if (!exit_mode) {
45923
+ return props.children;
45924
+ }
45925
+ switch (exit_mode) {
45926
+ case "quiet":
45927
+ return null;
45928
+ case "normal":
45929
+ return /* @__PURE__ */ React55.createElement(Box_default, {
45930
+ flexDirection: "column"
45931
+ }, /* @__PURE__ */ React55.createElement(Text, {
45932
+ color: colors.red
45933
+ }, /* @__PURE__ */ React55.createElement(FormatText, {
45934
+ message: "\uD83D\uDEA8 Exiting…"
45935
+ })));
45936
+ default:
45937
+ return null;
45938
+ }
45939
+ }
45940
+
45941
+ // src/app/App.tsx
45942
+ function App2() {
45943
+ const actions = Store.useActions();
45944
+ const ink = Store.useState((state) => state.ink);
45945
+ const argv = Store.useState((state) => state.argv);
45946
+ if (!ink || !argv || !argv.$0) {
45947
+ return null;
45948
+ }
45949
+ const positional_list = new Set(argv["_"]);
45950
+ const is_update = positional_list.has("update") || positional_list.has("upgrade");
45951
+ return /* @__PURE__ */ React56.createElement(Providers, null, /* @__PURE__ */ React56.createElement(ErrorBoundary, null, /* @__PURE__ */ React56.createElement(Debug, null), /* @__PURE__ */ React56.createElement(Output2, null), /* @__PURE__ */ React56.createElement(ExitingGate, null, /* @__PURE__ */ React56.createElement(AutoUpdate, {
45952
+ name: "git-stack-cli",
45953
+ verbose: argv.verbose,
45954
+ force: is_update,
45955
+ timeoutMs: is_update ? 30 * 1000 : 2 * 1000,
45956
+ onOutput: actions.output,
45957
+ onDone: () => {
45958
+ if (is_update) {
45959
+ actions.exit(0);
45960
+ }
45961
+ }
45962
+ }, /* @__PURE__ */ React56.createElement(VerboseDebugInfo, null, /* @__PURE__ */ React56.createElement(RebaseCheck, null, /* @__PURE__ */ React56.createElement(CherryPickCheck, null, /* @__PURE__ */ React56.createElement(MaybeMain, null))))), /* @__PURE__ */ React56.createElement(HandleCtrlCSigint, null))));
45963
+ }
45964
+ function MaybeMain() {
45965
+ const argv = Store.useState((state) => state.argv);
45966
+ const positional_list = new Set(argv["_"]);
45967
+ if (positional_list.has("fixup")) {
45968
+ return /* @__PURE__ */ React56.createElement(Fixup, null);
45969
+ } else if (positional_list.has("log")) {
45970
+ return /* @__PURE__ */ React56.createElement(Log, null);
45971
+ } else if (positional_list.has("update")) {
45972
+ return /* @__PURE__ */ React56.createElement(Update, null);
45973
+ } else if (positional_list.has("config")) {
45974
+ return /* @__PURE__ */ React56.createElement(Config, null);
45975
+ } else if (positional_list.has("rebase")) {
45976
+ return /* @__PURE__ */ React56.createElement(DependencyCheck, null, /* @__PURE__ */ React56.createElement(DirtyCheck, null, /* @__PURE__ */ React56.createElement(GatherMetadata, null, /* @__PURE__ */ React56.createElement(LocalCommitStatus, null, /* @__PURE__ */ React56.createElement(Rebase, null)))));
45977
+ }
45978
+ return /* @__PURE__ */ React56.createElement(React56.Fragment, null, !argv.verbose ? null : /* @__PURE__ */ React56.createElement(GithubApiError, null), /* @__PURE__ */ React56.createElement(DependencyCheck, null, /* @__PURE__ */ React56.createElement(DirtyCheck, null, /* @__PURE__ */ React56.createElement(GatherMetadata, null, /* @__PURE__ */ React56.createElement(RequireBranch, null, /* @__PURE__ */ React56.createElement(LocalCommitStatus, null, /* @__PURE__ */ React56.createElement(DetectInitialPR, null, /* @__PURE__ */ React56.createElement(Main, null))))))));
45979
+ }
45980
+
45891
45981
  // src/index.tsx
45892
45982
  (async function main() {
45893
45983
  try {
@@ -45897,7 +45987,7 @@ var FixupOptions = {
45897
45987
  console.error("Try again with `--verbose` to see more information.");
45898
45988
  }
45899
45989
  };
45900
- const argv = await command2();
45990
+ const argv = await argv_with_config_from_env();
45901
45991
  process.stdin.resume();
45902
45992
  process.on("uncaughtException", (error) => {
45903
45993
  console.error("\uD83D\uDEA8 uncaughtException");
@@ -45913,7 +46003,7 @@ var FixupOptions = {
45913
46003
  });
45914
46004
  const tmp_dir = await get_tmp_dir();
45915
46005
  await fs12.rm(tmp_dir, { recursive: true });
45916
- const ink = render_default(/* @__PURE__ */ React56.createElement(App2, null), {
46006
+ const ink = render_default(/* @__PURE__ */ React57.createElement(App2, null), {
45917
46007
  exitOnCtrlC: false
45918
46008
  });
45919
46009
  Store.setState((state) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
package/src/app/App.tsx CHANGED
@@ -17,6 +17,7 @@ import { RebaseCheck } from "~/app/RebaseCheck";
17
17
  import { RequireBranch } from "~/app/RequireBranch";
18
18
  import { Store } from "~/app/Store";
19
19
  import { VerboseDebugInfo } from "~/app/VerboseDebugInfo";
20
+ import { Config } from "~/commands/Config";
20
21
  import { Fixup } from "~/commands/Fixup";
21
22
  import { Log } from "~/commands/Log";
22
23
  import { Rebase } from "~/commands/Rebase";
@@ -92,6 +93,8 @@ function MaybeMain() {
92
93
  return <Log />;
93
94
  } else if (positional_list.has("update")) {
94
95
  return <Update />;
96
+ } else if (positional_list.has("config")) {
97
+ return <Config />;
95
98
  } else if (positional_list.has("rebase")) {
96
99
  return (
97
100
  <DependencyCheck>
@@ -10,8 +10,8 @@ import { Parens } from "~/app/Parens";
10
10
  import { Store } from "~/app/Store";
11
11
  import { TextInput } from "~/app/TextInput";
12
12
  import { colors } from "~/core/colors";
13
- import { gs_short_id } from "~/core/gs_short_id";
14
13
  import { invariant } from "~/core/invariant";
14
+ import { short_id } from "~/core/short_id";
15
15
  import { wrap_index } from "~/core/wrap_index";
16
16
 
17
17
  import type { State } from "~/app/Store";
@@ -35,6 +35,8 @@ function SelectCommitRangesInternal(props: Props) {
35
35
  const actions = Store.useActions();
36
36
 
37
37
  const argv = Store.useState((state) => state.argv);
38
+ const branch_name = Store.useState((state) => state.branch_name);
39
+ invariant(branch_name, "branch_name must exist");
38
40
 
39
41
  const [selected_group_id, set_selected_group_id] = React.useState(() => {
40
42
  const first_group = props.commit_range.group_list.find(
@@ -444,17 +446,7 @@ function SelectCommitRangesInternal(props: Props) {
444
446
  );
445
447
 
446
448
  function get_group_id() {
447
- let branch_prefix = "";
448
-
449
- // branch prefix via cli flag or env var
450
- // cli flag takes precedence since it is more explicit
451
- if (argv["branch-prefix"]) {
452
- branch_prefix = argv["branch-prefix"];
453
- } else if (process.env.GIT_STACK_BRANCH_PREFIX) {
454
- branch_prefix = process.env.GIT_STACK_BRANCH_PREFIX;
455
- }
456
-
457
- return `${branch_prefix}${gs_short_id()}`;
449
+ return `${branch_name}-${short_id()}`;
458
450
  }
459
451
 
460
452
  function submit_group_input(title: string) {
package/src/command.ts CHANGED
@@ -1,54 +1,78 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
3
 
4
- import type { Options, InferredOptionTypes, Arguments } from "yargs";
4
+ import type { Options, InferredOptionTypes, Arguments, ParserConfigurationOptions } from "yargs";
5
5
 
6
6
  export type Argv = Arguments & TGlobalOptions & TFixupOptions & TDefaultOptions;
7
7
 
8
- export async function command() {
8
+ type CommandOptions = {
9
+ env_config?: Partial<Argv>;
10
+ parserConfiguration?: Partial<ParserConfigurationOptions>;
11
+ };
12
+
13
+ export async function command(argv: string[], options: CommandOptions = {}) {
9
14
  // https://yargs.js.org/docs/#api-reference-optionkey-opt
10
- return (
11
- yargs(hideBin(process.argv))
12
- .scriptName("git stack")
13
- .usage("Usage: git stack [command] [options]")
14
-
15
- .command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions))
16
-
17
- .command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) =>
18
- yargs.positional("commit", FixupOptions.commit),
19
- )
20
-
21
- .command(
22
- "log [args...]",
23
- "Print an abbreviated log with numbered commits, useful for git stack fixup",
24
- (yargs) => yargs.strict(false),
25
- )
26
-
27
- .command(
28
- "rebase",
29
- "Update local branch via rebase with latest changes from origin master branch",
30
- (yargs) => yargs,
31
- )
32
-
33
- .command(
34
- ["update", "upgrade"],
35
- "Check and install the latest version of git stack",
36
- (yargs) => yargs,
37
- )
38
-
39
- .option("verbose", GlobalOptions.verbose)
40
-
41
- // yargs default wraps to 80 columns
42
- // passing null will wrap to terminal width
43
- // value below if what seems to look decent
44
- .wrap(123)
45
-
46
- // disallow unknown options
47
- .strict()
48
- .version(process.env.CLI_VERSION || "unknown")
49
- .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
50
- .help("help", "Show usage via `git stack help`").argv as unknown as Promise<Argv>
51
- );
15
+ let builder = yargs(hideBin(argv));
16
+
17
+ if (options.parserConfiguration) {
18
+ builder = builder.parserConfiguration(options.parserConfiguration);
19
+ }
20
+
21
+ // apply overrides from config
22
+ // higher precedence than defaults, but lower precendence than cli flags
23
+ // perfect since that's what we want, prefer config only if not explicitly set on cli
24
+ if (options.env_config) {
25
+ builder = builder.config(options.env_config);
26
+ }
27
+
28
+ const parsed = await builder
29
+ .scriptName("git stack")
30
+ .usage("Usage: git stack [command] [options]")
31
+
32
+ .command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions))
33
+
34
+ .command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) =>
35
+ yargs.positional("commit", FixupOptions.commit),
36
+ )
37
+
38
+ .command(
39
+ "log [args...]",
40
+ "Print an abbreviated log with numbered commits, useful for git stack fixup",
41
+ (yargs) => yargs.strict(false),
42
+ )
43
+
44
+ .command(
45
+ "rebase",
46
+ "Update local branch via rebase with latest changes from origin master branch",
47
+ (yargs) => yargs,
48
+ )
49
+
50
+ .command(
51
+ ["update", "upgrade"],
52
+ "Check and install the latest version of git stack",
53
+ (yargs) => yargs,
54
+ )
55
+ .command(
56
+ "config",
57
+ "Generate a one-time configuration json based on the passed arguments",
58
+ (yargs) => yargs.options(DefaultOptions),
59
+ )
60
+
61
+ .option("verbose", GlobalOptions.verbose)
62
+
63
+ // yargs default wraps to 80 columns
64
+ // passing null will wrap to terminal width
65
+ // value below if what seems to look decent
66
+ .wrap(123)
67
+
68
+ // disallow unknown options
69
+ .strict()
70
+ .version(process.env.CLI_VERSION || "unknown")
71
+ .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
72
+ .help("help", "Show usage via `git stack help`");
73
+
74
+ const result = parsed.argv as unknown as Argv;
75
+ return result;
52
76
  }
53
77
 
54
78
  const GlobalOptions = {
@@ -109,12 +133,6 @@ const DefaultOptions = {
109
133
  description: "Open all PRs as drafts",
110
134
  },
111
135
 
112
- "branch-prefix": {
113
- type: "string",
114
- default: "",
115
- description: "Prefix for generated branch names, e.g. dev/magus/",
116
- },
117
-
118
136
  "revise-sign": {
119
137
  type: "boolean",
120
138
  default: true,
@@ -0,0 +1,106 @@
1
+ import * as React from "react";
2
+
3
+ import * as Ink from "ink-cjs";
4
+
5
+ import { Await } from "~/app/Await";
6
+ import { FormatText } from "~/app/FormatText";
7
+ import { Store } from "~/app/Store";
8
+ import { command } from "~/command";
9
+ import { colors } from "~/core/colors";
10
+ import { invariant } from "~/core/invariant";
11
+
12
+ export function Config() {
13
+ return <Await fallback={null} function={run} />;
14
+
15
+ async function run() {
16
+ const state = Store.getState();
17
+ const actions = state.actions;
18
+
19
+ const config = await get_explicit_args();
20
+ const config_json = JSON.stringify(config).replace(/"/g, '\\"');
21
+
22
+ actions.output(
23
+ <Ink.Box flexDirection="column" gap={1} paddingTop={1}>
24
+ <Ink.Text></Ink.Text>
25
+
26
+ <FormatText
27
+ message="Add the line below to your shell rc file ({zshrc}, {bashrc}, etc.)"
28
+ values={{
29
+ zshrc: <Ink.Text color={colors.gray}>.zshrc</Ink.Text>,
30
+ bashrc: <Ink.Text color={colors.gray}>.bashrc</Ink.Text>,
31
+ }}
32
+ />
33
+
34
+ <FormatText
35
+ message={`{export} {ENV_VAR}{e}{q}{config_json}{q}`}
36
+ values={{
37
+ export: <Ink.Text color={colors.purple}>export</Ink.Text>,
38
+ ENV_VAR: <Ink.Text color={colors.yellow}>{ENV_VAR}</Ink.Text>,
39
+ e: <Ink.Text color={colors.purple}>{"="}</Ink.Text>,
40
+ q: <Ink.Text color={colors.white}>{'"'}</Ink.Text>,
41
+ config_json: <Ink.Text color={colors.green}>{config_json}</Ink.Text>,
42
+ }}
43
+ />
44
+ </Ink.Box>,
45
+ );
46
+
47
+ actions.exit(0);
48
+ }
49
+ }
50
+
51
+ export async function argv_with_config_from_env() {
52
+ if (!process.env.GIT_STACK_CONFIG) {
53
+ return await command(process.argv);
54
+ }
55
+
56
+ const env_config = parse_env_config();
57
+ return await command(process.argv, { env_config });
58
+ }
59
+
60
+ function parse_env_config() {
61
+ const GIT_STACK_CONFIG = process.env.GIT_STACK_CONFIG;
62
+ invariant(GIT_STACK_CONFIG, "GIT_STACK_CONFIG must exist");
63
+
64
+ try {
65
+ const env_config = JSON.parse(GIT_STACK_CONFIG);
66
+ return env_config;
67
+ } catch (error) {
68
+ // eslint-disable-next-line no-console
69
+ console.error(`ERROR GIT_STACK_CONFIG=${GIT_STACK_CONFIG}`);
70
+ // eslint-disable-next-line no-console
71
+ console.error("ERROR GIT_STACK_CONFIG environment variable is not valid JSON");
72
+ process.exit(18);
73
+ }
74
+ }
75
+
76
+ async function get_explicit_args() {
77
+ const default_argv = await command(["git", "stack"], COMMAND_OPTIONS);
78
+ const state_argv = await command(process.argv, COMMAND_OPTIONS);
79
+
80
+ const config: Record<string, any> = {};
81
+
82
+ // find delta between default_argv and argv
83
+ for (const key of Object.keys(state_argv)) {
84
+ if (key === "_" || key === "$0") continue;
85
+
86
+ const state_value = state_argv[key];
87
+ const default_value = default_argv[key];
88
+ const is_set = default_value !== state_value;
89
+ if (is_set) {
90
+ config[key] = state_value;
91
+ }
92
+ }
93
+
94
+ return config;
95
+ }
96
+
97
+ const ENV_VAR = "GIT_STACK_CONFIG";
98
+
99
+ type CommandOptions = NonNullable<Parameters<typeof command>[1]>;
100
+
101
+ const COMMAND_OPTIONS = {
102
+ parserConfiguration: {
103
+ // Should aliases be removed before returning results? Default is `false`
104
+ "strip-aliased": true,
105
+ },
106
+ } satisfies CommandOptions;
package/src/index.tsx CHANGED
@@ -11,13 +11,13 @@ import * as Ink from "ink-cjs";
11
11
 
12
12
  import { App } from "~/app/App";
13
13
  import { Store } from "~/app/Store";
14
- import { command } from "~/command";
14
+ import { argv_with_config_from_env } from "~/commands/Config";
15
15
  import { get_tmp_dir } from "~/core/get_tmp_dir";
16
16
  import { pretty_json } from "~/core/pretty_json";
17
17
 
18
18
  (async function main() {
19
19
  try {
20
- const argv = await command();
20
+ const argv = await argv_with_config_from_env();
21
21
 
22
22
  // required to get bun working with ink
23
23
  // https://github.com/oven-sh/bun/issues/6862#issuecomment-2429444852
@@ -4,6 +4,6 @@ declare namespace NodeJS {
4
4
  DEV?: "true" | "false";
5
5
  CLI_VERSION?: string;
6
6
  GIT_SEQUENCE_EDITOR_SCRIPT?: string;
7
- GIT_STACK_BRANCH_PREFIX?: string;
7
+ GIT_STACK_CONFIG?: string;
8
8
  }
9
9
  }
@@ -1,5 +0,0 @@
1
- import { short_id } from "~/core/short_id";
2
-
3
- export function gs_short_id() {
4
- return `gs-${short_id()}`;
5
- }