Skip to content

Commit f400165

Browse files
Add edit option for install scripts
- Added edit functionality to script selection menu - Users can now press 'e' to edit install scripts in default editor - Supports both single and multiple script scenarios - Creates temporary file for editing and cleans up afterward - Falls back to original script if editing fails
1 parent eeded58 commit f400165

2 files changed

Lines changed: 160 additions & 17 deletions

File tree

lib/installer.js

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const {
3939
checkPath,
4040
processInstallSnippetReplacements,
4141
promptChoice,
42+
promptChoiceWithEdit,
43+
editScriptInEditor,
4244
colors,
4345
} = require("./utils");
4446

@@ -356,16 +358,42 @@ const displayScriptList = (installScripts, log) => {
356358

357359
log.log(` ${continueOption}. Continue to regular installation`);
358360
log.log(` ${exitOption}. Exit installation`);
361+
log.log(` e. Edit selected script before running`);
359362
};
360363

361364
const handleSingleScript = async (script, log) => {
362365
displayScriptPreview(script, log);
363366

364-
const shouldRun = await confirm(
365-
"Run install script (y) or continue to regular installation (n)?",
366-
);
367+
while (true) {
368+
const readline = require('readline');
369+
const rli = readline.createInterface({
370+
input: process.stdin,
371+
output: process.stdout,
372+
});
373+
374+
const choice = await new Promise((resolve) => {
375+
rli.question(`${colors.fg.yellow}Run install script (y/n) or edit (e)?${colors.reset} `, (ans) => {
376+
rli.close();
377+
resolve(ans.trim().toLowerCase());
378+
});
379+
});
367380

368-
return shouldRun ? script : null;
381+
if (choice === 'e') {
382+
const editedScript = await editScriptInEditor(script);
383+
displayScriptPreview(editedScript, log);
384+
385+
const shouldRun = await confirm(
386+
"Run this edited install script (y) or continue to regular installation (n)?",
387+
);
388+
389+
if (shouldRun) return editedScript;
390+
return null;
391+
} else if (choice === 'y' || choice === 'yes') {
392+
return script;
393+
} else if (choice === 'n' || choice === 'no') {
394+
return null;
395+
}
396+
}
369397
};
370398

371399
const handleMultipleScripts = async (installScripts, log) => {
@@ -375,25 +403,53 @@ const handleMultipleScripts = async (installScripts, log) => {
375403
while (true) {
376404
displayScriptList(installScripts, log);
377405

378-
const choice = await promptChoice(
379-
`Preview install script (1-${EXIT_OPTION}): `,
380-
EXIT_OPTION,
381-
);
406+
const readline = require('readline');
407+
const rli = readline.createInterface({
408+
input: process.stdin,
409+
output: process.stdout,
410+
});
382411

383-
if (choice <= installScripts.length) {
384-
const script = installScripts[choice - 1];
385-
displayScriptPreview(script, log);
412+
const choice = await new Promise((resolve) => {
413+
rli.question(`${colors.fg.yellow}Preview install script (1-${EXIT_OPTION}) or 'e' to edit:${colors.reset} `, (ans) => {
414+
rli.close();
415+
resolve(ans.trim().toLowerCase());
416+
});
417+
});
386418

419+
if (choice === 'e') {
420+
const scriptChoice = await promptChoice(
421+
`Which script would you like to edit? (1-${installScripts.length}): `,
422+
installScripts.length,
423+
);
424+
const scriptToEdit = installScripts[scriptChoice - 1];
425+
const editedScript = await editScriptInEditor(scriptToEdit);
426+
displayScriptPreview(editedScript, log);
427+
387428
const shouldRun = await confirm(
388-
"Run this install script (y) or choose a different one (n)?",
429+
"Run this edited install script (y) or choose a different one (n)?",
389430
);
390431

391-
if (shouldRun) return script;
432+
if (shouldRun) return editedScript;
392433
// Continue loop if user chooses 'n'
393-
} else if (choice === CONTINUE_OPTION) {
394-
return null; // Continue to regular installation
395-
} else if (choice === EXIT_OPTION) {
396-
throw new Error("Installation aborted by user");
434+
} else {
435+
const numericChoice = parseInt(choice);
436+
if (numericChoice <= installScripts.length) {
437+
const script = installScripts[numericChoice - 1];
438+
displayScriptPreview(script, log);
439+
440+
const shouldRun = await confirm(
441+
"Run this install script (y) or choose a different one (n)?",
442+
);
443+
444+
if (shouldRun) return script;
445+
// Continue loop if user chooses 'n'
446+
} else if (numericChoice === CONTINUE_OPTION) {
447+
return null; // Continue to regular installation
448+
} else if (numericChoice === EXIT_OPTION) {
449+
throw new Error("Installation aborted by user");
450+
} else {
451+
console.log(`${colors.fg.red}Invalid choice. Please try again.${colors.reset}`);
452+
}
397453
}
398454
}
399455
};

lib/utils.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,91 @@ const promptChoice = (question, maxChoice) => {
112112
});
113113
};
114114

