zod-opts
Version:
node.js CLI option parser / validator using Zod
265 lines • 10.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandParser = void 0;
const node_path_1 = __importDefault(require("node:path"));
const zod_1 = require("zod");
const error_1 = require("./error");
const help_1 = require("./help");
const internal_parser_1 = require("./internal_parser");
const logger_1 = require("./logger");
const util = __importStar(require("./util"));
const validator_1 = require("./validator");
class CommandParser {
constructor({ name, version, description, handler, commands, } = {}) {
this._commands = [];
this._name = name;
this._version = version;
this._description = description;
this._handler = handler;
if (commands !== undefined) {
this._commands = commands;
}
}
name(name) {
this._name = name;
return this;
}
version(version) {
this._version = version;
return this;
}
description(description) {
this._description = description;
return this;
}
_internalHandler(handler) {
this._handler = handler;
return this;
}
subcommand(command) {
if (this._commands.some((c) => c.toInternalCommand().name === command.toInternalCommand().name)) {
throw new Error(`Duplicated command name: ${command.toInternalCommand().name}`);
}
this._commands = this._commands.concat([command]);
return this;
}
showHelp(commandName) {
const help = this.getHelp(commandName);
console.log(help); // eslint-disable-line no-console
}
getHelp(commandName) {
if (commandName === undefined) {
const internalCommands = this._commands.map((command) => command.toInternalCommand());
return (0, help_1.generateGlobalCommandHelp)({
commands: internalCommands,
name: this._scriptName(),
description: this._description,
version: this._version,
});
}
const foundCommand = this._commands.find((command) => command.toInternalCommand().name === commandName); // CommandParser wll be created by subcommand() that ensures that the command exists
return (0, help_1.generateCommandHelp)({
command: foundCommand.toInternalCommand(),
name: this._name,
version: this._version,
});
}
parse(args) {
const validArgs = args !== null && args !== void 0 ? args : process.argv.slice(2);
const commands = this._commands.map((command) => {
return command._toParseCommand();
});
const internalCommands = commands.map((command) => command.internalCommand);
const internalResult = this._internalParseAndValidate({
args: validArgs,
commands: internalCommands,
scriptName: this._scriptName(),
});
if (internalResult.type !== "match") {
this._handleAndExit(internalResult);
}
const usedCommandIndex = internalCommands.findIndex((command) => internalResult.commandName === command.name);
const { shape, action, validation } = commands[usedCommandIndex];
const zodParseResult = this._zodParse(internalResult, shape);
if (!zodParseResult.success) {
this._handleAndExit(zodParseResult.error);
}
const validationError = this._validateByCustomValidation(validation, zodParseResult.value, internalResult.help);
if (validationError !== undefined) {
this._handleAndExit(validationError);
}
if (this._handler !== undefined) {
this._handler({ ...internalResult, parsed: zodParseResult.value });
}
action(zodParseResult.value);
}
_handleAndExit(handlerArg) {
if (this._handler != null) {
this._handler(handlerArg);
}
util.errorExit(handlerArg, this._version);
}
_scriptName() {
if (this._name !== undefined) {
return this._name;
}
const pathName = process.argv[1];
return node_path_1.default.basename(pathName);
}
_generateParseHelp(commands, selectedCommand, scriptName) {
return selectedCommand === undefined
? (0, help_1.generateGlobalCommandHelp)({
commands,
name: scriptName,
description: this._description,
version: this._version,
})
: (0, help_1.generateCommandHelp)({
command: selectedCommand,
name: scriptName,
version: this._version,
});
}
_handleInternalParseHelpAndVersion(parsed, selectedCommand, commands, scriptName) {
const help = this._generateParseHelp(commands, selectedCommand, scriptName);
if (parsed.isHelp) {
return {
type: "help",
help,
exitCode: 0,
commandName: parsed.commandName,
};
}
if (parsed.isVersion) {
return {
type: "version",
help,
exitCode: 0,
};
}
util.assertNever(parsed);
}
_handleInternalParseError(e, commands, scriptName) {
const error = e;
const selectedCommand = error.commandName !== undefined
? commands.find((command) => command.name === error.commandName)
: undefined;
return {
type: "error",
error: e,
exitCode: 1,
help: this._generateParseHelp(commands, selectedCommand, scriptName),
commandName: error.commandName,
};
}
_handleInternalParseMatch(parsed, selectedCommand, commands, scriptName) {
const { options: validOptions, positionalArgs: validPositionalArguments } = (0, validator_1.validateMultipleCommands)(parsed, selectedCommand.options, selectedCommand.positionalArgs, parsed.commandName);
(0, logger_1.debugLog)("createInternalParserAndParse", {
validOptions: JSON.stringify(validOptions),
validPositionalArguments: JSON.stringify(validPositionalArguments),
});
const validOptionMap = Object.fromEntries(validOptions.map((option) => [option.name, option.value]));
const validPositionalArgMap = Object.fromEntries(validPositionalArguments.map((option) => [option.name, option.value]));
return {
type: "match",
parsed: {
...validOptionMap,
...validPositionalArgMap,
},
commandName: parsed.commandName,
help: this._generateParseHelp(commands, selectedCommand, scriptName),
};
}
_internalParseAndValidate({ args, commands, scriptName, }) {
try {
const parsed = (0, internal_parser_1.parseMultipleCommands)({
args,
commands,
});
(0, logger_1.debugLog)("parseMultipleCommands", {
parsed: JSON.stringify(parsed),
});
const selectedCommand = commands.find((command) => command.name === parsed.commandName);
if (parsed.isHelp || parsed.isVersion) {
return this._handleInternalParseHelpAndVersion(parsed, selectedCommand, commands, scriptName);
}
return this._handleInternalParseMatch(parsed, selectedCommand, commands, scriptName);
}
catch (e) {
(0, logger_1.debugLog)("createInternalParserAndParse handle error", e);
if (!(e instanceof error_1.ParseError)) {
throw e;
}
return this._handleInternalParseError(e, commands, scriptName);
}
}
_zodParse(prevResult, shape) {
const result = zod_1.z.object(shape).safeParse(prevResult.parsed);
if (!result.success) {
const firstError = result.error.errors[0];
return {
success: false,
error: {
type: "error",
error: new error_1.ParseError(`${firstError.message}: ${firstError.path.join("")}`, result.error),
help: prevResult.help,
exitCode: 1,
commandName: prevResult.commandName,
},
};
}
return { success: true, value: result.data };
}
_validateByCustomValidation(validation, value, help) {
if (validation === undefined) {
return undefined;
}
const validateResult = (() => {
try {
return validation(value);
}
catch (e) {
if (e instanceof Error) {
return e.message;
}
throw e;
}
})();
if (validateResult !== true) {
return {
type: "error",
error: new error_1.ParseError(validateResult),
help,
exitCode: 1,
};
}
}
}
exports.CommandParser = CommandParser;
//# sourceMappingURL=command_parser.js.map