@h3ravel/musket 0.1.0 → 0.1.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 +1 -1
- package/dist/index.cjs +951 -0
- package/dist/index.d.cts +308 -0
- package/dist/index.d.ts +308 -0
- package/dist/index.js +918 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
import { FileSystem, Logger } from "@h3ravel/shared";
|
|
2
|
+
import { Argument, Option, program } from "commander";
|
|
3
|
+
import { build } from "tsdown";
|
|
4
|
+
import { glob } from "glob";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { Arr, Str } from "@h3ravel/support";
|
|
7
|
+
import { mkdir } from "node:fs/promises";
|
|
8
|
+
|
|
9
|
+
//#region src/Core/Command.ts
|
|
10
|
+
var Command = class {
|
|
11
|
+
constructor(app, kernel) {
|
|
12
|
+
this.app = app;
|
|
13
|
+
this.kernel = kernel;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The underlying commander instance.
|
|
17
|
+
*
|
|
18
|
+
* @var Command
|
|
19
|
+
*/
|
|
20
|
+
program;
|
|
21
|
+
/**
|
|
22
|
+
* The name and signature of the console command.
|
|
23
|
+
*
|
|
24
|
+
* @var string
|
|
25
|
+
*/
|
|
26
|
+
signature;
|
|
27
|
+
/**
|
|
28
|
+
* A dictionary of signatures or what not.
|
|
29
|
+
*
|
|
30
|
+
* @var object
|
|
31
|
+
*/
|
|
32
|
+
dictionary = {};
|
|
33
|
+
/**
|
|
34
|
+
* The console command description.
|
|
35
|
+
*
|
|
36
|
+
* @var string
|
|
37
|
+
*/
|
|
38
|
+
description;
|
|
39
|
+
/**
|
|
40
|
+
* The console command input.
|
|
41
|
+
*
|
|
42
|
+
* @var object
|
|
43
|
+
*/
|
|
44
|
+
input = {
|
|
45
|
+
options: {},
|
|
46
|
+
arguments: {}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Execute the console command.
|
|
50
|
+
*/
|
|
51
|
+
async handle(..._args) {}
|
|
52
|
+
setApplication(app) {
|
|
53
|
+
this.app = app;
|
|
54
|
+
}
|
|
55
|
+
setInput(options, args, regArgs, dictionary, program$1) {
|
|
56
|
+
this.program = program$1;
|
|
57
|
+
this.dictionary = dictionary;
|
|
58
|
+
this.input.options = options;
|
|
59
|
+
this.input.arguments = regArgs.map((e, i) => ({ [e.name()]: args[i] })).reduce((e, x) => Object.assign(e, x), {});
|
|
60
|
+
this.loadBaseFlags();
|
|
61
|
+
Logger.configure({
|
|
62
|
+
verbosity: this.option("verbose"),
|
|
63
|
+
silent: this.option("silent"),
|
|
64
|
+
quiet: this.option("quiet")
|
|
65
|
+
});
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
setOption(key, value) {
|
|
69
|
+
this.program.setOptionValue(key, value);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
setProgram(program$1) {
|
|
73
|
+
this.program = program$1;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
getSignature() {
|
|
77
|
+
return this.signature;
|
|
78
|
+
}
|
|
79
|
+
getDescription() {
|
|
80
|
+
return this.description;
|
|
81
|
+
}
|
|
82
|
+
option(key, def) {
|
|
83
|
+
const option = this.input.options[key] ?? def;
|
|
84
|
+
return option === "null" || option === "undefined" ? void 0 : option;
|
|
85
|
+
}
|
|
86
|
+
options(key) {
|
|
87
|
+
if (key) return this.input.options[key];
|
|
88
|
+
return this.input.options;
|
|
89
|
+
}
|
|
90
|
+
argument(key, def) {
|
|
91
|
+
return this.input.arguments[key] ?? def;
|
|
92
|
+
}
|
|
93
|
+
arguments() {
|
|
94
|
+
return this.input.arguments;
|
|
95
|
+
}
|
|
96
|
+
loadBaseFlags() {
|
|
97
|
+
let verbose = 0;
|
|
98
|
+
if (this.program.getOptionValue("verbose") == "v") verbose = 2;
|
|
99
|
+
else if (this.program.getOptionValue("verbose") == "vv") verbose = 3;
|
|
100
|
+
else verbose = Number(this.program.getOptionValue("verbose") ?? 0);
|
|
101
|
+
this.input.options.quiet = this.program.getOptionValue("quiet") ?? false;
|
|
102
|
+
this.input.options.silent = this.program.getOptionValue("silent") ?? false;
|
|
103
|
+
this.input.options.verbose = verbose;
|
|
104
|
+
this.input.options.interaction = this.program.getOptionValue("interaction") ?? false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if the command is quiet
|
|
108
|
+
*
|
|
109
|
+
* @returns
|
|
110
|
+
*/
|
|
111
|
+
isQuiet() {
|
|
112
|
+
return this.option("quiet");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if the command is silent
|
|
116
|
+
*
|
|
117
|
+
* @returns
|
|
118
|
+
*/
|
|
119
|
+
isSilent() {
|
|
120
|
+
return this.option("silent");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if the command is non interactive
|
|
124
|
+
*
|
|
125
|
+
* @returns
|
|
126
|
+
*/
|
|
127
|
+
isNonInteractive() {
|
|
128
|
+
return this.option("interaction") === false;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get the verbosity of the command
|
|
132
|
+
*
|
|
133
|
+
* @returns
|
|
134
|
+
*/
|
|
135
|
+
getVerbosity() {
|
|
136
|
+
return Number(this.option("verbose"));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Log an info message
|
|
140
|
+
*/
|
|
141
|
+
info(message) {
|
|
142
|
+
Logger.info(message);
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Log a warning message
|
|
147
|
+
*/
|
|
148
|
+
warn(message) {
|
|
149
|
+
Logger.warn(message);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Log a line message
|
|
154
|
+
*/
|
|
155
|
+
line(message) {
|
|
156
|
+
Logger.log(message, "white");
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Log a new line
|
|
161
|
+
*/
|
|
162
|
+
newLine(count = 1) {
|
|
163
|
+
if (Number(this.getVerbosity()) >= 3 || !this.isSilent() && !this.isQuiet()) for (let i = 0; i < count; i++) console.log("");
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Log a success message
|
|
168
|
+
*/
|
|
169
|
+
success(message) {
|
|
170
|
+
Logger.success(message);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Log an error message
|
|
175
|
+
*/
|
|
176
|
+
error(message) {
|
|
177
|
+
Logger.error(message, false);
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Log an error message and terminate execution of the command
|
|
182
|
+
* return an exit code of 1
|
|
183
|
+
*
|
|
184
|
+
* This method is not chainable
|
|
185
|
+
*/
|
|
186
|
+
fail(message) {
|
|
187
|
+
this.error(message);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Log a debug message
|
|
192
|
+
*/
|
|
193
|
+
debug(message) {
|
|
194
|
+
Logger.debug(message);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/Signature.ts
|
|
201
|
+
var Signature = class Signature {
|
|
202
|
+
/**
|
|
203
|
+
* Helper to parse options inside a block of text
|
|
204
|
+
*
|
|
205
|
+
* @param block
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
static parseOptions(block) {
|
|
209
|
+
const options = [];
|
|
210
|
+
/**
|
|
211
|
+
* Match { ... } blocks at top level
|
|
212
|
+
*/
|
|
213
|
+
const regex = /\{([^{}]+(?:\{[^{}]*\}[^{}]*)*)\}/g;
|
|
214
|
+
let match;
|
|
215
|
+
while ((match = regex.exec(block)) !== null) {
|
|
216
|
+
const shared = "^" === match[1][0] || /:[#^]/.test(match[1]);
|
|
217
|
+
const isHidden = (["#", "^"].includes(match[1][0]) || /:[#^]/.test(match[1])) && !shared;
|
|
218
|
+
const content = match[1].trim().replace(/[#^]/, "");
|
|
219
|
+
/**
|
|
220
|
+
* Split by first ':' to separate name and description+nested
|
|
221
|
+
*/
|
|
222
|
+
const colonIndex = content.indexOf(":");
|
|
223
|
+
if (colonIndex === -1) {
|
|
224
|
+
/**
|
|
225
|
+
* No description, treat whole as name
|
|
226
|
+
*/
|
|
227
|
+
options.push({ name: content });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const namePart = content.substring(0, colonIndex).trim();
|
|
231
|
+
const rest = content.substring(colonIndex + 1).trim();
|
|
232
|
+
/**
|
|
233
|
+
* Check for nested options after '|'
|
|
234
|
+
*/
|
|
235
|
+
let description$1 = rest;
|
|
236
|
+
let nestedOptions;
|
|
237
|
+
const pipeIndex = rest.indexOf("|");
|
|
238
|
+
if (pipeIndex !== -1) {
|
|
239
|
+
description$1 = rest.substring(0, pipeIndex).trim();
|
|
240
|
+
/**
|
|
241
|
+
* nestedText should start with '{' and end with ')', clean it
|
|
242
|
+
* Also Remove trailing ')' if present
|
|
243
|
+
*/
|
|
244
|
+
const cleanedNestedText = rest.substring(pipeIndex + 1).trim().replace(/^\{/, "").trim();
|
|
245
|
+
/**
|
|
246
|
+
* Parse nested options recursively
|
|
247
|
+
*/
|
|
248
|
+
nestedOptions = Signature.parseOptions("{" + cleanedNestedText + "}");
|
|
249
|
+
} else
|
|
250
|
+
/**
|
|
251
|
+
* Trim the string
|
|
252
|
+
*/
|
|
253
|
+
description$1 = description$1.trim();
|
|
254
|
+
let name$1 = namePart;
|
|
255
|
+
let flags;
|
|
256
|
+
let choices = [];
|
|
257
|
+
let required = /[^a-zA-Z0-9_|-]/.test(name$1);
|
|
258
|
+
let multiple = false;
|
|
259
|
+
let placeholder;
|
|
260
|
+
let defaultValue;
|
|
261
|
+
/**
|
|
262
|
+
* Parse the command name
|
|
263
|
+
*/
|
|
264
|
+
if (name$1.includes("=")) {
|
|
265
|
+
const [rawName, rawDefault] = name$1.split("=");
|
|
266
|
+
name$1 = rawName.trim();
|
|
267
|
+
const hold = rawName.trim().split("|");
|
|
268
|
+
const holder = (hold.at(1) ?? hold.at(0)).replace("--", "");
|
|
269
|
+
defaultValue = rawDefault.trim();
|
|
270
|
+
placeholder = defaultValue ? `[${holder}]` : `<${holder}>`;
|
|
271
|
+
required = false;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Parse name modifiers (?, *, ?*)
|
|
275
|
+
*/
|
|
276
|
+
if (name$1.endsWith("?*")) {
|
|
277
|
+
required = false;
|
|
278
|
+
multiple = true;
|
|
279
|
+
name$1 = name$1.slice(0, -2);
|
|
280
|
+
} else if (name$1.endsWith("*")) {
|
|
281
|
+
multiple = true;
|
|
282
|
+
name$1 = name$1.slice(0, -1);
|
|
283
|
+
} else if (name$1.endsWith("?")) {
|
|
284
|
+
required = false;
|
|
285
|
+
name$1 = name$1.slice(0, -1);
|
|
286
|
+
placeholder = `[${name$1.split("--").at(1)?.split("|").at(1) ?? name$1}]`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Check if it's a flag option (starts with --)
|
|
290
|
+
*/
|
|
291
|
+
const isFlag = name$1.startsWith("--");
|
|
292
|
+
if (isFlag) {
|
|
293
|
+
/**
|
|
294
|
+
* Parse flags and default values
|
|
295
|
+
*/
|
|
296
|
+
const flagParts = name$1.split("|").map((s) => s.trim());
|
|
297
|
+
flags = [];
|
|
298
|
+
for (let part of flagParts) {
|
|
299
|
+
if (part.startsWith("--") && part.slice(2).length === 1) part = "-" + part.slice(2);
|
|
300
|
+
else if (part.startsWith("-") && !part.startsWith("--") && part.slice(1).length > 1) part = "--" + part.slice(1);
|
|
301
|
+
else if (!part.startsWith("-") && part.slice(1).length > 1) part = "--" + part;
|
|
302
|
+
const eqIndex = part.indexOf("=");
|
|
303
|
+
if (eqIndex !== -1) {
|
|
304
|
+
flags.push(part.substring(0, eqIndex));
|
|
305
|
+
const val = part.substring(eqIndex + 1);
|
|
306
|
+
if (val === "*") defaultValue = [];
|
|
307
|
+
else if (val === "true" || val === "false" || !val && !required) defaultValue = val === "true";
|
|
308
|
+
else if (!isNaN(Number(val))) defaultValue = Number(val);
|
|
309
|
+
else defaultValue = val;
|
|
310
|
+
} else flags.push(part);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const desc = description$1.match(/^([^:]+?)\s*:\s*\[?([\w\s,]+)\]?$/);
|
|
314
|
+
if (match) {
|
|
315
|
+
description$1 = desc?.[1].trim() ?? description$1;
|
|
316
|
+
choices = desc?.[2].split(",").map((s) => s.trim()).filter(Boolean) ?? choices;
|
|
317
|
+
}
|
|
318
|
+
options.push({
|
|
319
|
+
name: isFlag ? flags[flags.length - 1] : name$1,
|
|
320
|
+
choices,
|
|
321
|
+
required,
|
|
322
|
+
multiple,
|
|
323
|
+
description: description$1,
|
|
324
|
+
flags,
|
|
325
|
+
shared,
|
|
326
|
+
isFlag,
|
|
327
|
+
isHidden,
|
|
328
|
+
placeholder,
|
|
329
|
+
defaultValue,
|
|
330
|
+
nestedOptions
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return options;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Helper to parse a command's signature
|
|
337
|
+
*
|
|
338
|
+
* @param signature
|
|
339
|
+
* @param commandClass
|
|
340
|
+
* @returns
|
|
341
|
+
*/
|
|
342
|
+
static parseSignature(signature, commandClass) {
|
|
343
|
+
const lines = signature.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
344
|
+
const isHidden = ["#", "^"].includes(lines[0][0]) || /:[#^]/.test(lines[0]);
|
|
345
|
+
const baseCommand = lines[0].split("{")[0].trim().replace(/[^\w:-]/g, "");
|
|
346
|
+
const description$1 = commandClass.getDescription();
|
|
347
|
+
const isNamespaceCommand = baseCommand.endsWith(":");
|
|
348
|
+
/**
|
|
349
|
+
* Join the rest lines to a single string for parsing
|
|
350
|
+
*/
|
|
351
|
+
const rest = lines.slice(1).join(" ");
|
|
352
|
+
/**
|
|
353
|
+
* Parse all top-level options/subcommands
|
|
354
|
+
*/
|
|
355
|
+
const allOptions = Signature.parseOptions(rest);
|
|
356
|
+
if (isNamespaceCommand)
|
|
357
|
+
/**
|
|
358
|
+
* Separate subcommands (those without flags) and base options (flags)
|
|
359
|
+
* Here we assume subcommands are those without flags (isFlag false)
|
|
360
|
+
* and base options are flags or options after subcommands
|
|
361
|
+
|
|
362
|
+
* For simplicity, treat all top-level options as subcommands
|
|
363
|
+
* and assume base command options come after subcommands in signature (not shown in example)
|
|
364
|
+
*/
|
|
365
|
+
return {
|
|
366
|
+
baseCommand: baseCommand.slice(0, -1),
|
|
367
|
+
isNamespaceCommand,
|
|
368
|
+
subCommands: allOptions.filter((e) => !e.flags && !e.isHidden),
|
|
369
|
+
description: description$1,
|
|
370
|
+
commandClass,
|
|
371
|
+
options: allOptions.filter((e) => !!e.flags),
|
|
372
|
+
isHidden
|
|
373
|
+
};
|
|
374
|
+
else return {
|
|
375
|
+
baseCommand,
|
|
376
|
+
isNamespaceCommand,
|
|
377
|
+
options: allOptions,
|
|
378
|
+
description: description$1,
|
|
379
|
+
commandClass,
|
|
380
|
+
isHidden
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/logo.ts
|
|
387
|
+
const logo = String.raw`
|
|
388
|
+
111
|
|
389
|
+
111111111
|
|
390
|
+
1111111111 111111
|
|
391
|
+
111111 111 111111
|
|
392
|
+
111111 111 111111
|
|
393
|
+
11111 111 11111
|
|
394
|
+
1111111 111 1111111
|
|
395
|
+
111 11111 111 111111 111 1111 1111 11111111 1111
|
|
396
|
+
111 11111 1111 111111 111 1111 1111 1111 11111 1111
|
|
397
|
+
111 11111 11111 111 1111 1111 111111111111 111111111111 1111 1111111 1111
|
|
398
|
+
111 111111 1111 111 111111111111 111111 11111 1111 111 1111 11111111 1111 1111
|
|
399
|
+
111 111 11111111 111 1101 1101 111111111 11111111 1111 1111111111111111101
|
|
400
|
+
111 1111111111111111 1111 111 1111 1111 111 11111011 1111 111 1111111 1101 1111
|
|
401
|
+
111 11111 1110111111111111 111 1111 1111 1111111101 1111 111111111 1111011 111111111 1111
|
|
402
|
+
1111111 111110111110 111 1111 1111 111111 1111 11011101 10111 11111 1111
|
|
403
|
+
11011 111111 11 11111
|
|
404
|
+
111111 11101 111111
|
|
405
|
+
111111 111 111111
|
|
406
|
+
111111 111 111111
|
|
407
|
+
111111111
|
|
408
|
+
110
|
|
409
|
+
`;
|
|
410
|
+
const altLogo = String.raw`
|
|
411
|
+
_ _ _____ _
|
|
412
|
+
| | | |___ / _ __ __ ___ _____| |
|
|
413
|
+
| |_| | |_ \| '__/ _ \ \ / / _ \ |
|
|
414
|
+
| _ |___) | | | (_| |\ V / __/ |
|
|
415
|
+
|_| |_|____/|_| \__,_| \_/ \___|_|
|
|
416
|
+
|
|
417
|
+
`;
|
|
418
|
+
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/Commands/HelpCommand.ts
|
|
421
|
+
var HelpCommand = class extends Command {
|
|
422
|
+
/**
|
|
423
|
+
* The name and signature of the console command.
|
|
424
|
+
*
|
|
425
|
+
* @var string
|
|
426
|
+
*/
|
|
427
|
+
signature = `help
|
|
428
|
+
{command_name=help : The command name}
|
|
429
|
+
{--format=txt : The output format}
|
|
430
|
+
`;
|
|
431
|
+
/**
|
|
432
|
+
* The console command description.
|
|
433
|
+
*
|
|
434
|
+
* @var string
|
|
435
|
+
*/
|
|
436
|
+
description = "Display help for a command";
|
|
437
|
+
async handle() {
|
|
438
|
+
const cmd = this.argument("command_name");
|
|
439
|
+
if (!cmd) {
|
|
440
|
+
this.program.outputHelp();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const target = this.program.commands.find((c) => c.name() === cmd);
|
|
444
|
+
if (!target) {
|
|
445
|
+
this.error(`ERROR: Unknown command: ${Logger.log(cmd, ["italic", "grey"], false)}.`);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
target.outputHelp();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/Commands/ListCommand.ts
|
|
454
|
+
var ListCommand = class ListCommand extends Command {
|
|
455
|
+
/**
|
|
456
|
+
* The name and signature of the console command.
|
|
457
|
+
*
|
|
458
|
+
* @var string
|
|
459
|
+
*/
|
|
460
|
+
signature = "list";
|
|
461
|
+
/**
|
|
462
|
+
* The console command description.
|
|
463
|
+
*
|
|
464
|
+
* @var string
|
|
465
|
+
*/
|
|
466
|
+
description = "List all available commands";
|
|
467
|
+
async handle() {
|
|
468
|
+
const options = [{
|
|
469
|
+
short: "-h",
|
|
470
|
+
long: "--help",
|
|
471
|
+
description: "Display help for the given command. When no command is given display help for the list command"
|
|
472
|
+
}].concat(this.program.options).map((e) => {
|
|
473
|
+
return Logger.describe(Logger.log(" " + [e.short, e.long].filter((e$1) => !!e$1).join(", "), "green", false), e.description, 25, false).join("");
|
|
474
|
+
});
|
|
475
|
+
/** Get the program commands */
|
|
476
|
+
const commands = this.program.commands.map((e) => {
|
|
477
|
+
return Logger.describe(Logger.log(" " + e.name(), "green", false), e.description(), 25, false).join("");
|
|
478
|
+
});
|
|
479
|
+
const list = ListCommand.groupItems(commands);
|
|
480
|
+
/** Output the modules version */
|
|
481
|
+
const version$1 = this.kernel.modules.map((e) => {
|
|
482
|
+
return Logger.log([[Str.of(e.name).after("/").apa().toString(), "white"], [e.version, "green"]], " ", false);
|
|
483
|
+
}).join(" | ");
|
|
484
|
+
this.newLine();
|
|
485
|
+
console.log(version$1);
|
|
486
|
+
this.newLine();
|
|
487
|
+
console.log(altLogo);
|
|
488
|
+
this.newLine();
|
|
489
|
+
Logger.log("Usage:", "yellow");
|
|
490
|
+
Logger.log(" command [options] [arguments]", "white");
|
|
491
|
+
this.newLine();
|
|
492
|
+
/** Output the options */
|
|
493
|
+
Logger.log("Options:", "yellow");
|
|
494
|
+
console.log(options.join("\n").trim());
|
|
495
|
+
this.newLine();
|
|
496
|
+
/** Ootput the commands */
|
|
497
|
+
Logger.log("Available Commands:", "yellow");
|
|
498
|
+
console.log(list.join("\n\n").trim());
|
|
499
|
+
this.newLine();
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Group Commands based on thier names
|
|
503
|
+
*
|
|
504
|
+
* @param commands
|
|
505
|
+
* @returns
|
|
506
|
+
*/
|
|
507
|
+
static groupItems(commands, fmtd = false) {
|
|
508
|
+
const grouped = commands.reduce((acc, cmd) => {
|
|
509
|
+
/** strip colors before checking prefix */
|
|
510
|
+
const clean = cmd.replace(/\x1b\[\d+m/g, "");
|
|
511
|
+
const prefix = clean.includes(":") ? clean.split(":")[0].trim() : "__root__";
|
|
512
|
+
acc[prefix] ??= [];
|
|
513
|
+
/** keep original with colors */
|
|
514
|
+
acc[prefix].push(cmd);
|
|
515
|
+
return acc;
|
|
516
|
+
}, {});
|
|
517
|
+
return Object.entries(grouped).map(([group, cmds]) => {
|
|
518
|
+
const label = group === "__root__" ? "" : group;
|
|
519
|
+
let out = [Logger.log(label, "yellow", false), cmds.join("\n")].join("\n");
|
|
520
|
+
if (fmtd) out += "\n";
|
|
521
|
+
return out;
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/Musket.ts
|
|
528
|
+
var Musket = class Musket {
|
|
529
|
+
/**
|
|
530
|
+
* The name of the CLI app we're building
|
|
531
|
+
*
|
|
532
|
+
* @default musket
|
|
533
|
+
*/
|
|
534
|
+
cliName = "musket";
|
|
535
|
+
config = {};
|
|
536
|
+
commands = [];
|
|
537
|
+
constructor(app, kernel, baseCommands = [], resolver, tsDownConfig = {}) {
|
|
538
|
+
this.app = app;
|
|
539
|
+
this.kernel = kernel;
|
|
540
|
+
this.baseCommands = baseCommands;
|
|
541
|
+
this.resolver = resolver;
|
|
542
|
+
this.tsDownConfig = tsDownConfig;
|
|
543
|
+
}
|
|
544
|
+
async build() {
|
|
545
|
+
this.loadBaseCommands();
|
|
546
|
+
await this.loadDiscoveredCommands();
|
|
547
|
+
return await this.initialize();
|
|
548
|
+
}
|
|
549
|
+
loadBaseCommands() {
|
|
550
|
+
this.baseCommands.concat([new HelpCommand(this.app, this.kernel), new ListCommand(this.app, this.kernel)]).forEach((e) => this.addCommand(e));
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Provide the configuration to initialize the CLI with
|
|
554
|
+
*
|
|
555
|
+
* @param config
|
|
556
|
+
* @returns
|
|
557
|
+
*/
|
|
558
|
+
configure(config) {
|
|
559
|
+
this.config = config;
|
|
560
|
+
return this;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Set the paths where the cli can search and auto discover commands
|
|
564
|
+
*
|
|
565
|
+
* @param paths
|
|
566
|
+
*
|
|
567
|
+
* @example instance.discoverCommandsFrom('Console/Commands/*.js')
|
|
568
|
+
* @example instance.discoverCommandsFrom(['Console/Commands/*.js', 'App/Commands/*.js'])
|
|
569
|
+
*
|
|
570
|
+
* @returns the current cli intance
|
|
571
|
+
*/
|
|
572
|
+
discoverCommandsFrom(paths) {
|
|
573
|
+
this.config.discoveryPaths = paths;
|
|
574
|
+
return this;
|
|
575
|
+
}
|
|
576
|
+
async loadDiscoveredCommands() {
|
|
577
|
+
const commands = [...(this.app.registeredCommands ?? []).map((cmd) => new cmd(this.app, this.kernel))];
|
|
578
|
+
const paths = Arr.wrap(this.config.discoveryPaths);
|
|
579
|
+
/**
|
|
580
|
+
* CLI Commands auto registration
|
|
581
|
+
*/
|
|
582
|
+
for await (const pth of glob.stream(paths)) {
|
|
583
|
+
const name$1 = path.basename(pth).replace(".js", "").replace(".ts", "");
|
|
584
|
+
try {
|
|
585
|
+
const cmdClass = (await import(pth))[name$1];
|
|
586
|
+
commands.push(new cmdClass(this.app, this.kernel));
|
|
587
|
+
} catch {}
|
|
588
|
+
}
|
|
589
|
+
commands.forEach((e) => this.addCommand(e));
|
|
590
|
+
}
|
|
591
|
+
addCommand(command) {
|
|
592
|
+
this.commands.push(Signature.parseSignature(command.getSignature(), command));
|
|
593
|
+
}
|
|
594
|
+
async initialize() {
|
|
595
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) await this.rebuild("help");
|
|
596
|
+
/**
|
|
597
|
+
* Get the provided packages versions
|
|
598
|
+
*/
|
|
599
|
+
const moduleVersions = this.kernel.modules.map((e) => {
|
|
600
|
+
return Logger.parse([[`${Str.of(e.name).after("/").apa().title().replace("cli", "CLI", false)}:`, "white"], [e.version, "green"]], " ", false);
|
|
601
|
+
}).join("\n");
|
|
602
|
+
const additional = {
|
|
603
|
+
quiet: ["-q, --quiet", "Do not output any message except errors and warnings"],
|
|
604
|
+
silent: ["--silent", "Do not output any message"],
|
|
605
|
+
verbose: ["-v, --verbose [level]", "Increase the verbosity of messages: 1 for normal output, 2 and v for more verbose output and 3 and vv for debug"],
|
|
606
|
+
noInteraction: ["-n, --no-interaction", "Do not ask any interactive question"]
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Init Commander
|
|
610
|
+
*/
|
|
611
|
+
program.name(this.cliName).version(moduleVersions).description(this.config.logo ?? altLogo).configureHelp({ showGlobalOptions: true }).addOption(new Option(additional.quiet[0], additional.quiet[1])).addOption(new Option(additional.silent[0], additional.silent[1]).implies({ quiet: true })).addOption(new Option(additional.verbose[0], additional.verbose[1]).choices([
|
|
612
|
+
"1",
|
|
613
|
+
"2",
|
|
614
|
+
"3",
|
|
615
|
+
"v",
|
|
616
|
+
"vv"
|
|
617
|
+
]).default("1")).addOption(new Option(additional.noInteraction[0], additional.noInteraction[1])).action(async () => {
|
|
618
|
+
const instance = new ListCommand(this.app, this.kernel);
|
|
619
|
+
instance.setInput(program.opts(), program.args, program.registeredArguments, {}, program);
|
|
620
|
+
await this.handle(instance);
|
|
621
|
+
});
|
|
622
|
+
/**
|
|
623
|
+
* Format the help command display
|
|
624
|
+
*/
|
|
625
|
+
program.configureHelp({
|
|
626
|
+
styleTitle: (str) => Logger.log(str, "yellow", false),
|
|
627
|
+
styleOptionTerm: (str) => Logger.log(str, "green", false),
|
|
628
|
+
styleArgumentTerm: (str) => Logger.log(str, "green", false),
|
|
629
|
+
styleSubcommandTerm: (str) => Logger.log(str, "green", false),
|
|
630
|
+
formatItemList(heading, items) {
|
|
631
|
+
if (items.length < 1) return [];
|
|
632
|
+
if (!heading.includes("Commands:")) return items;
|
|
633
|
+
const c = (str) => str.replace(/[^A-Za-z0-9-,]/g, "").replace("32m", "");
|
|
634
|
+
let flags = items.filter((e) => c(e).startsWith("--") || c(e).includes(",--"));
|
|
635
|
+
if (flags.length > 0) flags = [Logger.log("\n" + heading + "\n", "yellow", false)].concat(flags);
|
|
636
|
+
const list = items.filter((e) => !c(e).startsWith("--") && !c(e).includes(",--"));
|
|
637
|
+
if (list.length < 1) return flags;
|
|
638
|
+
const _heading = c(heading).includes("Arguments") ? heading : "Available Commands:";
|
|
639
|
+
return flags.concat(Logger.log(`\n${_heading}`, "yellow", false), ListCommand.groupItems(list, true));
|
|
640
|
+
},
|
|
641
|
+
showGlobalOptions: true
|
|
642
|
+
});
|
|
643
|
+
/**
|
|
644
|
+
* Loop through all the available commands
|
|
645
|
+
*/
|
|
646
|
+
for (let i = 0; i < this.commands.length; i++) {
|
|
647
|
+
const command = this.commands[i];
|
|
648
|
+
const instance = command.commandClass;
|
|
649
|
+
if (command.isNamespaceCommand && command.subCommands) {
|
|
650
|
+
/**
|
|
651
|
+
* Initialize the base command
|
|
652
|
+
*/
|
|
653
|
+
const cmd = command.isHidden ? program : program.command(command.baseCommand).description(command.description ?? "").action(async () => {
|
|
654
|
+
instance.setInput(cmd.opts(), cmd.args, cmd.registeredArguments, command, program);
|
|
655
|
+
await this.handle(instance);
|
|
656
|
+
});
|
|
657
|
+
/**
|
|
658
|
+
* Add options to the base command if it has any
|
|
659
|
+
*/
|
|
660
|
+
if ((command.options?.length ?? 0) > 0) command.options?.filter((v, i$1, a) => a.findIndex((t) => t.name === v.name) === i$1).forEach((opt) => {
|
|
661
|
+
this.makeOption(opt, cmd);
|
|
662
|
+
});
|
|
663
|
+
/**
|
|
664
|
+
* Initialize the sub commands
|
|
665
|
+
*/
|
|
666
|
+
command.subCommands.filter((v, i$1, a) => !v.shared && a.findIndex((t) => t.name === v.name) === i$1).forEach((sub) => {
|
|
667
|
+
const cmd$1 = program.command(`${command.baseCommand}:${sub.name}`).description(sub.description || "").action(async () => {
|
|
668
|
+
instance.setInput(cmd$1.opts(), cmd$1.args, cmd$1.registeredArguments, sub, program);
|
|
669
|
+
await this.handle(instance);
|
|
670
|
+
});
|
|
671
|
+
/**
|
|
672
|
+
* Add the shared arguments here
|
|
673
|
+
*/
|
|
674
|
+
command.subCommands?.filter((e) => e.shared).forEach((opt) => {
|
|
675
|
+
this.makeOption(opt, cmd$1, false, sub);
|
|
676
|
+
});
|
|
677
|
+
/**
|
|
678
|
+
* Add the shared options here
|
|
679
|
+
*/
|
|
680
|
+
command.options?.filter((e) => e.shared).forEach((opt) => {
|
|
681
|
+
this.makeOption(opt, cmd$1, false, sub);
|
|
682
|
+
});
|
|
683
|
+
/**
|
|
684
|
+
* Add options to the sub command if it has any
|
|
685
|
+
*/
|
|
686
|
+
if (sub.nestedOptions) sub.nestedOptions.filter((v, i$1, a) => a.findIndex((t) => t.name === v.name) === i$1).forEach((opt) => {
|
|
687
|
+
this.makeOption(opt, cmd$1);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
/**
|
|
692
|
+
* Initialize command with options
|
|
693
|
+
*/
|
|
694
|
+
const cmd = program.command(command.baseCommand).description(command.description ?? "");
|
|
695
|
+
command?.options?.filter((v, i$1, a) => a.findIndex((t) => t.name === v.name) === i$1).forEach((opt) => {
|
|
696
|
+
this.makeOption(opt, cmd, true);
|
|
697
|
+
});
|
|
698
|
+
cmd.action(async () => {
|
|
699
|
+
instance.setInput(cmd.opts(), cmd.args, cmd.registeredArguments, command, program);
|
|
700
|
+
await this.handle(instance);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Rebuild the app on every command except fire so we wont need TS
|
|
706
|
+
*/
|
|
707
|
+
program.hook("preAction", async (_, cmd) => {
|
|
708
|
+
this.rebuild(cmd.name());
|
|
709
|
+
});
|
|
710
|
+
return program;
|
|
711
|
+
}
|
|
712
|
+
async rebuild(name$1) {
|
|
713
|
+
if (name$1 !== "fire" && name$1 !== "build") await build({
|
|
714
|
+
...this.tsDownConfig,
|
|
715
|
+
logLevel: "silent",
|
|
716
|
+
watch: false,
|
|
717
|
+
plugins: []
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
makeOption(opt, cmd, parse, parent) {
|
|
721
|
+
const description$1 = opt.description?.replace(/\[(\w+)\]/g, (_, k) => parent?.[k] ?? `[${k}]`) ?? "";
|
|
722
|
+
const type$1 = opt.name.replaceAll("-", "");
|
|
723
|
+
if (opt.isFlag) if (parse) {
|
|
724
|
+
let flags = opt.flags?.map((f) => f.length === 1 ? `-${f}` : `--${f.replace(/^-+/, "")}`).join(", ") ?? void 0;
|
|
725
|
+
if (opt.required && !opt.placeholder) flags += ` <${type$1}>`;
|
|
726
|
+
else if (opt.placeholder) flags += " " + opt.placeholder;
|
|
727
|
+
let optn = new Option(flags || "", description$1).default(opt.defaultValue);
|
|
728
|
+
if (opt.choices && opt.choices.length) optn = optn.choices(opt.choices ?? []);
|
|
729
|
+
cmd.addOption(optn);
|
|
730
|
+
} else {
|
|
731
|
+
let flags = opt.flags?.join(", ") ?? "";
|
|
732
|
+
if (opt.required && !opt.placeholder) flags += ` <${type$1}>`;
|
|
733
|
+
else if (opt.placeholder) flags += " " + opt.placeholder;
|
|
734
|
+
let optn = new Option(flags, description$1).default(opt.defaultValue);
|
|
735
|
+
if (opt.choices && opt.choices.length) optn = optn.choices(opt.choices ?? []);
|
|
736
|
+
cmd.addOption(optn);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
let name$1 = opt.placeholder;
|
|
740
|
+
if (!name$1) name$1 = opt.required ? `<${opt.name}>` : `[${opt.name}]`;
|
|
741
|
+
let arg = new Argument(name$1, description$1);
|
|
742
|
+
if (opt.choices && opt.choices.length) arg = arg.choices(opt.choices ?? []);
|
|
743
|
+
if (opt.defaultValue) arg.default(opt.defaultValue);
|
|
744
|
+
cmd.addArgument(arg);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async handle(cmd) {
|
|
748
|
+
if (this.resolver) return await this.resolver(cmd, "handle");
|
|
749
|
+
await cmd.handle(this.app);
|
|
750
|
+
}
|
|
751
|
+
static async parse(kernel, config = {}) {
|
|
752
|
+
const commands = config.baseCommands?.map((e) => new e(kernel.app, kernel));
|
|
753
|
+
const cli = new Musket(kernel.app, kernel, commands, config.resolver).configure(config);
|
|
754
|
+
if (config.cliName) cli.cliName = config.cliName;
|
|
755
|
+
const command = (await cli.build()).exitOverride((e) => {
|
|
756
|
+
if (e.exitCode <= 0) return;
|
|
757
|
+
Logger.log("Unknown command or argument.", "white");
|
|
758
|
+
Logger.log([
|
|
759
|
+
["Run", "white"],
|
|
760
|
+
[`\`${config.cliName} --help\``, ["grey", "italic"]],
|
|
761
|
+
["to see available commands.", "white"]
|
|
762
|
+
], " ");
|
|
763
|
+
});
|
|
764
|
+
if (!config.skipParsing) await command.parseAsync(process.argv).catch((e) => e);
|
|
765
|
+
if (cli.app) cli.app.musket = cli;
|
|
766
|
+
return command;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region package.json
|
|
772
|
+
var name = "@h3ravel/musket";
|
|
773
|
+
var version = "0.1.0";
|
|
774
|
+
var description = "Musket CLI is a framework-agnostic CLI framework designed to allow you build artisan-like CLI apps and for use in the H3ravel framework.";
|
|
775
|
+
var type = "module";
|
|
776
|
+
var exports = {
|
|
777
|
+
".": {
|
|
778
|
+
"import": "./dist/index.js",
|
|
779
|
+
"require": "./dist/index.cjs"
|
|
780
|
+
},
|
|
781
|
+
"./Utils": {
|
|
782
|
+
"import": "./dist/Utils.js",
|
|
783
|
+
"require": "./dist/Utils.cjs"
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var typesVersions = { "*": {
|
|
787
|
+
"Utils": ["dist/Utils.d.ts"],
|
|
788
|
+
"*": ["dist/index.d.ts"]
|
|
789
|
+
} };
|
|
790
|
+
var files = ["dist"];
|
|
791
|
+
var publishConfig = { "access": "public" };
|
|
792
|
+
var homepage = "https://h3ravel.toneflix.net/guide/deeper/musket";
|
|
793
|
+
var repository = {
|
|
794
|
+
"type": "git",
|
|
795
|
+
"url": "git+https://github.com/h3ravel/musket.git"
|
|
796
|
+
};
|
|
797
|
+
var keywords = [
|
|
798
|
+
"h3ravel",
|
|
799
|
+
"modern",
|
|
800
|
+
"web",
|
|
801
|
+
"H3",
|
|
802
|
+
"framework",
|
|
803
|
+
"nodejs",
|
|
804
|
+
"typescript",
|
|
805
|
+
"laravel",
|
|
806
|
+
"artisan",
|
|
807
|
+
"musket",
|
|
808
|
+
"commander",
|
|
809
|
+
"command",
|
|
810
|
+
"CLI",
|
|
811
|
+
"build",
|
|
812
|
+
"console"
|
|
813
|
+
];
|
|
814
|
+
var scripts = {
|
|
815
|
+
"build": "tsdown --config-loader unconfig",
|
|
816
|
+
"lint": "eslint . --ext .ts",
|
|
817
|
+
"barrel": "barrelize >/dev/null",
|
|
818
|
+
"test": "vitest",
|
|
819
|
+
"coverage": "vitest --coverage",
|
|
820
|
+
"cmd": "tsx --experimental-specifier-resolution=node tests/run",
|
|
821
|
+
"release:patch": "pnpm build && pnpm version patch && git add . && git commit -m \"version: bump console package version\" && pnpm publish --tag latest",
|
|
822
|
+
"version-patch": "pnpm version patch",
|
|
823
|
+
"postinstall": "pnpm barrel"
|
|
824
|
+
};
|
|
825
|
+
var peerDependencies = { "@h3ravel/support": "^0.14.1" };
|
|
826
|
+
var devDependencies = {
|
|
827
|
+
"@types/node": "^24.7.2",
|
|
828
|
+
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
|
829
|
+
"@typescript-eslint/parser": "^8.44.0",
|
|
830
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
831
|
+
"barrelize": "^1.6.4",
|
|
832
|
+
"eslint": "^9.36.0",
|
|
833
|
+
"husky": "^9.1.7",
|
|
834
|
+
"tsdown": "^0.15.7",
|
|
835
|
+
"typescript": "^5.9.2",
|
|
836
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
837
|
+
"vitest": "^3.2.4"
|
|
838
|
+
};
|
|
839
|
+
var dependencies = {
|
|
840
|
+
"@h3ravel/shared": "^0.22.2",
|
|
841
|
+
"chalk": "^5.6.2",
|
|
842
|
+
"commander": "^14.0.1",
|
|
843
|
+
"dayjs": "^1.11.18",
|
|
844
|
+
"execa": "^9.6.0",
|
|
845
|
+
"glob": "^11.0.3",
|
|
846
|
+
"preferred-pm": "^4.1.1",
|
|
847
|
+
"radashi": "^12.6.2",
|
|
848
|
+
"resolve-from": "^5.0.0",
|
|
849
|
+
"tsx": "^4.20.5"
|
|
850
|
+
};
|
|
851
|
+
var packageManager = "pnpm@10.18.2";
|
|
852
|
+
var engines = { "node": "^20.19.0 || >=22.12.0" };
|
|
853
|
+
var package_default = {
|
|
854
|
+
name,
|
|
855
|
+
version,
|
|
856
|
+
description,
|
|
857
|
+
type,
|
|
858
|
+
exports,
|
|
859
|
+
typesVersions,
|
|
860
|
+
files,
|
|
861
|
+
publishConfig,
|
|
862
|
+
homepage,
|
|
863
|
+
repository,
|
|
864
|
+
keywords,
|
|
865
|
+
scripts,
|
|
866
|
+
peerDependencies,
|
|
867
|
+
devDependencies,
|
|
868
|
+
dependencies,
|
|
869
|
+
packageManager,
|
|
870
|
+
engines
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/Core/Kernel.ts
|
|
875
|
+
var Kernel = class Kernel {
|
|
876
|
+
cwd;
|
|
877
|
+
output = typeof Logger;
|
|
878
|
+
modules = [];
|
|
879
|
+
basePath = "";
|
|
880
|
+
packages = [];
|
|
881
|
+
config = {};
|
|
882
|
+
constructor(app) {
|
|
883
|
+
this.app = app;
|
|
884
|
+
}
|
|
885
|
+
async ensureDirectoryExists(dir) {
|
|
886
|
+
await mkdir(dir, { recursive: true });
|
|
887
|
+
}
|
|
888
|
+
static async init(app, config = {}) {
|
|
889
|
+
const instance = new Kernel(app);
|
|
890
|
+
instance.config = config;
|
|
891
|
+
instance.packages = config.packages ?? [];
|
|
892
|
+
return (await instance.loadRequirements()).run();
|
|
893
|
+
}
|
|
894
|
+
async run() {
|
|
895
|
+
return await Musket.parse(this, this.config);
|
|
896
|
+
}
|
|
897
|
+
async loadRequirements() {
|
|
898
|
+
this.cwd = path.join(process.cwd(), this.basePath);
|
|
899
|
+
try {
|
|
900
|
+
package_default.name = this.config.cliName ?? package_default.name;
|
|
901
|
+
this.modules.push(package_default);
|
|
902
|
+
} catch {}
|
|
903
|
+
for (let i = 0; i < this.packages.length; i++) try {
|
|
904
|
+
const name$1 = this.packages[i];
|
|
905
|
+
const modulePath = FileSystem.findModulePkg(name$1, this.cwd) ?? "";
|
|
906
|
+
this.modules.push(await import(path.join(modulePath, "package.json")));
|
|
907
|
+
} catch (e) {
|
|
908
|
+
this.modules.push({
|
|
909
|
+
version: "N/A",
|
|
910
|
+
name: "Unknown"
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
return this;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
//#endregion
|
|
918
|
+
export { Command, Kernel, Musket, Signature };
|