175 lines
5.9 KiB
JavaScript
175 lines
5.9 KiB
JavaScript
/**
|
|
* Interactive prompts for nopy CLI
|
|
* @module nopy.prompts
|
|
*/
|
|
// @ts-ignore - no types available
|
|
import Enquirer from 'enquirer';
|
|
import fuzzy from 'fuzzy';
|
|
import inquirer from 'inquirer';
|
|
// @ts-ignore - no types available
|
|
import CheckboxPlus from 'inquirer-checkbox-plus-prompt';
|
|
import { z } from 'zod';
|
|
// Register the checkbox-plus prompt type for filterable multi-select
|
|
inquirer.registerPrompt('checkbox-plus', CheckboxPlus);
|
|
/**
|
|
* Prompts the user to select cubes to execute with filtering support
|
|
*/
|
|
export async function CubeSelection(cubes) {
|
|
const cubeChoices = Object.values(cubes)
|
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
.map((cube) => ({
|
|
name: `${cube.id} - ${cube.name}`,
|
|
value: cube.id,
|
|
short: cube.id,
|
|
}));
|
|
// Clear terminal and move cursor to top
|
|
process.stdout.write('\x1B[2J\x1B[0f');
|
|
const terminalHeight = process.stdout.rows || 24;
|
|
const pageSize = Math.max(10, terminalHeight - 5);
|
|
console.log('\n Cube Selection\n');
|
|
console.log(' Type to filter • Space to select • Enter to confirm\n');
|
|
const answers = await inquirer.prompt([
|
|
{
|
|
type: 'checkbox-plus',
|
|
name: 'selectedCubes',
|
|
message: 'Select cubes:',
|
|
pageSize,
|
|
highlight: true,
|
|
searchable: true,
|
|
source: (_answersSoFar, input) => {
|
|
const searchTerm = input || '';
|
|
if (!searchTerm)
|
|
return Promise.resolve(cubeChoices);
|
|
const results = fuzzy.filter(searchTerm, cubeChoices, {
|
|
extract: (choice) => choice.name,
|
|
});
|
|
return Promise.resolve(results.map((r) => r.original));
|
|
},
|
|
},
|
|
]);
|
|
return { selectedCubes: answers.selectedCubes };
|
|
}
|
|
export async function AuthSelection(useAuthKey) {
|
|
if (useAuthKey)
|
|
return { authMethod: 'ssh-key' };
|
|
const answers = await inquirer.prompt([
|
|
{
|
|
type: 'list',
|
|
name: 'authMethod',
|
|
message: 'Select authentication method:',
|
|
choices: ['ssh-key', 'password'],
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'username',
|
|
message: 'Enter username:',
|
|
when: (answers) => answers.authMethod !== 'ssh-key',
|
|
},
|
|
{
|
|
type: 'password',
|
|
name: 'password',
|
|
message: 'Enter password:',
|
|
when: (answers) => answers.authMethod !== 'ssh-key',
|
|
},
|
|
]);
|
|
return answers;
|
|
}
|
|
export async function PasswordSelection(username) {
|
|
const { password } = await inquirer.prompt([
|
|
{
|
|
type: 'password',
|
|
name: 'password',
|
|
message: `Enter password for ${username}:`,
|
|
},
|
|
]);
|
|
return password;
|
|
}
|
|
export async function HostSelection(hosts) {
|
|
const selectedHost = await inquirer.prompt([
|
|
{
|
|
type: 'list',
|
|
name: 'host',
|
|
message: 'Select host from inventory',
|
|
choices: ['docker', 'vagrant', ...hosts, 'custom'],
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'customHost',
|
|
message: 'Specify custom host address:',
|
|
when: (answers) => answers.host === 'custom',
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'vagrantVM',
|
|
message: 'Specify vagrant machine:',
|
|
default: 'default',
|
|
when: (answers) => answers.host === 'vagrant',
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'dockerContainer',
|
|
message: 'Specify docker container name:',
|
|
when: (answers) => answers.host === 'runtime:docker',
|
|
},
|
|
]);
|
|
if (selectedHost.host === 'vagrant')
|
|
return `@vagrant/${selectedHost.vagrantVM}`;
|
|
if (selectedHost.host === 'runtime:docker')
|
|
return `@docker/${selectedHost.dockerContainer}`;
|
|
return selectedHost.customHost ?? selectedHost.host;
|
|
}
|
|
function coerceValue(value, zodType) {
|
|
if (typeof value !== 'string')
|
|
return value;
|
|
if (zodType instanceof z.ZodDefault)
|
|
return coerceValue(value, zodType._def.innerType);
|
|
if (zodType instanceof z.ZodOptional)
|
|
return coerceValue(value, zodType._def.innerType);
|
|
if (zodType instanceof z.ZodNullable) {
|
|
if (value === 'null' || value === '')
|
|
return null;
|
|
return coerceValue(value, zodType._def.innerType);
|
|
}
|
|
if (zodType instanceof z.ZodBoolean)
|
|
return value === 'true' || value === 'yes' || value === '1';
|
|
if (zodType instanceof z.ZodNumber) {
|
|
const num = Number(value);
|
|
return Number.isNaN(num) ? value : num;
|
|
}
|
|
return value;
|
|
}
|
|
export async function VariableAssignment(cube, variables) {
|
|
const schema = cube.manifest.schema.shape;
|
|
const defaults = cube.getDefaults();
|
|
const variablesToConfigure = {};
|
|
for (const [key, defaultValue] of Object.entries(defaults)) {
|
|
if (variables.get(cube.id, 'params')[key] === undefined) {
|
|
variablesToConfigure[key] = defaultValue;
|
|
}
|
|
}
|
|
if (Object.keys(variablesToConfigure).length === 0)
|
|
return;
|
|
const choices = Object.entries(variablesToConfigure).map(([key, value]) => {
|
|
const zodType = schema[key];
|
|
const description = zodType?.description || key;
|
|
return { name: key, message: description, initial: String(value ?? '') };
|
|
});
|
|
const form = new Enquirer.Form({
|
|
name: 'variables',
|
|
message: `[${cube.id}] ${cube.name}\n (↑↓ navigate, Enter to submit)`,
|
|
choices,
|
|
});
|
|
try {
|
|
const result = await form.run();
|
|
const coercedResult = {};
|
|
for (const [key, value] of Object.entries(result)) {
|
|
const zodType = schema[key];
|
|
coercedResult[key] = zodType ? coerceValue(value, zodType) : value;
|
|
}
|
|
variables.assign(cube.id, 'prompts', coercedResult);
|
|
}
|
|
catch {
|
|
// User cancelled
|
|
}
|
|
}
|