@grandlinex/easy-cli
Version:
Cli lib to perform common tasks
264 lines (263 loc) • 11.3 kB
JavaScript
import fs from 'fs';
import { confirm, editor, input, number, select } from '@inquirer/prompts';
import { ShellCommand, } from '../../class/ShellComand.js';
import InteractionArgs from '../../class/InteractionArgs.js';
import ArgUtil from '../../utils/ArgUtil.js';
import { getBoolOrUndefined, getNumberOrUndefined, getStringOrUndefined, StricktOption, } from '../../lib/types.js';
export default class InteractiveAction extends ShellCommand {
constructor(props) {
super({
...props,
name: 'interactive',
description: 'Use the interactive mode',
properties: [
{
key: 'interactive',
type: 'null',
required: true,
},
],
});
}
getFileOption(prop) {
const path = this.resolvePath('.');
const files = fs.readdirSync(path, { withFileTypes: true });
return files
.filter((f) => {
if (prop.fileOnly) {
return f.isFile();
}
if (prop.dirOnly) {
return f.isDirectory();
}
return true;
})
.map((f) => ({
name: !prop.fileOnly && !prop.dirOnly
? `[${f.isFile() ? 'File' : 'Dir'}] ${f.name}`
: f.name,
value: f.name,
disabled: false,
isDir: f.isDirectory(),
}))
.sort((a, b) => {
if (a.isDir && !b.isDir) {
return -1;
}
if (!a.isDir && b.isDir) {
return 1;
}
return a.name.localeCompare(b.name);
});
}
async runCmd(sel, args) {
sel.properties
.filter(({ type, required }) => type === 'null' && required)
.forEach(({ key }) => {
args.param.set(key, null);
});
const uParam = sel.properties.filter(({ type, required }) => type !== 'null' || !required);
const validation = async (cnf) => {
const { promise, after, validate } = cnf;
let valid = false;
let data;
while (!valid) {
data = await promise();
if (validate && data !== undefined) {
const validated = validate(data);
if (validated === true) {
valid = true;
}
else if (typeof validated === 'string') {
this.error(validated);
}
else {
this.error('Invalid input');
}
}
else {
valid = true;
}
}
if (after && data !== undefined) {
return after(data);
}
return data;
};
if (uParam.length > 0) {
const options = {};
// eslint-disable-next-line no-unreachable-loop
for (const prop of uParam) {
switch (prop.type) {
case 'null':
options[prop.key] = await validation({
promise: async () => confirm({
message: prop.description ?? prop.key,
default: await getBoolOrUndefined(prop.default, false),
}),
validate: prop.validate,
});
break;
case 'path':
options[prop.key] = await validation({
promise: async () => select({
message: prop.description ?? prop.key,
choices: this.getFileOption(prop),
default: await getStringOrUndefined(prop.default),
pageSize: this.handler.getPageSize(),
}),
validate: prop.validate,
});
break;
case 'number':
if (prop.editor) {
options[prop.key] = await validation({
promise: async () => editor({
message: prop.description ?? prop.key,
default: await getStringOrUndefined(prop.default),
}),
validate: prop.validate,
});
}
else {
options[prop.key] = await validation({
promise: async () => number({
message: prop.description ?? prop.key,
default: await getNumberOrUndefined(prop.default),
}),
validate: (ip) => {
if (prop.validate) {
return prop.validate(ip);
}
return !prop.required || !Number.isNaN(Number(ip));
},
});
}
break;
case 'string':
if (!prop.options) {
if (prop.editor) {
options[prop.key] = await validation({
promise: async () => editor({
message: prop.description ?? prop.key,
default: await getStringOrUndefined(prop.default),
}),
validate: (ip) => {
if (prop.validate) {
return prop.validate(ip);
}
return !prop.required || (ip || '').length > 0;
},
});
}
else {
options[prop.key] = await validation({
promise: async () => input({
message: prop.description ?? prop.key,
default: await getStringOrUndefined(prop.default),
prefill: prop.prefill,
}),
validate: (ip) => {
if (prop.validate) {
return prop.validate(ip);
}
return !prop.required || (ip || '').length > 0;
},
});
}
}
else {
options[prop.key] = await validation({
promise: async () => {
const option = prop.options
? await StricktOption(prop.options, this.handler)
: undefined;
return select({
message: prop.description ?? prop.key,
default: await getStringOrUndefined(prop.default),
choices: option?.map((o) => ({
name: `${o.key} - ${o.description}`,
value: o.key,
disabled: false,
})) ?? [],
pageSize: this.handler.getPageSize(),
});
},
validate: prop.validate,
});
}
break;
default:
options[prop.key] = await validation({
promise: async () => select({
message: prop.description ?? prop.key,
default: await getStringOrUndefined(prop.default),
choices: [],
pageSize: this.handler.getPageSize(),
}),
validate: prop.validate,
});
}
}
this.debug(JSON.stringify(options));
uParam.forEach(({ key, type }) => {
switch (type) {
case 'null':
if (options[key]) {
args.param.set(key, null);
}
break;
case 'number':
if (options[key] !== '') {
args.param.set(key, Number(options[key]));
}
break;
case 'string':
case 'path':
if (options[key] !== '') {
args.param.set(key, options[key]);
}
break;
default:
}
});
}
ArgUtil.printDebug(this, args);
ArgUtil.buildCmd(this, args);
return sel.run(args);
}
async checkForSubCmd(cmd, args) {
args.cmd.push(cmd.name);
if (cmd.subCommands.length > 0) {
const next = await select({
message: `Select your action: > ${args.cmd.join(' > ')}`,
pageSize: this.handler.getPageSize(),
choices: cmd.subCommands.map((c) => ({
name: `${c.name} - ${c.description}`,
value: c.name,
disabled: false,
})),
});
const sel = cmd.subCommands.find((c) => c.name === next);
return this.checkForSubCmd(sel, args);
}
return this.runCmd(cmd, args);
}
async run() {
const cmdList = this.handler.getCmds(true);
const cmd = await select({
message: 'Select your action',
pageSize: this.handler.getPageSize(),
choices: cmdList
.filter((e) => e.name !== 'interactive')
.map((c) => ({
name: `${c.name} - ${c.description}`,
value: c.name,
disabled: false,
})),
});
const sel = cmdList.find((c) => c.name === cmd);
const args = new InteractionArgs();
return this.checkForSubCmd(sel, args);
}
}