@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.
- package/dist/commands/setup.js +43 -16
- package/dist/utils/prompter.js +18 -2
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -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:
|
|
43
|
-
// discovered item)
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 —
|
|
738
|
-
//
|
|
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
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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;
|
package/dist/utils/prompter.js
CHANGED
|
@@ -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
|
}
|