@optique/discover 1.2.0-dev.2252 → 1.2.0-dev.2255
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/dist/cli.cjs +4 -4
- package/dist/cli.js +4 -4
- package/dist/{command-CUn2_NIA.cjs → command-C9u5sJZS.cjs} +29 -0
- package/dist/command-DIfnalpt.d.cts +283 -0
- package/dist/{command-DO5zgkvS.js → command-DSHIV50v.js} +24 -1
- package/dist/command-s80EaEXl.d.ts +283 -0
- package/dist/command.cjs +3 -2
- package/dist/command.d.cts +2 -2
- package/dist/command.d.ts +2 -2
- package/dist/command.js +2 -2
- package/dist/{generator-BV1QDOot.cjs → generator-DW4rrPD2.cjs} +1 -1
- package/dist/{generator-B81thoIS.js → generator-HNXr5Vz2.js} +1 -1
- package/dist/generator.cjs +3 -3
- package/dist/generator.js +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +16 -25
- package/dist/index.d.ts +16 -25
- package/dist/index.js +2 -2
- package/dist/{src-kBDpUW2H.js → src-CVATvqru.js} +65 -13
- package/dist/{src-BDObn4mj.cjs → src-CzbbFDmi.cjs} +65 -13
- package/package.json +3 -3
- package/dist/command-DSHBTa5c.d.cts +0 -139
- package/dist/command-DrmNW0HO.d.ts +0 -139
package/dist/cli.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
require('./command-
|
|
4
|
-
require('./src-
|
|
5
|
-
const require_generator = require('./generator-
|
|
3
|
+
require('./command-C9u5sJZS.cjs');
|
|
4
|
+
require('./src-CzbbFDmi.cjs');
|
|
5
|
+
const require_generator = require('./generator-DW4rrPD2.cjs');
|
|
6
6
|
const require_main_check = require('./main-check-CwunSNpK.cjs');
|
|
7
7
|
const __optique_core_constructs = require_chunk.__toESM(require("@optique/core/constructs"));
|
|
8
8
|
const __optique_core_message = require_chunk.__toESM(require("@optique/core/message"));
|
|
@@ -15,7 +15,7 @@ const node_process = require_chunk.__toESM(require("node:process"));
|
|
|
15
15
|
|
|
16
16
|
//#region deno.json
|
|
17
17
|
var name = "@optique/discover";
|
|
18
|
-
var version = "1.2.0-dev.
|
|
18
|
+
var version = "1.2.0-dev.2255+a902c666";
|
|
19
19
|
var license = "MIT";
|
|
20
20
|
var exports$1 = {
|
|
21
21
|
".": "./src/index.ts",
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./command-
|
|
3
|
-
import "./src-
|
|
4
|
-
import { watchCommandsModule, writeCommandsModule } from "./generator-
|
|
2
|
+
import "./command-DSHIV50v.js";
|
|
3
|
+
import "./src-CVATvqru.js";
|
|
4
|
+
import { watchCommandsModule, writeCommandsModule } from "./generator-HNXr5Vz2.js";
|
|
5
5
|
import { isMainModuleUrl } from "./main-check-mMnKfUZs.js";
|
|
6
6
|
import { object } from "@optique/core/constructs";
|
|
7
7
|
import { message } from "@optique/core/message";
|
|
@@ -14,7 +14,7 @@ import process from "node:process";
|
|
|
14
14
|
|
|
15
15
|
//#region deno.json
|
|
16
16
|
var name = "@optique/discover";
|
|
17
|
-
var version = "1.2.0-dev.
|
|
17
|
+
var version = "1.2.0-dev.2255+a902c666";
|
|
18
18
|
var license = "MIT";
|
|
19
19
|
var exports = {
|
|
20
20
|
".": "./src/index.ts",
|
|
@@ -5,6 +5,7 @@ function defineCommand(command) {
|
|
|
5
5
|
if (!isParser(command.parser)) throw new TypeError("Command parser must be an Optique parser.");
|
|
6
6
|
if (command.path != null) validateCommandPath(command.path);
|
|
7
7
|
if (typeof command.handler !== "function") throw new TypeError("Command handler must be a function.");
|
|
8
|
+
if (command.hooks != null) validateHooks(command.hooks, "Command");
|
|
8
9
|
return {
|
|
9
10
|
...command,
|
|
10
11
|
[commandBrand]: true
|
|
@@ -20,6 +21,28 @@ function defineCommand(command) {
|
|
|
20
21
|
function isCommand(value) {
|
|
21
22
|
return value != null && typeof value === "object" && value[commandBrand] === true && (value.path == null || isCommandPath(value.path)) && isParser(value.parser) && typeof value.handler === "function";
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Validates a {@link ProgramHooks} value, throwing a descriptive error when it
|
|
26
|
+
* is malformed.
|
|
27
|
+
*
|
|
28
|
+
* @param hooks The value to validate.
|
|
29
|
+
* @param scope Label used in error messages: `"Command"` for command-level
|
|
30
|
+
* hooks and `"Program"` for program-level hooks.
|
|
31
|
+
* @throws {TypeError} If `hooks` is not an object, or a hook is neither
|
|
32
|
+
* nullish nor a function.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function validateHooks(hooks, scope) {
|
|
36
|
+
if (typeof hooks !== "object" || hooks == null) throw new TypeError(`${scope} hooks must be an object.`);
|
|
37
|
+
for (const name of [
|
|
38
|
+
"beforeEach",
|
|
39
|
+
"afterEach",
|
|
40
|
+
"onError"
|
|
41
|
+
]) {
|
|
42
|
+
const hook = hooks[name];
|
|
43
|
+
if (hook != null && typeof hook !== "function") throw new TypeError(`${scope} hook "${name}" must be a function.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
23
46
|
function validateCommandPath(path) {
|
|
24
47
|
if (!isCommandPath(path)) throw new TypeError("Command path must be an array of non-empty strings.");
|
|
25
48
|
}
|
|
@@ -42,4 +65,10 @@ Object.defineProperty(exports, 'isCommand', {
|
|
|
42
65
|
get: function () {
|
|
43
66
|
return isCommand;
|
|
44
67
|
}
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(exports, 'validateHooks', {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
get: function () {
|
|
72
|
+
return validateHooks;
|
|
73
|
+
}
|
|
45
74
|
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { CommandOptions } from "@optique/core/primitives";
|
|
2
|
+
import { Mode, Parser } from "@optique/core/parser";
|
|
3
|
+
|
|
4
|
+
//#region src/command.d.ts
|
|
5
|
+
declare const commandBrand: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* Metadata shown for a discovered command.
|
|
8
|
+
*
|
|
9
|
+
* This uses the same shape as Optique's `command()` options so discovered
|
|
10
|
+
* commands can provide descriptions, usage overrides, visibility, and custom
|
|
11
|
+
* command-level errors.
|
|
12
|
+
*
|
|
13
|
+
* @since 1.1.0
|
|
14
|
+
*/
|
|
15
|
+
type CommandMetadata = CommandOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Command path used by static command registration.
|
|
18
|
+
*
|
|
19
|
+
* An empty path represents the root command.
|
|
20
|
+
*
|
|
21
|
+
* @since 1.1.0
|
|
22
|
+
*/
|
|
23
|
+
type CommandPath = readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Resource bundle threaded through {@link ProgramHooks} for a single command
|
|
26
|
+
* run.
|
|
27
|
+
*
|
|
28
|
+
* A {@link ProgramHooks.beforeEach} hook returns this object; the dispatcher
|
|
29
|
+
* forwards it to the command handler's second parameter and to the matching
|
|
30
|
+
* {@link ProgramHooks.afterEach} and {@link ProgramHooks.onError} hooks. This
|
|
31
|
+
* threads handler-time resources without global state.
|
|
32
|
+
*
|
|
33
|
+
* @since 1.2.0
|
|
34
|
+
*/
|
|
35
|
+
interface ProgramHookContext {
|
|
36
|
+
/**
|
|
37
|
+
* Caller-defined resource. Common shapes include a database pool, a logger
|
|
38
|
+
* scope, or a tracing span.
|
|
39
|
+
*/
|
|
40
|
+
readonly resource?: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The parsed command selected by a discovered command parser.
|
|
44
|
+
*
|
|
45
|
+
* Most applications receive this only indirectly through `runProgram()`, which
|
|
46
|
+
* calls the handler automatically.
|
|
47
|
+
*
|
|
48
|
+
* @since 1.1.0
|
|
49
|
+
*/
|
|
50
|
+
interface ProgramInvocation {
|
|
51
|
+
/**
|
|
52
|
+
* The command definition that matched the input.
|
|
53
|
+
*/
|
|
54
|
+
readonly command: AnyCommand;
|
|
55
|
+
/**
|
|
56
|
+
* The resolved command path that matched the input.
|
|
57
|
+
*
|
|
58
|
+
* Unlike {@link CommandDefinition.path}, this is always populated: for
|
|
59
|
+
* file-based discovery it is the path derived from the module's location even
|
|
60
|
+
* when the command definition omits an explicit `path`. The root command
|
|
61
|
+
* uses an empty array. Lifecycle hooks can use this to identify which
|
|
62
|
+
* command is running.
|
|
63
|
+
*/
|
|
64
|
+
readonly path: CommandPath;
|
|
65
|
+
/**
|
|
66
|
+
* Parsed value produced by the command parser.
|
|
67
|
+
*/
|
|
68
|
+
readonly value: unknown;
|
|
69
|
+
/**
|
|
70
|
+
* Handler to call with {@link ProgramInvocation.value} and, when a
|
|
71
|
+
* {@link ProgramHooks} `beforeEach` produced one, a {@link ProgramHookContext}.
|
|
72
|
+
*
|
|
73
|
+
* The context is optional: `runProgram()` supplies it only when a program-level
|
|
74
|
+
* or command-level `beforeEach` ran, and callers that dispatch invocations
|
|
75
|
+
* directly can keep passing only the value.
|
|
76
|
+
*/
|
|
77
|
+
readonly handler: (value: unknown, context?: ProgramHookContext) => void | Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Lifecycle hooks invoked around a command handler.
|
|
81
|
+
*
|
|
82
|
+
* Hooks let cross-cutting concerns — log scopes, tracing spans, lazy resource
|
|
83
|
+
* setup, structured timing, error reporting — live in a single place instead
|
|
84
|
+
* of being duplicated inside every command handler. Pass them to
|
|
85
|
+
* `runProgram({ hooks })` to wrap every command, or to
|
|
86
|
+
* {@link CommandDefinition.hooks} to wrap a single command.
|
|
87
|
+
*
|
|
88
|
+
* When both program-level and command-level hooks are present, they nest:
|
|
89
|
+
*
|
|
90
|
+
* ```
|
|
91
|
+
* program.beforeEach → command.beforeEach → handler
|
|
92
|
+
* ↓
|
|
93
|
+
* program.afterEach ← command.afterEach ←─┘
|
|
94
|
+
* program.onError ← command.onError ← on failure
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @since 1.2.0
|
|
98
|
+
*/
|
|
99
|
+
interface ProgramHooks {
|
|
100
|
+
/**
|
|
101
|
+
* Called before the command handler runs, receiving the matched command, its
|
|
102
|
+
* resolved {@link ProgramInvocation.path}, the parsed value, and the handler
|
|
103
|
+
* via {@link ProgramInvocation}.
|
|
104
|
+
*
|
|
105
|
+
* The returned {@link ProgramHookContext} is threaded forward as the second
|
|
106
|
+
* argument to the command handler (when this is the most specific hook scope)
|
|
107
|
+
* and to {@link afterEach} and {@link onError}.
|
|
108
|
+
*
|
|
109
|
+
* Returning a promise is supported; the dispatcher awaits it. A rejected
|
|
110
|
+
* promise (or a thrown error) aborts the command before the handler runs and
|
|
111
|
+
* invokes {@link onError}.
|
|
112
|
+
*/
|
|
113
|
+
readonly beforeEach?: (invocation: ProgramInvocation) => ProgramHookContext | Promise<ProgramHookContext>;
|
|
114
|
+
/**
|
|
115
|
+
* Called after the handler returns successfully, receiving the context from
|
|
116
|
+
* {@link beforeEach} (or an empty object when no `beforeEach` ran) and the
|
|
117
|
+
* handler's return value.
|
|
118
|
+
*
|
|
119
|
+
* Returning a promise is supported; the dispatcher awaits it. If this hook
|
|
120
|
+
* throws or rejects, the dispatcher treats it as a handler failure and
|
|
121
|
+
* invokes {@link onError} with the thrown error.
|
|
122
|
+
*/
|
|
123
|
+
readonly afterEach?: (context: ProgramHookContext, result: unknown) => void | Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Called when the handler (or {@link beforeEach}/{@link afterEach}) throws or
|
|
126
|
+
* rejects, receiving the context from {@link beforeEach} (or an empty object)
|
|
127
|
+
* and the thrown error.
|
|
128
|
+
*
|
|
129
|
+
* The dispatcher re-throws the original error after this hook resolves, so
|
|
130
|
+
* process exit-code behavior is unchanged; the hook is for observation and
|
|
131
|
+
* cleanup, not for swallowing the error. An error thrown by this hook itself
|
|
132
|
+
* is suppressed so it cannot mask the original failure.
|
|
133
|
+
*
|
|
134
|
+
* Returning a promise is supported; the dispatcher awaits it.
|
|
135
|
+
*/
|
|
136
|
+
readonly onError?: (context: ProgramHookContext, error: unknown) => void | Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Input accepted by {@link defineCommand}.
|
|
140
|
+
*
|
|
141
|
+
* @template M The mode of the command parser.
|
|
142
|
+
* @template T The parsed value passed to the command handler.
|
|
143
|
+
* @since 1.1.0
|
|
144
|
+
*/
|
|
145
|
+
interface CommandDefinition<M extends Mode, T> {
|
|
146
|
+
/**
|
|
147
|
+
* Command path used when commands are passed directly to `runProgram()`.
|
|
148
|
+
* Use an empty path (`[]`) to register the root command.
|
|
149
|
+
*
|
|
150
|
+
* File-based discovery derives the command path from the file name and uses
|
|
151
|
+
* this field only to validate that the declared path matches.
|
|
152
|
+
*/
|
|
153
|
+
readonly path?: CommandPath;
|
|
154
|
+
/**
|
|
155
|
+
* Parser for this command's command-specific arguments and options.
|
|
156
|
+
*/
|
|
157
|
+
readonly parser: Parser<M, T, unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* Metadata used in help output and shell completion.
|
|
160
|
+
*/
|
|
161
|
+
readonly metadata?: CommandMetadata;
|
|
162
|
+
/**
|
|
163
|
+
* Lifecycle hooks scoped to this command.
|
|
164
|
+
*
|
|
165
|
+
* These run inside any program-level hooks passed to `runProgram({ hooks })`:
|
|
166
|
+
* the program-level `beforeEach` runs first, then this command's
|
|
167
|
+
* `beforeEach`, then the handler; teardown unwinds in reverse. Use this when
|
|
168
|
+
* a single command needs its own preflight, such as a `deploy` command that
|
|
169
|
+
* always refreshes an auth token, instead of program-wide logic.
|
|
170
|
+
*
|
|
171
|
+
* @since 1.2.0
|
|
172
|
+
*/
|
|
173
|
+
readonly hooks?: ProgramHooks;
|
|
174
|
+
/**
|
|
175
|
+
* Handles the parsed command value.
|
|
176
|
+
*
|
|
177
|
+
* @param value Parsed command value.
|
|
178
|
+
* @param context Resource bundle from the most specific {@link ProgramHooks}
|
|
179
|
+
* `beforeEach` that ran. It is omitted when no program-level
|
|
180
|
+
* or command-level `beforeEach` ran, so a plain command
|
|
181
|
+
* without hooks receives only the value, exactly as before.
|
|
182
|
+
* Existing single-argument handlers can ignore it.
|
|
183
|
+
* @returns Nothing, or a promise that resolves when command handling
|
|
184
|
+
* completes.
|
|
185
|
+
*/
|
|
186
|
+
readonly handler: (value: T, context?: ProgramHookContext) => void | Promise<void>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A discovered command module definition.
|
|
190
|
+
*
|
|
191
|
+
* @template M The mode of the command parser.
|
|
192
|
+
* @template T The parsed value passed to the command handler.
|
|
193
|
+
* @since 1.1.0
|
|
194
|
+
*/
|
|
195
|
+
interface Command<M extends Mode, T> extends CommandDefinition<M, T> {
|
|
196
|
+
/**
|
|
197
|
+
* Internal marker used to validate discovered modules.
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
readonly [commandBrand]: true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A command that declares its own command path.
|
|
205
|
+
*
|
|
206
|
+
* Static `runProgram({ commands })` registration accepts this shape.
|
|
207
|
+
*
|
|
208
|
+
* @template M The mode of the command parser.
|
|
209
|
+
* @template T The parsed value passed to the command handler.
|
|
210
|
+
* @since 1.1.0
|
|
211
|
+
*/
|
|
212
|
+
interface StaticCommand<M extends Mode, T> extends Command<M, T> {
|
|
213
|
+
/**
|
|
214
|
+
* Command path used by static command registration.
|
|
215
|
+
*/
|
|
216
|
+
readonly path: CommandPath;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* A command with its handler value type erased.
|
|
220
|
+
*
|
|
221
|
+
* This type is used by discovery APIs that collect commands with different
|
|
222
|
+
* parsed value types. The handler cannot be called directly without first
|
|
223
|
+
* recovering the parser's value type.
|
|
224
|
+
*
|
|
225
|
+
* @since 1.1.0
|
|
226
|
+
*/
|
|
227
|
+
type AnyCommand = Omit<Command<Mode, unknown>, "handler"> & {
|
|
228
|
+
/**
|
|
229
|
+
* Erased command handler.
|
|
230
|
+
*/
|
|
231
|
+
readonly handler: (value: never, context?: ProgramHookContext) => void | Promise<void>;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* A statically registered command with its handler value type erased.
|
|
235
|
+
*
|
|
236
|
+
* @since 1.1.0
|
|
237
|
+
*/
|
|
238
|
+
type AnyStaticCommand = Omit<StaticCommand<Mode, unknown>, "handler"> & {
|
|
239
|
+
/**
|
|
240
|
+
* Erased command handler.
|
|
241
|
+
*/
|
|
242
|
+
readonly handler: (value: never, context?: ProgramHookContext) => void | Promise<void>;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Defines a command module for `@optique/discover`.
|
|
246
|
+
*
|
|
247
|
+
* This helper returns its argument unchanged while preserving parser value
|
|
248
|
+
* inference for the handler callback.
|
|
249
|
+
*
|
|
250
|
+
* @template M The mode of the command parser.
|
|
251
|
+
* @template T The parsed value passed to the command handler.
|
|
252
|
+
* @param command The command definition.
|
|
253
|
+
* @returns The same command definition with inferred types.
|
|
254
|
+
* @throws {TypeError} If the parser, path, handler, or hooks are missing or
|
|
255
|
+
* malformed.
|
|
256
|
+
* @since 1.1.0
|
|
257
|
+
*/
|
|
258
|
+
declare function defineCommand<M extends Mode, T>(command: CommandDefinition<M, T> & {
|
|
259
|
+
readonly path: CommandPath;
|
|
260
|
+
}): StaticCommand<M, T>;
|
|
261
|
+
declare function defineCommand<M extends Mode, T>(command: CommandDefinition<M, T>): Command<M, T>;
|
|
262
|
+
/**
|
|
263
|
+
* Returns whether a value is a command created by {@link defineCommand}.
|
|
264
|
+
*
|
|
265
|
+
* @param value The value to inspect.
|
|
266
|
+
* @returns `true` when the value is a discovered command definition.
|
|
267
|
+
* @since 1.1.0
|
|
268
|
+
*/
|
|
269
|
+
declare function isCommand(value: unknown): value is AnyCommand;
|
|
270
|
+
/**
|
|
271
|
+
* Validates a {@link ProgramHooks} value, throwing a descriptive error when it
|
|
272
|
+
* is malformed.
|
|
273
|
+
*
|
|
274
|
+
* @param hooks The value to validate.
|
|
275
|
+
* @param scope Label used in error messages: `"Command"` for command-level
|
|
276
|
+
* hooks and `"Program"` for program-level hooks.
|
|
277
|
+
* @throws {TypeError} If `hooks` is not an object, or a hook is neither
|
|
278
|
+
* nullish nor a function.
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
declare function validateHooks(hooks: unknown, scope: "Command" | "Program"): asserts hooks is ProgramHooks;
|
|
282
|
+
//#endregion
|
|
283
|
+
export { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, ProgramHookContext, ProgramHooks, ProgramInvocation, StaticCommand, defineCommand, isCommand, validateHooks };
|
|
@@ -4,6 +4,7 @@ function defineCommand(command) {
|
|
|
4
4
|
if (!isParser(command.parser)) throw new TypeError("Command parser must be an Optique parser.");
|
|
5
5
|
if (command.path != null) validateCommandPath(command.path);
|
|
6
6
|
if (typeof command.handler !== "function") throw new TypeError("Command handler must be a function.");
|
|
7
|
+
if (command.hooks != null) validateHooks(command.hooks, "Command");
|
|
7
8
|
return {
|
|
8
9
|
...command,
|
|
9
10
|
[commandBrand]: true
|
|
@@ -19,6 +20,28 @@ function defineCommand(command) {
|
|
|
19
20
|
function isCommand(value) {
|
|
20
21
|
return value != null && typeof value === "object" && value[commandBrand] === true && (value.path == null || isCommandPath(value.path)) && isParser(value.parser) && typeof value.handler === "function";
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Validates a {@link ProgramHooks} value, throwing a descriptive error when it
|
|
25
|
+
* is malformed.
|
|
26
|
+
*
|
|
27
|
+
* @param hooks The value to validate.
|
|
28
|
+
* @param scope Label used in error messages: `"Command"` for command-level
|
|
29
|
+
* hooks and `"Program"` for program-level hooks.
|
|
30
|
+
* @throws {TypeError} If `hooks` is not an object, or a hook is neither
|
|
31
|
+
* nullish nor a function.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
function validateHooks(hooks, scope) {
|
|
35
|
+
if (typeof hooks !== "object" || hooks == null) throw new TypeError(`${scope} hooks must be an object.`);
|
|
36
|
+
for (const name of [
|
|
37
|
+
"beforeEach",
|
|
38
|
+
"afterEach",
|
|
39
|
+
"onError"
|
|
40
|
+
]) {
|
|
41
|
+
const hook = hooks[name];
|
|
42
|
+
if (hook != null && typeof hook !== "function") throw new TypeError(`${scope} hook "${name}" must be a function.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
22
45
|
function validateCommandPath(path) {
|
|
23
46
|
if (!isCommandPath(path)) throw new TypeError("Command path must be an array of non-empty strings.");
|
|
24
47
|
}
|
|
@@ -30,4 +53,4 @@ function isParser(value) {
|
|
|
30
53
|
}
|
|
31
54
|
|
|
32
55
|
//#endregion
|
|
33
|
-
export { defineCommand, isCommand };
|
|
56
|
+
export { defineCommand, isCommand, validateHooks };
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { CommandOptions } from "@optique/core/primitives";
|
|
2
|
+
import { Mode, Parser } from "@optique/core/parser";
|
|
3
|
+
|
|
4
|
+
//#region src/command.d.ts
|
|
5
|
+
declare const commandBrand: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* Metadata shown for a discovered command.
|
|
8
|
+
*
|
|
9
|
+
* This uses the same shape as Optique's `command()` options so discovered
|
|
10
|
+
* commands can provide descriptions, usage overrides, visibility, and custom
|
|
11
|
+
* command-level errors.
|
|
12
|
+
*
|
|
13
|
+
* @since 1.1.0
|
|
14
|
+
*/
|
|
15
|
+
type CommandMetadata = CommandOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Command path used by static command registration.
|
|
18
|
+
*
|
|
19
|
+
* An empty path represents the root command.
|
|
20
|
+
*
|
|
21
|
+
* @since 1.1.0
|
|
22
|
+
*/
|
|
23
|
+
type CommandPath = readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Resource bundle threaded through {@link ProgramHooks} for a single command
|
|
26
|
+
* run.
|
|
27
|
+
*
|
|
28
|
+
* A {@link ProgramHooks.beforeEach} hook returns this object; the dispatcher
|
|
29
|
+
* forwards it to the command handler's second parameter and to the matching
|
|
30
|
+
* {@link ProgramHooks.afterEach} and {@link ProgramHooks.onError} hooks. This
|
|
31
|
+
* threads handler-time resources without global state.
|
|
32
|
+
*
|
|
33
|
+
* @since 1.2.0
|
|
34
|
+
*/
|
|
35
|
+
interface ProgramHookContext {
|
|
36
|
+
/**
|
|
37
|
+
* Caller-defined resource. Common shapes include a database pool, a logger
|
|
38
|
+
* scope, or a tracing span.
|
|
39
|
+
*/
|
|
40
|
+
readonly resource?: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The parsed command selected by a discovered command parser.
|
|
44
|
+
*
|
|
45
|
+
* Most applications receive this only indirectly through `runProgram()`, which
|
|
46
|
+
* calls the handler automatically.
|
|
47
|
+
*
|
|
48
|
+
* @since 1.1.0
|
|
49
|
+
*/
|
|
50
|
+
interface ProgramInvocation {
|
|
51
|
+
/**
|
|
52
|
+
* The command definition that matched the input.
|
|
53
|
+
*/
|
|
54
|
+
readonly command: AnyCommand;
|
|
55
|
+
/**
|
|
56
|
+
* The resolved command path that matched the input.
|
|
57
|
+
*
|
|
58
|
+
* Unlike {@link CommandDefinition.path}, this is always populated: for
|
|
59
|
+
* file-based discovery it is the path derived from the module's location even
|
|
60
|
+
* when the command definition omits an explicit `path`. The root command
|
|
61
|
+
* uses an empty array. Lifecycle hooks can use this to identify which
|
|
62
|
+
* command is running.
|
|
63
|
+
*/
|
|
64
|
+
readonly path: CommandPath;
|
|
65
|
+
/**
|
|
66
|
+
* Parsed value produced by the command parser.
|
|
67
|
+
*/
|
|
68
|
+
readonly value: unknown;
|
|
69
|
+
/**
|
|
70
|
+
* Handler to call with {@link ProgramInvocation.value} and, when a
|
|
71
|
+
* {@link ProgramHooks} `beforeEach` produced one, a {@link ProgramHookContext}.
|
|
72
|
+
*
|
|
73
|
+
* The context is optional: `runProgram()` supplies it only when a program-level
|
|
74
|
+
* or command-level `beforeEach` ran, and callers that dispatch invocations
|
|
75
|
+
* directly can keep passing only the value.
|
|
76
|
+
*/
|
|
77
|
+
readonly handler: (value: unknown, context?: ProgramHookContext) => void | Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Lifecycle hooks invoked around a command handler.
|
|
81
|
+
*
|
|
82
|
+
* Hooks let cross-cutting concerns — log scopes, tracing spans, lazy resource
|
|
83
|
+
* setup, structured timing, error reporting — live in a single place instead
|
|
84
|
+
* of being duplicated inside every command handler. Pass them to
|
|
85
|
+
* `runProgram({ hooks })` to wrap every command, or to
|
|
86
|
+
* {@link CommandDefinition.hooks} to wrap a single command.
|
|
87
|
+
*
|
|
88
|
+
* When both program-level and command-level hooks are present, they nest:
|
|
89
|
+
*
|
|
90
|
+
* ```
|
|
91
|
+
* program.beforeEach → command.beforeEach → handler
|
|
92
|
+
* ↓
|
|
93
|
+
* program.afterEach ← command.afterEach ←─┘
|
|
94
|
+
* program.onError ← command.onError ← on failure
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @since 1.2.0
|
|
98
|
+
*/
|
|
99
|
+
interface ProgramHooks {
|
|
100
|
+
/**
|
|
101
|
+
* Called before the command handler runs, receiving the matched command, its
|
|
102
|
+
* resolved {@link ProgramInvocation.path}, the parsed value, and the handler
|
|
103
|
+
* via {@link ProgramInvocation}.
|
|
104
|
+
*
|
|
105
|
+
* The returned {@link ProgramHookContext} is threaded forward as the second
|
|
106
|
+
* argument to the command handler (when this is the most specific hook scope)
|
|
107
|
+
* and to {@link afterEach} and {@link onError}.
|
|
108
|
+
*
|
|
109
|
+
* Returning a promise is supported; the dispatcher awaits it. A rejected
|
|
110
|
+
* promise (or a thrown error) aborts the command before the handler runs and
|
|
111
|
+
* invokes {@link onError}.
|
|
112
|
+
*/
|
|
113
|
+
readonly beforeEach?: (invocation: ProgramInvocation) => ProgramHookContext | Promise<ProgramHookContext>;
|
|
114
|
+
/**
|
|
115
|
+
* Called after the handler returns successfully, receiving the context from
|
|
116
|
+
* {@link beforeEach} (or an empty object when no `beforeEach` ran) and the
|
|
117
|
+
* handler's return value.
|
|
118
|
+
*
|
|
119
|
+
* Returning a promise is supported; the dispatcher awaits it. If this hook
|
|
120
|
+
* throws or rejects, the dispatcher treats it as a handler failure and
|
|
121
|
+
* invokes {@link onError} with the thrown error.
|
|
122
|
+
*/
|
|
123
|
+
readonly afterEach?: (context: ProgramHookContext, result: unknown) => void | Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Called when the handler (or {@link beforeEach}/{@link afterEach}) throws or
|
|
126
|
+
* rejects, receiving the context from {@link beforeEach} (or an empty object)
|
|
127
|
+
* and the thrown error.
|
|
128
|
+
*
|
|
129
|
+
* The dispatcher re-throws the original error after this hook resolves, so
|
|
130
|
+
* process exit-code behavior is unchanged; the hook is for observation and
|
|
131
|
+
* cleanup, not for swallowing the error. An error thrown by this hook itself
|
|
132
|
+
* is suppressed so it cannot mask the original failure.
|
|
133
|
+
*
|
|
134
|
+
* Returning a promise is supported; the dispatcher awaits it.
|
|
135
|
+
*/
|
|
136
|
+
readonly onError?: (context: ProgramHookContext, error: unknown) => void | Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Input accepted by {@link defineCommand}.
|
|
140
|
+
*
|
|
141
|
+
* @template M The mode of the command parser.
|
|
142
|
+
* @template T The parsed value passed to the command handler.
|
|
143
|
+
* @since 1.1.0
|
|
144
|
+
*/
|
|
145
|
+
interface CommandDefinition<M extends Mode, T> {
|
|
146
|
+
/**
|
|
147
|
+
* Command path used when commands are passed directly to `runProgram()`.
|
|
148
|
+
* Use an empty path (`[]`) to register the root command.
|
|
149
|
+
*
|
|
150
|
+
* File-based discovery derives the command path from the file name and uses
|
|
151
|
+
* this field only to validate that the declared path matches.
|
|
152
|
+
*/
|
|
153
|
+
readonly path?: CommandPath;
|
|
154
|
+
/**
|
|
155
|
+
* Parser for this command's command-specific arguments and options.
|
|
156
|
+
*/
|
|
157
|
+
readonly parser: Parser<M, T, unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* Metadata used in help output and shell completion.
|
|
160
|
+
*/
|
|
161
|
+
readonly metadata?: CommandMetadata;
|
|
162
|
+
/**
|
|
163
|
+
* Lifecycle hooks scoped to this command.
|
|
164
|
+
*
|
|
165
|
+
* These run inside any program-level hooks passed to `runProgram({ hooks })`:
|
|
166
|
+
* the program-level `beforeEach` runs first, then this command's
|
|
167
|
+
* `beforeEach`, then the handler; teardown unwinds in reverse. Use this when
|
|
168
|
+
* a single command needs its own preflight, such as a `deploy` command that
|
|
169
|
+
* always refreshes an auth token, instead of program-wide logic.
|
|
170
|
+
*
|
|
171
|
+
* @since 1.2.0
|
|
172
|
+
*/
|
|
173
|
+
readonly hooks?: ProgramHooks;
|
|
174
|
+
/**
|
|
175
|
+
* Handles the parsed command value.
|
|
176
|
+
*
|
|
177
|
+
* @param value Parsed command value.
|
|
178
|
+
* @param context Resource bundle from the most specific {@link ProgramHooks}
|
|
179
|
+
* `beforeEach` that ran. It is omitted when no program-level
|
|
180
|
+
* or command-level `beforeEach` ran, so a plain command
|
|
181
|
+
* without hooks receives only the value, exactly as before.
|
|
182
|
+
* Existing single-argument handlers can ignore it.
|
|
183
|
+
* @returns Nothing, or a promise that resolves when command handling
|
|
184
|
+
* completes.
|
|
185
|
+
*/
|
|
186
|
+
readonly handler: (value: T, context?: ProgramHookContext) => void | Promise<void>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A discovered command module definition.
|
|
190
|
+
*
|
|
191
|
+
* @template M The mode of the command parser.
|
|
192
|
+
* @template T The parsed value passed to the command handler.
|
|
193
|
+
* @since 1.1.0
|
|
194
|
+
*/
|
|
195
|
+
interface Command<M extends Mode, T> extends CommandDefinition<M, T> {
|
|
196
|
+
/**
|
|
197
|
+
* Internal marker used to validate discovered modules.
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
readonly [commandBrand]: true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A command that declares its own command path.
|
|
205
|
+
*
|
|
206
|
+
* Static `runProgram({ commands })` registration accepts this shape.
|
|
207
|
+
*
|
|
208
|
+
* @template M The mode of the command parser.
|
|
209
|
+
* @template T The parsed value passed to the command handler.
|
|
210
|
+
* @since 1.1.0
|
|
211
|
+
*/
|
|
212
|
+
interface StaticCommand<M extends Mode, T> extends Command<M, T> {
|
|
213
|
+
/**
|
|
214
|
+
* Command path used by static command registration.
|
|
215
|
+
*/
|
|
216
|
+
readonly path: CommandPath;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* A command with its handler value type erased.
|
|
220
|
+
*
|
|
221
|
+
* This type is used by discovery APIs that collect commands with different
|
|
222
|
+
* parsed value types. The handler cannot be called directly without first
|
|
223
|
+
* recovering the parser's value type.
|
|
224
|
+
*
|
|
225
|
+
* @since 1.1.0
|
|
226
|
+
*/
|
|
227
|
+
type AnyCommand = Omit<Command<Mode, unknown>, "handler"> & {
|
|
228
|
+
/**
|
|
229
|
+
* Erased command handler.
|
|
230
|
+
*/
|
|
231
|
+
readonly handler: (value: never, context?: ProgramHookContext) => void | Promise<void>;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* A statically registered command with its handler value type erased.
|
|
235
|
+
*
|
|
236
|
+
* @since 1.1.0
|
|
237
|
+
*/
|
|
238
|
+
type AnyStaticCommand = Omit<StaticCommand<Mode, unknown>, "handler"> & {
|
|
239
|
+
/**
|
|
240
|
+
* Erased command handler.
|
|
241
|
+
*/
|
|
242
|
+
readonly handler: (value: never, context?: ProgramHookContext) => void | Promise<void>;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Defines a command module for `@optique/discover`.
|
|
246
|
+
*
|
|
247
|
+
* This helper returns its argument unchanged while preserving parser value
|
|
248
|
+
* inference for the handler callback.
|
|
249
|
+
*
|
|
250
|
+
* @template M The mode of the command parser.
|
|
251
|
+
* @template T The parsed value passed to the command handler.
|
|
252
|
+
* @param command The command definition.
|
|
253
|
+
* @returns The same command definition with inferred types.
|
|
254
|
+
* @throws {TypeError} If the parser, path, handler, or hooks are missing or
|
|
255
|
+
* malformed.
|
|
256
|
+
* @since 1.1.0
|
|
257
|
+
*/
|
|
258
|
+
declare function defineCommand<M extends Mode, T>(command: CommandDefinition<M, T> & {
|
|
259
|
+
readonly path: CommandPath;
|
|
260
|
+
}): StaticCommand<M, T>;
|
|
261
|
+
declare function defineCommand<M extends Mode, T>(command: CommandDefinition<M, T>): Command<M, T>;
|
|
262
|
+
/**
|
|
263
|
+
* Returns whether a value is a command created by {@link defineCommand}.
|
|
264
|
+
*
|
|
265
|
+
* @param value The value to inspect.
|
|
266
|
+
* @returns `true` when the value is a discovered command definition.
|
|
267
|
+
* @since 1.1.0
|
|
268
|
+
*/
|
|
269
|
+
declare function isCommand(value: unknown): value is AnyCommand;
|
|
270
|
+
/**
|
|
271
|
+
* Validates a {@link ProgramHooks} value, throwing a descriptive error when it
|
|
272
|
+
* is malformed.
|
|
273
|
+
*
|
|
274
|
+
* @param hooks The value to validate.
|
|
275
|
+
* @param scope Label used in error messages: `"Command"` for command-level
|
|
276
|
+
* hooks and `"Program"` for program-level hooks.
|
|
277
|
+
* @throws {TypeError} If `hooks` is not an object, or a hook is neither
|
|
278
|
+
* nullish nor a function.
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
declare function validateHooks(hooks: unknown, scope: "Command" | "Program"): asserts hooks is ProgramHooks;
|
|
282
|
+
//#endregion
|
|
283
|
+
export { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, ProgramHookContext, ProgramHooks, ProgramInvocation, StaticCommand, defineCommand, isCommand, validateHooks };
|