115+
const promptChoiceWithEdit = (question, maxChoice, allowEdit = false) => {
116+
if (maxChoice < 1) {
117+
throw new Error("maxChoice must be at least 1");
118+
}
119+
120+
return new Promise((resolve) => {
121+
const askQuestion = () => {
122+
const rli = readline.createInterface({
123+
input: process.stdin,
124+
output: process.stdout,
125+
});
126+
127+
let editPrompt = "";
128+
if (allowEdit) {
129+
editPrompt = ` or 'e' to edit`;
130+
}
131+
132+
rli.question(`${colors.fg.yellow}${question}${editPrompt}${colors.reset} `, (ans) => {
133+
const trimmed = ans.trim().toLowerCase();
134+
rli.close();
135+
136+
if (allowEdit && trimmed === 'e') {
137+
resolve('edit');
138+
return;
139+
}
140+
141+
const choice = parseInt(trimmed);
142+
if (isNaN(choice) || choice < 1 || choice > maxChoice) {
143+
console.log(
144+
`${colors.fg.red}Invalid choice. Please enter a number between 1 and ${maxChoice}${allowEdit ? " or 'e' to edit" : ""}.${colors.reset}`
145+
);
146+
// Use setTimeout to avoid stack overflow
147+
setTimeout(askQuestion, 0);
148+
} else {
149+
resolve(choice);
150+
}
151+
});
152+
};
153+
154+
askQuestion();
155+
});
156+
};
157+
158+
const editScriptInEditor = async (script) => {
159+
const { execSync } = require('child_process');
160+
const fs = require('fs');
161+
const os = require('os');
162+
const path = require('path');
163+
164+
// Create a temporary file with the script content
165+
const tempDir = os.tmpdir();
166+
const tempFile = path.join(tempDir, `justinstall-script-${Date.now()}.sh`);
167+
168+
try {
169+
fs.writeFileSync(tempFile, script.code);
170+
171+
// Determine the default editor
172+
const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
173+
174+
console.log(`${colors.fg.cyan}Opening editor: ${editor}${colors.reset}`);
175+
176+
// Open the editor and wait for it to close
177+
execSync(`${editor} "${tempFile}"`, { stdio: 'inherit' });
178+
179+
// Read the modified content
180+
const modifiedCode = fs.readFileSync(tempFile, 'utf8');
181+
182+
// Return the updated script
183+
return {
184+
...script,
185+
code: modifiedCode
186+
};
187+
} catch (error) {
188+
console.error(`${colors.fg.red}Error opening editor: ${error.message}${colors.reset}`);
189+
return script; // Return original script if editing fails
190+
} finally {
191+
// Clean up the temporary file
192+
try {
193+
fs.unlinkSync(tempFile);
194+
} catch (e) {
195+
// Ignore cleanup errors
196+
}
197+
}
198+
};
199+
115200
const fileSize = (bytes, si = false, dp = 1) => {
116201
const thresh = si ? 1000 : 1024;
117202

@@ -233,6 +318,8 @@ module.exports = {
233318
createLogger,
234319
confirm,
235320
promptChoice,
321+
promptChoiceWithEdit,
322+
editScriptInEditor,
236323
fileSize,
237324
createLink,
238325
checkPath,

0 commit comments

Comments
 (0)