@haystackeditor/cli 0.15.1 → 0.15.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.
@@ -39,8 +39,11 @@ function buildAnswerMap(options) {
39
39
  answers.install_hooks = false;
40
40
  }
41
41
  if (options.yes) {
42
- // Accept defaults non-interactively: don't open the toggle UI (keep every
43
- // discovered item) and confirm the write. Only fill ids not already set.
42
+ // Accept the interactive wizard's defaults non-interactively: keep every
43
+ // discovered item (skip the toggle UI), confirm the write, and say yes to
44
+ // the same things the prompts default to (auto-merge, session tracking,
45
+ // hooks). Use --skip-entire / --no-auto-merge to opt out. Only fill ids not
46
+ // already set by an explicit flag or --answers.
44
47
  if (!('want_to_toggle' in answers))
45
48
  answers.want_to_toggle = false;
46
49
  if (!('confirm_write' in answers))
@@ -48,9 +51,11 @@ function buildAnswerMap(options) {
48
51
  if (!('auto_merge' in answers))
49
52
  answers.auto_merge = true;
50
53
  if (!('install_entire' in answers))
51
- answers.install_entire = false;
54
+ answers.install_entire = true;
55
+ if (!('upgrade_entire' in answers))
56
+ answers.upgrade_entire = true;
52
57
  if (!('install_hooks' in answers))
53
- answers.install_hooks = false;
58
+ answers.install_hooks = true;
54
59
  }
55
60
  return answers;
56
61
  }
@@ -730,14 +735,26 @@ function tryOpenBrowser(url) {
730
735
  * the poll keeps waiting) but rethrows a genuine auth failure so the caller can
731
736
  * bail and prompt re-auth. */
732
737
  async function isAppInstalled(token) {
733
- return (await fetchHaystackInstallations(token)).length > 0;
738
+ try {
739
+ return (await fetchHaystackInstallations(token)).length > 0;
740
+ }
741
+ catch (err) {
742
+ // Auth failures are permanent — surface them. Anything else (5xx, network
743
+ // blip) is transient during the install poll: return false so the poll
744
+ // keeps waiting instead of crashing the wizard.
745
+ if (err instanceof InstallationsAuthError)
746
+ throw err;
747
+ return false;
748
+ }
734
749
  }
735
750
  async function stepEnsureAppInstalled(token) {
736
751
  console.log(chalk.bold(' Step 0: Verify GitHub App'));
737
- // Initial check — distinguish a genuine auth failure (bail, prompt re-auth)
738
- // from a transient probe error (refuse-to-fail and proceed).
752
+ // Initial check — use the raw fetch (not isAppInstalled, which swallows
753
+ // transient errors) so we can distinguish three outcomes: installed (done),
754
+ // genuine auth failure (bail + prompt re-auth), or a transient probe error
755
+ // (refuse-to-fail and proceed rather than brick setup over a blip).
739
756
  try {
740
- if (await isAppInstalled(token)) {
757
+ if ((await fetchHaystackInstallations(token)).length > 0) {
741
758
  console.log(chalk.green(' ✓ Haystack App installed\n'));
742
759
  return;
743
760
  }
@@ -803,14 +820,24 @@ async function stepSelectRepos(token) {
803
820
  console.log(chalk.yellow('\n No repositories found.\n'));
804
821
  process.exit(1);
805
822
  }
806
- const selectedRepos = await ui.multiselect({
807
- id: 'select_repos',
808
- message: 'Select repositories to configure:',
809
- choices: repos.map((r) => ({ name: r, value: r })),
810
- });
811
- if (selectedRepos.length === 0) {
812
- console.log(chalk.yellow('\n No repositories selected.\n'));
813
- process.exit(1);
823
+ let selectedRepos = [];
824
+ for (;;) {
825
+ selectedRepos = await ui.multiselect({
826
+ id: 'select_repos',
827
+ message: 'Select repositories to configure:',
828
+ choices: repos.map((r) => ({ name: r, value: r })),
829
+ });
830
+ if (selectedRepos.length > 0)
831
+ break;
832
+ // Interactive: keep the picker open so the user can correct an empty pick
833
+ // (restores the old inquirer `validate` behavior). Non-interactive: a
834
+ // pre-supplied / agent-sent empty selection can't be fixed by re-prompting,
835
+ // so fail clearly instead of looping forever.
836
+ if (!ui.interactive) {
837
+ console.log(chalk.yellow('\n No repositories selected. Pass --repo <owner/name>.\n'));
838
+ process.exit(1);
839
+ }
840
+ console.log(chalk.yellow(' Select at least one repository.'));
814
841
  }
815
842
  console.log(chalk.green(` ${selectedRepos.length} repo(s) selected\n`));
816
843
  return selectedRepos;
@@ -50,7 +50,7 @@ class TtyPrompter {
50
50
  }
51
51
  async multiselect(q) {
52
52
  if (q.id in this.answers)
53
- return this.answers[q.id];
53
+ return asArray(this.answers[q.id], q.id);
54
54
  const choices = q.choices.map((c) => isSeparator(c) ? new inquirer.Separator(c.separator) : { name: c.name, value: c.value, checked: c.checked });
55
55
  const { value } = await inquirer.prompt([
56
56
  { type: 'checkbox', name: 'value', message: q.message, choices: choices, pageSize: 20 },
@@ -77,9 +77,16 @@ class TtyPrompter {
77
77
  return false;
78
78
  }
79
79
  progress(message, detail) {
80
+ // Only emit carriage-return / clear-line ANSI to a real TTY; piped to a file
81
+ // or CI log (non-interactive, no --json) those sequences corrupt the output,
82
+ // so stay silent there.
83
+ if (!process.stdout.isTTY)
84
+ return;
80
85
  process.stdout.write(`\r${chalk.dim(' ' + message)}${detail ? chalk.dim(' — ' + detail) : ''}\x1b[K`);
81
86
  }
82
87
  clearProgress() {
88
+ if (!process.stdout.isTTY)
89
+ return;
83
90
  process.stdout.write('\r\x1b[K');
84
91
  }
85
92
  result(_data) {
@@ -179,7 +186,7 @@ class JsonPrompter {
179
186
  }
180
187
  async multiselect(q) {
181
188
  if (q.id in this.answers)
182
- return this.answers[q.id];
189
+ return asArray(this.answers[q.id], q.id);
183
190
  const options = q.choices
184
191
  .filter((c) => !isSeparator(c))
185
192
  .map((c) => ({ value: c.value, label: c.name, default: c.checked ?? false }));
@@ -249,6 +256,15 @@ export function createPrompter(options = {}) {
249
256
  function sleep(ms) {
250
257
  return new Promise((resolve) => setTimeout(resolve, ms));
251
258
  }
259
+ /** Validate a pre-supplied multiselect answer is actually an array before use,
260
+ * so a bad --answers value (e.g. a string) fails fast with a clear message
261
+ * instead of crashing downstream array operations. */
262
+ function asArray(value, id) {
263
+ if (!Array.isArray(value)) {
264
+ throw new Error(`Answer for "${id}" must be a JSON array, got ${typeof value}.`);
265
+ }
266
+ return value;
267
+ }
252
268
  function stringifyArg(a) {
253
269
  return typeof a === 'string' ? a : String(a);
254
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {