UNPKG

zod-opts

Version:

node.js CLI option parser / validator using Zod

1,137 lines (1,134 loc) 48.2 kB
"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