zod-opts
Version:
node.js CLI option parser / validator using Zod
1,137 lines (1,134 loc) • 48.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const expect_type_1 = require("expect-type");
const zod_1 = require("zod");
const error_1 = require("../src/error");
const parser_1 = require("../src/parser");
const test_util_1 = require("./test_util");
describe("complex", () => {
test("returns parsed args when variable string arguments exist", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string() },
opt2: { type: zod_1.z.string().default("default2") },
opt3: { type: zod_1.z.string(), alias: "a" },
opt4: { type: zod_1.z.string() },
opt5: { type: zod_1.z.string(), alias: "b" },
})
.parse(["--opt1", "str1", "-a", "str3", "--opt4=str4", "-bstr5"]);
expect(parsed).toEqual({
opt1: "str1",
opt2: "default2",
opt3: "str3",
opt4: "str4",
opt5: "str5",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when variable union arguments exist", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.union([zod_1.z.string(), zod_1.z.string()]) },
opt2: { type: zod_1.z.union([zod_1.z.number(), zod_1.z.number()]) },
opt3: { type: zod_1.z.union([zod_1.z.boolean(), zod_1.z.boolean()]) },
opt4: {
type: zod_1.z.union([
zod_1.z.string().default("default1"),
zod_1.z.string().default("default2"),
]),
}, // ignore inner default
})
.parse(["--opt1", "str1", "--opt2", "10", "--opt3", "--opt4", "str2"]);
expect(parsed).toEqual({
opt1: "str1",
opt2: 10,
opt3: true,
opt4: "str2",
});
expect(parsed).toMatchObject({
opt1: expect.any(String),
opt2: expect.any(Number),
opt3: expect.any(Boolean),
opt4: expect.any(String),
});
});
test("returns parsed args when options exist", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string(), description: "a" },
opt2: { type: zod_1.z.number().default(10) },
})
.parse(["--opt1", "str"]);
expect(parsed).toEqual({ opt1: "str", opt2: 10 });
expect(parsed).toMatchObject();
});
test("returns parsed args when no positional arguments are provided", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string(), description: "a" },
opt2: { type: zod_1.z.number().default(10) },
})
.args([])
.parse(["--opt1", "str"]);
expect(parsed).toEqual({ opt1: "str", opt2: 10 });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when optional options/positional exist with default values", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string().optional() },
opt2: { type: zod_1.z.string().default("default").optional() },
opt3: { type: zod_1.z.string().optional().default("default") },
opt4: { type: zod_1.z.optional(zod_1.z.string().default("default")) },
})
.args([
{ name: "pos1", type: zod_1.z.string().optional() },
{ name: "pos2", type: zod_1.z.string().default("default").optional() },
{ name: "pos3", type: zod_1.z.string().optional().default("default") },
{ name: "pos4", type: zod_1.z.optional(zod_1.z.string().default("default")) },
])
.parse([]);
expect(parsed).toEqual({
opt1: undefined,
opt2: "default",
opt3: "default",
opt4: "default",
pos1: undefined,
pos2: "default",
pos3: "default",
pos4: "default",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when optional options and positional args exist", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string().optional() },
opt2: { type: zod_1.z.string().default("default").optional() },
opt3: { type: zod_1.z.string().optional().default("default") },
opt4: { type: zod_1.z.optional(zod_1.z.string().default("default")) },
})
.args([
{ name: "pos1", type: zod_1.z.string().optional() },
{ name: "pos2", type: zod_1.z.string().default("default").optional() },
{ name: "pos3", type: zod_1.z.string().optional().default("default") },
{ name: "pos4", type: zod_1.z.optional(zod_1.z.string().default("default")) },
])
.parse([
"--opt1",
"str1",
"--opt2",
"str2",
"--opt3",
"str3",
"--opt4",
"str4",
"str5",
"str6",
"str7",
"str8",
]);
expect(parsed).toEqual({
opt1: "str1",
opt2: "str2",
opt3: "str3",
opt4: "str4",
pos1: "str5",
pos2: "str6",
pos3: "str7",
pos4: "str8",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when options and positional arguments are provided", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string(), description: "a" },
opt2: { type: zod_1.z.number().default(10) },
})
.args([
{ name: "pos1", type: zod_1.z.string() },
{ name: "pos2", type: zod_1.z.number() },
{ name: "pos3", type: zod_1.z.string().default("default1") },
])
.parse(["--opt1", "str1", "--opt2", "10", "str2", "10"]);
expect(parsed).toEqual({
opt1: "str1",
opt2: 10,
pos1: "str2",
pos2: 10,
pos3: "default1",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when multiple positional arguments exist", () => {
const parsed = (0, parser_1.parser)()
.args([
{ name: "pos1", type: zod_1.z.string() },
{ name: "pos2", type: zod_1.z.number() },
])
.parse(["str1", "10"]);
expect(parsed).toEqual({
pos1: "str1",
pos2: 10,
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("returns parsed args when variable positional string exist", () => {
const parsed = (0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.string().array() }])
.parse(["str1", "10"]);
expect(parsed).toEqual({ pos: ["str1", "10"] });
expect(parsed).toMatchObject();
});
test("returns parsed args when variable number positional options exist", () => {
const parsed = (0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.number().array() }])
.parse(["-1.5", "40"]);
expect(parsed).toEqual({ pos: [-1.5, 40] });
expect(parsed).toMatchObject();
});
});
describe("type", () => {
describe("string", () => {
test("with arg - refactored", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string().min(2) },
opt2: { type: zod_1.z.string() },
})
.parse(["--opt1", "str1", "--opt2", ""]);
expect(parsed).toEqual({ opt1: "str1", opt2: "" });
expect(parsed).toMatchObject();
});
test("error on missing required argument", () => {
(0, test_util_1.expectProcessExit)("Option 'opt1' requires a value: opt1", 1, () => (0, parser_1.parser)()
.options({ opt1: { type: zod_1.z.string() } })
.parse(["--opt1"]));
});
test("error on validation of opt1 with min length of 10 characters", () => {
(0, test_util_1.expectProcessExit)("String must contain at least 10 character(s): opt1", 1, () => (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string().min(10) },
})
.parse(["--opt1", "short"]));
});
test("Test default value with argument", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.string().default("default") } })
.parse(["--opt", "str1"]);
expect(parsed).toEqual({ opt: "str1" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("default option without argument is set to 'default'", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.string().default("default") } })
.parse([]);
expect(parsed).toEqual({ opt: "default" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("optional argument with value is parsed correctly", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.string().optional() } })
.parse(["--opt", "str1"]);
expect(parsed).toEqual({ opt: "str1" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("Check that optional argument is undefined when not provided", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.string().optional() } })
.parse([]);
expect(parsed).toEqual({ opt: undefined });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("boolean argument with '--opt' flag sets 'opt' to true", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean() } })
.parse(["--opt"]);
expect(parsed).toEqual({ opt: true });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error on negative value", () => {
(0, test_util_1.expectProcessExit)("Non boolean option 'opt1' does not accept --no- prefix: opt1", 1, () => (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string().optional() },
})
.args([{ name: "pos", type: zod_1.z.string() }])
.parse(["--no-opt1", "arg"]));
});
});
describe("number", () => {
test("with arg - validates options with min and max values", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number().min(-10) },
opt2: { type: zod_1.z.number().max(10) },
})
.parse(["--opt1", "-0.5", "--opt2", "5"]);
expect(parsed).toEqual({
opt1: -0.5,
opt2: 5,
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error on required arg", () => {
expect(() => (0, parser_1.parser)()
.options({ opt1: { type: zod_1.z.number(), required: true } })
.parse(["--opt1"])).toThrowError("Option 'opt1' needs value: opt1");
});
test("error on validation of opt1 option", () => {
(0, test_util_1.expectProcessExit)("Number must be greater than or equal to 10: opt1", 1, () => (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number().min(10) },
})
.parse(["--opt1", "5"]));
});
});
describe("boolean", () => {
test("boolean options with arguments", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.boolean() },
opt2: { type: zod_1.z.boolean(), alias: "a" },
})
.parse(["--opt1", "-a"]);
expect(parsed).toEqual({ opt1: true, opt2: true });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error when required arg is missing", () => {
(0, test_util_1.expectProcessExit)("Error: Missing required option: opt", 1, () => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean() } })
.parse([]);
});
});
test("error when too many arguments are provided", () => {
(0, test_util_1.expectProcessExit)("Too many positional arguments", 1, () => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean() } })
.parse(["--opt", "str1"]);
});
});
test("error thrown when boolean is used as positional argument", () => {
expect(() => {
(0, parser_1.parser)()
.args([{ name: "opt", type: zod_1.z.boolean() }])
.parse([]);
}).toThrowError("Unsupported zod type (positional argument): ZodBoolean");
});
test("default with arg - refactored", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().default(false) } })
.parse(["--opt"]);
expect(parsed).toEqual({ opt: true });
expect(typeof parsed).toEqual("object");
expect(parsed).toHaveProperty("opt", true);
});
test("Checking the default value of 'opt' when a negative argument is passed", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().default(true) } })
.parse(["--no-opt"]);
expect(parsed).toEqual({ opt: false });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("Check default value of 'opt' when no argument is passed", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().default(true) } })
.parse([]);
expect(parsed).toEqual({ opt: true });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("Checking default value of boolean option without argument", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().default(false) } })
.parse([]);
expect(parsed).toEqual({ opt: false });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("optional argument with boolean flag set to true", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().optional() } })
.parse(["--opt"]);
expect(parsed).toEqual({ opt: true });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("optional argument without value should be undefined", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean().optional() } })
.parse([]);
expect(parsed).toEqual({ opt: undefined });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error when --opt=str is provided", () => {
(0, test_util_1.expectProcessExit)("Boolean option 'opt' does not need value: opt", 1, () => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean() } })
.parse(["--opt=str"]);
});
});
test("don't support --opt=true or --opt=false format", () => {
(0, test_util_1.expectProcessExit)("Boolean option 'opt' does not need value: opt", 1, () => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean() } })
.parse(["--opt=true"]);
});
});
test("error when -a10 is passed", () => {
(0, test_util_1.expectProcessExit)("Invalid option: 1", 1, () => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.boolean(), alias: "a" } })
.parse(["-a10"]);
});
});
});
describe("enum", () => {
test("with arg - check that 'opt' is of type 'a', 'b', or 'c'", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.enum(["a", "b", "c"]) } })
.parse(["--opt", "b"]);
expect(parsed).toEqual({ opt: "b" });
expect(parsed.opt).toBeOneOf(["a", "b", "c"]);
});
test("throws error on invalid value", () => {
expect(() => {
(0, parser_1.parser)()
.options({ opt: { type: zod_1.z.enum(["a", "b", "c"]) } })
.parse(["--opt", "d"]);
}).toThrowError("Invalid enum value. Expected 'a' | 'b' | 'c', received 'd': opt");
});
test("Checking parsing of enum option", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.enum(["a", "b", "c"]) } })
.parse(["--opt", "b"]);
expect(parsed).toEqual({ opt: "b" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("default with arg should parse correctly and have the correct type", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.enum(["a", "b", "c"]).default("b") } })
.parse(["--opt", "c"]);
expect(parsed).toEqual({ opt: "c" });
expect(parsed).toHaveProperty("opt", expect.oneOf(["a", "b", "c"]));
});
test("default option should be 'b' when no argument is passed", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: { type: zod_1.z.enum(["a", "b", "c"]).default("b") } })
.parse([]);
expect(parsed).toEqual({ opt: "b" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("optional with arg - refactored", () => {
const parsed = (0, parser_1.parser)()
.options({ opt: zod_1.z.enum(["a", "b", "c"]).optional() })
.parse(["--opt", "b"]);
expect(parsed).toEqual({ opt: "b" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
});
describe("union", () => {
test("validates command line arguments with given options", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.union([zod_1.z.string().min(5), zod_1.z.string().min(1)]) },
opt2: { type: zod_1.z.union([zod_1.z.number().min(10), zod_1.z.number().min(1)]) },
opt3: { type: zod_1.z.union([zod_1.z.boolean(), zod_1.z.boolean()]) },
opt4: {
type: zod_1.z.union([
zod_1.z.union([zod_1.z.string().min(5), zod_1.z.string()]),
zod_1.z.string().min(1),
]),
},
})
.parse(["--opt1", "str", "--opt2", "5", "--opt3", "--opt4", "str2"]);
expect(parsed).toEqual({
opt1: "str",
opt2: 5,
opt3: true,
opt4: "str2",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error when invalid string value is provided", () => {
(0, test_util_1.expectProcessExit)("Invalid option: opt", 1, () => {
(0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.union([
zod_1.z.string().minLength(5),
zod_1.z.string().minLength(10),
]),
},
})
.parse(["--opt", "d"]);
});
});
test("error on invalid number value", () => {
(0, test_util_1.expectProcessExit)("Invalid option: opt", 1, () => {
(0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.union([zod_1.z.number().min(5), zod_1.z.number().min(10)]),
},
})
.parse(["--opt", "3"]);
});
});
test("error thrown when union of different types (string, number) is used", () => {
expect(() => {
(0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]),
},
})
.parse(["--opt", "d"]);
}).toThrowError("Union types are not the same");
});
test("Check if '--opt' argument is parsed correctly with a default value", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.union([zod_1.z.string(), zod_1.z.string()]).default("default"),
},
})
.parse(["--opt", "str"]);
expect(parsed).toEqual({ opt: "str" });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("default without arg", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.union([zod_1.z.string(), zod_1.z.string()]).default("default"),
},
})
.parse([]);
expect(parsed).toEqual({ opt: "default" });
expect(parsed).toMatchObject();
});
test("optional with arg - refactored", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.union([zod_1.z.string(), zod_1.z.string()]).optional(),
},
})
.parse(["--opt", "str"]);
expect(parsed).toEqual({ opt: "str" });
expect(parsed).toMatchObject();
});
test("optional without arg should return undefined", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.union([zod_1.z.string(), zod_1.z.string()]).optional(),
},
})
.parse([]);
expect(parsed).toEqual({ opt: undefined });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
});
describe("array", () => {
describe("when option", () => {
test("Check parsing of '--opt' argument with string array values", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.parse(["--opt", "str1", "str2"]);
expect(parsed).toEqual({ opt: ["str1", "str2"] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("with arg and next option, parsed correctly", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.array(zod_1.z.string()),
},
opt2: {
type: zod_1.z.array(zod_1.z.string()),
alias: "a",
},
})
.parse(["--opt1", "str1", "str2", "-a", "str3", "str4"]);
expect(parsed).toEqual({
opt1: ["str1", "str2"],
opt2: ["str3", "str4"],
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("with arg and short option, parsed correctly", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.array(zod_1.z.string()),
alias: "a",
},
opt2: {
type: zod_1.z.boolean(),
alias: "b",
},
})
.parse(["-ba", "str1", "str2"]);
expect(parsed).toEqual({
opt1: ["str1", "str2"],
opt2: true,
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("should throw error for invalid value (string)", () => {
(0, test_util_1.expectProcessExit)("String must contain at least 10 character(s): opt", 1, () => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string().min(10)),
},
})
.parse(["--opt", "short"]);
});
});
test("error on invalid value(number). invalid format", () => {
(0, test_util_1.expectProcessExit)("Invalid option value. number is expected: opt", 1, () => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.number().min(10)),
},
})
.parse(["--opt", "short"]);
});
});
test("error when 'opt' is not a valid number greater than or equal to 10", () => {
(0, test_util_1.expectProcessExit)("Number must be greater than or equal to 10: opt", 1, () => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.number().min(10)),
},
})
.parse(["--opt", "5"]);
});
});
test("error on missing arg", () => {
(0, test_util_1.expectProcessExit)("Option 'opt' requires a value: opt", 1, () => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.parse(["--opt"]);
});
});
test("error on missing arg2", () => {
(0, test_util_1.expectProcessExit)("Missing required option 'opt'", 1, () => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.parse([]);
});
});
test("error on array of boolean", () => {
expect(() => {
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.boolean()),
},
})
.parse(["--opt", "short"]);
}).toThrowError("Unsupported zod type: Array of ZodBoolean");
});
test("optional argument should be undefined when not provided", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()).optional(),
},
})
.parse([]);
expect(parsed).toEqual({ opt: undefined });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("default array option should be empty when no argument is provided", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()).default([]),
},
})
.parse([]);
expect(parsed).toEqual({ opt: [] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test('default(["default1", "default2"]) without arg', () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()).default(["default1", "default2"]),
},
})
.parse([]);
expect(parsed).toEqual({ opt: ["default1", "default2"] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("when splitted, should parse and validate options with array of strings", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.array(zod_1.z.string()),
},
opt2: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.parse(["--opt1", "str1", "--opt2=str2", "--opt1", "str3"]);
expect(parsed).toEqual({
opt1: ["str1", "str3"],
opt2: ["str2"],
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("when parsing arguments with options and positional arguments", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: {
type: zod_1.z.array(zod_1.z.string()),
},
opt2: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.args([{ name: "pos", type: zod_1.z.string() }])
.parse(["--opt1", "str1", "--opt2=str2", "arg", "--opt1", "str3"]);
expect(parsed).toEqual({
opt1: ["str1", "str3"],
opt2: ["str2"],
pos: "arg",
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error with required positional arguments", () => {
(0, test_util_1.expectProcessExit)("Required argument is missing: pos", 1, () => {
// zod-opts doesn't support "-a 1 2 3"(option:[1], args[2, 3]).
// max length can be used, but can't handle '-a 1 pos_arg -a 2'
(0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.args([{ name: "pos", type: zod_1.z.string() }])
.parse(["--opt", "str1", "str3"]);
});
});
});
describe("when positional argument", () => {
test("with arg, should parse '--opt str1' to { opt: ['str1'] }", () => {
const parsed = (0, parser_1.parser)()
.options({
opt: {
type: zod_1.z.array(zod_1.z.string()),
},
})
.parse(["--opt", "str1"]);
expect(parsed).toEqual({ opt: ["str1"] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error thrown when invalid string value is provided", () => {
(0, test_util_1.expectProcessExit)("String must contain at least 10 character(s): pos", 1, () => {
(0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.string().min(10)) }])
.parse(["short"]);
});
});
test("error on invalid value (number). invalid format", () => {
(0, test_util_1.expectProcessExit)("Invalid positional argument value: pos", 1, () => {
(0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.number().min(10)) }])
.parse(["short"]);
});
});
test("error when value is not greater than or equal to 10", () => {
(0, test_util_1.expectProcessExit)("Number must be greater than or equal to 10: pos0", 1, () => {
(0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.number().min(10)) }])
.parse(["5"]);
});
});
test("error on array of boolean", () => {
expect(() => {
(0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.boolean()) }])
.parse(["short"]);
}).toThrowError("Unsupported zod type: Array of ZodBoolean");
});
test("optional argument with array of strings", () => {
const parsed = (0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.string()).optional() }])
.parse([]);
expect(parsed).toEqual({ pos: undefined });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("default array argument is empty", () => {
const parsed = (0, parser_1.parser)()
.args([{ name: "pos", type: zod_1.z.array(zod_1.z.string()).default([]) }])
.parse([]);
expect(parsed).toEqual({ pos: [] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test('default(["default1", "default2"]) without arg', () => {
const parsed = (0, parser_1.parser)()
.args([
{
name: "pos",
type: zod_1.z.array(zod_1.z.string()).default(["default1", "default2"]),
},
])
.parse([]);
expect(parsed).toEqual({ pos: ["default1", "default2"] });
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
});
});
});
describe("options and args are empty or don't exist", () => {
test("returns empty object when options and args don't exist", () => {
expect((0, parser_1.parser)().options({}).args([]).parse([])).toEqual({});
expect((0, parser_1.parser)().options({}).parse([])).toEqual({});
expect((0, parser_1.parser)().args([]).parse([])).toEqual({});
expect((0, parser_1.parser)().parse([])).toEqual({});
});
});
describe("format error", () => {
test("no option value provided", () => {
(0, test_util_1.expectProcessExit)("Option 'opt1' requires a value: opt1", 1, () => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string(), description: "a", alias: "n" },
})
.parse(["-n"]);
});
});
test("invalid option: opt1", () => {
expect(() => (0, parser_1.parser)().parse(["--opt1", "opt1"])).toThrowError("Invalid option: opt1");
expect(() => (0, parser_1.parser)().parse(["--opt1", "opt1"])).toExitWith(1);
});
test("too many positional arguments should cause an error", () => {
expect(() => {
(0, parser_1.parser)().parse(["str1"]);
}).toThrowError("Too many positional arguments");
});
test("value mismatch: opt1 should be a number", () => {
(0, test_util_1.expectProcessExit)("Invalid option value. number is expected: opt1", 1, () => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number() },
})
.parse(["--opt1", "str"]);
});
});
test("duplicated options", () => {
expect(() => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.string() },
})
.parse(["--opt1", "str", "--opt1", "str"]);
}).toThrowError("Duplicated option: opt1");
});
});
describe("refine", () => {
test("success - validating option1 must be foo or bar", () => {
const a = zod_1.z.string().refine((v) => v === "foo" || v === "bar", {
message: "option1 must be foo or bar",
});
const parsed = (0, parser_1.parser)()
.options({
opt1: {
type: a,
},
})
.parse(["--opt1", "foo"]);
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error - option1 must be foo or bar", () => {
(0, test_util_1.expectProcessExit)("option1 must be foo or bar: opt1", 1, () => {
const a = zod_1.z.string().refine((v) => v === "foo" || v === "bar", {
message: "option1 must be foo or bar",
});
(0, parser_1.parser)()
.options({
opt1: {
type: a,
},
})
.parse(["--opt1", "other"]);
});
});
});
describe("custom validation", () => {
test("Validate opt1 + opt2 is greater than 12", () => {
const parsed = (0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number() },
opt2: { type: zod_1.z.number() },
})
.validation((parsed) => {
if (parsed.opt1 + parsed.opt2 <= 12) {
throw new Error("opt1 + opt2 must be greater than 12");
}
return true;
})
.parse(["--opt1", "5", "--opt2", "10"]);
expect(parsed).toEqual({
opt1: 5,
opt2: 10,
});
(0, expect_type_1.expectTypeOf)(parsed).toEqualTypeOf();
});
test("error thrown when opt1 + opt2 is less than or equal to 12", () => {
(0, test_util_1.expectProcessExit)("opt1 + opt2 must be greater than 12", 1, () => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number() },
opt2: { type: zod_1.z.number() },
})
.validation((parsed) => {
if (parsed.opt1 + parsed.opt2 <= 12) {
throw new Error("opt1 + opt2 must be greater than 12");
}
return true;
})
.parse(["--opt1", "5", "--opt2", "5"]);
});
});
test("error message is thrown when opt1 + opt2 is less than or equal to 12", () => {
(0, test_util_1.expectProcessExit)("opt1 + opt2 must be greater than 12", 1, () => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.number() },
opt2: { type: zod_1.z.number() },
})
.validation((parsed) => {
if (parsed.opt1 + parsed.opt2 <= 12) {
return "opt1 + opt2 must be greater than 12";
}
return true;
})
.parse(["--opt1", "5", "--opt2", "5"]);
});
});
});
describe("help", () => {
test("Show help for scriptA", () => {
const expectedHelp = `Usage: scriptA [options] <pos1> <pos2> [pos3]
desc
Arguments:
pos1 desc5 [required]
pos2 desc6 [required]
pos3 desc8 (default: "default2")
Options:
-h, --help Show help
-V, --version Show version
--opt1 <string> desc1 [required]
--opt2 <number> desc2 [required]
--opt3 desc3 [required]
--opt4 <number> desc4 (default: 10)
`;
(0, test_util_1.expectExit0)(expectedHelp, () => {
(0, parser_1.parser)()
.name("scriptA")
.version("1.0.0")
.description("desc")
.options({
opt1: { type: zod_1.z.string().describe("desc1") },
opt2: { type: zod_1.z.number(), description: "desc2" },
opt3: {
type: zod_1.z.boolean().describe("dummy"),
description: "desc3",
},
opt4: {
type: zod_1.z.number().default(10).describe("desc4"),
},
})
.args([
{
name: "pos1",
description: "desc5",
type: zod_1.z.string(),
},
{
name: "pos2",
type: zod_1.z.string().describe("desc6"),
},
{
name: "pos3",
type: zod_1.z.string().default("default2").describe("desc7"),
description: "desc8",
},
])
._internalHandler((result) => {
expect(result).toEqual({
type: "help",
help: expect.stringContaining("Usage: scriptA"),
exitCode: 0,
});
if (result.type === "help") {
(0, expect_type_1.expectTypeOf)(result).toEqualTypeOf();
}
})
.parse(["--help"]);
});
});
});
describe("version", () => {
test("show specified version", () => {
(0, test_util_1.expectExit0)("1.1.1", () => {
(0, parser_1.parser)()
.name("scriptA")
.version("1.1.1")
.options({
opt1: { type: zod_1.z.string().describe("desc1") },
})
.args([])
._internalHandler((result) => {
expect(result).toEqual({
type: "version",
help: expect.stringContaining("Usage: scriptA"),
exitCode: 0,
});
(0, expect_type_1.expectTypeOf)(result).toEqualTypeOf();
})
.parse(["--version"]);
});
});
test("show none when version is not specified", () => {
(0, test_util_1.expectExit0)("none", () => {
(0, parser_1.parser)()
.name("scriptA")
.options({
opt1: { type: zod_1.z.string().describe("desc1") },
})
.args([])
.parse(["--version"]);
});
});
});
describe("unsupported zod types", () => {
test("zod literal is not supported", () => {
expect(() => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.literal(1) },
})
.parse(["--opt1", "1"]);
}).toThrowErrorMatchingSnapshot();
});
test("zod date is not supported", () => {
expect(() => {
(0, parser_1.parser)()
.options({
opt1: { type: zod_1.z.date() },
})
.parse(["--opt1", "2022-01-12T00:00:00.000Z"]);
}).toThrowError("Unsupported zod type: ZodDate");
});
});
// currently, custom handler is not supported and _internalHandler is internal function.
describe("_internalHandler()", () => {
test("match with expected result and type", () => {
const expectedResult = {
type: "match",
parsed: { opt1: "str1" },
help: expect.stringContaining("Usage: scriptNameA"),
};
const expectedType = {
opt1: expect.any(String),
};
(0, parser_1.parser)()
.name("scriptNameA")
.options({
opt1: { type: zod_1.z.string() },
})
._internalHandler((result) => {
expect(result).toEqual(expectedResult);
if (result.type === "match") {
(0, expect_type_1.expectTypeOf)(result.parsed).toEqualTypeOf(expectedType);
}
})
.parse(["--opt1", "str1"]);
});
test("internal parser error should return an error object with exit code 1", () => {
(0, test_util_1.expectProcessExit)("Invalid option: -invalid=10", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.options({
opt1: { type: zod_1.z.string() },
})
._internalHandler((result) => {
expect(result).toEqual({
type: "error",
error: new error_1.ParseError("Invalid option: -invalid=10"),
exitCode: 1,
help: expect.stringContaining("Usage: scriptNameA"),
});
if (result.type === "error") {
(0, expect_type_1.expectTypeOf)(result).toEqualTypeOf();
}
})
.parse(["-invalid=10"]);
});
});
test("internal validation error", () => {
(0, test_util_1.expectProcessExit)("Required option is missing: opt1", 1, () => {
(0, parser_1.parser)()
.name("scriptNameA")
.options({
opt1: { type: zod_1.z.string() },
})
._internalHandler((result) => {
expect(result).toEqual({
type: "error",
error: new error_1.ParseError("Required option is missing: opt1"),
exitCode: 1,
help: expect.stringContaining("Usage: scriptNameA"),
});
(0, expect_type_1.expectTypeOf)(result).toEqualTypeOf();
})
.parse([]);
});
});
});
describe("type test", () => {
const OptionParams = zod_1.z.object({
opt1: zod_1.z.string(),
opt2: zod_1.z.number().optional(),
pos1: zod_1.z.enum(["a", "b"]),
});
test("matches original zod type", () => {
const parsed = (0, parser_1.parser)()
.name("scriptA")
.version("1.0.0")
.description("desc")
.options({
opt1: { type: OptionParams.shape.opt1 },
opt2: { type: OptionParams.shape.opt2 },
})
.args([
{
name: "pos1",
type: OptionParams.shape.pos1,
},
])
.parse(["--opt1", "str1", "--opt2", "10", "a"]);
expect(parsed).toEqualTypeOf();
});
});
//# sourceMappingURL=parser.parse.test.js.map