@rune-cli/rune 0.0.5 → 0.0.7
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.mjs +118 -40
- package/dist/{dist-Bcn0FpHi.mjs → dist-FWLEc-op.mjs} +39 -36
- package/dist/{index-BF5_G9J2.d.mts → index-B6XsJ6ON.d.mts} +3 -7
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{write-result-qylmqXvG.mjs → run-manifest-command-BmeVwPA5.mjs} +168 -133
- package/dist/runtime.d.mts +5 -7
- package/dist/runtime.mjs +3 -2
- package/dist/test.d.mts +45 -2
- package/dist/test.mjs +46 -2
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import "./dist-FWLEc-op.mjs";
|
|
3
|
+
import { n as isHelpFlag, r as isVersionFlag, t as runManifestCommand } from "./run-manifest-command-BmeVwPA5.mjs";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { build } from "esbuild";
|
|
5
6
|
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import ts from "typescript";
|
|
8
9
|
//#region package.json
|
|
9
|
-
var version = "0.0.
|
|
10
|
+
var version = "0.0.7";
|
|
10
11
|
//#endregion
|
|
11
12
|
//#region src/manifest/generate-manifest.ts
|
|
12
13
|
const COMMAND_ENTRY_FILE = "index.ts";
|
|
@@ -116,20 +117,25 @@ function resolveCommandsDirectory(projectRoot) {
|
|
|
116
117
|
function resolveDistDirectory(projectRoot) {
|
|
117
118
|
return path.join(projectRoot, DIST_DIRECTORY_NAME);
|
|
118
119
|
}
|
|
119
|
-
async function
|
|
120
|
+
async function readProjectCliInfo(projectRoot) {
|
|
120
121
|
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
121
122
|
try {
|
|
122
123
|
const packageJsonContents = await readFile(packageJsonPath, "utf8");
|
|
123
124
|
const packageJson = JSON.parse(packageJsonContents);
|
|
125
|
+
let name;
|
|
124
126
|
if (packageJson.bin && typeof packageJson.bin === "object") {
|
|
125
127
|
const binNames = Object.keys(packageJson.bin).sort((left, right) => left.localeCompare(right));
|
|
126
|
-
if (binNames.length > 0)
|
|
128
|
+
if (binNames.length > 0) name = binNames[0];
|
|
127
129
|
}
|
|
128
|
-
if (packageJson.name && packageJson.name.length > 0)
|
|
130
|
+
if (!name && packageJson.name && packageJson.name.length > 0) name = packageJson.name.split("/").at(-1) ?? packageJson.name;
|
|
131
|
+
return {
|
|
132
|
+
name: name ?? path.basename(projectRoot),
|
|
133
|
+
version: packageJson.version
|
|
134
|
+
};
|
|
129
135
|
} catch (error) {
|
|
130
136
|
if (error.code !== "ENOENT") throw error;
|
|
131
137
|
}
|
|
132
|
-
return path.basename(projectRoot);
|
|
138
|
+
return { name: path.basename(projectRoot) };
|
|
133
139
|
}
|
|
134
140
|
async function assertCommandsDirectoryExists(commandsDirectory) {
|
|
135
141
|
if (!(await stat(commandsDirectory).catch((error) => {
|
|
@@ -138,6 +144,32 @@ async function assertCommandsDirectoryExists(commandsDirectory) {
|
|
|
138
144
|
}))?.isDirectory()) throw new Error(`Commands directory not found at ${COMMANDS_DIRECTORY_NAME}. Create it or check the --project <path> option.`);
|
|
139
145
|
}
|
|
140
146
|
//#endregion
|
|
147
|
+
//#region src/cli/write-result.ts
|
|
148
|
+
async function writeStream(stream, contents) {
|
|
149
|
+
if (contents.length === 0) return;
|
|
150
|
+
await new Promise((resolve, reject) => {
|
|
151
|
+
stream.write(contents, (error) => {
|
|
152
|
+
if (error) {
|
|
153
|
+
reject(error);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function ensureTrailingNewline(contents) {
|
|
161
|
+
return contents.endsWith("\n") ? contents : `${contents}\n`;
|
|
162
|
+
}
|
|
163
|
+
async function writeStdout(contents) {
|
|
164
|
+
await writeStream(process.stdout, contents);
|
|
165
|
+
}
|
|
166
|
+
async function writeStderr(contents) {
|
|
167
|
+
await writeStream(process.stderr, contents);
|
|
168
|
+
}
|
|
169
|
+
async function writeStderrLine(contents) {
|
|
170
|
+
await writeStderr(ensureTrailingNewline(contents));
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
141
173
|
//#region src/cli/build-command.ts
|
|
142
174
|
const BUILD_CLI_FILENAME = "cli.mjs";
|
|
143
175
|
const BUILD_MANIFEST_FILENAME = "manifest.json";
|
|
@@ -200,13 +232,14 @@ async function copyBuiltAssets(sourceDirectory, distDirectory) {
|
|
|
200
232
|
await cp(sourceEntryPath, distEntryPath);
|
|
201
233
|
}));
|
|
202
234
|
}
|
|
203
|
-
function renderBuiltCliEntry(cliName, runtimeImportPath) {
|
|
235
|
+
function renderBuiltCliEntry(cliName, version, runtimeImportPath) {
|
|
204
236
|
return `import { readFile } from "node:fs/promises";
|
|
205
237
|
import { fileURLToPath } from "node:url";
|
|
206
238
|
|
|
207
|
-
import { runManifestCommand
|
|
239
|
+
import { runManifestCommand } from ${JSON.stringify(runtimeImportPath)};
|
|
208
240
|
|
|
209
241
|
const cliName = ${JSON.stringify(cliName)};
|
|
242
|
+
const version = ${JSON.stringify(version)};
|
|
210
243
|
const distDirectoryUrl = new URL("./", import.meta.url);
|
|
211
244
|
const manifestPath = fileURLToPath(new URL("./${BUILD_MANIFEST_FILENAME}", distDirectoryUrl));
|
|
212
245
|
const manifestContents = await readFile(manifestPath, "utf8");
|
|
@@ -222,14 +255,13 @@ const runtimeManifest = {
|
|
|
222
255
|
: node,
|
|
223
256
|
),
|
|
224
257
|
};
|
|
225
|
-
|
|
258
|
+
process.exitCode = await runManifestCommand({
|
|
226
259
|
manifest: runtimeManifest,
|
|
227
260
|
rawArgs: process.argv.slice(2),
|
|
228
261
|
cliName,
|
|
262
|
+
version,
|
|
229
263
|
cwd: process.cwd(),
|
|
230
264
|
});
|
|
231
|
-
|
|
232
|
-
await writeCommandExecutionResult(result);
|
|
233
265
|
`;
|
|
234
266
|
}
|
|
235
267
|
function collectCommandEntryPoints(manifest) {
|
|
@@ -292,12 +324,12 @@ async function buildCommandEntries(projectRoot, sourceDirectory, distDirectory,
|
|
|
292
324
|
write: true
|
|
293
325
|
});
|
|
294
326
|
}
|
|
295
|
-
async function buildCliEntry(projectRoot, distDirectory, cliName) {
|
|
327
|
+
async function buildCliEntry(projectRoot, distDirectory, cliName, version) {
|
|
296
328
|
const runtimeHelperEntryPath = await resolveRuntimeHelperEntryPath();
|
|
297
329
|
await build({
|
|
298
330
|
absWorkingDir: projectRoot,
|
|
299
331
|
stdin: {
|
|
300
|
-
contents: renderBuiltCliEntry(cliName, `./${path.basename(runtimeHelperEntryPath)}`),
|
|
332
|
+
contents: renderBuiltCliEntry(cliName, version, `./${path.basename(runtimeHelperEntryPath)}`),
|
|
301
333
|
loader: "ts",
|
|
302
334
|
resolveDir: path.dirname(runtimeHelperEntryPath),
|
|
303
335
|
sourcefile: "rune-built-cli-entry.ts"
|
|
@@ -341,7 +373,7 @@ async function runBuildCommand(options) {
|
|
|
341
373
|
await assertCommandsDirectoryExists(commandsDirectory);
|
|
342
374
|
const sourceManifest = await generateCommandManifest({ commandsDirectory });
|
|
343
375
|
const builtManifest = createBuiltManifest(sourceManifest, sourceDirectory);
|
|
344
|
-
const
|
|
376
|
+
const cliInfo = await readProjectCliInfo(projectRoot);
|
|
345
377
|
await rm(distDirectory, {
|
|
346
378
|
recursive: true,
|
|
347
379
|
force: true
|
|
@@ -349,13 +381,18 @@ async function runBuildCommand(options) {
|
|
|
349
381
|
await writeBuiltRuntimeFiles(distDirectory, builtManifest);
|
|
350
382
|
await Promise.all([
|
|
351
383
|
buildCommandEntries(projectRoot, sourceDirectory, distDirectory, sourceManifest),
|
|
352
|
-
buildCliEntry(projectRoot, distDirectory,
|
|
384
|
+
buildCliEntry(projectRoot, distDirectory, cliInfo.name, cliInfo.version),
|
|
353
385
|
copyBuiltAssets(sourceDirectory, distDirectory)
|
|
354
386
|
]);
|
|
355
|
-
|
|
387
|
+
await writeStdout(`Built CLI to ${path.join(distDirectory, BUILD_CLI_FILENAME)}\n`);
|
|
388
|
+
return 0;
|
|
356
389
|
} catch (error) {
|
|
357
|
-
if (isBuildFailure(error))
|
|
358
|
-
|
|
390
|
+
if (isBuildFailure(error)) {
|
|
391
|
+
await writeStderrLine(formatBuildFailure(projectRoot, error));
|
|
392
|
+
return 1;
|
|
393
|
+
}
|
|
394
|
+
await writeStderrLine(error instanceof Error ? error.message : "Failed to run rune build");
|
|
395
|
+
return 1;
|
|
359
396
|
}
|
|
360
397
|
}
|
|
361
398
|
//#endregion
|
|
@@ -386,6 +423,11 @@ Examples:
|
|
|
386
423
|
async function runDevCommand(options) {
|
|
387
424
|
try {
|
|
388
425
|
const projectRoot = resolveProjectPath(options);
|
|
426
|
+
const cliInfo = await readProjectCliInfo(projectRoot);
|
|
427
|
+
if (cliInfo.version && options.rawArgs.length === 1 && isVersionFlag(options.rawArgs[0])) {
|
|
428
|
+
await writeStdout(`${cliInfo.name} v${cliInfo.version}\n`);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
389
431
|
const commandsDirectory = resolveCommandsDirectory(projectRoot);
|
|
390
432
|
await assertCommandsDirectoryExists(commandsDirectory);
|
|
391
433
|
const manifest = await generateCommandManifest({ commandsDirectory });
|
|
@@ -393,36 +435,44 @@ async function runDevCommand(options) {
|
|
|
393
435
|
return runManifestCommand({
|
|
394
436
|
manifest,
|
|
395
437
|
rawArgs: options.rawArgs,
|
|
396
|
-
cliName:
|
|
438
|
+
cliName: cliInfo.name,
|
|
439
|
+
version: cliInfo.version,
|
|
397
440
|
cwd: options.cwd
|
|
398
441
|
});
|
|
399
442
|
} catch (error) {
|
|
400
|
-
|
|
443
|
+
await writeStderrLine(error instanceof Error ? error.message : "Failed to run rune dev");
|
|
444
|
+
return 1;
|
|
401
445
|
}
|
|
402
446
|
}
|
|
403
447
|
//#endregion
|
|
404
448
|
//#region src/cli/rune-cli.ts
|
|
449
|
+
async function writeEarlyExit(exit) {
|
|
450
|
+
if (exit.stream === "stdout") await writeStdout(exit.output);
|
|
451
|
+
else await writeStderrLine(exit.output);
|
|
452
|
+
return exit.exitCode;
|
|
453
|
+
}
|
|
405
454
|
function tryParseProjectOption(argv, index) {
|
|
406
455
|
const token = argv[index];
|
|
407
456
|
if (token.startsWith("--project=")) return {
|
|
457
|
+
ok: true,
|
|
408
458
|
projectPath: token.slice(10),
|
|
409
459
|
nextIndex: index + 1
|
|
410
460
|
};
|
|
411
461
|
if (token === "--project") {
|
|
412
462
|
const nextToken = argv[index + 1];
|
|
413
|
-
if (!nextToken) return
|
|
463
|
+
if (!nextToken) return {
|
|
464
|
+
ok: false,
|
|
465
|
+
exitCode: 1,
|
|
466
|
+
output: "Missing value for --project. Usage: --project <path>",
|
|
467
|
+
stream: "stderr"
|
|
468
|
+
};
|
|
414
469
|
return {
|
|
470
|
+
ok: true,
|
|
415
471
|
projectPath: nextToken,
|
|
416
472
|
nextIndex: index + 2
|
|
417
473
|
};
|
|
418
474
|
}
|
|
419
475
|
}
|
|
420
|
-
function isHelpFlag(token) {
|
|
421
|
-
return token === "--help" || token === "-h";
|
|
422
|
-
}
|
|
423
|
-
function isVersionFlag(token) {
|
|
424
|
-
return token === "--version" || token === "-V";
|
|
425
|
-
}
|
|
426
476
|
function getRuneVersion() {
|
|
427
477
|
return version;
|
|
428
478
|
}
|
|
@@ -434,25 +484,33 @@ function parseDevArgs(argv) {
|
|
|
434
484
|
if (token === "--") {
|
|
435
485
|
commandArgs.push(...argv.slice(index + 1));
|
|
436
486
|
return {
|
|
487
|
+
ok: true,
|
|
437
488
|
projectPath,
|
|
438
489
|
commandArgs
|
|
439
490
|
};
|
|
440
491
|
}
|
|
441
|
-
if (isHelpFlag(token)) return
|
|
492
|
+
if (isHelpFlag(token)) return {
|
|
493
|
+
ok: false,
|
|
494
|
+
exitCode: 0,
|
|
495
|
+
output: renderRuneDevHelp(),
|
|
496
|
+
stream: "stdout"
|
|
497
|
+
};
|
|
442
498
|
const projectResult = tryParseProjectOption(argv, index);
|
|
443
499
|
if (projectResult) {
|
|
444
|
-
if (
|
|
500
|
+
if (!projectResult.ok) return projectResult;
|
|
445
501
|
projectPath = projectResult.projectPath;
|
|
446
502
|
index = projectResult.nextIndex - 1;
|
|
447
503
|
continue;
|
|
448
504
|
}
|
|
449
505
|
commandArgs.push(token, ...argv.slice(index + 1));
|
|
450
506
|
return {
|
|
507
|
+
ok: true,
|
|
451
508
|
projectPath,
|
|
452
509
|
commandArgs
|
|
453
510
|
};
|
|
454
511
|
}
|
|
455
512
|
return {
|
|
513
|
+
ok: true,
|
|
456
514
|
projectPath,
|
|
457
515
|
commandArgs
|
|
458
516
|
};
|
|
@@ -461,17 +519,30 @@ function parseBuildArgs(argv) {
|
|
|
461
519
|
let projectPath;
|
|
462
520
|
for (let index = 0; index < argv.length; index += 1) {
|
|
463
521
|
const token = argv[index];
|
|
464
|
-
if (isHelpFlag(token)) return
|
|
522
|
+
if (isHelpFlag(token)) return {
|
|
523
|
+
ok: false,
|
|
524
|
+
exitCode: 0,
|
|
525
|
+
output: renderRuneBuildHelp(),
|
|
526
|
+
stream: "stdout"
|
|
527
|
+
};
|
|
465
528
|
const projectResult = tryParseProjectOption(argv, index);
|
|
466
529
|
if (projectResult) {
|
|
467
|
-
if (
|
|
530
|
+
if (!projectResult.ok) return projectResult;
|
|
468
531
|
projectPath = projectResult.projectPath;
|
|
469
532
|
index = projectResult.nextIndex - 1;
|
|
470
533
|
continue;
|
|
471
534
|
}
|
|
472
|
-
return
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
exitCode: 1,
|
|
538
|
+
output: `Unexpected argument for rune build: ${token}`,
|
|
539
|
+
stream: "stderr"
|
|
540
|
+
};
|
|
473
541
|
}
|
|
474
|
-
return {
|
|
542
|
+
return {
|
|
543
|
+
ok: true,
|
|
544
|
+
projectPath
|
|
545
|
+
};
|
|
475
546
|
}
|
|
476
547
|
function renderRuneCliHelp() {
|
|
477
548
|
return `\
|
|
@@ -488,11 +559,17 @@ Options:
|
|
|
488
559
|
}
|
|
489
560
|
async function runRuneCli(options) {
|
|
490
561
|
const [subcommand, ...restArgs] = options.argv;
|
|
491
|
-
if (!subcommand || isHelpFlag(subcommand))
|
|
492
|
-
|
|
562
|
+
if (!subcommand || isHelpFlag(subcommand)) {
|
|
563
|
+
await writeStdout(renderRuneCliHelp());
|
|
564
|
+
return 0;
|
|
565
|
+
}
|
|
566
|
+
if (isVersionFlag(subcommand)) {
|
|
567
|
+
await writeStdout(`rune v${getRuneVersion()}\n`);
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
493
570
|
if (subcommand === "dev") {
|
|
494
571
|
const parsedDevArgs = parseDevArgs(restArgs);
|
|
495
|
-
if (
|
|
572
|
+
if (!parsedDevArgs.ok) return writeEarlyExit(parsedDevArgs);
|
|
496
573
|
return runDevCommand({
|
|
497
574
|
rawArgs: parsedDevArgs.commandArgs,
|
|
498
575
|
projectPath: parsedDevArgs.projectPath,
|
|
@@ -501,19 +578,20 @@ async function runRuneCli(options) {
|
|
|
501
578
|
}
|
|
502
579
|
if (subcommand === "build") {
|
|
503
580
|
const parsedBuildArgs = parseBuildArgs(restArgs);
|
|
504
|
-
if (
|
|
581
|
+
if (!parsedBuildArgs.ok) return writeEarlyExit(parsedBuildArgs);
|
|
505
582
|
return runBuildCommand({
|
|
506
583
|
projectPath: parsedBuildArgs.projectPath,
|
|
507
584
|
cwd: options.cwd
|
|
508
585
|
});
|
|
509
586
|
}
|
|
510
|
-
|
|
587
|
+
await writeStderrLine(`Unknown command: ${subcommand}. Available commands: build, dev`);
|
|
588
|
+
return 1;
|
|
511
589
|
}
|
|
512
590
|
//#endregion
|
|
513
591
|
//#region src/cli.ts
|
|
514
|
-
|
|
592
|
+
process.exitCode = await runRuneCli({
|
|
515
593
|
argv: process.argv.slice(2),
|
|
516
594
|
cwd: process.cwd()
|
|
517
|
-
})
|
|
595
|
+
});
|
|
518
596
|
//#endregion
|
|
519
597
|
export {};
|
|
@@ -3,7 +3,7 @@ import { format, parseArgs } from "node:util";
|
|
|
3
3
|
function isSchemaField(field) {
|
|
4
4
|
return "schema" in field && field.schema !== void 0;
|
|
5
5
|
}
|
|
6
|
-
const
|
|
6
|
+
const DEFINED_COMMAND_BRAND = Symbol.for("@rune-cli/defined-command");
|
|
7
7
|
function isOptionalArg(field) {
|
|
8
8
|
if (isSchemaField(field)) return;
|
|
9
9
|
return field.required !== true || field.default !== void 0;
|
|
@@ -70,18 +70,43 @@ function validateArgOrdering(args) {
|
|
|
70
70
|
*/
|
|
71
71
|
function defineCommand(input) {
|
|
72
72
|
if (input.args) validateArgOrdering(input.args);
|
|
73
|
-
|
|
73
|
+
const command = {
|
|
74
74
|
description: input.description,
|
|
75
|
-
args: input.args ??
|
|
76
|
-
options: input.options ??
|
|
75
|
+
args: input.args ?? [],
|
|
76
|
+
options: input.options ?? [],
|
|
77
77
|
run: input.run
|
|
78
78
|
};
|
|
79
|
+
Object.defineProperty(command, DEFINED_COMMAND_BRAND, {
|
|
80
|
+
value: true,
|
|
81
|
+
enumerable: false
|
|
82
|
+
});
|
|
83
|
+
return command;
|
|
84
|
+
}
|
|
85
|
+
function isDefinedCommand(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && value[DEFINED_COMMAND_BRAND] === true;
|
|
79
87
|
}
|
|
80
88
|
function formatExecutionError(error) {
|
|
81
89
|
if (error instanceof Error) return error.message === "" ? "" : error.message || error.name || "Unknown error";
|
|
82
90
|
if (typeof error === "string") return error;
|
|
83
91
|
return "Unknown error";
|
|
84
92
|
}
|
|
93
|
+
async function executeCommand(command, input = {}) {
|
|
94
|
+
try {
|
|
95
|
+
await command.run({
|
|
96
|
+
options: input.options ?? {},
|
|
97
|
+
args: input.args ?? {},
|
|
98
|
+
cwd: input.cwd ?? process.cwd(),
|
|
99
|
+
rawArgs: input.rawArgs ?? []
|
|
100
|
+
});
|
|
101
|
+
return { exitCode: 0 };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const message = formatExecutionError(error);
|
|
104
|
+
return message ? {
|
|
105
|
+
exitCode: 1,
|
|
106
|
+
errorMessage: message
|
|
107
|
+
} : { exitCode: 1 };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
85
110
|
async function captureProcessOutput(action) {
|
|
86
111
|
const stdoutChunks = [];
|
|
87
112
|
const stderrChunks = [];
|
|
@@ -119,17 +144,18 @@ async function captureProcessOutput(action) {
|
|
|
119
144
|
]) console[method] = (...args) => captureConsole(stdoutChunks, args);
|
|
120
145
|
for (const method of ["warn", "error"]) console[method] = (...args) => captureConsole(stderrChunks, args);
|
|
121
146
|
try {
|
|
122
|
-
const value = await action();
|
|
123
147
|
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
value: await action(),
|
|
124
150
|
stdout: stdoutChunks.join(""),
|
|
125
|
-
stderr: stderrChunks.join("")
|
|
126
|
-
value
|
|
151
|
+
stderr: stderrChunks.join("")
|
|
127
152
|
};
|
|
128
153
|
} catch (error) {
|
|
129
154
|
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error,
|
|
130
157
|
stdout: stdoutChunks.join(""),
|
|
131
|
-
stderr: stderrChunks.join("")
|
|
132
|
-
error
|
|
158
|
+
stderr: stderrChunks.join("")
|
|
133
159
|
};
|
|
134
160
|
} finally {
|
|
135
161
|
process.stdout.write = originalStdoutWrite;
|
|
@@ -137,34 +163,11 @@ async function captureProcessOutput(action) {
|
|
|
137
163
|
Object.assign(console, originalConsoleMethods);
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const result = await captureProcessOutput(async () => {
|
|
143
|
-
await command.run({
|
|
144
|
-
options: input.options ?? {},
|
|
145
|
-
args: input.args ?? {},
|
|
146
|
-
cwd: input.cwd ?? process.cwd(),
|
|
147
|
-
rawArgs: input.rawArgs ?? EMPTY_ARGS
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
if (result.error === void 0) return {
|
|
151
|
-
exitCode: 0,
|
|
152
|
-
stdout: result.stdout,
|
|
153
|
-
stderr: result.stderr
|
|
154
|
-
};
|
|
155
|
-
const message = formatExecutionError(result.error);
|
|
156
|
-
return {
|
|
157
|
-
exitCode: 1,
|
|
158
|
-
stdout: result.stdout,
|
|
159
|
-
stderr: `${result.stderr}${message ? `${message}\n` : ""}`
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function formatFieldTypeHint(field) {
|
|
163
|
-
if (isSchemaField(field)) return "";
|
|
164
|
-
return ` <${field.type}>`;
|
|
166
|
+
function formatTypeHint(field) {
|
|
167
|
+
return isSchemaField(field) ? "" : ` <${field.type}>`;
|
|
165
168
|
}
|
|
166
169
|
function formatOptionLabel(field) {
|
|
167
|
-
return `--${field.name}${
|
|
170
|
+
return `--${field.name}${formatTypeHint(field)}`;
|
|
168
171
|
}
|
|
169
172
|
function formatArgumentLabel(field) {
|
|
170
173
|
return field.name;
|
|
@@ -397,4 +400,4 @@ async function parseCommand(command, rawArgs) {
|
|
|
397
400
|
};
|
|
398
401
|
}
|
|
399
402
|
//#endregion
|
|
400
|
-
export {
|
|
403
|
+
export { isSchemaField as a, isDefinedCommand as i, defineCommand as n, parseCommand as o, executeCommand as r, captureProcessOutput as t };
|
|
@@ -269,7 +269,8 @@ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readon
|
|
|
269
269
|
* a variable without a concrete type), optionality information is lost and
|
|
270
270
|
* the ordering check is skipped for that field.
|
|
271
271
|
*/
|
|
272
|
-
declare function defineCommand<const TArgsFields extends readonly CommandArgField[] | undefined = undefined, const TOptionsFields extends readonly CommandOptionField[] | undefined = undefined>(input: DefineCommandInput<TArgsFields, TOptionsFields> & ValidateArgOrder<TArgsFields>): DefinedCommand<NormalizeFields<TArgsFields, CommandArgField>, NormalizeFields<TOptionsFields, CommandOptionField>>;
|
|
272
|
+
declare function defineCommand<const TArgsFields extends readonly CommandArgField[] | undefined = undefined, const TOptionsFields extends readonly CommandOptionField[] | undefined = undefined>(input: DefineCommandInput<TArgsFields, TOptionsFields> & ValidateArgOrder<TArgsFields>): DefinedCommand<NormalizeFields<TArgsFields, CommandArgField>, NormalizeFields<TOptionsFields, CommandOptionField>>;
|
|
273
|
+
//#endregion
|
|
273
274
|
//#region src/execute-command.d.ts
|
|
274
275
|
interface ExecuteCommandInput<TOptions, TArgs> {
|
|
275
276
|
readonly options?: TOptions | undefined;
|
|
@@ -277,10 +278,5 @@ interface ExecuteCommandInput<TOptions, TArgs> {
|
|
|
277
278
|
readonly cwd?: string | undefined;
|
|
278
279
|
readonly rawArgs?: readonly string[] | undefined;
|
|
279
280
|
}
|
|
280
|
-
interface CommandExecutionResult {
|
|
281
|
-
readonly exitCode: number;
|
|
282
|
-
readonly stdout: string;
|
|
283
|
-
readonly stderr: string;
|
|
284
|
-
}
|
|
285
281
|
//#endregion
|
|
286
|
-
export {
|
|
282
|
+
export { ExecuteCommandInput as a, PrimitiveFieldType as c, SchemaOptionField as d, defineCommand as f, DefinedCommand as i, PrimitiveOptionField as l, CommandContext as n, InferExecutionFields as o, CommandOptionField as r, PrimitiveArgField as s, CommandArgField as t, SchemaArgField as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { type CommandArgField, type CommandContext, type
|
|
1
|
+
import { a as ExecuteCommandInput, c as PrimitiveFieldType, d as SchemaOptionField, f as defineCommand, i as DefinedCommand, l as PrimitiveOptionField, n as CommandContext, o as InferExecutionFields, r as CommandOptionField, s as PrimitiveArgField, t as CommandArgField, u as SchemaArgField } from "./index-B6XsJ6ON.mjs";
|
|
2
|
+
export { type CommandArgField, type CommandContext, type CommandOptionField, type DefinedCommand, type ExecuteCommandInput, type InferExecutionFields, type PrimitiveArgField, type PrimitiveFieldType, type PrimitiveOptionField, type SchemaArgField, type SchemaOptionField, defineCommand };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as defineCommand } from "./dist-FWLEc-op.mjs";
|
|
2
2
|
export { defineCommand };
|
|
@@ -1,110 +1,33 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-FWLEc-op.mjs";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
|
-
//#region src/cli/
|
|
4
|
-
function
|
|
5
|
-
return
|
|
6
|
-
exitCode: 0,
|
|
7
|
-
stdout,
|
|
8
|
-
stderr: ""
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function failureResult(stderr) {
|
|
12
|
-
return {
|
|
13
|
-
exitCode: 1,
|
|
14
|
-
stdout: "",
|
|
15
|
-
stderr: stderr.endsWith("\n") ? stderr : `${stderr}\n`
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/manifest/manifest-map.ts
|
|
20
|
-
function commandManifestPathToKey(pathSegments) {
|
|
21
|
-
return pathSegments.join(" ");
|
|
3
|
+
//#region src/cli/flags.ts
|
|
4
|
+
function isHelpFlag(token) {
|
|
5
|
+
return token === "--help" || token === "-h";
|
|
22
6
|
}
|
|
23
|
-
function
|
|
24
|
-
return
|
|
7
|
+
function isVersionFlag(token) {
|
|
8
|
+
return token === "--version" || token === "-V";
|
|
25
9
|
}
|
|
26
10
|
//#endregion
|
|
27
|
-
//#region src/manifest/
|
|
11
|
+
//#region src/manifest/command-loader.ts
|
|
28
12
|
async function loadCommandFromModule(sourceFilePath) {
|
|
29
13
|
const loadedModule = await import(pathToFileURL(sourceFilePath).href);
|
|
30
14
|
if (loadedModule.default === void 0) throw new Error(`Command module did not export a default command: ${sourceFilePath}`);
|
|
15
|
+
if (!isDefinedCommand(loadedModule.default)) throw new Error(`Command module must export a value created with defineCommand(). Got ${describeCommandModuleExport(loadedModule.default)}.`);
|
|
31
16
|
return loadedModule.default;
|
|
32
17
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const longOptionLabel = `--${field.name}${formatFieldTypeHint(field)}`;
|
|
45
|
-
if (!field.alias) return longOptionLabel;
|
|
46
|
-
return `-${field.alias}, ${longOptionLabel}`;
|
|
47
|
-
}
|
|
48
|
-
async function isFieldRequired(field) {
|
|
49
|
-
if (!isSchemaField(field)) return field.required === true && field.default === void 0;
|
|
50
|
-
return !("value" in await field.schema["~standard"].validate(void 0));
|
|
51
|
-
}
|
|
52
|
-
async function formatUsageArguments(fields) {
|
|
53
|
-
const usageParts = [];
|
|
54
|
-
for (const field of fields) {
|
|
55
|
-
const required = await isFieldRequired(field);
|
|
56
|
-
usageParts.push(required ? `<${field.name}>` : `[${field.name}]`);
|
|
57
|
-
}
|
|
58
|
-
return usageParts.join(" ");
|
|
59
|
-
}
|
|
60
|
-
function getOptionUsageSuffix(fields) {
|
|
61
|
-
return fields.length === 0 ? "" : "[options]";
|
|
62
|
-
}
|
|
63
|
-
function renderGroupHelp(manifest, node, cliName) {
|
|
64
|
-
const nodeMap = createCommandManifestNodeMap(manifest);
|
|
65
|
-
const entries = node.childNames.map((childName) => {
|
|
66
|
-
return {
|
|
67
|
-
label: childName,
|
|
68
|
-
description: nodeMap[commandManifestPathToKey([...node.pathSegments, childName])]?.description
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
const parts = [`Usage: ${formatCommandName(cliName, node.pathSegments)} <command>`];
|
|
72
|
-
if (entries.length > 0) parts.push(`Subcommands:\n${formatSectionEntries(entries)}`);
|
|
73
|
-
return `${parts.join("\n\n")}\n`;
|
|
74
|
-
}
|
|
75
|
-
async function renderCommandHelp(command, pathSegments, cliName) {
|
|
76
|
-
const usageArguments = await formatUsageArguments(command.args);
|
|
77
|
-
const optionUsageSuffix = getOptionUsageSuffix(command.options);
|
|
78
|
-
const parts = [`Usage: ${[
|
|
79
|
-
formatCommandName(cliName, pathSegments),
|
|
80
|
-
usageArguments,
|
|
81
|
-
optionUsageSuffix
|
|
82
|
-
].filter((part) => part.length > 0).join(" ")}`];
|
|
83
|
-
if (command.description) parts.push(`Description:\n ${command.description}`);
|
|
84
|
-
if (command.args.length > 0) parts.push(`Arguments:\n${formatSectionEntries(command.args.map((field) => ({
|
|
85
|
-
label: formatArgumentLabel(field),
|
|
86
|
-
description: field.description
|
|
87
|
-
})))}`);
|
|
88
|
-
const optionEntries = [...command.options.map((field) => ({
|
|
89
|
-
label: formatOptionLabel(field),
|
|
90
|
-
description: field.description
|
|
91
|
-
})), {
|
|
92
|
-
label: "-h, --help",
|
|
93
|
-
description: "Show help"
|
|
94
|
-
}];
|
|
95
|
-
parts.push(`Options:\n${formatSectionEntries(optionEntries)}`);
|
|
96
|
-
return `${parts.join("\n\n")}\n`;
|
|
97
|
-
}
|
|
98
|
-
function renderUnknownCommandMessage(route, cliName) {
|
|
99
|
-
const parts = [`Unknown command: ${formatCommandName(cliName, route.attemptedPath)}`];
|
|
100
|
-
if (route.suggestions.length > 0) parts.push(`Did you mean?\n${route.suggestions.map((name) => ` ${name}`).join("\n")}`);
|
|
101
|
-
return `${parts.join("\n\n")}\n`;
|
|
102
|
-
}
|
|
103
|
-
async function renderResolvedHelp(options) {
|
|
104
|
-
if (options.route.kind === "unknown") return renderUnknownCommandMessage(options.route, options.cliName);
|
|
105
|
-
if (options.route.kind === "group") return renderGroupHelp(options.manifest, options.route.node, options.cliName);
|
|
106
|
-
return renderCommandHelp(await (options.loadCommand ?? defaultLoadCommand)(options.route.node), options.route.matchedPath, options.cliName);
|
|
18
|
+
function describeCommandModuleExport(value) {
|
|
19
|
+
if (value === null) return "null";
|
|
20
|
+
if (Array.isArray(value)) return "an array";
|
|
21
|
+
if (typeof value === "object") return "a plain object";
|
|
22
|
+
if (typeof value === "string") return "a string";
|
|
23
|
+
if (typeof value === "number") return "a number";
|
|
24
|
+
if (typeof value === "boolean") return "a boolean";
|
|
25
|
+
if (typeof value === "bigint") return "a bigint";
|
|
26
|
+
if (typeof value === "symbol") return "a symbol";
|
|
27
|
+
if (typeof value === "function") return "a function";
|
|
28
|
+
return "an unsupported value";
|
|
107
29
|
}
|
|
30
|
+
const defaultLoadCommand = (node) => loadCommandFromModule(node.sourceFilePath);
|
|
108
31
|
//#endregion
|
|
109
32
|
//#region src/manifest/damerau-levenshtein.ts
|
|
110
33
|
function damerauLevenshteinDistance(left, right) {
|
|
@@ -121,12 +44,20 @@ function damerauLevenshteinDistance(left, right) {
|
|
|
121
44
|
return matrix[left.length][right.length];
|
|
122
45
|
}
|
|
123
46
|
//#endregion
|
|
47
|
+
//#region src/manifest/manifest-map.ts
|
|
48
|
+
function commandManifestPathToKey(pathSegments) {
|
|
49
|
+
return pathSegments.join(" ");
|
|
50
|
+
}
|
|
51
|
+
function createCommandManifestNodeMap(manifest) {
|
|
52
|
+
return Object.fromEntries(manifest.nodes.map((node) => [commandManifestPathToKey(node.pathSegments), node]));
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
124
55
|
//#region src/manifest/resolve-command-path.ts
|
|
125
56
|
function isOptionLikeToken(token) {
|
|
126
57
|
return token === "--" || token.startsWith("-");
|
|
127
58
|
}
|
|
128
59
|
function getHelpRequested(args) {
|
|
129
|
-
return args.
|
|
60
|
+
return args.some(isHelpFlag);
|
|
130
61
|
}
|
|
131
62
|
function getSuggestionThreshold(candidate) {
|
|
132
63
|
return Math.max(2, Math.floor(candidate.length / 3));
|
|
@@ -180,46 +111,150 @@ function resolveCommandPath(manifest, rawArgs) {
|
|
|
180
111
|
};
|
|
181
112
|
}
|
|
182
113
|
//#endregion
|
|
183
|
-
//#region src/manifest/
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
114
|
+
//#region src/manifest/render-help.ts
|
|
115
|
+
function formatCommandName(cliName, pathSegments) {
|
|
116
|
+
return pathSegments.length === 0 ? cliName : `${cliName} ${pathSegments.join(" ")}`;
|
|
117
|
+
}
|
|
118
|
+
function formatSectionEntries(entries) {
|
|
119
|
+
return entries.map(({ label, description }) => ` ${label}${description ? ` ${description}` : ""}`).join("\n");
|
|
120
|
+
}
|
|
121
|
+
function formatTypeHint(field) {
|
|
122
|
+
return isSchemaField(field) ? "" : ` <${field.type}>`;
|
|
123
|
+
}
|
|
124
|
+
function formatArgumentLabel(field) {
|
|
125
|
+
return `${field.name}${formatTypeHint(field)}`;
|
|
126
|
+
}
|
|
127
|
+
function formatOptionLabel(field) {
|
|
128
|
+
const longOptionLabel = `--${field.name}${formatTypeHint(field)}`;
|
|
129
|
+
if (!field.alias) return longOptionLabel;
|
|
130
|
+
return `-${field.alias}, ${longOptionLabel}`;
|
|
131
|
+
}
|
|
132
|
+
async function isFieldRequired(field) {
|
|
133
|
+
if (!isSchemaField(field)) return field.required === true && field.default === void 0;
|
|
134
|
+
return !("value" in await field.schema["~standard"].validate(void 0));
|
|
135
|
+
}
|
|
136
|
+
async function formatUsageArguments(fields) {
|
|
137
|
+
const usageParts = [];
|
|
138
|
+
for (const field of fields) {
|
|
139
|
+
const required = await isFieldRequired(field);
|
|
140
|
+
usageParts.push(required ? `<${field.name}>` : `[${field.name}]`);
|
|
194
141
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
142
|
+
return usageParts.join(" ");
|
|
143
|
+
}
|
|
144
|
+
function getOptionUsageSuffix(fields) {
|
|
145
|
+
return fields.length === 0 ? "" : "[options]";
|
|
146
|
+
}
|
|
147
|
+
function renderGroupHelp(options) {
|
|
148
|
+
const { manifest, node, cliName, version } = options;
|
|
149
|
+
const nodeMap = createCommandManifestNodeMap(manifest);
|
|
150
|
+
const entries = node.childNames.map((childName) => {
|
|
151
|
+
return {
|
|
152
|
+
label: childName,
|
|
153
|
+
description: nodeMap[commandManifestPathToKey([...node.pathSegments, childName])]?.description
|
|
154
|
+
};
|
|
203
155
|
});
|
|
156
|
+
const parts = [`Usage: ${formatCommandName(cliName, node.pathSegments)} <command>`];
|
|
157
|
+
if (entries.length > 0) parts.push(`Subcommands:\n${formatSectionEntries(entries)}`);
|
|
158
|
+
const optionEntries = [{
|
|
159
|
+
label: "-h, --help",
|
|
160
|
+
description: "Show help"
|
|
161
|
+
}, ...node.pathSegments.length === 0 && version ? [{
|
|
162
|
+
label: "-V, --version",
|
|
163
|
+
description: "Show the version number"
|
|
164
|
+
}] : []];
|
|
165
|
+
parts.push(`Options:\n${formatSectionEntries(optionEntries)}`);
|
|
166
|
+
return `${parts.join("\n\n")}\n`;
|
|
167
|
+
}
|
|
168
|
+
async function renderCommandHelp(command, pathSegments, cliName) {
|
|
169
|
+
const usageArguments = await formatUsageArguments(command.args);
|
|
170
|
+
const optionUsageSuffix = getOptionUsageSuffix(command.options);
|
|
171
|
+
const parts = [`Usage: ${[
|
|
172
|
+
formatCommandName(cliName, pathSegments),
|
|
173
|
+
usageArguments,
|
|
174
|
+
optionUsageSuffix
|
|
175
|
+
].filter((part) => part.length > 0).join(" ")}`];
|
|
176
|
+
if (command.description) parts.push(`Description:\n ${command.description}`);
|
|
177
|
+
if (command.args.length > 0) parts.push(`Arguments:\n${formatSectionEntries(command.args.map((field) => ({
|
|
178
|
+
label: formatArgumentLabel(field),
|
|
179
|
+
description: field.description
|
|
180
|
+
})))}`);
|
|
181
|
+
const optionEntries = [...command.options.map((field) => ({
|
|
182
|
+
label: formatOptionLabel(field),
|
|
183
|
+
description: field.description
|
|
184
|
+
})), {
|
|
185
|
+
label: "-h, --help",
|
|
186
|
+
description: "Show help"
|
|
187
|
+
}];
|
|
188
|
+
parts.push(`Options:\n${formatSectionEntries(optionEntries)}`);
|
|
189
|
+
return `${parts.join("\n\n")}\n`;
|
|
190
|
+
}
|
|
191
|
+
function renderUnknownCommandMessage(route, cliName) {
|
|
192
|
+
const parts = [`Unknown command: ${formatCommandName(cliName, route.attemptedPath)}`];
|
|
193
|
+
if (route.suggestions.length > 0) parts.push(`Did you mean?\n${route.suggestions.map((name) => ` ${name}`).join("\n")}`);
|
|
194
|
+
return `${parts.join("\n\n")}\n`;
|
|
204
195
|
}
|
|
205
196
|
//#endregion
|
|
206
|
-
//#region src/
|
|
207
|
-
async function
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
resolve();
|
|
216
|
-
});
|
|
197
|
+
//#region src/manifest/resolve-help.ts
|
|
198
|
+
async function renderResolvedHelp(options) {
|
|
199
|
+
if (options.route.kind === "unknown") return renderUnknownCommandMessage(options.route, options.cliName);
|
|
200
|
+
if (options.route.kind === "group") return renderGroupHelp({
|
|
201
|
+
manifest: options.manifest,
|
|
202
|
+
node: options.route.node,
|
|
203
|
+
cliName: options.cliName,
|
|
204
|
+
version: options.version
|
|
217
205
|
});
|
|
206
|
+
return renderCommandHelp(await (options.loadCommand ?? defaultLoadCommand)(options.route.node), options.route.matchedPath, options.cliName);
|
|
218
207
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/manifest/run-manifest-command.ts
|
|
210
|
+
function ensureTrailingNewline(text) {
|
|
211
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
212
|
+
}
|
|
213
|
+
function formatRuntimeError(error) {
|
|
214
|
+
if (error instanceof Error) return error.message || error.name || "Failed to run command";
|
|
215
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
216
|
+
return "Failed to run command";
|
|
217
|
+
}
|
|
218
|
+
async function runManifestCommand(options) {
|
|
219
|
+
try {
|
|
220
|
+
if (options.version && options.rawArgs.length === 1 && isVersionFlag(options.rawArgs[0])) {
|
|
221
|
+
process.stdout.write(`${options.cliName} v${options.version}\n`);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
const route = resolveCommandPath(options.manifest, options.rawArgs);
|
|
225
|
+
if (route.kind === "unknown" || route.kind === "group" || route.helpRequested) {
|
|
226
|
+
const output = await renderResolvedHelp({
|
|
227
|
+
manifest: options.manifest,
|
|
228
|
+
route,
|
|
229
|
+
cliName: options.cliName,
|
|
230
|
+
version: options.version,
|
|
231
|
+
loadCommand: options.loadCommand
|
|
232
|
+
});
|
|
233
|
+
if (route.kind === "unknown") {
|
|
234
|
+
process.stderr.write(ensureTrailingNewline(output));
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
process.stdout.write(output);
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
const command = await (options.loadCommand ?? defaultLoadCommand)(route.node);
|
|
241
|
+
const parsed = await parseCommand(command, route.remainingArgs);
|
|
242
|
+
if (!parsed.ok) {
|
|
243
|
+
process.stderr.write(ensureTrailingNewline(parsed.error.message));
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
const result = await executeCommand(command, {
|
|
247
|
+
options: parsed.value.options,
|
|
248
|
+
args: parsed.value.args,
|
|
249
|
+
cwd: options.cwd,
|
|
250
|
+
rawArgs: parsed.value.rawArgs
|
|
251
|
+
});
|
|
252
|
+
if (result.errorMessage) process.stderr.write(ensureTrailingNewline(result.errorMessage));
|
|
253
|
+
return result.exitCode;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
process.stderr.write(ensureTrailingNewline(formatRuntimeError(error)));
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
223
258
|
}
|
|
224
259
|
//#endregion
|
|
225
|
-
export {
|
|
260
|
+
export { isHelpFlag as n, isVersionFlag as r, runManifestCommand as t };
|
package/dist/runtime.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-B6XsJ6ON.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/manifest/manifest-types.d.ts
|
|
4
4
|
type CommandManifestPath = readonly string[];
|
|
@@ -22,7 +22,7 @@ interface CommandManifest {
|
|
|
22
22
|
readonly nodes: readonly CommandManifestNode[];
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
|
-
//#region src/manifest/
|
|
25
|
+
//#region src/manifest/command-loader.d.ts
|
|
26
26
|
type LoadCommandFn = (node: CommandManifestCommandNode) => Promise<DefinedCommand<readonly CommandArgField[], readonly CommandOptionField[]>>;
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/manifest/run-manifest-command.d.ts
|
|
@@ -30,12 +30,10 @@ interface RunManifestCommandOptions {
|
|
|
30
30
|
readonly manifest: CommandManifest;
|
|
31
31
|
readonly rawArgs: readonly string[];
|
|
32
32
|
readonly cliName: string;
|
|
33
|
+
readonly version?: string | undefined;
|
|
33
34
|
readonly cwd?: string | undefined;
|
|
34
35
|
readonly loadCommand?: LoadCommandFn | undefined;
|
|
35
36
|
}
|
|
36
|
-
declare function runManifestCommand(options: RunManifestCommandOptions): Promise<
|
|
37
|
+
declare function runManifestCommand(options: RunManifestCommandOptions): Promise<number>;
|
|
37
38
|
//#endregion
|
|
38
|
-
|
|
39
|
-
declare function writeCommandExecutionResult(result: CommandExecutionResult): Promise<void>;
|
|
40
|
-
//#endregion
|
|
41
|
-
export { runManifestCommand, writeCommandExecutionResult };
|
|
39
|
+
export { runManifestCommand };
|
package/dist/runtime.mjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import "./dist-FWLEc-op.mjs";
|
|
2
|
+
import { t as runManifestCommand } from "./run-manifest-command-BmeVwPA5.mjs";
|
|
3
|
+
export { runManifestCommand };
|
package/dist/test.d.mts
CHANGED
|
@@ -1,7 +1,50 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ExecuteCommandInput, i as DefinedCommand, o as InferExecutionFields, r as CommandOptionField, t as CommandArgField } from "./index-B6XsJ6ON.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/test.d.ts
|
|
4
4
|
type RunCommandOptions<TOptions, TArgs> = ExecuteCommandInput<TOptions, TArgs>;
|
|
5
|
+
interface CommandExecutionResult {
|
|
6
|
+
readonly exitCode: number;
|
|
7
|
+
readonly stdout: string;
|
|
8
|
+
readonly stderr: string;
|
|
9
|
+
readonly errorMessage?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs a command definition directly in-process for testing.
|
|
13
|
+
*
|
|
14
|
+
* This helper bypasses Rune's CLI parser and validation layers. Callers
|
|
15
|
+
* provide already-normalized `options` and `args` values, and the command's
|
|
16
|
+
* `run` function is executed with those values injected into the context.
|
|
17
|
+
*
|
|
18
|
+
* All output written to `process.stdout`, `process.stderr`, and `console` is
|
|
19
|
+
* captured and returned as strings so tests can assert on them.
|
|
20
|
+
*
|
|
21
|
+
* @param command - A command created with {@link defineCommand}.
|
|
22
|
+
* @param options - Pre-validated options, args, cwd, and rawArgs to inject.
|
|
23
|
+
* @returns The exit code, captured stdout/stderr, and an optional error message.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { defineCommand } from "rune";
|
|
28
|
+
* import { runCommand } from "rune/test";
|
|
29
|
+
* import { expect, test } from "vitest";
|
|
30
|
+
*
|
|
31
|
+
* const hello = defineCommand({
|
|
32
|
+
* options: [{ name: "name", type: "string", required: true }],
|
|
33
|
+
* run(ctx) {
|
|
34
|
+
* console.log(`Hello, ${ctx.options.name}!`);
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* test("hello command", async () => {
|
|
39
|
+
* const result = await runCommand(hello, {
|
|
40
|
+
* options: { name: "Rune" },
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* expect(result.exitCode).toBe(0);
|
|
44
|
+
* expect(result.stdout).toBe("Hello, Rune!\n");
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
5
48
|
declare function runCommand<TArgsFields extends readonly CommandArgField[], TOptionsFields extends readonly CommandOptionField[]>(command: DefinedCommand<TArgsFields, TOptionsFields>, options?: RunCommandOptions<InferExecutionFields<TOptionsFields>, InferExecutionFields<TArgsFields>>): Promise<CommandExecutionResult>;
|
|
6
49
|
//#endregion
|
|
7
|
-
export { RunCommandOptions, runCommand };
|
|
50
|
+
export { CommandExecutionResult, RunCommandOptions, runCommand };
|
package/dist/test.mjs
CHANGED
|
@@ -1,7 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as executeCommand, t as captureProcessOutput } from "./dist-FWLEc-op.mjs";
|
|
2
2
|
//#region src/test.ts
|
|
3
|
+
/**
|
|
4
|
+
* Runs a command definition directly in-process for testing.
|
|
5
|
+
*
|
|
6
|
+
* This helper bypasses Rune's CLI parser and validation layers. Callers
|
|
7
|
+
* provide already-normalized `options` and `args` values, and the command's
|
|
8
|
+
* `run` function is executed with those values injected into the context.
|
|
9
|
+
*
|
|
10
|
+
* All output written to `process.stdout`, `process.stderr`, and `console` is
|
|
11
|
+
* captured and returned as strings so tests can assert on them.
|
|
12
|
+
*
|
|
13
|
+
* @param command - A command created with {@link defineCommand}.
|
|
14
|
+
* @param options - Pre-validated options, args, cwd, and rawArgs to inject.
|
|
15
|
+
* @returns The exit code, captured stdout/stderr, and an optional error message.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { defineCommand } from "rune";
|
|
20
|
+
* import { runCommand } from "rune/test";
|
|
21
|
+
* import { expect, test } from "vitest";
|
|
22
|
+
*
|
|
23
|
+
* const hello = defineCommand({
|
|
24
|
+
* options: [{ name: "name", type: "string", required: true }],
|
|
25
|
+
* run(ctx) {
|
|
26
|
+
* console.log(`Hello, ${ctx.options.name}!`);
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* test("hello command", async () => {
|
|
31
|
+
* const result = await runCommand(hello, {
|
|
32
|
+
* options: { name: "Rune" },
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* expect(result.exitCode).toBe(0);
|
|
36
|
+
* expect(result.stdout).toBe("Hello, Rune!\n");
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
3
40
|
async function runCommand(command, options = {}) {
|
|
4
|
-
|
|
41
|
+
const captured = await captureProcessOutput(() => executeCommand(command, options));
|
|
42
|
+
if (!captured.ok) throw captured.error;
|
|
43
|
+
return {
|
|
44
|
+
exitCode: captured.value.exitCode,
|
|
45
|
+
stdout: captured.stdout,
|
|
46
|
+
stderr: captured.stderr,
|
|
47
|
+
errorMessage: captured.value.errorMessage
|
|
48
|
+
};
|
|
5
49
|
}
|
|
6
50
|
//#endregion
|
|
7
51
|
export { runCommand };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-cli/rune",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Rune is a CLI framework built around the concept of file-based command routing.",
|
|
5
5
|
"homepage": "https://github.com/morinokami/rune#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "24.12.0",
|
|
46
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
46
|
+
"@typescript/native-preview": "7.0.0-dev.20260322.1",
|
|
47
47
|
"typescript": "5.9.3",
|
|
48
48
|
"vite-plus": "v0.1.13",
|
|
49
49
|
"@rune-cli/core": "0.0.0"
|