zod-opts
Version:
node.js CLI option parser / validator using Zod
489 lines (475 loc) • 16.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const expect_type_1 = require("expect-type");
const zod_1 = require("zod");
const command_1 = require("../src/command");
const error_1 = require("../src/error");
const parser_1 = require("../src/parser");
const test_util_1 = require("./test_util");
function createActionUnexpectedCommand(name) {
return (0, command_1.command)(name)
.options({
opt1: {
type: zod_1.z.string(),
},
})
.action((parsed) => {
expect(1).toBe(0);
});
}
describe("parse()", () => {
test("simple", () => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos1",
type: zod_1.z.string(),
},
])
.action((parsed) => {
expect(parsed).toEqual({ opt1: "str1", pos1: "pos1" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
}))
.parse(["command1", "--opt1", "str1", "pos1"]);
});
test("multiple commands", () => {
(0, parser_1.parser)()
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos1",
type: zod_1.z.string(),
},
])
.action((parsed) => {
expect(parsed).toEqual({ opt1: "str1", pos1: "pos1" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
}))
.subcommand((0, command_1.command)("command2")
.options({
opt2: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos2",
type: zod_1.z.string(),
},
])
.action((parsed) => {
expect(parsed).toEqual({ opt2: "str1", pos2: "pos2" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
}))
.name("scriptNameA")
.parse(["command2", "--opt2", "str1", "pos2"]);
});
describe("custom validation", () => {
test("success", () => {
(0, parser_1.parser)()
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
},
})
.validation((parsed) => {
if (parsed.opt1 !== "str1") {
throw new Error("opt1 must be str1");
}
return true;
})
.action((parsed) => {
expect(parsed).toEqual({ opt1: "str1" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
}))
.parse(["command1", "--opt1", "str1"]);
});
test("error by Error()", () => {
(0, test_util_1.expectProcessExit)("opt1 must be str1", 1, () => {
(0, parser_1.parser)()
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
},
})
.validation((parsed) => {
if (parsed.opt1 !== "str1") {
throw new Error("opt1 must be str1");
}
return true;
})
.action((parsed) => {
expect(1).toBe(0);
}))
.parse(["command1", "--opt1", "invalid"]);
});
});
test("error by message", () => {
(0, test_util_1.expectProcessExit)("opt1 must be str1", 1, () => {
(0, parser_1.parser)()
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
},
})
.validation((parsed) => {
if (parsed.opt1 !== "str1") {
return "opt1 must be str1";
}
return true;
})
.action((parsed) => {
expect(1).toBe(0);
}))
.parse(["command1", "--opt1", "invalid"]);
});
});
});
describe("help", () => {
test("global help", () => {
const expectedHelp = `Usage: scriptA [options] <command>
desc
Commands:
command1
command2
Options:
-h, --help Show help
-V, --version Show version
`;
(0, test_util_1.expectExit0)(expectedHelp, () => {
(0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.description("desc")
.subcommand(createActionUnexpectedCommand("command1"))
.subcommand(createActionUnexpectedCommand("command2"))
.parse(["--help"]);
});
});
test("global help(name() and description() after subcommand())0", () => {
const expectedHelp = `Usage: scriptA [options] <command>
desc
Commands:
command1
command2
Options:
-h, --help Show help
-V, --version Show version
`;
(0, test_util_1.expectExit0)(expectedHelp, () => {
(0, parser_1.parser)()
.subcommand(createActionUnexpectedCommand("command1"))
.subcommand(createActionUnexpectedCommand("command2"))
.name("scriptA")
.version("1.1.1")
.description("desc")
.parse(["--help"]);
});
});
test("command help(--help command)", () => {
const expectedHelp = `Usage: scriptA command2 [options]
Options:
-h, --help Show help
-V, --version Show version
--opt1 <string> [required]
`;
(0, test_util_1.expectExit0)(expectedHelp, () => {
(0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.subcommand(createActionUnexpectedCommand("command1"))
.subcommand(createActionUnexpectedCommand("command2"))
.parse(["--help", "command2"]);
});
});
test("command help(command --help)", () => {
const expectedHelp = `Usage: scriptA command2 [options]
Options:
-h, --help Show help
-V, --version Show version
--opt1 <string> [required]
`;
(0, test_util_1.expectExit0)(expectedHelp, () => {
(0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.subcommand(createActionUnexpectedCommand("command1"))
.subcommand(createActionUnexpectedCommand("command2"))
.parse(["command2", "--help"]);
});
});
});
describe("version", () => {
test("show specified version", () => {
(0, test_util_1.expectExit0)("1.1.1", () => {
(0, parser_1.parser)()
.version("1.1.1")
.subcommand(createActionUnexpectedCommand("command1"))
.parse(["--version"]);
});
});
test("show none when version is not specified", () => {
(0, test_util_1.expectExit0)("none", () => {
(0, parser_1.parser)()
.subcommand(createActionUnexpectedCommand("command1"))
.parse(["-V"]);
});
});
test("show specified version after subcommand()", () => {
(0, test_util_1.expectExit0)("1.1.1", () => {
(0, parser_1.parser)()
.subcommand(createActionUnexpectedCommand("command1"))
.version("1.1.1")
.parse(["--version"]);
});
});
});
test("error on finding command", () => {
(0, test_util_1.expectProcessExit)("Unknown argument: missing_command", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.action((parsed) => {
expect(1).toBe(0);
}))
._internalHandler((result) => {
expect(result).toEqual({
type: "error",
error: new error_1.ParseError("Unknown argument: missing_command"),
help: expect.stringContaining("Usage: scriptNameA [options] <command>"),
exitCode: 1,
});
})
.parse(["missing_command"]);
});
});
test("error on parse()", () => {
(0, test_util_1.expectProcessExit)("Invalid option: opt-missing", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.action((parsed) => {
expect(1).toBe(0);
}))
._internalHandler((result) => {
expect(result).toEqual({
commandName: "command1",
type: "error",
error: new error_1.ParseError("Invalid option: opt-missing"),
help: expect.stringContaining("Usage: scriptNameA command1 [options]"),
exitCode: 1,
});
})
.parse(["command1", "--opt-missing"]);
});
});
test("error on validation()", () => {
(0, test_util_1.expectProcessExit)("Required option is missing: opt1", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([])
.action((parsed) => {
expect(1).toBe(0);
}))
._internalHandler((result) => {
expect(result).toEqual({
commandName: "command1",
type: "error",
error: new error_1.ParseError("Required option is missing: opt1"),
help: expect.stringContaining("Usage: scriptNameA command1 [options]"),
exitCode: 1,
});
})
.parse(["command1"]);
});
});
test("error on validation()", () => {
(0, test_util_1.expectProcessExit)("String must contain at least 10 character(s): opt1", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string().min(10),
description: "a",
},
})
.args([])
.action((parsed) => {
expect(1).toBe(0);
}))
._internalHandler((result) => {
expect(result).toEqual({
commandName: "command1",
type: "error",
error: new error_1.ParseError("String must contain at least 10 character(s): opt1"),
help: expect.stringContaining("Usage: scriptNameA command1 [options]"),
exitCode: 1,
});
})
.parse(["command1", "--opt1", "short"]);
});
});
});
describe("subcommand()", () => {
test("throws runtime error on command without action", () => {
expect(() => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos1",
type: zod_1.z.string(),
},
]))
.parse(["command1", "--opt1", "str1", "pos1"]);
}).toThrowError("action is required for command");
});
test("throws runtime error on duplicated command name", () => {
expect(() => {
(0, parser_1.parser)()
.name("scriptNameA")
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.action(() => { }))
.subcommand((0, command_1.command)("command1")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.action(() => { }))
.parse(["command1", "--opt1", "str1", "pos1"]);
}).toThrowError("Duplicated command name: command1");
});
});
describe("getHelp()", () => {
const testCommand = (0, command_1.command)("command1")
.description("desc2")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos1",
type: zod_1.z.string(),
},
])
.action((parsed) => {
expect(1).toBe(0);
});
test("global help", () => {
const expectedHelp = `Usage: scriptA [options] <command>
desc
Commands:
command1 desc2
Options:
-h, --help Show help
-V, --version Show version
`;
const help = (0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.description("desc")
.subcommand(testCommand)
.getHelp();
expect(help).toEqual(expectedHelp);
});
test("command help", () => {
const expectedHelp = `Usage: scriptA command1 [options] <pos1>
desc2
Arguments:
pos1 [required]
Options:
-h, --help Show help
-V, --version Show version
--opt1 <string> a [required]
`;
const help = (0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.description("desc")
.subcommand(testCommand)
.getHelp("command1");
expect(help).toEqual(expectedHelp);
});
});
describe("showHelp()", () => {
const testCommand = (0, command_1.command)("command1")
.description("desc2")
.options({
opt1: {
type: zod_1.z.string(),
description: "a",
},
})
.args([
{
name: "pos1",
type: zod_1.z.string(),
},
])
.action((parsed) => {
expect(1).toBe(0);
});
test("global help", () => {
const mockedConsoleLog = (0, test_util_1.mockConsole)("log");
(0, parser_1.parser)().name("scriptA").subcommand(testCommand).showHelp();
const logText = mockedConsoleLog.mock.calls.flat().join("\n");
expect(logText).toContain("Usage: scriptA [options] <command>");
});
});
//# sourceMappingURL=command_parser.test.js.map