@crustjs/validate 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/dist/effect/index.d.ts +148 -0
- package/dist/effect/index.js +274 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1 -0
- package/dist/shared/chunk-zfhm7pmv.js +196 -0
- package/dist/zod/index.d.ts +212 -0
- package/dist/zod/index.js +255 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @crustjs/validate
|
|
2
|
+
|
|
3
|
+
> Experimental: API may change between minor versions.
|
|
4
|
+
|
|
5
|
+
Validation support for the [Crust](https://crustjs.com) CLI framework.
|
|
6
|
+
|
|
7
|
+
## Entry points
|
|
8
|
+
|
|
9
|
+
| Entry | Import | Purpose |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Shared contracts | `@crustjs/validate` | Provider-agnostic types (`ValidatedContext`, `ValidationIssue`) |
|
|
12
|
+
| Effect provider | `@crustjs/validate/effect` | Schema-first command API (`defineEffectCommand`, `arg`, `flag`) |
|
|
13
|
+
| Zod provider | `@crustjs/validate/zod` | Schema-first command API (`defineZodCommand`, `arg`, `flag`) |
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
bun add @crustjs/validate
|
|
19
|
+
|
|
20
|
+
# choose one or both providers
|
|
21
|
+
bun add zod
|
|
22
|
+
bun add effect
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Effect schema-first mode (`defineEffectCommand`)
|
|
26
|
+
|
|
27
|
+
Define schemas once and let Crust `args`/`flags` definitions be generated automatically.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { runMain } from "@crustjs/core";
|
|
31
|
+
import {
|
|
32
|
+
arg,
|
|
33
|
+
defineEffectCommand,
|
|
34
|
+
flag,
|
|
35
|
+
} from "@crustjs/validate/effect";
|
|
36
|
+
import * as Schema from "effect/Schema";
|
|
37
|
+
|
|
38
|
+
const serve = defineEffectCommand({
|
|
39
|
+
meta: { name: "serve", description: "Start dev server" },
|
|
40
|
+
args: [
|
|
41
|
+
arg("port", Schema.Number.annotations({ description: "Port to listen on" })),
|
|
42
|
+
arg("host", Schema.UndefinedOr(
|
|
43
|
+
Schema.String.annotations({ description: "Host to bind" }),
|
|
44
|
+
)),
|
|
45
|
+
],
|
|
46
|
+
flags: {
|
|
47
|
+
verbose: flag(
|
|
48
|
+
Schema.Boolean.annotations({ description: "Enable verbose logging" }),
|
|
49
|
+
{ alias: "v" },
|
|
50
|
+
),
|
|
51
|
+
format: flag(
|
|
52
|
+
Schema.Literal("json", "text").annotations({ description: "Output format" }),
|
|
53
|
+
{ alias: "f" },
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
run({ args, flags, input }) {
|
|
57
|
+
// args: { port: number; host: string | undefined }
|
|
58
|
+
// flags: { verbose: boolean; format: "json" | "text" }
|
|
59
|
+
console.log(args.port, args.host, flags.verbose, flags.format);
|
|
60
|
+
console.log(input.args, input.flags); // original parser output
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
runMain(serve);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Effect schema support
|
|
68
|
+
|
|
69
|
+
- Primitive schemas: `Schema.String`, `Schema.Number`, `Schema.Boolean`
|
|
70
|
+
- Enums/literals: `Schema.Enums(...)`, `Schema.Literal(...)`
|
|
71
|
+
- Arrays: `Schema.Array(...)` and array-like tuple rest schemas
|
|
72
|
+
- Wrappers: refinement/transformation/suspend wrappers are unwrapped for parser-shape analysis
|
|
73
|
+
- Descriptions: `schema.annotations({ description: "..." })` — auto-extracted through wrappers
|
|
74
|
+
|
|
75
|
+
For optional args/flags, use schemas whose encoded input allows `undefined` (for example `Schema.UndefinedOr(Schema.String)`).
|
|
76
|
+
|
|
77
|
+
## Zod schema-first mode (`defineZodCommand`)
|
|
78
|
+
|
|
79
|
+
Define schemas once and let Crust `args`/`flags` definitions be generated automatically.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { runMain } from "@crustjs/core";
|
|
83
|
+
import { arg, defineZodCommand, flag } from "@crustjs/validate/zod";
|
|
84
|
+
import { z } from "zod";
|
|
85
|
+
|
|
86
|
+
const serve = defineZodCommand({
|
|
87
|
+
meta: { name: "serve", description: "Start dev server" },
|
|
88
|
+
args: [
|
|
89
|
+
arg("port", z.number().int().min(1).max(65535).describe("Port to listen on")),
|
|
90
|
+
arg("host", z.string().default("localhost").describe("Host to bind")),
|
|
91
|
+
],
|
|
92
|
+
flags: {
|
|
93
|
+
verbose: flag(
|
|
94
|
+
z.boolean().default(false).describe("Enable verbose logging"),
|
|
95
|
+
{ alias: "v" },
|
|
96
|
+
),
|
|
97
|
+
format: flag(
|
|
98
|
+
z.enum(["json", "text"]).default("text").describe("Output format"),
|
|
99
|
+
{ alias: "f" },
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
run({ args, flags, input }) {
|
|
103
|
+
// args: { port: number; host: string }
|
|
104
|
+
// flags: { verbose: boolean; format: "json" | "text" }
|
|
105
|
+
console.log(args.port, args.host, flags.verbose, flags.format);
|
|
106
|
+
console.log(input.args, input.flags); // original parser output
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
runMain(serve);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Zod schema support
|
|
114
|
+
|
|
115
|
+
- Primitive schemas: `z.string()`, `z.number()`, `z.boolean()`
|
|
116
|
+
- Enums/literals: `z.enum(...)`, `z.literal(...)`
|
|
117
|
+
- Arrays: `z.array(...)` for flags with `multiple: true`
|
|
118
|
+
- Wrappers: `.optional()`, `.default()`, `.nullable()`, `.transform()`, `.pipe()`
|
|
119
|
+
- Descriptions: `.describe("...")` — auto-extracted through wrappers
|
|
120
|
+
|
|
121
|
+
### Positional args
|
|
122
|
+
|
|
123
|
+
- Use ordered `arg(name, schema, options?)` entries.
|
|
124
|
+
- Optional/default schemas become optional CLI args (`[name]`).
|
|
125
|
+
- Variadic args use `{ variadic: true }` and must be last.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
args: [
|
|
129
|
+
arg("mode", z.string()),
|
|
130
|
+
arg("files", z.string(), { variadic: true }),
|
|
131
|
+
];
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Flags
|
|
135
|
+
|
|
136
|
+
- Pass plain Zod schemas or `flag(schema, options?)` wrappers.
|
|
137
|
+
- Use `flag(..., { alias })` for short aliases.
|
|
138
|
+
- Use `.describe("...")` on the schema for help text.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
flags: {
|
|
142
|
+
debug: z.boolean().default(false).describe("Enable debug mode"),
|
|
143
|
+
outDir: flag(z.string().default("dist").describe("Output directory"), { alias: "o" }),
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Help plugin compatibility
|
|
148
|
+
|
|
149
|
+
Generated definitions are compatible with `helpPlugin`.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { runMain } from "@crustjs/core";
|
|
153
|
+
import { helpPlugin } from "@crustjs/plugins";
|
|
154
|
+
|
|
155
|
+
runMain(serve, { plugins: [helpPlugin()] });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Lifecycle hooks
|
|
159
|
+
|
|
160
|
+
`defineZodCommand` supports `preRun` and `postRun` passthrough hooks.
|
|
161
|
+
|
|
162
|
+
- Both hooks receive the core `CommandContext` (raw parser output).
|
|
163
|
+
- Schema validation/transforms run inside `run`, so validated values are only
|
|
164
|
+
available in the schema-first `run` handler.
|
|
165
|
+
|
|
166
|
+
## Validation errors
|
|
167
|
+
|
|
168
|
+
Failures throw `CrustError("VALIDATION")`.
|
|
169
|
+
|
|
170
|
+
- Message: bullet-list output with dot paths (for example `args.port`, `flags.verbose`).
|
|
171
|
+
- Structured issues: available on both `error.details.issues` and `error.cause`.
|
|
172
|
+
|
|
173
|
+
## v1 constraints
|
|
174
|
+
|
|
175
|
+
- Args and flags only (no env/config validation).
|
|
176
|
+
- Effect mode currently supports context-free schemas only (`R = never`).
|
|
177
|
+
- Zod mode requires Zod 4+.
|
|
178
|
+
- No automatic schema inheritance across subcommands.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { AnyCommand as AnyCommand2, ValidateFlagAliases, ValidateVariadicArgs } from "@crustjs/core";
|
|
2
|
+
import { CommandContext, CommandDef } from "@crustjs/core";
|
|
3
|
+
import * as schema from "effect/Schema";
|
|
4
|
+
import { AnyCommand } from "@crustjs/core";
|
|
5
|
+
/**
|
|
6
|
+
* Extended command context passed to validated handlers.
|
|
7
|
+
*
|
|
8
|
+
* After validation, `args` and `flags` contain the transformed schema output.
|
|
9
|
+
* The original pre-validation parsed values are preserved in `input` for
|
|
10
|
+
* advanced or debug use.
|
|
11
|
+
*/
|
|
12
|
+
interface ValidatedContext<
|
|
13
|
+
ArgsOut,
|
|
14
|
+
FlagsOut
|
|
15
|
+
> {
|
|
16
|
+
/** Transformed positional arguments after schema validation */
|
|
17
|
+
args: ArgsOut;
|
|
18
|
+
/** Transformed flags after schema validation */
|
|
19
|
+
flags: FlagsOut;
|
|
20
|
+
/** Raw arguments that appeared after the `--` separator */
|
|
21
|
+
rawArgs: string[];
|
|
22
|
+
/** The resolved command being executed */
|
|
23
|
+
command: AnyCommand;
|
|
24
|
+
/** Original pre-validation parsed values from the Crust parser */
|
|
25
|
+
input: {
|
|
26
|
+
/** Original parsed args before schema transformation */
|
|
27
|
+
args: Record<string, unknown>;
|
|
28
|
+
/** Original parsed flags before schema transformation */
|
|
29
|
+
flags: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* An Effect schema used by the Effect entrypoint.
|
|
34
|
+
*
|
|
35
|
+
* v1 intentionally supports context-free schemas only (`R = never`).
|
|
36
|
+
* Schemas must also be **synchronous** — async combinators such as
|
|
37
|
+
* `Schema.filterEffect` or async `Schema.transformOrFail` will cause
|
|
38
|
+
* `Effect.runSync` to throw at runtime.
|
|
39
|
+
*/
|
|
40
|
+
type EffectSchemaLike = schema.Schema.AnyNoContext;
|
|
41
|
+
/** Infer output type from an Effect schema. */
|
|
42
|
+
type InferSchemaOutput<S> = S extends schema.Schema<infer A, infer _I, infer _R> ? A : never;
|
|
43
|
+
/** Optional metadata for a positional argument declared with `arg()`. */
|
|
44
|
+
interface ArgOptions {
|
|
45
|
+
/** Collect remaining positionals into this arg as an array. */
|
|
46
|
+
readonly variadic?: true;
|
|
47
|
+
}
|
|
48
|
+
/** A single positional argument spec produced by `arg()`. */
|
|
49
|
+
interface ArgSpec<
|
|
50
|
+
Name extends string = string,
|
|
51
|
+
SchemaType extends EffectSchemaLike = EffectSchemaLike,
|
|
52
|
+
Variadic extends true | undefined = true | undefined
|
|
53
|
+
> {
|
|
54
|
+
readonly kind: "arg";
|
|
55
|
+
readonly name: Name;
|
|
56
|
+
readonly schema: SchemaType;
|
|
57
|
+
readonly variadic: Variadic;
|
|
58
|
+
}
|
|
59
|
+
/** Ordered positional argument specs. */
|
|
60
|
+
type ArgSpecs = readonly ArgSpec[];
|
|
61
|
+
/** Output type for one ArgSpec in the validated handler context. */
|
|
62
|
+
type InferArgValue<S extends ArgSpec> = S["variadic"] extends true ? InferSchemaOutput<S["schema"]>[] : InferSchemaOutput<S["schema"]>;
|
|
63
|
+
/** Flattens an intersection of objects for readable inferred types. */
|
|
64
|
+
type Simplify<T> = { [K in keyof T] : T[K] };
|
|
65
|
+
/** Recursively maps ordered ArgSpec entries to a named output object type. */
|
|
66
|
+
type InferArgsFromTuple<A extends readonly ArgSpec[]> = A extends readonly [infer Head extends ArgSpec, ...infer Tail extends readonly ArgSpec[]] ? { [K in Head["name"]] : InferArgValue<Head> } & InferArgsFromTuple<Tail> : {};
|
|
67
|
+
/** Infer validated args object type from ordered ArgSpec entries. */
|
|
68
|
+
type InferArgsFromSpecs<A extends ArgSpecs> = Simplify<InferArgsFromTuple<A>>;
|
|
69
|
+
/** Optional metadata for a flag declared with `flag()`. */
|
|
70
|
+
interface FlagOptions {
|
|
71
|
+
/** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`). */
|
|
72
|
+
readonly alias?: string | readonly string[];
|
|
73
|
+
}
|
|
74
|
+
/** A named flag schema wrapper produced by `flag()`. */
|
|
75
|
+
interface FlagSpec<
|
|
76
|
+
SchemaType extends EffectSchemaLike = EffectSchemaLike,
|
|
77
|
+
Alias extends string | readonly string[] | undefined = string | readonly string[] | undefined
|
|
78
|
+
> {
|
|
79
|
+
readonly kind: "flag";
|
|
80
|
+
readonly schema: SchemaType;
|
|
81
|
+
readonly alias: Alias;
|
|
82
|
+
}
|
|
83
|
+
/** Allowed value shape for `flags` in `defineEffectCommand()`. */
|
|
84
|
+
type FlagShape = Record<string, EffectSchemaLike | FlagSpec>;
|
|
85
|
+
/** Extract the schema from a flag shape value (plain schema or `flag()` wrapper). */
|
|
86
|
+
type ExtractFlagSchema<V> = V extends FlagSpec<infer S> ? S : V extends EffectSchemaLike ? V : never;
|
|
87
|
+
/** Infer validated flags object type from the flags shape. */
|
|
88
|
+
type InferFlagsFromShape<F extends FlagShape> = { [K in keyof F] : InferSchemaOutput<ExtractFlagSchema<F[K]>> };
|
|
89
|
+
/** Handler type for `defineEffectCommand()` with validated/transformed context. */
|
|
90
|
+
type EffectCommandRunHandler<
|
|
91
|
+
ArgsOut,
|
|
92
|
+
FlagsOut
|
|
93
|
+
> = (context: ValidatedContext<ArgsOut, FlagsOut>) => void | Promise<void>;
|
|
94
|
+
/** Infer args output type from command config args. */
|
|
95
|
+
type InferArgsFromConfig<A> = A extends ArgSpecs ? InferArgsFromSpecs<A> : Record<string, never>;
|
|
96
|
+
/** Infer flags output type from command config flags. */
|
|
97
|
+
type InferFlagsFromConfig<F> = F extends FlagShape ? InferFlagsFromShape<F> : Record<string, never>;
|
|
98
|
+
type EffectOverriddenKeys = "args" | "flags" | "run" | "preRun" | "postRun";
|
|
99
|
+
/** Config for `defineEffectCommand()` using `arg()` + `flag()` schema-first DSL. */
|
|
100
|
+
interface EffectCommandDef<
|
|
101
|
+
A extends ArgSpecs | undefined = undefined,
|
|
102
|
+
F extends FlagShape | undefined = undefined
|
|
103
|
+
> extends Omit<CommandDef, EffectOverriddenKeys> {
|
|
104
|
+
/** Ordered positional args as `arg()` specs. */
|
|
105
|
+
readonly args?: A;
|
|
106
|
+
/** Named flags as plain schemas or `flag()` wrappers. */
|
|
107
|
+
readonly flags?: F;
|
|
108
|
+
/** Optional setup hook before schema validation runs. */
|
|
109
|
+
readonly preRun?: (context: CommandContext) => void | Promise<void>;
|
|
110
|
+
/** Main handler with validated/transformed args and flags. */
|
|
111
|
+
readonly run?: EffectCommandRunHandler<InferArgsFromConfig<A>, InferFlagsFromConfig<F>>;
|
|
112
|
+
/** Optional teardown hook after command execution. */
|
|
113
|
+
readonly postRun?: (context: CommandContext) => void | Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Define a Crust command where Effect schemas are the source of truth.
|
|
117
|
+
*
|
|
118
|
+
* Only context-free (`R = never`), synchronous schemas are supported.
|
|
119
|
+
* Async combinators like `Schema.filterEffect` or async `Schema.transformOrFail`
|
|
120
|
+
* will throw at runtime.
|
|
121
|
+
*/
|
|
122
|
+
declare function defineEffectCommand<
|
|
123
|
+
const A extends readonly ArgSpec[] | undefined,
|
|
124
|
+
const F extends FlagShape | undefined
|
|
125
|
+
>(config: EffectCommandDef<A, F> & {
|
|
126
|
+
args?: A extends readonly object[] ? ValidateVariadicArgs<A> : A;
|
|
127
|
+
flags?: F extends Record<string, unknown> ? ValidateFlagAliases<F> : F;
|
|
128
|
+
}): AnyCommand2;
|
|
129
|
+
/**
|
|
130
|
+
* Define a named positional argument schema for `defineEffectCommand()`.
|
|
131
|
+
*/
|
|
132
|
+
declare function arg<
|
|
133
|
+
Name extends string,
|
|
134
|
+
SchemaType extends EffectSchemaLike,
|
|
135
|
+
const Variadic extends true | undefined = undefined
|
|
136
|
+
>(name: Name, schema: SchemaType, options?: ArgOptions & {
|
|
137
|
+
variadic?: Variadic;
|
|
138
|
+
}): ArgSpec<Name, SchemaType, Variadic>;
|
|
139
|
+
/**
|
|
140
|
+
* Define a flag schema for `defineEffectCommand()` with optional alias metadata.
|
|
141
|
+
*/
|
|
142
|
+
declare function flag<
|
|
143
|
+
SchemaType extends EffectSchemaLike,
|
|
144
|
+
const Alias extends string | readonly string[] | undefined = undefined
|
|
145
|
+
>(schema: SchemaType, options?: FlagOptions & {
|
|
146
|
+
alias?: Alias;
|
|
147
|
+
}): FlagSpec<SchemaType, Alias>;
|
|
148
|
+
export { flag, defineEffectCommand, arg, InferSchemaOutput, InferFlagsFromShape, InferFlagsFromConfig, InferArgsFromSpecs, InferArgsFromConfig, FlagSpec, FlagShape, FlagOptions, EffectSchemaLike, EffectCommandRunHandler, EffectCommandDef, ArgSpecs, ArgSpec, ArgOptions };
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
buildArgDefinitions,
|
|
4
|
+
buildFlagDefinitions,
|
|
5
|
+
buildRunHandler,
|
|
6
|
+
normalizeIssues
|
|
7
|
+
} from "../shared/chunk-zfhm7pmv.js";
|
|
8
|
+
|
|
9
|
+
// src/effect/command.ts
|
|
10
|
+
import { defineCommand } from "@crustjs/core";
|
|
11
|
+
import { either, runSync } from "effect/Effect";
|
|
12
|
+
import * as Either from "effect/Either";
|
|
13
|
+
import * as ParseResult from "effect/ParseResult";
|
|
14
|
+
import { decodeUnknown } from "effect/Schema";
|
|
15
|
+
|
|
16
|
+
// src/effect/definitions.ts
|
|
17
|
+
import { CrustError as CrustError2 } from "@crustjs/core";
|
|
18
|
+
import { encodedSchema } from "effect/Schema";
|
|
19
|
+
|
|
20
|
+
// src/effect/schema.ts
|
|
21
|
+
import { CrustError } from "@crustjs/core";
|
|
22
|
+
import { isSome } from "effect/Option";
|
|
23
|
+
import { isSchema } from "effect/Schema";
|
|
24
|
+
import { getDescriptionAnnotation } from "effect/SchemaAST";
|
|
25
|
+
function arg(name, schema, options) {
|
|
26
|
+
if (!name.trim()) {
|
|
27
|
+
throw new CrustError("DEFINITION", "arg(): name is required and must be a non-empty string");
|
|
28
|
+
}
|
|
29
|
+
if (!isSchema(schema)) {
|
|
30
|
+
throw new CrustError("DEFINITION", `arg("${name}"): schema must be an Effect schema`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
kind: "arg",
|
|
34
|
+
name,
|
|
35
|
+
schema,
|
|
36
|
+
variadic: options?.variadic
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function flag(schema, options) {
|
|
40
|
+
if (!isSchema(schema)) {
|
|
41
|
+
throw new CrustError("DEFINITION", "flag(): schema must be an Effect schema");
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: "flag",
|
|
45
|
+
schema,
|
|
46
|
+
alias: options?.alias
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function resolveDescription(schema) {
|
|
50
|
+
return resolveDescriptionFromAst(schema.ast);
|
|
51
|
+
}
|
|
52
|
+
function resolveDescriptionFromAst(ast) {
|
|
53
|
+
const seen = new Set;
|
|
54
|
+
let current = ast;
|
|
55
|
+
for (;; ) {
|
|
56
|
+
if (seen.has(current)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
seen.add(current);
|
|
60
|
+
const annotated = getDescriptionAnnotation(current);
|
|
61
|
+
if (isSome(annotated) && typeof annotated.value === "string") {
|
|
62
|
+
return annotated.value;
|
|
63
|
+
}
|
|
64
|
+
if (current._tag === "Transformation") {
|
|
65
|
+
current = current.from;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (current._tag === "Refinement") {
|
|
69
|
+
current = current.from;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (current._tag === "Suspend") {
|
|
73
|
+
current = current.f();
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (current._tag === "Union") {
|
|
77
|
+
for (const member of current.types) {
|
|
78
|
+
if (member._tag === "UndefinedKeyword") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const desc = resolveDescriptionFromAst(member);
|
|
82
|
+
if (desc !== undefined) {
|
|
83
|
+
return desc;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/effect/definitions.ts
|
|
93
|
+
function unwrapInputAst(ast) {
|
|
94
|
+
let current = ast;
|
|
95
|
+
const seen = new Set;
|
|
96
|
+
for (;; ) {
|
|
97
|
+
if (seen.has(current)) {
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
seen.add(current);
|
|
101
|
+
if (current._tag === "Refinement") {
|
|
102
|
+
current = current.from;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (current._tag === "Transformation") {
|
|
106
|
+
current = current.from;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (current._tag === "Suspend") {
|
|
110
|
+
current = current.f();
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
return current;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function resolveEnumInputType(enums) {
|
|
117
|
+
let kind;
|
|
118
|
+
for (const [, value] of enums) {
|
|
119
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const current = typeof value === "string" ? "string" : "number";
|
|
123
|
+
if (kind === undefined) {
|
|
124
|
+
kind = current;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (kind !== current) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return kind;
|
|
132
|
+
}
|
|
133
|
+
function resolvePrimitiveInputType(ast) {
|
|
134
|
+
const unwrapped = unwrapInputAst(ast);
|
|
135
|
+
if (unwrapped._tag === "StringKeyword" || unwrapped._tag === "TemplateLiteral") {
|
|
136
|
+
return "string";
|
|
137
|
+
}
|
|
138
|
+
if (unwrapped._tag === "NumberKeyword") {
|
|
139
|
+
return "number";
|
|
140
|
+
}
|
|
141
|
+
if (unwrapped._tag === "BooleanKeyword") {
|
|
142
|
+
return "boolean";
|
|
143
|
+
}
|
|
144
|
+
if (unwrapped._tag === "Literal") {
|
|
145
|
+
if (typeof unwrapped.literal === "string")
|
|
146
|
+
return "string";
|
|
147
|
+
if (typeof unwrapped.literal === "number")
|
|
148
|
+
return "number";
|
|
149
|
+
if (typeof unwrapped.literal === "boolean")
|
|
150
|
+
return "boolean";
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (unwrapped._tag === "Enums") {
|
|
154
|
+
return resolveEnumInputType(unwrapped.enums);
|
|
155
|
+
}
|
|
156
|
+
if (unwrapped._tag === "Union") {
|
|
157
|
+
let kind;
|
|
158
|
+
for (const member of unwrapped.types) {
|
|
159
|
+
const resolved = resolvePrimitiveInputType(member);
|
|
160
|
+
if (resolved === undefined) {
|
|
161
|
+
if (unwrapInputAst(member)._tag === "UndefinedKeyword") {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (kind === undefined) {
|
|
167
|
+
kind = resolved;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (kind !== resolved) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return kind;
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
function resolveTupleArrayShape(ast, label) {
|
|
179
|
+
if (ast._tag !== "TupleType") {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (ast.elements.length > 0) {
|
|
183
|
+
throw new CrustError2("DEFINITION", `${label}: tuple schemas with fixed elements are not supported for CLI parsing. Use Schema.Array(T) for repeatable arguments.`);
|
|
184
|
+
}
|
|
185
|
+
if (ast.rest.length !== 1) {
|
|
186
|
+
throw new CrustError2("DEFINITION", `${label}: tuple schemas are not supported for CLI parsing. Use scalar schemas or array schemas with a single element type.`);
|
|
187
|
+
}
|
|
188
|
+
const rest = ast.rest[0];
|
|
189
|
+
if (!rest) {
|
|
190
|
+
throw new CrustError2("DEFINITION", `${label}: unable to inspect array element schema`);
|
|
191
|
+
}
|
|
192
|
+
const primitive = resolvePrimitiveInputType(rest.type);
|
|
193
|
+
if (!primitive) {
|
|
194
|
+
throw new CrustError2("DEFINITION", `${label}: array element type must be string, number, or boolean`);
|
|
195
|
+
}
|
|
196
|
+
return { type: primitive, multiple: true };
|
|
197
|
+
}
|
|
198
|
+
function resolveInputShape(schema, label) {
|
|
199
|
+
const ast = unwrapInputAst(encodedSchema(schema).ast);
|
|
200
|
+
const tupleShape = resolveTupleArrayShape(ast, label);
|
|
201
|
+
if (tupleShape) {
|
|
202
|
+
return tupleShape;
|
|
203
|
+
}
|
|
204
|
+
const primitive = resolvePrimitiveInputType(ast);
|
|
205
|
+
if (primitive) {
|
|
206
|
+
return { type: primitive, multiple: false };
|
|
207
|
+
}
|
|
208
|
+
throw new CrustError2("DEFINITION", `${label}: unsupported schema type for CLI parsing. Use string, number, boolean, enum/literal, or array of these.`);
|
|
209
|
+
}
|
|
210
|
+
function acceptsUndefined(ast) {
|
|
211
|
+
const unwrapped = unwrapInputAst(ast);
|
|
212
|
+
if (unwrapped._tag === "UndefinedKeyword") {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (unwrapped._tag === "Union") {
|
|
216
|
+
return unwrapped.types.some((member) => acceptsUndefined(member));
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
function isOptionalInputSchema(schema) {
|
|
221
|
+
return acceptsUndefined(encodedSchema(schema).ast);
|
|
222
|
+
}
|
|
223
|
+
var effectAdapter = {
|
|
224
|
+
resolveInputShape,
|
|
225
|
+
isOptionalInputSchema,
|
|
226
|
+
resolveDescription,
|
|
227
|
+
commandLabel: "defineEffectCommand",
|
|
228
|
+
arrayHint: "Schema.Array(...)"
|
|
229
|
+
};
|
|
230
|
+
function argsToDefinitions(args) {
|
|
231
|
+
return buildArgDefinitions(args, effectAdapter);
|
|
232
|
+
}
|
|
233
|
+
function flagsToDefinitions(flags) {
|
|
234
|
+
return buildFlagDefinitions(flags, effectAdapter);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/effect/command.ts
|
|
238
|
+
function validateValue(schema, value, prefix) {
|
|
239
|
+
const result = runSync(either(decodeUnknown(schema)(value)));
|
|
240
|
+
if (Either.isRight(result)) {
|
|
241
|
+
return { ok: true, value: result.right };
|
|
242
|
+
}
|
|
243
|
+
const flattened = ParseResult.ArrayFormatter.formatErrorSync(result.left);
|
|
244
|
+
const prefixed = flattened.map((issue) => ({
|
|
245
|
+
message: issue.message,
|
|
246
|
+
path: [...prefix, ...issue.path]
|
|
247
|
+
}));
|
|
248
|
+
return { ok: false, issues: normalizeIssues(prefixed) };
|
|
249
|
+
}
|
|
250
|
+
function defineEffectCommand(config) {
|
|
251
|
+
const {
|
|
252
|
+
args: effectArgs,
|
|
253
|
+
flags: effectFlags,
|
|
254
|
+
run: userRun,
|
|
255
|
+
...passthrough
|
|
256
|
+
} = config;
|
|
257
|
+
const argSpecs = effectArgs ?? [];
|
|
258
|
+
const generatedArgs = argsToDefinitions(argSpecs);
|
|
259
|
+
const generatedFlags = flagsToDefinitions(effectFlags);
|
|
260
|
+
const command = defineCommand({
|
|
261
|
+
...passthrough,
|
|
262
|
+
...generatedArgs.length > 0 && { args: generatedArgs },
|
|
263
|
+
...Object.keys(generatedFlags).length > 0 && { flags: generatedFlags },
|
|
264
|
+
...userRun && {
|
|
265
|
+
run: buildRunHandler(argSpecs, effectFlags, userRun, validateValue)
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
return command;
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
flag,
|
|
272
|
+
defineEffectCommand,
|
|
273
|
+
arg
|
|
274
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AnyCommand } from "@crustjs/core";
|
|
2
|
+
/**
|
|
3
|
+
* A normalized validation issue used internally across both entrypoints.
|
|
4
|
+
*
|
|
5
|
+
* Provider issues may use path arrays. This type normalizes them to a
|
|
6
|
+
* flat string-based dot-path for consistent rendering
|
|
7
|
+
* and programmatic consumption.
|
|
8
|
+
*/
|
|
9
|
+
interface ValidationIssue {
|
|
10
|
+
/** Human-readable error message for this issue. */
|
|
11
|
+
readonly message: string;
|
|
12
|
+
/** Dot-path string describing the location of the issue (e.g. `"flags.verbose"`, `"args[0]"`). Empty string for root-level issues. */
|
|
13
|
+
readonly path: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extended command context passed to validated handlers.
|
|
17
|
+
*
|
|
18
|
+
* After validation, `args` and `flags` contain the transformed schema output.
|
|
19
|
+
* The original pre-validation parsed values are preserved in `input` for
|
|
20
|
+
* advanced or debug use.
|
|
21
|
+
*/
|
|
22
|
+
interface ValidatedContext<
|
|
23
|
+
ArgsOut,
|
|
24
|
+
FlagsOut
|
|
25
|
+
> {
|
|
26
|
+
/** Transformed positional arguments after schema validation */
|
|
27
|
+
args: ArgsOut;
|
|
28
|
+
/** Transformed flags after schema validation */
|
|
29
|
+
flags: FlagsOut;
|
|
30
|
+
/** Raw arguments that appeared after the `--` separator */
|
|
31
|
+
rawArgs: string[];
|
|
32
|
+
/** The resolved command being executed */
|
|
33
|
+
command: AnyCommand;
|
|
34
|
+
/** Original pre-validation parsed values from the Crust parser */
|
|
35
|
+
input: {
|
|
36
|
+
/** Original parsed args before schema transformation */
|
|
37
|
+
args: Record<string, unknown>;
|
|
38
|
+
/** Original parsed flags before schema transformation */
|
|
39
|
+
flags: Record<string, unknown>;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export { ValidationIssue, ValidatedContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @bun
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/validation.ts
|
|
3
|
+
import { CrustError } from "@crustjs/core";
|
|
4
|
+
function formatPath(path) {
|
|
5
|
+
let result = "";
|
|
6
|
+
for (const segment of path) {
|
|
7
|
+
if (typeof segment === "number") {
|
|
8
|
+
result += `[${String(segment)}]`;
|
|
9
|
+
} else {
|
|
10
|
+
const str = String(segment);
|
|
11
|
+
if (result.length > 0) {
|
|
12
|
+
result += `.${str}`;
|
|
13
|
+
} else {
|
|
14
|
+
result = str;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function normalizeIssue(issue) {
|
|
21
|
+
return {
|
|
22
|
+
message: issue.message,
|
|
23
|
+
path: issue.path ? formatPath(issue.path) : ""
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function normalizeIssues(issues) {
|
|
27
|
+
return issues.map(normalizeIssue);
|
|
28
|
+
}
|
|
29
|
+
function renderIssueLine(issue) {
|
|
30
|
+
if (issue.path) {
|
|
31
|
+
return ` - ${issue.path}: ${issue.message}`;
|
|
32
|
+
}
|
|
33
|
+
return ` - ${issue.message}`;
|
|
34
|
+
}
|
|
35
|
+
function renderBulletList(prefix, issues) {
|
|
36
|
+
if (issues.length === 0)
|
|
37
|
+
return prefix;
|
|
38
|
+
const lines = issues.map(renderIssueLine);
|
|
39
|
+
return `${prefix}
|
|
40
|
+
${lines.join(`
|
|
41
|
+
`)}`;
|
|
42
|
+
}
|
|
43
|
+
function throwValidationError(issues, prefix = "Validation failed") {
|
|
44
|
+
const message = renderBulletList(prefix, issues);
|
|
45
|
+
throw new CrustError("VALIDATION", message, { issues }).withCause(issues);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/flagSpec.ts
|
|
49
|
+
function isFlagSpec(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "flag";
|
|
51
|
+
}
|
|
52
|
+
function getFlagSchema(value) {
|
|
53
|
+
return isFlagSpec(value) ? value.schema : value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/runner.ts
|
|
57
|
+
async function validateArgs(argSpecs, context, issues, validateValue) {
|
|
58
|
+
const output = {};
|
|
59
|
+
for (const spec of argSpecs) {
|
|
60
|
+
const input = context.args[spec.name];
|
|
61
|
+
if (spec.variadic) {
|
|
62
|
+
const items = Array.isArray(input) ? input : input === undefined ? [] : [input];
|
|
63
|
+
const transformed = [];
|
|
64
|
+
for (let i = 0;i < items.length; i++) {
|
|
65
|
+
const value = items[i];
|
|
66
|
+
const validated2 = await validateValue(spec.schema, value, [
|
|
67
|
+
"args",
|
|
68
|
+
spec.name,
|
|
69
|
+
i
|
|
70
|
+
]);
|
|
71
|
+
if (!validated2.ok) {
|
|
72
|
+
issues.push(...validated2.issues);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
transformed.push(validated2.value);
|
|
76
|
+
}
|
|
77
|
+
output[spec.name] = transformed;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const validated = await validateValue(spec.schema, input, [
|
|
81
|
+
"args",
|
|
82
|
+
spec.name
|
|
83
|
+
]);
|
|
84
|
+
if (!validated.ok) {
|
|
85
|
+
issues.push(...validated.issues);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
output[spec.name] = validated.value;
|
|
89
|
+
}
|
|
90
|
+
return output;
|
|
91
|
+
}
|
|
92
|
+
async function validateFlags(flags, context, issues, validateValue) {
|
|
93
|
+
if (!flags) {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
const output = {};
|
|
97
|
+
for (const [name, rawValue] of Object.entries(flags)) {
|
|
98
|
+
const schema = getFlagSchema(rawValue);
|
|
99
|
+
const input = context.flags[name];
|
|
100
|
+
const validated = await validateValue(schema, input, ["flags", name]);
|
|
101
|
+
if (!validated.ok) {
|
|
102
|
+
issues.push(...validated.issues);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
output[name] = validated.value;
|
|
106
|
+
}
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
109
|
+
function buildRunHandler(argSpecs, flags, userRun, validateValue) {
|
|
110
|
+
return async (context) => {
|
|
111
|
+
const issues = [];
|
|
112
|
+
const validatedArgs = await validateArgs(argSpecs, context, issues, validateValue);
|
|
113
|
+
const validatedFlags = await validateFlags(flags, context, issues, validateValue);
|
|
114
|
+
if (issues.length > 0) {
|
|
115
|
+
throwValidationError(issues);
|
|
116
|
+
}
|
|
117
|
+
const validatedContext = {
|
|
118
|
+
args: validatedArgs,
|
|
119
|
+
flags: validatedFlags,
|
|
120
|
+
rawArgs: context.rawArgs,
|
|
121
|
+
command: context.command,
|
|
122
|
+
input: {
|
|
123
|
+
args: context.args,
|
|
124
|
+
flags: context.flags
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return userRun(validatedContext);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/definitionBuilders.ts
|
|
132
|
+
import { CrustError as CrustError2 } from "@crustjs/core";
|
|
133
|
+
function buildArgDefinitions(args, adapter) {
|
|
134
|
+
const seen = new Set;
|
|
135
|
+
for (let i = 0;i < args.length; i++) {
|
|
136
|
+
const spec = args[i];
|
|
137
|
+
if (!spec)
|
|
138
|
+
continue;
|
|
139
|
+
if (seen.has(spec.name)) {
|
|
140
|
+
throw new CrustError2("DEFINITION", `${adapter.commandLabel}: duplicate arg name "${spec.name}"`);
|
|
141
|
+
}
|
|
142
|
+
seen.add(spec.name);
|
|
143
|
+
if (spec.variadic && i !== args.length - 1) {
|
|
144
|
+
throw new CrustError2("DEFINITION", `${adapter.commandLabel}: only the last arg can be variadic (arg "${spec.name}")`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return args.map((spec) => {
|
|
148
|
+
const shape = adapter.resolveInputShape(spec.schema, `arg "${spec.name}"`);
|
|
149
|
+
if (spec.variadic && shape.multiple) {
|
|
150
|
+
throw new CrustError2("DEFINITION", `arg "${spec.name}": variadic args must use a scalar schema; do not wrap the schema in ${adapter.arrayHint}`);
|
|
151
|
+
}
|
|
152
|
+
if (!spec.variadic && shape.multiple) {
|
|
153
|
+
throw new CrustError2("DEFINITION", `arg "${spec.name}": array schema requires { variadic: true }`);
|
|
154
|
+
}
|
|
155
|
+
const description = adapter.resolveDescription(spec.schema);
|
|
156
|
+
const required = !adapter.isOptionalInputSchema(spec.schema);
|
|
157
|
+
const def = {
|
|
158
|
+
name: spec.name,
|
|
159
|
+
type: shape.type,
|
|
160
|
+
...description !== undefined && { description },
|
|
161
|
+
...spec.variadic && { variadic: true },
|
|
162
|
+
...required && { required: true }
|
|
163
|
+
};
|
|
164
|
+
return def;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function getFlagMetadata(value) {
|
|
168
|
+
if (isFlagSpec(value)) {
|
|
169
|
+
return { schema: value.schema, alias: value.alias };
|
|
170
|
+
}
|
|
171
|
+
return { schema: value };
|
|
172
|
+
}
|
|
173
|
+
function buildFlagDefinitions(flags, adapter) {
|
|
174
|
+
if (!flags) {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
const result = {};
|
|
178
|
+
for (const [name, value] of Object.entries(flags)) {
|
|
179
|
+
const metadata = getFlagMetadata(value);
|
|
180
|
+
const { schema } = metadata;
|
|
181
|
+
const shape = adapter.resolveInputShape(schema, `flag "--${name}"`);
|
|
182
|
+
const required = !adapter.isOptionalInputSchema(schema);
|
|
183
|
+
const description = adapter.resolveDescription(schema);
|
|
184
|
+
const alias = metadata.alias === undefined ? undefined : typeof metadata.alias === "string" ? metadata.alias : [...metadata.alias];
|
|
185
|
+
result[name] = {
|
|
186
|
+
type: shape.type,
|
|
187
|
+
...shape.multiple && { multiple: true },
|
|
188
|
+
...alias !== undefined && { alias },
|
|
189
|
+
...description !== undefined && { description },
|
|
190
|
+
...required && { required: true }
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { normalizeIssues, buildRunHandler, buildArgDefinitions, buildFlagDefinitions };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { AnyCommand as AnyCommand2, ValidateFlagAliases, ValidateVariadicArgs } from "@crustjs/core";
|
|
2
|
+
import { CommandContext, CommandDef } from "@crustjs/core";
|
|
3
|
+
import * as z from "zod/v4/core";
|
|
4
|
+
import { AnyCommand } from "@crustjs/core";
|
|
5
|
+
/**
|
|
6
|
+
* Extended command context passed to validated handlers.
|
|
7
|
+
*
|
|
8
|
+
* After validation, `args` and `flags` contain the transformed schema output.
|
|
9
|
+
* The original pre-validation parsed values are preserved in `input` for
|
|
10
|
+
* advanced or debug use.
|
|
11
|
+
*/
|
|
12
|
+
interface ValidatedContext<
|
|
13
|
+
ArgsOut,
|
|
14
|
+
FlagsOut
|
|
15
|
+
> {
|
|
16
|
+
/** Transformed positional arguments after schema validation */
|
|
17
|
+
args: ArgsOut;
|
|
18
|
+
/** Transformed flags after schema validation */
|
|
19
|
+
flags: FlagsOut;
|
|
20
|
+
/** Raw arguments that appeared after the `--` separator */
|
|
21
|
+
rawArgs: string[];
|
|
22
|
+
/** The resolved command being executed */
|
|
23
|
+
command: AnyCommand;
|
|
24
|
+
/** Original pre-validation parsed values from the Crust parser */
|
|
25
|
+
input: {
|
|
26
|
+
/** Original parsed args before schema transformation */
|
|
27
|
+
args: Record<string, unknown>;
|
|
28
|
+
/** Original parsed flags before schema transformation */
|
|
29
|
+
flags: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** A Zod schema used by the Zod entrypoint. */
|
|
33
|
+
type ZodSchemaLike<
|
|
34
|
+
Input = unknown,
|
|
35
|
+
Output = Input
|
|
36
|
+
> = z.$ZodType<Output, Input>;
|
|
37
|
+
/** Infer output type from a Zod schema. */
|
|
38
|
+
type InferSchemaOutput<S> = S extends z.$ZodType ? z.output<S> : never;
|
|
39
|
+
/** Optional metadata for a positional argument declared with `arg()`. */
|
|
40
|
+
interface ArgOptions {
|
|
41
|
+
/** Collect remaining positionals into this arg as an array. */
|
|
42
|
+
readonly variadic?: true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A single positional argument spec produced by `arg()`.
|
|
46
|
+
*
|
|
47
|
+
* The `Variadic` generic parameter preserves the literal `true` from
|
|
48
|
+
* `arg()` calls so `ValidateVariadicArgs` can distinguish variadic args
|
|
49
|
+
* from non-variadic ones at compile time.
|
|
50
|
+
*
|
|
51
|
+
* - `ArgSpec<N, S, true>` — variadic arg (only valid in last position)
|
|
52
|
+
* - `ArgSpec<N, S, undefined>` — normal positional arg
|
|
53
|
+
* - `ArgSpec<N, S>` (default) — type-erased form used in constraints
|
|
54
|
+
*/
|
|
55
|
+
interface ArgSpec<
|
|
56
|
+
Name extends string = string,
|
|
57
|
+
Schema extends ZodSchemaLike = ZodSchemaLike,
|
|
58
|
+
Variadic extends true | undefined = true | undefined
|
|
59
|
+
> {
|
|
60
|
+
readonly kind: "arg";
|
|
61
|
+
readonly name: Name;
|
|
62
|
+
readonly schema: Schema;
|
|
63
|
+
readonly variadic: Variadic;
|
|
64
|
+
}
|
|
65
|
+
/** Ordered positional argument specs. */
|
|
66
|
+
type ArgSpecs = readonly ArgSpec[];
|
|
67
|
+
/** Output type for one ArgSpec in the validated handler context. */
|
|
68
|
+
type InferArgValue<S extends ArgSpec> = S["variadic"] extends true ? InferSchemaOutput<S["schema"]>[] : InferSchemaOutput<S["schema"]>;
|
|
69
|
+
/** Flattens an intersection of objects for readable inferred types. */
|
|
70
|
+
type Simplify<T> = { [K in keyof T] : T[K] };
|
|
71
|
+
/** Recursively maps ordered ArgSpec entries to a named output object type. */
|
|
72
|
+
type InferArgsFromTuple<A extends readonly ArgSpec[]> = A extends readonly [infer Head extends ArgSpec, ...infer Tail extends readonly ArgSpec[]] ? { [K in Head["name"]] : InferArgValue<Head> } & InferArgsFromTuple<Tail> : {};
|
|
73
|
+
/** Infer validated args object type from ordered ArgSpec entries. */
|
|
74
|
+
type InferArgsFromSpecs<A extends ArgSpecs> = Simplify<InferArgsFromTuple<A>>;
|
|
75
|
+
/** Optional metadata for a flag declared with `flag()`. */
|
|
76
|
+
interface FlagOptions {
|
|
77
|
+
/** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`). */
|
|
78
|
+
readonly alias?: string | readonly string[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A named flag schema wrapper produced by `flag()`.
|
|
82
|
+
*
|
|
83
|
+
* The `Alias` generic parameter preserves alias literals (e.g. `"v"` or
|
|
84
|
+
* `readonly ["v", "V"]`) from `flag()` calls so `ValidateFlagAliases` can
|
|
85
|
+
* detect collisions at compile time.
|
|
86
|
+
*
|
|
87
|
+
* - `FlagSpec<S, "v">` — flag with alias `"v"` (collision-detectable)
|
|
88
|
+
* - `FlagSpec<S, undefined>` — flag without an alias
|
|
89
|
+
* - `FlagSpec<S>` (default) — type-erased form used in constraints
|
|
90
|
+
*/
|
|
91
|
+
interface FlagSpec<
|
|
92
|
+
Schema extends ZodSchemaLike = ZodSchemaLike,
|
|
93
|
+
Alias extends string | readonly string[] | undefined = string | readonly string[] | undefined
|
|
94
|
+
> {
|
|
95
|
+
readonly kind: "flag";
|
|
96
|
+
readonly schema: Schema;
|
|
97
|
+
readonly alias: Alias;
|
|
98
|
+
}
|
|
99
|
+
/** Allowed value shape for `flags` in `defineZodCommand()`. */
|
|
100
|
+
type FlagShape = Record<string, ZodSchemaLike | FlagSpec>;
|
|
101
|
+
/** Extract the schema from a flag shape value (plain schema or `flag()` wrapper). */
|
|
102
|
+
type ExtractFlagSchema<V> = V extends FlagSpec<infer S> ? S : V extends ZodSchemaLike ? V : never;
|
|
103
|
+
/** Infer validated flags object type from the flags shape. */
|
|
104
|
+
type InferFlagsFromShape<F extends FlagShape> = { [K in keyof F] : InferSchemaOutput<ExtractFlagSchema<F[K]>> };
|
|
105
|
+
/** Handler type for `defineZodCommand()` with validated/transformed context. */
|
|
106
|
+
type ZodCommandRunHandler<
|
|
107
|
+
ArgsOut,
|
|
108
|
+
FlagsOut
|
|
109
|
+
> = (context: ValidatedContext<ArgsOut, FlagsOut>) => void | Promise<void>;
|
|
110
|
+
/** Infer args output type from command config args. */
|
|
111
|
+
type InferArgsFromConfig<A> = A extends ArgSpecs ? InferArgsFromSpecs<A> : Record<string, never>;
|
|
112
|
+
/** Infer flags output type from command config flags. */
|
|
113
|
+
type InferFlagsFromConfig<F> = F extends FlagShape ? InferFlagsFromShape<F> : Record<string, never>;
|
|
114
|
+
/**
|
|
115
|
+
* Keys from `CommandDef` that `ZodCommandDef` redefines with different types.
|
|
116
|
+
*
|
|
117
|
+
* - `args` / `flags`: Zod schema-based definitions replace core's `ArgsDef`/`FlagsDef`
|
|
118
|
+
* - `run`: receives `ValidatedContext` instead of raw `CommandContext`
|
|
119
|
+
* - `preRun` / `postRun`: use raw `CommandContext` (no `NoInfer` wrapper)
|
|
120
|
+
*
|
|
121
|
+
* All remaining `CommandDef` keys (e.g. `meta`, `subCommands`) are inherited
|
|
122
|
+
* automatically via `Omit`. If a new passthrough field is added to `CommandDef`,
|
|
123
|
+
* it propagates here without changes. The compile-time key exhaustiveness
|
|
124
|
+
* assertion in `command.test.ts` will fail, forcing a review.
|
|
125
|
+
*/
|
|
126
|
+
type ZodOverriddenKeys = "args" | "flags" | "run" | "preRun" | "postRun";
|
|
127
|
+
/**
|
|
128
|
+
* Config for `defineZodCommand()` using `arg()` + `flag()` schema-first DSL.
|
|
129
|
+
*
|
|
130
|
+
* Extends `CommandDef` (minus overridden keys) so passthrough fields like
|
|
131
|
+
* `meta` and `subCommands` stay in sync automatically. If a new field is
|
|
132
|
+
* added to `CommandDef`, the key exhaustiveness assertion in
|
|
133
|
+
* `command.test.ts` fails at compile time, forcing a review.
|
|
134
|
+
*/
|
|
135
|
+
interface ZodCommandDef<
|
|
136
|
+
A extends ArgSpecs | undefined = undefined,
|
|
137
|
+
F extends FlagShape | undefined = undefined
|
|
138
|
+
> extends Omit<CommandDef, ZodOverriddenKeys> {
|
|
139
|
+
/** Ordered positional args as `arg()` specs. */
|
|
140
|
+
readonly args?: A;
|
|
141
|
+
/** Named flags as plain schemas or `flag()` wrappers. */
|
|
142
|
+
readonly flags?: F;
|
|
143
|
+
/**
|
|
144
|
+
* Optional setup hook before schema validation runs.
|
|
145
|
+
*
|
|
146
|
+
* Receives raw parser output (`CommandContext`), not schema-transformed values.
|
|
147
|
+
*/
|
|
148
|
+
readonly preRun?: (context: CommandContext) => void | Promise<void>;
|
|
149
|
+
/** Main handler with validated/transformed args and flags. */
|
|
150
|
+
readonly run?: ZodCommandRunHandler<InferArgsFromConfig<A>, InferFlagsFromConfig<F>>;
|
|
151
|
+
/**
|
|
152
|
+
* Optional teardown hook after command execution.
|
|
153
|
+
*
|
|
154
|
+
* Receives raw parser output (`CommandContext`), not schema-transformed values.
|
|
155
|
+
*/
|
|
156
|
+
readonly postRun?: (context: CommandContext) => void | Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Define a Crust command where schemas are the source of truth.
|
|
160
|
+
*
|
|
161
|
+
* Positional args are declared with `arg(name, schema)` in an ordered array,
|
|
162
|
+
* flags are declared as plain schemas or `flag(schema, meta)` wrappers.
|
|
163
|
+
*
|
|
164
|
+
* The factory generates Crust parser/help definitions and runs schema
|
|
165
|
+
* validation after parsing but before user handler execution.
|
|
166
|
+
*
|
|
167
|
+
* Compile-time validation (via intersection branding) catches:
|
|
168
|
+
* - Variadic args that aren't in the last position
|
|
169
|
+
* - Flag alias collisions (alias→name or alias→alias)
|
|
170
|
+
*/
|
|
171
|
+
declare function defineZodCommand<
|
|
172
|
+
const A extends readonly ArgSpec[] | undefined,
|
|
173
|
+
const F extends FlagShape | undefined
|
|
174
|
+
>(config: ZodCommandDef<A, F> & {
|
|
175
|
+
args?: A extends readonly object[] ? ValidateVariadicArgs<A> : A;
|
|
176
|
+
flags?: F extends Record<string, unknown> ? ValidateFlagAliases<F> : F;
|
|
177
|
+
}): AnyCommand2;
|
|
178
|
+
/**
|
|
179
|
+
* Define a named positional argument schema for `defineZodCommand()`.
|
|
180
|
+
*
|
|
181
|
+
* The `const Variadic` parameter preserves `{ variadic: true }` as a
|
|
182
|
+
* literal in the return type, enabling compile-time variadic position
|
|
183
|
+
* validation in `defineZodCommand()`.
|
|
184
|
+
*
|
|
185
|
+
* @param name - Positional arg name used in parser output and help text
|
|
186
|
+
* @param schema - Zod schema
|
|
187
|
+
* @param options - Optional CLI metadata (description, variadic)
|
|
188
|
+
*/
|
|
189
|
+
declare function arg<
|
|
190
|
+
Name extends string,
|
|
191
|
+
Schema extends ZodSchemaLike,
|
|
192
|
+
const Variadic extends true | undefined = undefined
|
|
193
|
+
>(name: Name, schema: Schema, options?: ArgOptions & {
|
|
194
|
+
variadic?: Variadic;
|
|
195
|
+
}): ArgSpec<Name, Schema, Variadic>;
|
|
196
|
+
/**
|
|
197
|
+
* Define a flag schema for `defineZodCommand()` with optional alias/description.
|
|
198
|
+
*
|
|
199
|
+
* The `const Alias` parameter preserves alias literals (e.g. `"v"` or
|
|
200
|
+
* `readonly ["v", "V"]`) in the return type, enabling compile-time alias
|
|
201
|
+
* collision detection in `defineZodCommand()`.
|
|
202
|
+
*
|
|
203
|
+
* @param schema - Zod schema
|
|
204
|
+
* @param options - Optional flag metadata
|
|
205
|
+
*/
|
|
206
|
+
declare function flag<
|
|
207
|
+
Schema extends ZodSchemaLike,
|
|
208
|
+
const Alias extends string | readonly string[] | undefined = undefined
|
|
209
|
+
>(schema: Schema, options?: FlagOptions & {
|
|
210
|
+
alias?: Alias;
|
|
211
|
+
}): FlagSpec<Schema, Alias>;
|
|
212
|
+
export { flag, defineZodCommand, arg, ZodSchemaLike, ZodCommandRunHandler, ZodCommandDef, InferSchemaOutput, InferFlagsFromShape, InferFlagsFromConfig, InferArgsFromSpecs, InferArgsFromConfig, FlagSpec, FlagShape, FlagOptions, ArgSpecs, ArgSpec, ArgOptions };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
buildArgDefinitions,
|
|
4
|
+
buildFlagDefinitions,
|
|
5
|
+
buildRunHandler,
|
|
6
|
+
normalizeIssues
|
|
7
|
+
} from "../shared/chunk-zfhm7pmv.js";
|
|
8
|
+
|
|
9
|
+
// src/zod/command.ts
|
|
10
|
+
import { defineCommand } from "@crustjs/core";
|
|
11
|
+
import { safeParseAsync } from "zod/v4/core";
|
|
12
|
+
|
|
13
|
+
// src/zod/definitions.ts
|
|
14
|
+
import { CrustError as CrustError2 } from "@crustjs/core";
|
|
15
|
+
|
|
16
|
+
// src/zod/schema.ts
|
|
17
|
+
import { CrustError } from "@crustjs/core";
|
|
18
|
+
function isZodSchema(value) {
|
|
19
|
+
if (typeof value !== "object" || value === null) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (!("_zod" in value)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function arg(name, schema, options) {
|
|
28
|
+
if (!name.trim()) {
|
|
29
|
+
throw new CrustError("DEFINITION", "arg(): name is required and must be a non-empty string");
|
|
30
|
+
}
|
|
31
|
+
if (!isZodSchema(schema)) {
|
|
32
|
+
throw new CrustError("DEFINITION", `arg("${name}"): schema must be a Zod schema`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
kind: "arg",
|
|
36
|
+
name,
|
|
37
|
+
schema,
|
|
38
|
+
variadic: options?.variadic
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function flag(schema, options) {
|
|
42
|
+
if (!isZodSchema(schema)) {
|
|
43
|
+
throw new CrustError("DEFINITION", "flag(): schema must be a Zod schema");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
kind: "flag",
|
|
47
|
+
schema,
|
|
48
|
+
alias: options?.alias
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveDescription(schema) {
|
|
52
|
+
let current = schema;
|
|
53
|
+
const seen = new Set;
|
|
54
|
+
for (;; ) {
|
|
55
|
+
if (current === undefined || current === null || seen.has(current)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
seen.add(current);
|
|
59
|
+
if (typeof current !== "object") {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if ("description" in current && typeof current.description === "string") {
|
|
63
|
+
return current.description;
|
|
64
|
+
}
|
|
65
|
+
const type = "type" in current ? current.type : undefined;
|
|
66
|
+
if (typeof type !== "string") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (type === "pipe" || type === "transform") {
|
|
70
|
+
const input = current.in;
|
|
71
|
+
if (input !== undefined) {
|
|
72
|
+
current = input;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (type === "optional" || type === "nullable" || type === "default" || type === "prefault" || type === "nonoptional" || type === "readonly" || type === "catch") {
|
|
78
|
+
const unwrap = current.unwrap;
|
|
79
|
+
if (typeof unwrap === "function") {
|
|
80
|
+
current = unwrap();
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/zod/definitions.ts
|
|
90
|
+
function asRuntimeSchema(value) {
|
|
91
|
+
if (typeof value !== "object" || value === null) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function getSchemaType(schema) {
|
|
97
|
+
const runtime = asRuntimeSchema(schema);
|
|
98
|
+
if (!runtime || typeof runtime.type !== "string") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
return runtime.type;
|
|
102
|
+
}
|
|
103
|
+
function unwrapInputSchema(schema) {
|
|
104
|
+
let current = schema;
|
|
105
|
+
for (;; ) {
|
|
106
|
+
const type = getSchemaType(current);
|
|
107
|
+
const runtime = asRuntimeSchema(current);
|
|
108
|
+
if (!type || !runtime) {
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
if (type === "pipe" || type === "transform") {
|
|
112
|
+
if (runtime.in === undefined) {
|
|
113
|
+
return current;
|
|
114
|
+
}
|
|
115
|
+
current = runtime.in;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (type === "optional" || type === "nullable" || type === "default" || type === "prefault" || type === "nonoptional" || type === "readonly" || type === "catch") {
|
|
119
|
+
if (typeof runtime.unwrap !== "function") {
|
|
120
|
+
return current;
|
|
121
|
+
}
|
|
122
|
+
current = runtime.unwrap();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function resolvePrimitiveInputType(schema) {
|
|
129
|
+
const type = getSchemaType(schema);
|
|
130
|
+
if (!type) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (type === "string" || type === "enum") {
|
|
134
|
+
return "string";
|
|
135
|
+
}
|
|
136
|
+
if (type === "number") {
|
|
137
|
+
return "number";
|
|
138
|
+
}
|
|
139
|
+
if (type === "boolean") {
|
|
140
|
+
return "boolean";
|
|
141
|
+
}
|
|
142
|
+
if (type === "literal") {
|
|
143
|
+
const runtime = asRuntimeSchema(schema);
|
|
144
|
+
const first = runtime?.values?.values().next().value;
|
|
145
|
+
if (typeof first === "string") {
|
|
146
|
+
return "string";
|
|
147
|
+
}
|
|
148
|
+
if (typeof first === "number") {
|
|
149
|
+
return "number";
|
|
150
|
+
}
|
|
151
|
+
if (typeof first === "boolean") {
|
|
152
|
+
return "boolean";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
function resolveInputShape(schema, label) {
|
|
158
|
+
const inputSchema = unwrapInputSchema(schema);
|
|
159
|
+
if (getSchemaType(inputSchema) === "array") {
|
|
160
|
+
const runtime = asRuntimeSchema(inputSchema);
|
|
161
|
+
if (typeof runtime?.unwrap !== "function") {
|
|
162
|
+
throw new CrustError2("DEFINITION", `${label}: unable to inspect array element schema`);
|
|
163
|
+
}
|
|
164
|
+
const elementSchema = unwrapInputSchema(runtime.unwrap());
|
|
165
|
+
const primitive2 = resolvePrimitiveInputType(elementSchema);
|
|
166
|
+
if (primitive2) {
|
|
167
|
+
return { type: primitive2, multiple: true };
|
|
168
|
+
}
|
|
169
|
+
throw new CrustError2("DEFINITION", `${label}: array element type must be string, number, or boolean`);
|
|
170
|
+
}
|
|
171
|
+
const primitive = resolvePrimitiveInputType(inputSchema);
|
|
172
|
+
if (primitive) {
|
|
173
|
+
return { type: primitive, multiple: false };
|
|
174
|
+
}
|
|
175
|
+
throw new CrustError2("DEFINITION", `${label}: unsupported schema type for CLI parsing. Use string, number, boolean, enum/literal, or array of these.`);
|
|
176
|
+
}
|
|
177
|
+
function isOptionalInputSchema(schema) {
|
|
178
|
+
let current = schema;
|
|
179
|
+
for (;; ) {
|
|
180
|
+
const type = getSchemaType(current);
|
|
181
|
+
const runtime = asRuntimeSchema(current);
|
|
182
|
+
if (!type || !runtime) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (type === "optional" || type === "default" || type === "prefault" || type === "catch") {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
if (type === "pipe" || type === "transform") {
|
|
189
|
+
if (runtime.in === undefined) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
current = runtime.in;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (type === "nullable" || type === "nonoptional" || type === "readonly") {
|
|
196
|
+
if (typeof runtime.unwrap !== "function") {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
current = runtime.unwrap();
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var zodAdapter = {
|
|
206
|
+
resolveInputShape,
|
|
207
|
+
isOptionalInputSchema,
|
|
208
|
+
resolveDescription,
|
|
209
|
+
commandLabel: "defineZodCommand",
|
|
210
|
+
arrayHint: "z.array(...)"
|
|
211
|
+
};
|
|
212
|
+
function argsToDefinitions(args) {
|
|
213
|
+
return buildArgDefinitions(args, zodAdapter);
|
|
214
|
+
}
|
|
215
|
+
function flagsToDefinitions(flags) {
|
|
216
|
+
return buildFlagDefinitions(flags, zodAdapter);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/zod/command.ts
|
|
220
|
+
async function validateValue(schema, value, prefix) {
|
|
221
|
+
const parseResult = await safeParseAsync(schema, value);
|
|
222
|
+
if (parseResult.success) {
|
|
223
|
+
return { ok: true, value: parseResult.data };
|
|
224
|
+
}
|
|
225
|
+
const prefixed = parseResult.error.issues.map((issue) => ({
|
|
226
|
+
message: issue.message,
|
|
227
|
+
path: [...prefix, ...issue.path ?? []]
|
|
228
|
+
}));
|
|
229
|
+
return { ok: false, issues: normalizeIssues(prefixed) };
|
|
230
|
+
}
|
|
231
|
+
function defineZodCommand(config) {
|
|
232
|
+
const {
|
|
233
|
+
args: zodArgs,
|
|
234
|
+
flags: zodFlags,
|
|
235
|
+
run: userRun,
|
|
236
|
+
...passthrough
|
|
237
|
+
} = config;
|
|
238
|
+
const argSpecs = zodArgs ?? [];
|
|
239
|
+
const generatedArgs = argsToDefinitions(argSpecs);
|
|
240
|
+
const generatedFlags = flagsToDefinitions(zodFlags);
|
|
241
|
+
const command = defineCommand({
|
|
242
|
+
...passthrough,
|
|
243
|
+
...generatedArgs.length > 0 && { args: generatedArgs },
|
|
244
|
+
...Object.keys(generatedFlags).length > 0 && { flags: generatedFlags },
|
|
245
|
+
...userRun && {
|
|
246
|
+
run: buildRunHandler(argSpecs, zodFlags, userRun, validateValue)
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
return command;
|
|
250
|
+
}
|
|
251
|
+
export {
|
|
252
|
+
flag,
|
|
253
|
+
defineZodCommand,
|
|
254
|
+
arg
|
|
255
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crustjs/validate",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Validation helpers for the Crust CLI framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "chenxin-yan",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/chenxin-yan/crust.git",
|
|
11
|
+
"directory": "packages/validate"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://crustjs.com",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/chenxin-yan/crust/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"cli",
|
|
19
|
+
"validation",
|
|
20
|
+
"effect",
|
|
21
|
+
"zod",
|
|
22
|
+
"bun",
|
|
23
|
+
"typescript"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./effect": {
|
|
34
|
+
"import": "./dist/effect/index.js",
|
|
35
|
+
"types": "./dist/effect/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./zod": {
|
|
38
|
+
"import": "./dist/zod/index.js",
|
|
39
|
+
"types": "./dist/zod/index.d.ts"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "bunup",
|
|
47
|
+
"dev": "bunup --watch",
|
|
48
|
+
"check:types": "tsc --noEmit",
|
|
49
|
+
"test": "bun test"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@crustjs/config": "0.0.0",
|
|
53
|
+
"@crustjs/core": "0.0.6",
|
|
54
|
+
"@crustjs/plugins": "0.0.6",
|
|
55
|
+
"bunup": "^0.16.29",
|
|
56
|
+
"effect": "^3.19.0",
|
|
57
|
+
"zod": "^4.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@crustjs/core": "0.0.6",
|
|
61
|
+
"effect": "^3.19.0",
|
|
62
|
+
"zod": "^4.0.0",
|
|
63
|
+
"typescript": "^5"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"effect": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"zod": {
|
|
70
|
+
"optional": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|