From 5fe8c0fd9463c87004a5cd449ad30e0902a81399 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Sun, 19 Nov 2023 09:32:29 -0500 Subject: [PATCH] feat(@molt/command)!: zod as extension (#249) --- packages/@molt/command/README.md | 240 ++++++++++------ packages/@molt/command/examples/intro.ts | 6 +- .../@molt/command/examples/kitchen-sink.ts | 2 + packages/@molt/command/examples/prompt.ts | 2 + packages/@molt/command/examples/publish.ts | 2 + packages/@molt/command/package.json | 2 +- packages/@molt/command/src/Command/State.ts | 173 ------------ .../src/Command/exclusive/constructor.ts | 34 --- .../command/src/Command/exclusive/types.ts | 65 ----- .../command/src/Command/root/constructor.ts | 92 ------ .../@molt/command/src/Command/root/types.ts | 76 ----- .../src/CommandParameter/CommandParameter.ts | 84 ------ .../command/src/CommandParameter/index.ts | 1 - .../command/src/CommandParameter/input.ts | 39 --- .../command/src/CommandParameter/output.ts | 68 ++--- .../processor/parameterTypes/basic.ts | 36 --- .../processor/parameterTypes/exclusive.ts | 56 ---- .../src/CommandParameter/processor/process.ts | 35 --- packages/@molt/command/src/Errors/Errors.ts | 33 ++- packages/@molt/command/src/Help/Help.ts | 118 ++++---- .../OpeningArgs/Environment/Environment.ts | 9 +- .../command/src/OpeningArgs/Line/Line.ts | 9 +- .../command/src/OpeningArgs/OpeningArgs.ts | 7 +- .../@molt/command/src/OpeningArgs/helpers.ts | 11 +- .../@molt/command/src/OpeningArgs/types.ts | 34 +-- packages/@molt/command/src/Parameter/basic.ts | 50 ++++ .../@molt/command/src/Parameter/exclusive.ts | 92 ++++++ .../src/Parameter/helpers/CommandParameter.ts | 81 ++++++ .../helpers/environment.ts | 7 +- .../command/src/Parameter/helpers/types.ts | 9 + packages/@molt/command/src/Parameter/types.ts | 14 + .../@molt/command/src/Settings/settings.ts | 22 +- packages/@molt/command/src/Type/helpers.ts | 4 +- .../command/src/Type/types/Scalars/Boolean.ts | 1 + .../src/Type/types/Scalars/Enumeration.ts | 1 + .../command/src/Type/types/Scalars/Literal.ts | 1 + .../command/src/Type/types/Scalars/Number.ts | 1 + .../command/src/Type/types/Scalars/String.ts | 1 + .../command/src/TypeAdaptors/TypeAdaptors.ts | 1 - .../@molt/command/src/TypeAdaptors/index.ts | 1 - .../command/src/TypeAdaptors/zod/index.ts | 1 - .../command/src/TypeAdaptors/zod/types.ts | 51 ---- .../command/src/_entrypoints/extensions.ts | 1 + .../{entrypoints => _entrypoints}/types.ts | 0 .../src/builders/command/constructor.ts | 109 +++++++ .../command/src/builders/command/state.ts | 194 +++++++++++++ .../command/src/builders/command/types.ts | 70 +++++ .../src/builders/exclusive/constructor.ts | 58 ++++ .../command/src/builders/exclusive/state.ts | 27 ++ .../command/src/builders/exclusive/types.ts | 58 ++++ packages/@molt/command/src/eventPatterns.ts | 13 +- .../src/executor/helpers/createParameters.ts | 39 +++ packages/@molt/command/src/executor/parse.ts | 27 +- packages/@molt/command/src/executor/prompt.ts | 3 +- .../{CommandParameter => executor}/types.ts | 2 - packages/@molt/command/src/extension.ts | 27 ++ .../zodHelpers => extensions/zod}/guards.ts | 0 .../zod/typeAdaptor/runtime.ts} | 74 ++--- .../src/extensions/zod/typeAdaptor/types.ts | 36 +++ .../zod.ts => extensions/zod/types.ts} | 15 +- .../@molt/command/src/extensions/zod/zod.ts | 18 ++ packages/@molt/command/src/helpers.ts | 24 +- packages/@molt/command/src/index_.ts | 2 +- .../@molt/command/src/lib/zodHelpers/index.ts | 1 - .../command/src/lib/zodHelpers/index_.ts | 88 ------ packages/@molt/command/tests/_/helpers.ts | 5 + .../command/tests/chaining/chaining.spec.ts | 19 +- .../command/tests/environment/case.spec.ts | 7 +- .../tests/environment/global/prefix.spec.ts | 29 +- .../tests/environment/global/toggling.spec.ts | 21 +- .../command/tests/environment/parsing.spec.ts | 31 +- .../environment/selective/prefix.spec.ts | 9 +- .../environment/selective/toggling.spec.ts | 24 +- .../tests/environment/types/boolean.spec.ts | 5 +- .../@molt/command/tests/exclusive.spec.ts | 103 ++++--- .../@molt/command/tests/help/help.spec.ts | 17 +- .../command/tests/help/rendering.spec.ts | 215 ++++++-------- .../@molt/command/tests/line/builder.spec.ts | 8 +- .../@molt/command/tests/line/name.spec.ts | 32 +-- .../command/tests/line/types/boolean.spec.ts | 28 +- .../command/tests/line/types/enum.spec.ts | 13 +- .../command/tests/line/types/number.spec.ts | 18 +- .../command/tests/line/types/string.spec.ts | 17 +- .../parameters/configurationObject.spec.ts | 15 +- .../tests/parameters/description.spec.ts | 25 +- .../tests/parameters/unionType.spec.ts | 48 ++-- .../tests/parameters/zod/nativeEnum.spec.ts | 5 +- .../tests/parameters/zod/number.spec.ts | 5 +- .../tests/parameters/zod/static.spec.ts | 38 +-- .../tests/parameters/zod/string.spec.ts | 7 +- .../@molt/command/tests/prompt/prompt.spec.ts | 266 ++++++++---------- .../command/tests/prompt/settings.spec.ts | 36 +-- scripts/release.ts | 2 + 93 files changed, 1706 insertions(+), 1800 deletions(-) delete mode 100644 packages/@molt/command/src/Command/State.ts delete mode 100644 packages/@molt/command/src/Command/exclusive/constructor.ts delete mode 100644 packages/@molt/command/src/Command/exclusive/types.ts delete mode 100644 packages/@molt/command/src/Command/root/constructor.ts delete mode 100644 packages/@molt/command/src/Command/root/types.ts delete mode 100644 packages/@molt/command/src/CommandParameter/CommandParameter.ts delete mode 100644 packages/@molt/command/src/CommandParameter/index.ts delete mode 100644 packages/@molt/command/src/CommandParameter/input.ts delete mode 100644 packages/@molt/command/src/CommandParameter/processor/parameterTypes/basic.ts delete mode 100644 packages/@molt/command/src/CommandParameter/processor/parameterTypes/exclusive.ts delete mode 100644 packages/@molt/command/src/CommandParameter/processor/process.ts create mode 100644 packages/@molt/command/src/Parameter/basic.ts create mode 100644 packages/@molt/command/src/Parameter/exclusive.ts create mode 100644 packages/@molt/command/src/Parameter/helpers/CommandParameter.ts rename packages/@molt/command/src/{CommandParameter/processor => Parameter}/helpers/environment.ts (77%) create mode 100644 packages/@molt/command/src/Parameter/helpers/types.ts create mode 100644 packages/@molt/command/src/Parameter/types.ts delete mode 100644 packages/@molt/command/src/TypeAdaptors/TypeAdaptors.ts delete mode 100644 packages/@molt/command/src/TypeAdaptors/index.ts delete mode 100644 packages/@molt/command/src/TypeAdaptors/zod/index.ts delete mode 100644 packages/@molt/command/src/TypeAdaptors/zod/types.ts create mode 100644 packages/@molt/command/src/_entrypoints/extensions.ts rename packages/@molt/command/src/{entrypoints => _entrypoints}/types.ts (100%) create mode 100644 packages/@molt/command/src/builders/command/constructor.ts create mode 100644 packages/@molt/command/src/builders/command/state.ts create mode 100644 packages/@molt/command/src/builders/command/types.ts create mode 100644 packages/@molt/command/src/builders/exclusive/constructor.ts create mode 100644 packages/@molt/command/src/builders/exclusive/state.ts create mode 100644 packages/@molt/command/src/builders/exclusive/types.ts create mode 100644 packages/@molt/command/src/executor/helpers/createParameters.ts rename packages/@molt/command/src/{CommandParameter => executor}/types.ts (89%) create mode 100644 packages/@molt/command/src/extension.ts rename packages/@molt/command/src/{lib/zodHelpers => extensions/zod}/guards.ts (100%) rename packages/@molt/command/src/{TypeAdaptors/zod/zod.ts => extensions/zod/typeAdaptor/runtime.ts} (62%) create mode 100644 packages/@molt/command/src/extensions/zod/typeAdaptor/types.ts rename packages/@molt/command/src/{CommandParameter/zod.ts => extensions/zod/types.ts} (52%) create mode 100644 packages/@molt/command/src/extensions/zod/zod.ts delete mode 100644 packages/@molt/command/src/lib/zodHelpers/index.ts delete mode 100644 packages/@molt/command/src/lib/zodHelpers/index_.ts diff --git a/packages/@molt/command/README.md b/packages/@molt/command/README.md index 506ce2e6..8f2e986e 100644 --- a/packages/@molt/command/README.md +++ b/packages/@molt/command/README.md @@ -7,24 +7,25 @@ - [Installation](#installation) - [Example](#example) - [Features](#features) - - [⛑ Type safe](#%E2%9B%91-type-safe) - - [👘 Expressive parameter specification](#%F0%9F%91%98-expressive-parameter-specification) - - [📖 Autogenerated help](#%F0%9F%93%96-autogenerated-help) + - [⛑ Type Safe](#%E2%9B%91-type-safe) + - [👘 Expressive Parameter Specification](#%F0%9F%91%98-expressive-parameter-specification) + - [📖 Autogenerated Help](#%F0%9F%93%96-autogenerated-help) + - [🧩 Extensible](#%F0%9F%A7%A9-extensible) - [Video Introduction](#video-introduction) - [Docs](#docs) + - [Immutability](#immutability) - [Parameter Naming](#parameter-naming) - - [Property Name Syntax](#property-name-syntax) - [Flag Syntax](#flag-syntax) - [Short, Long, & Aliasing](#short-long--aliasing) - [Kebab / Camel Case](#kebab--camel-case) - - [Type Safety](#type-safety) + - [Duplicate Detection](#duplicate-detection) + - [Reserved Names](#reserved-names) + - [Internal Canonical Form](#internal-canonical-form) - [Parameter Types](#parameter-types) - - [Zod Support](#zod-support) - [Boolean](#boolean) - [String](#string) - [Number](#number) - [Enum](#enum) - - [Native Enum](#native-enum) - [Union](#union) - [Parameter Prompts](#parameter-prompts) - [Overview](#overview) @@ -45,12 +46,14 @@ - [Case Insensitive](#case-insensitive) - [Validation](#validation) - [Mutually Exclusive Parameters](#mutually-exclusive-parameters) - - [Type Safety](#type-safety-1) + - [Type Safety](#type-safety) - [Optional](#optional) - [Default](#default) - [Autogenerated Documentation](#autogenerated-documentation) - [Description](#description) - [Settings](#settings) +- [Extensions](#extensions) + - [Zod](#zod) - [Recipes](#recipes) - [Optional Argument With Default Behavior](#optional-argument-with-default-behavior) - [Architecture](#architecture) @@ -60,16 +63,18 @@ ## Installation ``` -npm add @molt/command zod +npm add @molt/command ``` ## Example ```ts -import { Command } from '../src/index.js' +import { Command } from '@molt/command' +import { Zod } from '@molt/command/extensions' import { z } from 'zod' const args = Command.create() + .use(Zod) .parameter(`filePath`, z.string().describe(`Path to the file to convert.`)) .parameter(`to`, z.enum([`json`, `yaml`, `toml`]).describe(`Format to convert to.`)) .parameter( @@ -104,11 +109,13 @@ $ mybin --help ## Features -#### ⛑ Type safe +#### ⛑ Type Safe -#### 👘 Expressive parameter specification +#### 👘 Expressive Parameter Specification -- Automatic casting and validation based on specified Zod types. +- Types that determine value parsing (casting) +- Validations +- Descriptions - Normalization between camel and kebab case and optional dash prefix: ```ts @@ -123,13 +130,13 @@ $ mybin --help args3.doItC ``` -- Specify one or multiple (aka. aliases) short and long flags: +- One or multiple short and/or long names: ```ts Command.create().parameter('-f --force --forcefully', z.boolean()).parse() ``` -- Use Zod `.default(...)` method for setting default values. +- Default values: ```ts const args = Command.create().parameter('--path', z.string().default('./a/b/c')).parse() @@ -137,32 +144,42 @@ $ mybin --help args.path === '/over/ride' // $ mybin --path /over/ride ``` -- Define [mutually exclusive parameters](#mutually-exclusive-parameters) declaratively. - -- Use Zod `.describe(...)` to add parameter descriptions in autogenerated docs. +- [mutually exclusive parameters](#mutually-exclusive-parameters) -- 🤾‍♀️ Expressive argument passing +- flexible argument passing - - Pass via environment variables (customizable) + - via environment variables (customizable) ```ts const args = Command.create().parameter('--path', z.string()).parse() args.path === './a/b/c/' // $ CLI_PARAM_PATH='./a/b/c' mybin ``` - - Kebab or camel case flags + - via flags in Kebab or camel case ``` $ mybin --do-it $ mybin --doIt ``` - Parameter stacking e.g. `mybin -abc` instead of `mybin -a -b -c` - - Separator of `=` or space, e.g. `mybin -a=foo -b=bar` or `mybin -a foo -b bar` + - Separator symbol of equals or space + ``` + $ mybin -a=foo -b=bar + $ mybin -a foo -b bar + ``` -#### 📖 Autogenerated help +#### 📖 Autogenerated Help ![doc](../../../assets/example-doc-mx-params.png) ![doc](../../../assets/example-doc-kitchen-sink.png) +#### 🧩 Extensible + +- Extend Molt Command with new capabilities tailored to your use-case +- Zod Extension + - Use `z.string()`, `z.boolean` etc. + - `.describe(...)` to add description + - `.default(...)` to set default value + ## Video Introduction A [video introduction](https://www.youtube.com/watch?v=UtXM4FKCUDo) if you like that format: @@ -171,37 +188,48 @@ A [video introduction](https://www.youtube.com/watch?v=UtXM4FKCUDo) if you like ## Docs -### Parameter Naming +### Immutability -#### Property Name Syntax +Molt Command is a fluent API which means methods return a central object of methods allowing you to chain methods one after another. -You can define parameters as a zod object schema using regular property names. These are flags for your CLI but arguments can also be passed by environment variables so in a way this is a neutral form that doesn't privilege either argument passing mechanism. +Each method in the chain returns a _new version_ of the chain. So for example let's say you had two scripts that had some overlapping requirements. You could factor those requirements out into a common base chain. ```ts -const args = Command.create() - .parameter('foo', z.string()) - .parameter('bar', z.number()) - .parameter('qux', z.boolean()) - .parse() +// helpers.ts +import { Command } from '@molt/command' +import { Zod } from '@molt/command/extensions' -args.foo -args.bar -args.qux +export const command = Command.create() + .use(Zod) + .parameter('woof', z.enum(['soft', 'loud', 'deafening'])) +``` + +```ts +// foo.ts +import { command } from './helpers.ts' + +const args = command.parameter('bravo', z.boolean()) ``` +```ts +// bar.ts +const args = command.parameter('charlie', z.number()) +``` + +### Parameter Naming + #### Flag Syntax -You can also define them using flag syntax if you prefer. Thanks to `@molt/types` this style doesn't sacrifice any type safety. +You can define parameters using dash prefixes (flag syntax). ```ts +// prettier-ignore const args = Command.create() - .parameter('--foo', z.string()) - .parameter('--bar', z.number()) - .parameter('--qux', z.boolean()) + .parameter('--foo -f', z.string()) + .parameter('qux q', z.string()) .parse() args.foo -args.bar args.qux ``` @@ -209,11 +237,6 @@ args.qux You can give your parameters short and long names, as well as aliases. -A set of parameter names gets normalized into its canonical name internally (again thanks to `@molt/types` this is all represented statically as well). The canonical name choice is as follows: - -1. The first long flag -2. Otherwise the first short flag - ```ts const args = Command.create() .parameter('--foobar --foo -f ', z.string()) @@ -244,10 +267,15 @@ const args = Command.create() #### Kebab / Camel Case -You can use kebab or camel case (and likewise your users can pass flags in either style). Canonical form internally uses camel case. +You can use kebab or camel case. ```ts -const args = Command.create().parameter('foo-bar', z.string()).parameter('quxLot', z.string()).parse() +const args = Command.create() + .parameter('--foo-bar', z.string()) + .parameter('--quxLot', z.string()) + .parameter('foo-bar-2', z.string()) + .parameter('quxLot2', z.string()) + .parse() // $ mybin --foo-bar moo --qux-lot zoo // $ mybin --fooBar moo --quxLot zoo @@ -255,62 +283,67 @@ args.fooBar === 'moo' args.quxLot === 'zoo' ``` -#### Type Safety +#### Duplicate Detection -Duplicate parameter names will be caught statically via TypeScript. +Duplicate parameter names will be caught statically via TypeScript. The following shows examples of the many forms duplication can happen. Note how case and syntax are not contributing factors to uniqueness. ```ts const args = Command.create() - .parameter('f foo bar', z.string()) - .parameter('bar', z.string()) // <-- TS error: already taken - .parameter('f', z.string()) // <-- TS error: already taken - .parameter('foo', z.string()) // <-- TS error: already taken - .parameter('help', z.string()) // <-- TS error: reserved name - .parameter('h', z.string()) // <-- TS error: reserved name + .parameter('f foo barBar', z.string()) + .parameter('bar-bar', z.string()) // <-- TS error: already taken + .parameter('f', z.string()) // <-- TS error: already taken + .parameter('--foo', z.string()) // <-- TS error: already taken .parse() ``` -### Parameter Types - -This section covers how parameters are typed via a subset of Zod schemas and the types used will affect parsing. +#### Reserved Names -#### Zod Support - -Only the following set of Zod types are supported. In addition to the docs below, static typing will raise an error if you pass in something invalid. +Some names are reserved for use by default. ```ts -z.boolean() -z.string() -z.enum(['...', '...']) -z.nativeEnum({'...':'...', '...':'...'}) -z.number() -z.union([...]) // where ... can be any other type above. +const args = Command.create() + .parameter('help', z.string()) // <-- TS error: reserved name + .parameter('h', z.string()) // <-- TS error: reserved name + .parse() ``` -Validation methods like `.min(1)` and `.regex(/.../)` should all just work. +#### Internal Canonical Form -The following modifiers are accepted: +Internally, the canonical form of a parameter name is the representation that will be used whenever that name has to be referenced. Primarily this means the names you will find on the arguments returned from `.parse`. -```ts -.optional() -.default(...) -``` +The algorithm is: -If both `optional` and `default` are used then `default` takes precedence. -The `describe` method is used for adding docs. It can show up in any part of the chain. All the following are fine: +1. The first long name, else first short +2. Stripped of flag syntax +3. Using camel case + +The following shows an exaggerated example of how the many permutations normalize. ```ts -z.string().describe('...').optional() -z.string().optional().describe('...') -z.string().min(1).describe('...').optional() +const args = Command.create() + .parameter('--foo-foo f', z.string()) + .parameter('-q quxQux', z.string()) + .parameter('a b c --a-b-c', z.string()) + .parameter('x', z.string()) + .parameter('-z', z.string()) + .parse() + +args.fooFoo +args.quxQux +args.ABC +args.x +args.z ``` +### Parameter Types + +This section covers the different kinds of built-in types and how they affect argument parsing. + #### Boolean - Flag does not accept any arguments. - Environment variable accepts `"true"` or `"1"` for `true` and `"false"` or `"0"` for `false`. - Negated form of parameters automatically accepted. -- [Zod docs](https://github.com/colinhacks/zod#booleans) Examples: @@ -344,7 +377,6 @@ args.force === true #### String - Flag expects an argument. -- [Zod docs](https://github.com/colinhacks/zod#strings) ##### Transformations @@ -381,7 +413,6 @@ args.force === true - Flag expects an argument. - Argument is cast via the `Number()` function. -- [Zod docs](https://github.com/colinhacks/zod#numbers) ##### Validations @@ -392,12 +423,6 @@ args.force === true #### Enum -- Flag expects an argument. -- [Zod docs](https://github.com/colinhacks/zod#zod-enums) - -#### Native Enum - -- [Zod docs](https://github.com/colinhacks/zod#native-enums) - Flag expects an argument. #### Union @@ -424,8 +449,6 @@ args.force === true Command.create().parameter('xee', z.union([z.string(), z.number()])) ``` -- [Zod docs](https://github.com/colinhacks/zod#unions) - ##### Help Rendering - By default help rendering will render something like so: @@ -1088,6 +1111,47 @@ Settings documentation is not co-located. Documentation for various features wil Command.create().settings({...}) ``` +## Extensions + +### Zod + +The `Zod` extension lets you use Zod schemas wherever types are accepted. + +To use this extension you must install `zod` into your project: + +``` +npm add zod +``` + +The supported Zod types and their mapping is as follows: + +| Zod | Molt Type | +| --------------------------------------------------------------------------------- | --------------------- | +| [`boolean`](https://github.com/colinhacks/zod#booleans) | [`boolean`](#boolean) | +| [`string`](https://github.com/colinhacks/zod#strings) | [`string`](#string) | +| [`number`](https://github.com/colinhacks/zod#numbers) | [`number`](#number) | +| [`enum`](https://github.com/colinhacks/zod#zod-enums) | [`enum`](#enum) | +| [`nativeEnum`](https://github.com/colinhacks/zod#native-enums) | [`enum`](#enum) | +| [`union`](https://github.com/colinhacks/zod#unions) (of any other Zod type above) | [`union`](#union) | + +All validation methods are accepted (`.min(1)`, `.regex(/.../)`, `.cuid()`, ...). + +The following modifiers are accepted: + +```ts +.optional() +.default(...) +``` + +If both `optional` and `default` are used then `default` takes precedence. +The `describe` method is used for adding docs. It can show up in any part of the chain. All the following are fine: + +```ts +z.string().describe('...').optional() +z.string().optional().describe('...') +z.string().min(1).describe('...').optional() +``` + ## Recipes ### Optional Argument With Default Behavior diff --git a/packages/@molt/command/examples/intro.ts b/packages/@molt/command/examples/intro.ts index 76d86910..e22da9d0 100644 --- a/packages/@molt/command/examples/intro.ts +++ b/packages/@molt/command/examples/intro.ts @@ -1,12 +1,12 @@ +import { Zod } from '../src/extensions/zod/zod.js' import { Command } from '../src/index.js' import { z } from 'zod' const args = await Command.create() + .use(Zod) .parameter(`filePath`, z.string().describe(`Path to the file to convert.`)) .parameter(`to`, z.enum([`json`, `yaml`, `toml`]).describe(`Format to convert to.`)) - .parameter(`from`, { - schema: z.enum([`json`, `yaml`, `toml`]).optional(), - }) + .parameter(`from`, z.enum([`json`, `yaml`, `toml`]).optional()) .parameter( `verbose v`, z.boolean().default(false).describe(`Log detailed progress as conversion executes.`), diff --git a/packages/@molt/command/examples/kitchen-sink.ts b/packages/@molt/command/examples/kitchen-sink.ts index 5089211a..5e66cf8d 100644 --- a/packages/@molt/command/examples/kitchen-sink.ts +++ b/packages/@molt/command/examples/kitchen-sink.ts @@ -1,7 +1,9 @@ +import { Zod } from '../src/_entrypoints/extensions.js' import { Command } from '../src/index.js' import { z } from 'zod' const args = Command.create() + .use(Zod) .description( `This is a so-called "kitchen-sink" Molt Command example. Many features are demonstrated here though the overall CLI itself makes no sense. Take a look around, see how the help renders, try running with different inputs, etc.`, ) diff --git a/packages/@molt/command/examples/prompt.ts b/packages/@molt/command/examples/prompt.ts index 13131431..87f4670f 100644 --- a/packages/@molt/command/examples/prompt.ts +++ b/packages/@molt/command/examples/prompt.ts @@ -1,7 +1,9 @@ +import { Zod } from '../src/_entrypoints/extensions.js' import { Command } from '../src/index.js' import { z } from 'zod' const args = await Command.create() + .use(Zod) // required .parameter(`alpha`, z.string()) .parameter(`bravo`, z.number()) diff --git a/packages/@molt/command/examples/publish.ts b/packages/@molt/command/examples/publish.ts index 72e5cbca..8b6f749a 100644 --- a/packages/@molt/command/examples/publish.ts +++ b/packages/@molt/command/examples/publish.ts @@ -1,8 +1,10 @@ +import { Zod } from '../src/_entrypoints/extensions.js' import { Command } from '../src/index.js' import semverRegex from 'semver-regex' import { z } from 'zod' const args = Command.create() + .use(Zod) .parameter(`githubToken`, z.string()) .parameter(`publish`, z.boolean().default(true)) .parameter(`githubRelease`, z.boolean().default(true)) diff --git a/packages/@molt/command/package.json b/packages/@molt/command/package.json index 877962e5..38f31d04 100644 --- a/packages/@molt/command/package.json +++ b/packages/@molt/command/package.json @@ -36,7 +36,7 @@ }, "./types": { "import": { - "types": "./build/esm/entrypoints/types.d.ts" + "types": "./build/esm/_entrypoints/types.d.ts" } } }, diff --git a/packages/@molt/command/src/Command/State.ts b/packages/@molt/command/src/Command/State.ts deleted file mode 100644 index de214747..00000000 --- a/packages/@molt/command/src/Command/State.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { CommandParameter } from '../CommandParameter/index.js' -import type { Values } from '../helpers.js' -import type { Type } from '../Type/index.js' -import type { TypeAdaptors } from '../TypeAdaptors/index.js' -import type { ExclusiveParameterConfiguration } from './exclusive/types.js' -import type { ParameterConfiguration } from './root/types.js' -import type { Name } from '@molt/types' -import type { Simplify } from 'type-fest' - -export namespace State { - export interface BaseEmpty extends Base { - IsPromptEnabled: false - ParametersExclusive: {} // eslint-disable-line - Parameters: {} // eslint-disable-line - } - - export type Base = { - IsPromptEnabled: boolean - ParametersExclusive: { - [label: string]: { - Optional: boolean - Parameters: { - [canonicalName: string]: { - NameParsed: Name.Data.NameParsed - NameUnion: string - Type: Type.Type - Schema: CommandParameter.SomeBasicType - } - } - } - } - Parameters: { - [nameExpression: string]: { - NameParsed: Name.Data.NameParsed - NameUnion: string - Type: Type.Type - Schema: CommandParameter.SomeBasicType - } - } - } - - type ReservedParameterNames = 'help' | 'h' - - // prettier-ignore - export type ValidateNameExpression = - Name.Data.IsParseError; reservedNames: ReservedParameterNames }>> extends true - ? Name.Parse; reservedNames: ReservedParameterNames }> - : NameExpression - - export type GetUsedNames = Values['NameUnion'] - - // // prettier-ignore - // export type AddParameter< - // State extends Base, - // NameExpression extends string, - // Configuration extends ParameterConfiguration - // > = - // Omit & { - // Parameters: State['Parameters'] & { [_ in NameExpression]: CreateParameter } - // } - - export type ParametersSchemaObjectBase = Record - - export type ParametersConfigBase = Record< - string, - { - schema: ParameterConfiguration['schema'] - prompt?: CommandParameter.Input.Prompt - } - > - - // prettier-ignore - export type SetExclusiveOptional< - State extends Base, - Label extends string, - Value extends boolean, - > = { - IsPromptEnabled: State['IsPromptEnabled'] - Parameters: State['Parameters'] - ParametersExclusive: Omit & - { - [_ in Label]: { - Optional: Value - Parameters: State['ParametersExclusive'][_]['Parameters'] - } - } - } - // prettier-ignore - export type AddExclusiveParameter< - State extends Base, - Label extends string, - NameExpression extends string, - Configuration extends ExclusiveParameterConfiguration - > = - MergeIntoProperty>]: { - Schema: Configuration['schema'] - Type: TypeAdaptors.Zod.FromZod - NameParsed: Name.Parse; reservedNames: ReservedParameterNames }> - NameUnion: Name.Data.GetNamesFromParseResult< - Name.Parse; reservedNames: ReservedParameterNames }> - > - } - - } - } - }> - - // prettier-ignore - export type CreateParameter< - State extends Base, - NameExpression extends string, - Configuration extends ParameterConfiguration, - > = { - Schema: Configuration['schema'] - Type: TypeAdaptors.Zod.FromZod - NameParsed: Name.Parse; reservedNames: ReservedParameterNames }> - NameUnion: Name.Data.GetNamesFromParseResult; reservedNames: ReservedParameterNames }>> - } - - // prettier-ignore - export type ToArgs = - State['IsPromptEnabled'] extends true - ? Promise> - : ToArgs_ - - // prettier-ignore - type ToArgs_ = - Simplify< - { - [Name in keyof State['Parameters'] & string as State['Parameters'][Name]['NameParsed']['canonical']]: - Type.Infer - } & - { - [Label in keyof State['ParametersExclusive'] & string]: - | Simplify - } - }>> - | (State['ParametersExclusive'][Label]['Optional'] extends true ? undefined : never) - } - > - - // prettier-ignore - export type ToSchema = { - [K in keyof Spec['Parameters'] & string as Spec['Parameters'][K]['NameParsed']['canonical']]: - Spec['Parameters'][K]['Schema'] - } -} - -/** - * @see https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type - */ -// eslint-disable-next-line -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void - ? I - : never - -type SetProperty = Omit & { - [P in PropertyName]: Value -} - -type MergeIntoProperty = SetProperty< - Obj, - PropertyName, - Obj[PropertyName] & Value -> diff --git a/packages/@molt/command/src/Command/exclusive/constructor.ts b/packages/@molt/command/src/Command/exclusive/constructor.ts deleted file mode 100644 index 28e257f2..00000000 --- a/packages/@molt/command/src/Command/exclusive/constructor.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { CommandParameter } from '../../CommandParameter/index.js' -import type { Pam } from '../../lib/Pam/index.js' -import type { InternalState, SomeBuilderExclusiveInitial } from './types.js' - -export const create = (): SomeBuilderExclusiveInitial => { - const _: InternalState = { - input: { - _tag: `Exclusive`, - optionality: { _tag: `required` }, - parameters: [], - } satisfies CommandParameter.Input.Exclusive, - typeState: undefined as any, // eslint-disable-line - } - - const chain: SomeBuilderExclusiveInitial = { - parameter: (nameExpression: string, schemaOrConfiguration) => { - const configuration = - `schema` in schemaOrConfiguration ? schemaOrConfiguration : { schema: schemaOrConfiguration } - _.input.parameters.push({ nameExpression, type: configuration.schema }) - return chain as any // eslint-disable-line - }, - optional: () => { - _.input.optionality = { _tag: `optional` } - return chain - }, - default: (tag: string, value: Pam.Value) => { - _.input.optionality = { _tag: `default`, tag, value } - return chain - }, - _, - } - - return chain -} diff --git a/packages/@molt/command/src/Command/exclusive/types.ts b/packages/@molt/command/src/Command/exclusive/types.ts deleted file mode 100644 index 276b20e0..00000000 --- a/packages/@molt/command/src/Command/exclusive/types.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { CommandParameter } from '../../CommandParameter/index.js' -import type { TypeAdaptors } from '../../TypeAdaptors/index.js' -import type { RawArgInputs } from '../root/types.js' -import type { State } from '../State.js' -import type { z } from 'zod' - -export interface ExclusiveParameterConfiguration { - schema: CommandParameter.SomeExclusiveZodType -} - -// prettier-ignore -interface Parameter { - (name: State.ValidateNameExpression, configuration: Configuration): BuilderExclusiveInitial, Label> - (name: State.ValidateNameExpression, schema: Schema ): BuilderExclusiveInitial}>, Label> -} - -/** - * This property is present to support internal functions. It is not intended to be used by you. - */ -export type InternalState = { - /** - * Used for build time. Type inference functionality. - */ - typeState: State - /** - * Used for runtime. - */ - input: CommandParameter.Input.Exclusive -} - -// prettier-ignore -export interface BuilderExclusiveInitial { - _: InternalState - parameter: Parameter - optional: () => BuilderExclusiveAfterOptional> - default: (tag: Tag, value: z.infer) => BuilderExclusiveAfterDefault> -} - -export type BuilderExclusiveAfterOptional = { - _: InternalState -} - -export type BuilderExclusiveAfterDefault = { - _: InternalState -} - -export interface BuilderAfterSettings { - parse: (inputs?: RawArgInputs) => State.ToArgs -} - -export interface SomeParameter { - (nameExpression: any, type: CommandParameter.SomeExclusiveZodType): any // eslint-disable-line - (nameExpression: any, configuration: ExclusiveParameterConfiguration): any // eslint-disable-line -} - -export type SomeBuilderExclusiveInitial = { - _: any // eslint-disable-line - parameter: SomeParameter - optional: any // eslint-disable-line - default: (tag: any, value: any) => any // eslint-disable-line -} - -export type SomeBuilderMutuallyExclusiveAfterOptional = BuilderExclusiveAfterOptional - -export type SomeBuilderExclusive = SomeBuilderExclusiveInitial | SomeBuilderMutuallyExclusiveAfterOptional diff --git a/packages/@molt/command/src/Command/root/constructor.ts b/packages/@molt/command/src/Command/root/constructor.ts deleted file mode 100644 index 915f2911..00000000 --- a/packages/@molt/command/src/Command/root/constructor.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { CommandParameter } from '../../CommandParameter/index.js' -import { parse } from '../../executor/parse.js' -import { getLowerCaseEnvironment, lowerCaseObjectKeys } from '../../helpers.js' -import { Settings } from '../../Settings/index.js' -import { TypeAdaptors } from '../../TypeAdaptors/index.js' -import * as ExclusiveBuilder from '../exclusive/constructor.js' -import type { ParameterConfiguration, RawArgInputs, RootBuilder } from './types.js' - -export const create = (): RootBuilder => { - const $: State = { - newSettingsBuffer: [], - settings: null, - parameterSpecInputs: {}, - } - - const $$ = { - addParameterBasic: (nameExpression: string, configuration: ParameterConfiguration) => { - const prompt = configuration.prompt ?? null - const parameter = { - _tag: `Basic`, - type: configuration.schema, - nameExpression: nameExpression, - prompt, - } satisfies CommandParameter.Input.Basic - $.parameterSpecInputs[nameExpression] = parameter - }, - } - - const chain: InternalRootBuilder = { - description: (description) => { - $.newSettingsBuffer.push({ - description, - }) - return chain - }, - settings: (newSettings) => { - $.newSettingsBuffer.push(newSettings) - return chain - }, - parameter: (nameExpression, typeOrConfiguration) => { - const configuration = - `schema` in typeOrConfiguration - ? typeOrConfiguration - : { schema: typeOrConfiguration, type: TypeAdaptors.Zod.fromZod(typeOrConfiguration) } - $$.addParameterBasic(nameExpression, configuration) - - return chain - }, - parametersExclusive: (label, builderContainer) => { - $.parameterSpecInputs[label] = builderContainer(ExclusiveBuilder.create() as any)._.input // eslint-disable-line - return chain - }, - parse: (argInputs) => { - const argInputsEnvironment = argInputs?.environment - ? lowerCaseObjectKeys(argInputs.environment) - : getLowerCaseEnvironment() - $.settings = { - ...Settings.getDefaults(argInputsEnvironment), - } - $.newSettingsBuffer.forEach((newSettings) => - Settings.change($.settings!, newSettings, argInputsEnvironment), - ) - return parse($.settings, $.parameterSpecInputs, argInputs) - }, - } - - return chain as any -} - -// -// Internal Types -// - -interface State { - settings: null | Settings.Output - newSettingsBuffer: Settings.Input[] - parameterSpecInputs: Record -} - -interface Parameter { - (nameExpression: string, type: CommandParameter.SomeBasicType): InternalRootBuilder - (nameExpression: string, configuration: ParameterConfiguration): InternalRootBuilder -} - -// prettier-ignore -interface InternalRootBuilder { - description: (description: string) => InternalRootBuilder - settings: (newSettings: Settings.Input) => InternalRootBuilder - parameter: Parameter - parametersExclusive: (label: string, builderContainer: any) => InternalRootBuilder - parse: (args: RawArgInputs) => object -} diff --git a/packages/@molt/command/src/Command/root/types.ts b/packages/@molt/command/src/Command/root/types.ts deleted file mode 100644 index c9de0b24..00000000 --- a/packages/@molt/command/src/Command/root/types.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { CommandParameter } from '../../CommandParameter/index.js' -import type { Prompter } from '../../lib/Prompter/Prompter.js' -import type { OpeningArgs } from '../../OpeningArgs/index.js' -import type { Settings } from '../../Settings/index.js' -import type { TypeAdaptors } from '../../TypeAdaptors/index.js' -import type { - BuilderAfterSettings, - BuilderExclusiveInitial, - SomeBuilderExclusive, -} from '../exclusive/types.js' -// eslint-disable-next-line -import { State } from '../State.js' - -export type Schema = CommandParameter.SomeBasicType | CommandParameter.SomeUnionType - -export interface ParameterConfiguration { - schema: Schema - prompt?: CommandParameter.Input.Prompt> -} - -export type IsHasKey = Key extends keyof Obj ? true : false - -// prettier-ignore -export type IsPromptEnabledInParameterSettings

= - IsHasKey extends false ? false : - IsPromptEnabled -// prettier-ignore -export type IsPromptEnabledInCommandSettings

> = - IsHasKey extends false ? false : - IsPromptEnabled - -// prettier-ignore -export type IsPromptEnabled

|undefined> = - P extends undefined ? false : - P extends false ? false : - P extends true ? true : - P extends null ? false : - Exclude['enabled'] extends false ? false : - true - -// prettier-ignore -export interface RootBuilder { - s: State - description (this:void, description:string): - RootBuilder - parameter (this:void, name:State.ValidateNameExpression, configuration:Configuration): - RootBuilder<{ - IsPromptEnabled : State['IsPromptEnabled'] extends true ? true : IsPromptEnabledInParameterSettings - ParametersExclusive: State['ParametersExclusive'] - Parameters : State['Parameters'] & { [_ in NameExpression]: State.CreateParameter } - }> - parameter (this:void, name:State.ValidateNameExpression, schema:S): - RootBuilder<{ - IsPromptEnabled : State['IsPromptEnabled'] - ParametersExclusive: State['ParametersExclusive'] - Parameters : State['Parameters'] & { [_ in NameExpression]: State.CreateParameter}> } - }> - parametersExclusive

> = + IsHasKey extends false ? false : + IsPromptEnabled +// prettier-ignore +export type IsPromptEnabledInCommandSettings

> = + IsHasKey extends false ? false : + IsPromptEnabled + +// prettier-ignore +export type IsPromptEnabled

|undefined> = + P extends undefined ? false : + P extends false ? false : + P extends true ? true : + P extends null ? false : + Exclude['enabled'] extends false ? false : + true + +// prettier-ignore +export interface CommandBuilder<$State extends BuilderCommandState.Base = BuilderCommandState.BaseEmpty> { + use<$Extension extends SomeExtension>(extension: $Extension): + CommandBuilder<{ + IsPromptEnabled: $State['IsPromptEnabled'], + Parameters: $State['Parameters'], + ParametersExclusive: $State['ParametersExclusive'], + Type: $Extension['types']['type'] + TypeMapper: $Extension['types']['typeMapper'] + }> + description (this:void, description:string): + CommandBuilder<$State> + parameter> (this:void, name:BuilderCommandState.ValidateNameExpression<$State,NameExpression>, configuration:Configuration): + CommandBuilder> + parameter (this:void, name:BuilderCommandState.ValidateNameExpression<$State,NameExpression>, type:$Type): + CommandBuilder> + parametersExclusive