@crustjs/core 0.0.8 → 0.0.9
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/index.d.ts +12 -1
- package/dist/index.js +1 -458
- package/dist/shared/chunk-36287c08.js +3 -0
- package/dist/shared/chunk-n6bfy2wh.js +2 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -601,10 +601,21 @@ interface CrustPlugin {
|
|
|
601
601
|
setup?: (context: SetupContext, actions: SetupActions) => void | Promise<void>;
|
|
602
602
|
middleware?: PluginMiddleware;
|
|
603
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Build-time validation protocol.
|
|
606
|
+
*
|
|
607
|
+
* `crust build` spawns the user's entrypoint as a subprocess with
|
|
608
|
+
* `CRUST_INTERNAL_VALIDATE_ONLY=1`. When `runMain` detects this env flag it
|
|
609
|
+
* runs validation, surfaces errors via stderr/exitCode, then force-exits.
|
|
610
|
+
*
|
|
611
|
+
* `VALIDATION_RESULT_GLOBAL_KEY` stores the result on `globalThis` for
|
|
612
|
+
* in-process consumers (tests that call `runMain` directly).
|
|
613
|
+
*/
|
|
614
|
+
declare const VALIDATION_MODE_ENV = "CRUST_INTERNAL_VALIDATE_ONLY";
|
|
604
615
|
interface RunOptions {
|
|
605
616
|
argv?: string[];
|
|
606
617
|
plugins?: CrustPlugin[];
|
|
607
618
|
}
|
|
608
619
|
declare function runCommand(command: AnyCommand, options?: RunOptions): Promise<void>;
|
|
609
620
|
declare function runMain(command: AnyCommand, options?: RunOptions): Promise<void>;
|
|
610
|
-
export { runMain, runCommand, resolveCommand, parseArgs, defineCommand, ValueType, ValidateVariadicArgs, ValidateNoPrefixedFlags, ValidateFlagAliases, SetupContext, SetupActions, RunOptions, PluginMiddleware, ParseResult, MiddlewareContext, InferFlags, InferArgs, FlagsDef, FlagDef, CrustPlugin, CrustErrorCode, CrustError, CommandRoute, CommandMeta, CommandDef, CommandContext, Command, ArgsDef, ArgDef, AnyCommand };
|
|
621
|
+
export { runMain, runCommand, resolveCommand, parseArgs, defineCommand, ValueType, ValidateVariadicArgs, ValidateNoPrefixedFlags, ValidateFlagAliases, VALIDATION_MODE_ENV, SetupContext, SetupActions, RunOptions, PluginMiddleware, ParseResult, MiddlewareContext, InferFlags, InferArgs, FlagsDef, FlagDef, CrustPlugin, CrustErrorCode, CrustError, CommandRoute, CommandMeta, CommandDef, CommandContext, Command, ArgsDef, ArgDef, AnyCommand };
|
package/dist/index.js
CHANGED
|
@@ -1,459 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
class CrustError extends Error {
|
|
4
|
-
code;
|
|
5
|
-
details;
|
|
6
|
-
cause;
|
|
7
|
-
constructor(code, message, ...details) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = "CrustError";
|
|
10
|
-
this.code = code;
|
|
11
|
-
this.details = details[0];
|
|
12
|
-
}
|
|
13
|
-
is(code) {
|
|
14
|
-
return this.code === code;
|
|
15
|
-
}
|
|
16
|
-
withCause(cause) {
|
|
17
|
-
this.cause = cause;
|
|
18
|
-
return this;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/command.ts
|
|
23
|
-
function defineCommand(config) {
|
|
24
|
-
if (!config.meta.name.trim()) {
|
|
25
|
-
throw new CrustError("DEFINITION", "defineCommand: meta.name is required and must be a non-empty string");
|
|
26
|
-
}
|
|
27
|
-
if (config.flags) {
|
|
28
|
-
for (const [name, def] of Object.entries(config.flags)) {
|
|
29
|
-
if (name.startsWith("no-")) {
|
|
30
|
-
const base = name.slice(3);
|
|
31
|
-
throw new CrustError("DEFINITION", `Flag name "--${name}" must not start with "no-"; define "${base}" instead and use "--no-${base}" at runtime`);
|
|
32
|
-
}
|
|
33
|
-
if (def.alias) {
|
|
34
|
-
const aliases = Array.isArray(def.alias) ? def.alias : [def.alias];
|
|
35
|
-
for (const alias of aliases) {
|
|
36
|
-
if (alias.startsWith("no-")) {
|
|
37
|
-
throw new CrustError("DEFINITION", `Alias "--${alias}" on flag "--${name}" must not start with "no-"; the "no-" prefix is reserved for boolean negation`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const copy = {
|
|
44
|
-
...config,
|
|
45
|
-
meta: { ...config.meta },
|
|
46
|
-
...config.args && {
|
|
47
|
-
args: config.args.map((def) => ({ ...def }))
|
|
48
|
-
},
|
|
49
|
-
flags: config.flags ? Object.fromEntries(Object.entries(config.flags).map(([k, v]) => [k, { ...v }])) : {},
|
|
50
|
-
subCommands: config.subCommands ? { ...config.subCommands } : {}
|
|
51
|
-
};
|
|
52
|
-
return Object.freeze(copy);
|
|
53
|
-
}
|
|
54
|
-
// src/parser.ts
|
|
55
|
-
import {
|
|
56
|
-
parseArgs as nodeParseArgs
|
|
57
|
-
} from "util";
|
|
58
|
-
function buildParseArgsOptionDescriptor(flagsDef) {
|
|
59
|
-
const options = {};
|
|
60
|
-
const aliasToName = {};
|
|
61
|
-
if (!flagsDef)
|
|
62
|
-
return { options, aliasToName };
|
|
63
|
-
const aliasRegistry = new Map;
|
|
64
|
-
for (const name of Object.keys(flagsDef)) {
|
|
65
|
-
aliasRegistry.set(name, name);
|
|
66
|
-
}
|
|
67
|
-
for (const [name, def] of Object.entries(flagsDef)) {
|
|
68
|
-
if (name.startsWith("no-")) {
|
|
69
|
-
const base = name.slice(3);
|
|
70
|
-
throw new CrustError("DEFINITION", `Flag name "--${name}" must not start with "no-"; define "${base}" instead and use "--no-${base}" at runtime`);
|
|
71
|
-
}
|
|
72
|
-
const parseType = def.type === "boolean" ? "boolean" : "string";
|
|
73
|
-
const opt = { type: parseType };
|
|
74
|
-
if (def.multiple) {
|
|
75
|
-
opt.multiple = true;
|
|
76
|
-
}
|
|
77
|
-
if (def.alias) {
|
|
78
|
-
const aliases = Array.isArray(def.alias) ? def.alias : [def.alias];
|
|
79
|
-
for (const alias of aliases) {
|
|
80
|
-
if (alias.startsWith("no-")) {
|
|
81
|
-
throw new CrustError("DEFINITION", `Alias "--${alias}" on flag "--${name}" must not start with "no-"; the "no-" prefix is reserved for boolean negation`);
|
|
82
|
-
}
|
|
83
|
-
const existing = aliasRegistry.get(alias);
|
|
84
|
-
if (existing) {
|
|
85
|
-
throw new CrustError("DEFINITION", `Alias collision: "${alias.length === 1 ? "-" : "--"}${alias}" is used by both "--${existing}" and "--${name}"`);
|
|
86
|
-
}
|
|
87
|
-
aliasRegistry.set(alias, name);
|
|
88
|
-
aliasToName[alias] = name;
|
|
89
|
-
if (alias.length === 1 && !opt.short) {
|
|
90
|
-
opt.short = alias;
|
|
91
|
-
} else {
|
|
92
|
-
const aliasOpt = { type: parseType };
|
|
93
|
-
if (def.multiple) {
|
|
94
|
-
aliasOpt.multiple = true;
|
|
95
|
-
}
|
|
96
|
-
options[alias] = aliasOpt;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
options[name] = opt;
|
|
101
|
-
}
|
|
102
|
-
return { options, aliasToName };
|
|
103
|
-
}
|
|
104
|
-
function coerceValue(value, type, label) {
|
|
105
|
-
if (type === "number") {
|
|
106
|
-
const num = Number(value);
|
|
107
|
-
if (Number.isNaN(num)) {
|
|
108
|
-
throw new CrustError("PARSE", `Expected number for ${label}, got "${value}"`);
|
|
109
|
-
}
|
|
110
|
-
return num;
|
|
111
|
-
}
|
|
112
|
-
if (type === "boolean") {
|
|
113
|
-
return value === "true" || value === "1";
|
|
114
|
-
}
|
|
115
|
-
return value;
|
|
116
|
-
}
|
|
117
|
-
function applyDefaultOrThrow(def, label) {
|
|
118
|
-
if (def.default !== undefined)
|
|
119
|
-
return def.default;
|
|
120
|
-
if (def.required === true) {
|
|
121
|
-
throw new CrustError("VALIDATION", `Missing required ${label}`);
|
|
122
|
-
}
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
function coerceFlagValue(name, def, parsedValue) {
|
|
126
|
-
const label = `--${name}`;
|
|
127
|
-
if (def.multiple && Array.isArray(parsedValue)) {
|
|
128
|
-
return def.type === "boolean" ? parsedValue.filter((v) => typeof v === "boolean") : parsedValue.map((v) => coerceValue(v, def.type, label));
|
|
129
|
-
}
|
|
130
|
-
if (def.type === "boolean") {
|
|
131
|
-
if (typeof parsedValue === "boolean") {
|
|
132
|
-
return parsedValue;
|
|
133
|
-
}
|
|
134
|
-
throw new CrustError("PARSE", `Expected boolean value for flag "${label}", got ${typeof parsedValue}`);
|
|
135
|
-
}
|
|
136
|
-
if (typeof parsedValue === "string") {
|
|
137
|
-
return coerceValue(parsedValue, def.type, label);
|
|
138
|
-
}
|
|
139
|
-
if (parsedValue === true) {
|
|
140
|
-
return def.default ?? undefined;
|
|
141
|
-
}
|
|
142
|
-
return parsedValue;
|
|
143
|
-
}
|
|
144
|
-
function resolveAliases(parsedValues, aliasToName, flagsDef) {
|
|
145
|
-
const canonical = {};
|
|
146
|
-
for (const key in parsedValues) {
|
|
147
|
-
const canonicalName = aliasToName[key] ?? key;
|
|
148
|
-
if (!(canonicalName in flagsDef))
|
|
149
|
-
continue;
|
|
150
|
-
const value = parsedValues[key];
|
|
151
|
-
const existing = canonical[canonicalName];
|
|
152
|
-
if (existing !== undefined && Array.isArray(existing) && Array.isArray(value)) {
|
|
153
|
-
existing.push(...value);
|
|
154
|
-
} else {
|
|
155
|
-
canonical[canonicalName] = value;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return canonical;
|
|
159
|
-
}
|
|
160
|
-
function resolveFlags(flagsDef, parsedValues, aliasToName) {
|
|
161
|
-
if (!flagsDef)
|
|
162
|
-
return {};
|
|
163
|
-
const canonical = resolveAliases(parsedValues, aliasToName, flagsDef);
|
|
164
|
-
const resolved = {};
|
|
165
|
-
for (const [name, def] of Object.entries(flagsDef)) {
|
|
166
|
-
const parsedValue = canonical[name];
|
|
167
|
-
if (parsedValue !== undefined) {
|
|
168
|
-
resolved[name] = coerceFlagValue(name, def, parsedValue);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
resolved[name] = def.default ?? undefined;
|
|
172
|
-
}
|
|
173
|
-
return resolved;
|
|
174
|
-
}
|
|
175
|
-
function validateRequiredFlags(flagsDef, resolvedFlags) {
|
|
176
|
-
if (!flagsDef)
|
|
177
|
-
return;
|
|
178
|
-
for (const [name, def] of Object.entries(flagsDef)) {
|
|
179
|
-
if (def.required === true && def.default === undefined) {
|
|
180
|
-
if (resolvedFlags[name] === undefined) {
|
|
181
|
-
throw new CrustError("VALIDATION", `Missing required flag "--${name}"`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function resolveArgs(argsDef, positionals) {
|
|
187
|
-
if (!argsDef)
|
|
188
|
-
return {};
|
|
189
|
-
const resolved = {};
|
|
190
|
-
let index = 0;
|
|
191
|
-
for (const def of argsDef) {
|
|
192
|
-
const { name } = def;
|
|
193
|
-
const label = `argument "<${name}>"`;
|
|
194
|
-
if (def.variadic) {
|
|
195
|
-
const remaining = positionals.slice(index);
|
|
196
|
-
if (def.required === true && remaining.length === 0) {
|
|
197
|
-
throw new CrustError("VALIDATION", `Missing required ${label}`);
|
|
198
|
-
}
|
|
199
|
-
resolved[name] = def.type === "string" ? remaining : remaining.map((v) => coerceValue(v, def.type, `<${name}>`));
|
|
200
|
-
index = positionals.length;
|
|
201
|
-
} else if (index < positionals.length) {
|
|
202
|
-
resolved[name] = coerceValue(positionals[index], def.type, `<${name}>`);
|
|
203
|
-
index++;
|
|
204
|
-
} else {
|
|
205
|
-
resolved[name] = applyDefaultOrThrow(def, label);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return resolved;
|
|
209
|
-
}
|
|
210
|
-
function validateCanonicalNegationUsage(argv, flagsDef, aliasToName) {
|
|
211
|
-
if (!flagsDef)
|
|
212
|
-
return;
|
|
213
|
-
for (const arg of argv) {
|
|
214
|
-
if (arg === "--")
|
|
215
|
-
return;
|
|
216
|
-
if (!arg.startsWith("--no-"))
|
|
217
|
-
continue;
|
|
218
|
-
const assignmentIndex = arg.indexOf("=");
|
|
219
|
-
const rawName = assignmentIndex === -1 ? arg.slice("--no-".length) : arg.slice("--no-".length, assignmentIndex);
|
|
220
|
-
if (!rawName)
|
|
221
|
-
continue;
|
|
222
|
-
const canonical = aliasToName[rawName];
|
|
223
|
-
if (!canonical)
|
|
224
|
-
continue;
|
|
225
|
-
if (canonical === rawName)
|
|
226
|
-
continue;
|
|
227
|
-
const def = flagsDef[canonical];
|
|
228
|
-
if (def?.type !== "boolean")
|
|
229
|
-
continue;
|
|
230
|
-
throw new CrustError("PARSE", `Cannot negate alias "--no-${rawName}"; use "--no-${canonical}" instead`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function parseArgs(command, argv) {
|
|
234
|
-
const argsDef = command.args;
|
|
235
|
-
const flagsDef = command.flags;
|
|
236
|
-
const { options: parseOptions, aliasToName } = buildParseArgsOptionDescriptor(flagsDef);
|
|
237
|
-
validateCanonicalNegationUsage(argv, flagsDef, aliasToName);
|
|
238
|
-
let parsed;
|
|
239
|
-
try {
|
|
240
|
-
parsed = nodeParseArgs({
|
|
241
|
-
args: argv,
|
|
242
|
-
options: parseOptions,
|
|
243
|
-
strict: true,
|
|
244
|
-
allowPositionals: true,
|
|
245
|
-
allowNegative: true,
|
|
246
|
-
tokens: true
|
|
247
|
-
});
|
|
248
|
-
} catch (error) {
|
|
249
|
-
if (error instanceof Error) {
|
|
250
|
-
const unknownMatch = error.message.match(/Unknown option '(.+?)'/);
|
|
251
|
-
if (unknownMatch) {
|
|
252
|
-
throw new CrustError("PARSE", `Unknown flag "${unknownMatch[1]}"`).withCause(error);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
throw new CrustError("PARSE", "Failed to parse command arguments").withCause(error);
|
|
256
|
-
}
|
|
257
|
-
const rawArgs = [];
|
|
258
|
-
const preSeparatorPositionals = [];
|
|
259
|
-
if (parsed.tokens) {
|
|
260
|
-
let afterSeparator = false;
|
|
261
|
-
for (const token of parsed.tokens) {
|
|
262
|
-
if (token.kind === "option-terminator") {
|
|
263
|
-
afterSeparator = true;
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
if (token.kind === "positional") {
|
|
267
|
-
(afterSeparator ? rawArgs : preSeparatorPositionals).push(token.value ?? "");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
preSeparatorPositionals.push(...parsed.positionals);
|
|
272
|
-
}
|
|
273
|
-
const resolvedFlags = resolveFlags(flagsDef, parsed.values, aliasToName);
|
|
274
|
-
const resolvedArgs = resolveArgs(argsDef, preSeparatorPositionals);
|
|
275
|
-
validateRequiredFlags(flagsDef, resolvedFlags);
|
|
276
|
-
return {
|
|
277
|
-
args: resolvedArgs,
|
|
278
|
-
flags: resolvedFlags,
|
|
279
|
-
rawArgs
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
// src/router.ts
|
|
283
|
-
function resolveCommand(command, argv) {
|
|
284
|
-
const path = [command.meta.name];
|
|
285
|
-
let current = command;
|
|
286
|
-
let routedArgv = argv;
|
|
287
|
-
while (routedArgv.length > 0) {
|
|
288
|
-
const subCommands = current.subCommands;
|
|
289
|
-
if (!subCommands || Object.keys(subCommands).length === 0) {
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
const candidate = routedArgv[0];
|
|
293
|
-
if (!candidate || candidate.startsWith("-")) {
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
if (candidate in subCommands) {
|
|
297
|
-
current = subCommands[candidate];
|
|
298
|
-
path.push(candidate);
|
|
299
|
-
routedArgv = routedArgv.slice(1);
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (current.run) {
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
const available = Object.keys(subCommands);
|
|
306
|
-
throw new CrustError("COMMAND_NOT_FOUND", `Unknown command "${candidate}".`, {
|
|
307
|
-
input: candidate,
|
|
308
|
-
available,
|
|
309
|
-
commandPath: [...path],
|
|
310
|
-
parentCommand: current
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
return {
|
|
314
|
-
command: current,
|
|
315
|
-
argv: routedArgv,
|
|
316
|
-
commandPath: path
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
// src/run.ts
|
|
320
|
-
function createPluginState() {
|
|
321
|
-
const map = new Map;
|
|
322
|
-
return {
|
|
323
|
-
get(key) {
|
|
324
|
-
return map.get(key);
|
|
325
|
-
},
|
|
326
|
-
has(key) {
|
|
327
|
-
return map.has(key);
|
|
328
|
-
},
|
|
329
|
-
set(key, value) {
|
|
330
|
-
map.set(key, value);
|
|
331
|
-
},
|
|
332
|
-
delete(key) {
|
|
333
|
-
return map.delete(key);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
async function runSetupHooks(plugins, context, actions) {
|
|
338
|
-
for (const plugin of plugins) {
|
|
339
|
-
if (!plugin.setup)
|
|
340
|
-
continue;
|
|
341
|
-
await plugin.setup(context, actions);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async function executeCommand(command, parsed) {
|
|
345
|
-
if (!command.run)
|
|
346
|
-
return;
|
|
347
|
-
const context = {
|
|
348
|
-
args: parsed.args,
|
|
349
|
-
flags: parsed.flags,
|
|
350
|
-
rawArgs: parsed.rawArgs,
|
|
351
|
-
command
|
|
352
|
-
};
|
|
353
|
-
try {
|
|
354
|
-
if (command.preRun) {
|
|
355
|
-
await command.preRun(context);
|
|
356
|
-
}
|
|
357
|
-
await command.run(context);
|
|
358
|
-
} finally {
|
|
359
|
-
if (command.postRun) {
|
|
360
|
-
await command.postRun(context);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
async function runMiddlewareChain(plugins, context, terminal) {
|
|
365
|
-
const stack = plugins.map((plugin) => plugin.middleware).filter((middleware) => Boolean(middleware));
|
|
366
|
-
let index = -1;
|
|
367
|
-
const dispatch = async (i) => {
|
|
368
|
-
if (i <= index) {
|
|
369
|
-
throw new CrustError("DEFINITION", "Plugin middleware called next() multiple times");
|
|
370
|
-
}
|
|
371
|
-
index = i;
|
|
372
|
-
if (i === stack.length) {
|
|
373
|
-
await terminal();
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
const middleware = stack[i];
|
|
377
|
-
if (!middleware) {
|
|
378
|
-
throw new CrustError("DEFINITION", "Plugin middleware stack is invalid");
|
|
379
|
-
}
|
|
380
|
-
await middleware(context, () => dispatch(i + 1));
|
|
381
|
-
};
|
|
382
|
-
await dispatch(0);
|
|
383
|
-
}
|
|
384
|
-
async function runCommand(command, options) {
|
|
385
|
-
const argv = options?.argv ?? process.argv.slice(2);
|
|
386
|
-
const plugins = options?.plugins ?? [];
|
|
387
|
-
const actions = {
|
|
388
|
-
addFlag(target, name, def) {
|
|
389
|
-
if (!target.flags) {
|
|
390
|
-
throw new CrustError("DEFINITION", `Cannot add flag "${name}": command "${target.meta.name}" has no flags object.`);
|
|
391
|
-
}
|
|
392
|
-
target.flags[name] = def;
|
|
393
|
-
},
|
|
394
|
-
addSubCommand(parent, name, subCommand) {
|
|
395
|
-
if (!name.trim()) {
|
|
396
|
-
throw new CrustError("DEFINITION", "addSubCommand: name is required and must be a non-empty string");
|
|
397
|
-
}
|
|
398
|
-
if (parent.subCommands[name])
|
|
399
|
-
return;
|
|
400
|
-
parent.subCommands[name] = subCommand;
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
const middlewareContext = {
|
|
404
|
-
argv: [...argv],
|
|
405
|
-
rootCommand: command,
|
|
406
|
-
state: createPluginState(),
|
|
407
|
-
route: null,
|
|
408
|
-
input: null
|
|
409
|
-
};
|
|
410
|
-
try {
|
|
411
|
-
await runSetupHooks(plugins, middlewareContext, actions);
|
|
412
|
-
let parsed;
|
|
413
|
-
let resolvedCommand;
|
|
414
|
-
try {
|
|
415
|
-
const resolved = resolveCommand(command, [...argv]);
|
|
416
|
-
middlewareContext.route = resolved;
|
|
417
|
-
parsed = parseArgs(resolved.command, resolved.argv);
|
|
418
|
-
middlewareContext.input = {
|
|
419
|
-
args: parsed.args,
|
|
420
|
-
flags: parsed.flags,
|
|
421
|
-
rawArgs: parsed.rawArgs
|
|
422
|
-
};
|
|
423
|
-
resolvedCommand = resolved.command;
|
|
424
|
-
} catch (error) {
|
|
425
|
-
await runMiddlewareChain(plugins, middlewareContext, async () => {
|
|
426
|
-
throw error;
|
|
427
|
-
});
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
await runMiddlewareChain(plugins, middlewareContext, async () => {
|
|
431
|
-
await executeCommand(resolvedCommand, parsed);
|
|
432
|
-
});
|
|
433
|
-
} catch (error) {
|
|
434
|
-
if (error instanceof CrustError) {
|
|
435
|
-
throw error;
|
|
436
|
-
}
|
|
437
|
-
if (error instanceof Error) {
|
|
438
|
-
throw new CrustError("EXECUTION", error.message).withCause(error);
|
|
439
|
-
}
|
|
440
|
-
throw new CrustError("EXECUTION", String(error)).withCause(error);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
async function runMain(command, options) {
|
|
444
|
-
try {
|
|
445
|
-
await runCommand(command, options);
|
|
446
|
-
} catch (error) {
|
|
447
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
448
|
-
console.error(`Error: ${message}`);
|
|
449
|
-
process.exitCode = 1;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
export {
|
|
453
|
-
runMain,
|
|
454
|
-
runCommand,
|
|
455
|
-
resolveCommand,
|
|
456
|
-
parseArgs,
|
|
457
|
-
defineCommand,
|
|
458
|
-
CrustError
|
|
459
|
-
};
|
|
2
|
+
import{a as K,b as I,c as W}from"./shared/chunk-36287c08.js";function G(D){if(!D.meta.name.trim())throw new I("DEFINITION","meta.name must be a non-empty string");if(D.flags)for(let[F,M]of Object.entries(D.flags)){if(F.startsWith("no-")){let P=F.slice(3);throw new I("DEFINITION",`Flag "--${F}" must not use "no-" prefix; define "${P}" and negate with "--no-${P}"`)}if(M.alias){let P=Array.isArray(M.alias)?M.alias:[M.alias];for(let q of P)if(q.startsWith("no-"))throw new I("DEFINITION",`Alias "--${q}" on "--${F}" must not use "no-" prefix (reserved for negation)`)}}let V={...D,meta:{...D.meta},...D.args&&{args:D.args.map((F)=>({...F}))},flags:D.flags?Object.fromEntries(Object.entries(D.flags).map(([F,M])=>[F,{...M}])):{},subCommands:D.subCommands?{...D.subCommands}:{}};return Object.freeze(V)}function Q(D,V){let F=[D.meta.name],M=D,P=V;while(P.length>0){let q=M.subCommands;if(!q||Object.keys(q).length===0)break;let R=P[0];if(!R||R.startsWith("-"))break;if(R in q){M=q[R],F.push(R),P=P.slice(1);continue}if(M.run)break;let J=Object.keys(q);throw new I("COMMAND_NOT_FOUND",`Unknown command "${R}".`,{input:R,available:J,commandPath:[...F],parentCommand:M})}return{command:M,argv:P,commandPath:F}}var Z="CRUST_INTERNAL_VALIDATE_ONLY",O="__CRUST_VALIDATE_RESULT__";function $(){let D=new Map;return{get(V){return D.get(V)},has(V){return D.has(V)},set(V,F){D.set(V,F)},delete(V){return D.delete(V)}}}async function j(D,V,F){for(let M of D){if(!M.setup)continue;await M.setup(V,F)}}function z(D){return{addFlag(V,F,M){if(!V.flags)throw new I("DEFINITION",`Cannot add flag "${F}": "${V.meta.name}" has no flags object`);if(F in V.flags)D?.push(`Plugin flag "--${F}" on "${V.meta.name}" overrides existing flag`);V.flags[F]=M},addSubCommand(V,F,M){if(!F.trim())throw new I("DEFINITION","addSubCommand: name must be a non-empty string");if(V.subCommands[F]){D?.push(`Plugin subcommand "${F}" on "${V.meta.name}" skipped (already exists)`);return}V.subCommands[F]=M}}}async function Y(D,V){let F=V?.argv??process.argv.slice(2),M=V?.plugins??[],P=[],q={argv:[...F],rootCommand:D,state:$()};await j(M,q,z(P));let{validateCommandTree:R}=await import("./shared/chunk-n6bfy2wh.js");R(D);for(let J of P)console.warn(`Warning: ${J}`)}async function y(D,V){if(!D.run)return;let F={args:V.args,flags:V.flags,rawArgs:V.rawArgs,command:D};try{if(D.preRun)await D.preRun(F);await D.run(F)}finally{if(D.postRun)await D.postRun(F)}}async function X(D,V,F){let M=D.map((R)=>R.middleware).filter((R)=>Boolean(R)),P=-1,q=async(R)=>{if(R<=P)throw new I("DEFINITION","Plugin middleware called next() multiple times");if(P=R,R===M.length){await F();return}let J=M[R];if(!J)throw new I("DEFINITION","Plugin middleware stack is invalid");await J(V,()=>q(R+1))};await q(0)}async function B(D,V){let F=V?.argv??process.argv.slice(2),M=V?.plugins??[],P=z(),q={argv:[...F],rootCommand:D,state:$(),route:null,input:null};try{await j(M,q,P);let R,J;try{let N=Q(D,[...F]);q.route=N,R=W(N.command,N.argv),q.input={args:R.args,flags:R.flags,rawArgs:R.rawArgs},J=N.command}catch(N){await X(M,q,async()=>{throw N});return}await X(M,q,async()=>{await y(J,R)})}catch(R){if(R instanceof I)throw R;if(R instanceof Error)throw new I("EXECUTION",R.message).withCause(R);throw new I("EXECUTION",String(R)).withCause(R)}}async function H(D,V){if(process.env[Z]==="1"){let F=(async()=>{try{return await Y(D,V),{ok:!0}}catch(M){let P=M instanceof Error?M.message:String(M);return console.error(P),process.exitCode=1,{ok:!1,error:M}}})();return globalThis[O]=F,await F,process.exit(process.exitCode??0)}try{await B(D,V)}catch(F){let M=F instanceof Error?F.message:String(F);console.error(`Error: ${M}`),process.exitCode=1}}export{H as runMain,B as runCommand,Q as resolveCommand,W as parseArgs,G as defineCommand,Z as VALIDATION_MODE_ENV,I as CrustError};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var A=import.meta.require;class W extends Error{code;details;cause;constructor(j,G,...H){super(G);this.name="CrustError",this.code=j,this.details=H[0]}is(j){return this.code===j}withCause(j){return this.cause=j,this}}import{parseArgs as q}from"util";function U(j){let G={},H={};if(!j)return{options:G,aliasToName:H};let z=new Map;for(let B of Object.keys(j))z.set(B,B);for(let[B,J]of Object.entries(j)){if(B.startsWith("no-")){let X=B.slice(3);throw new W("DEFINITION",`Flag "--${B}" must not use "no-" prefix; define "${X}" and negate with "--no-${X}"`)}let K=J.type==="boolean"?"boolean":"string",L={type:K};if(J.multiple)L.multiple=!0;if(J.alias){let X=Array.isArray(J.alias)?J.alias:[J.alias];for(let Q of X){if(Q.startsWith("no-"))throw new W("DEFINITION",`Alias "--${Q}" on "--${B}" must not use "no-" prefix (reserved for negation)`);let _=z.get(Q);if(_)throw new W("DEFINITION",`Alias collision: "${Q.length===1?"-":"--"}${Q}" is used by both "--${_}" and "--${B}"`);if(z.set(Q,B),H[Q]=B,Q.length===1&&!L.short)L.short=Q;else{let Y={type:K};if(J.multiple)Y.multiple=!0;G[Q]=Y}}}G[B]=L}return{options:G,aliasToName:H}}function $(j,G,H){if(G==="number"){let z=Number(j);if(Number.isNaN(z))throw new W("PARSE",`Expected number for ${H}, got "${j}"`);return z}if(G==="boolean")return j==="true"||j==="1";return j}function I(j,G){if(j.default!==void 0)return j.default;if(j.required===!0)throw new W("VALIDATION",`Missing required ${G}`);return}function M(j,G,H){let z=`--${j}`;if(G.multiple&&Array.isArray(H))return G.type==="boolean"?H.filter((B)=>typeof B==="boolean"):H.map((B)=>$(B,G.type,z));if(G.type==="boolean"){if(typeof H==="boolean")return H;throw new W("PARSE",`Expected boolean value for flag "${z}", got ${typeof H}`)}if(typeof H==="string")return $(H,G.type,z);if(H===!0)return G.default??void 0;return H}function R(j,G,H){let z={};for(let B in j){let J=G[B]??B;if(!(J in H))continue;let K=j[B],L=z[J];if(L!==void 0&&Array.isArray(L)&&Array.isArray(K))L.push(...K);else z[J]=K}return z}function h(j,G,H){if(!j)return{};let z=R(G,H,j),B={};for(let[J,K]of Object.entries(j)){let L=z[J];if(L!==void 0){B[J]=M(J,K,L);continue}B[J]=K.default??void 0}return B}function P(j,G){if(!j)return;for(let[H,z]of Object.entries(j))if(z.required===!0&&z.default===void 0){if(G[H]===void 0)throw new W("VALIDATION",`Missing required flag "--${H}"`)}}function S(j,G){if(!j)return{};let H={},z=0;for(let B of j){let{name:J}=B,K=`argument "<${J}>"`;if(B.variadic){let L=G.slice(z);if(B.required===!0&&L.length===0)throw new W("VALIDATION",`Missing required ${K}`);H[J]=B.type==="string"?L:L.map((X)=>$(X,B.type,`<${J}>`)),z=G.length}else if(z<G.length)H[J]=$(G[z],B.type,`<${J}>`),z++;else H[J]=I(B,K)}return H}function O(j,G,H){if(!G)return;for(let z of j){if(z==="--")return;if(!z.startsWith("--no-"))continue;let B=z.indexOf("="),J=B===-1?z.slice(5):z.slice(5,B);if(!J)continue;let K=H[J];if(!K)continue;if(K===J)continue;if(G[K]?.type!=="boolean")continue;throw new W("PARSE",`Cannot negate alias "--no-${J}"; use "--no-${K}" instead`)}}function x(j,G){let{args:H,flags:z}=j,{options:B,aliasToName:J}=U(z);O(G,z,J);let K;try{K=q({args:G,options:B,strict:!0,allowPositionals:!0,allowNegative:!0,tokens:!0})}catch(Y){if(Y instanceof Error){let Z=Y.message.match(/Unknown option '(.+?)'/);if(Z)throw new W("PARSE",`Unknown flag "${Z[1]}"`).withCause(Y)}throw new W("PARSE","Failed to parse command arguments").withCause(Y)}let L=[],X=[];if(K.tokens){let Y=!1;for(let Z of K.tokens){if(Z.kind==="option-terminator"){Y=!0;continue}if(Z.kind==="positional")(Y?L:X).push(Z.value??"")}}else X.push(...K.positionals);let Q=h(z,K.values,J),_=S(H,X);return P(z,Q),{args:_,flags:Q,rawArgs:L}}
|
|
3
|
+
export{A as a,W as b,x as c};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{b as G,c as H}from"./chunk-36287c08.js";function F(j){switch(j.type){case"number":return"1";case"boolean":return"true";default:return"sample"}}function I(j){let q=[];if(j.flags)for(let[w,x]of Object.entries(j.flags)){if(x.required!==!0||x.default!==void 0)continue;if(q.push(`--${w}`),x.type!=="boolean")q.push(F(x))}if(j.args)for(let w of j.args){if(w.required!==!0||w.default!==void 0)continue;q.push(F(w))}return q}function L(j){let q=[{command:j,path:[j.meta.name]}],w=new Set;while(q.length>0){let x=q.pop();if(!x)break;let{command:y,path:D}=x;if(w.has(y))continue;w.add(y);try{H(y,I(y))}catch(z){let B=z instanceof Error?z.message:"Unknown validation error";throw new G("DEFINITION",`Command "${D.join(" ")}" failed runtime validation: ${B}`).withCause(z)}for(let[z,B]of Object.entries(y.subCommands))q.push({command:B,path:[...D,z]})}}export{L as validateCommandTree};
|