@rune-cli/rune 0.0.6 → 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 +99 -25
- 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-DOrdlrbw.mjs → run-manifest-command-BmeVwPA5.mjs} +145 -134
- package/dist/runtime.d.mts +4 -7
- package/dist/runtime.mjs +3 -2
- package/dist/test.d.mts +45 -2
- package/dist/test.mjs +46 -2
- package/package.json +1 -1
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";
|
|
@@ -143,6 +144,32 @@ async function assertCommandsDirectoryExists(commandsDirectory) {
|
|
|
143
144
|
}))?.isDirectory()) throw new Error(`Commands directory not found at ${COMMANDS_DIRECTORY_NAME}. Create it or check the --project <path> option.`);
|
|
144
145
|
}
|
|
145
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
|
|
146
173
|
//#region src/cli/build-command.ts
|
|
147
174
|
const BUILD_CLI_FILENAME = "cli.mjs";
|
|
148
175
|
const BUILD_MANIFEST_FILENAME = "manifest.json";
|
|
@@ -209,7 +236,7 @@ function renderBuiltCliEntry(cliName, version, runtimeImportPath) {
|
|
|
209
236
|
return `import { readFile } from "node:fs/promises";
|
|
210
237
|
import { fileURLToPath } from "node:url";
|
|
211
238
|
|
|
212
|
-
import { runManifestCommand
|
|
239
|
+
import { runManifestCommand } from ${JSON.stringify(runtimeImportPath)};
|
|
213
240
|
|
|
214
241
|
const cliName = ${JSON.stringify(cliName)};
|
|
215
242
|
const version = ${JSON.stringify(version)};
|
|
@@ -228,15 +255,13 @@ const runtimeManifest = {
|
|
|
228
255
|
: node,
|
|
229
256
|
),
|
|
230
257
|
};
|
|
231
|
-
|
|
258
|
+
process.exitCode = await runManifestCommand({
|
|
232
259
|
manifest: runtimeManifest,
|
|
233
260
|
rawArgs: process.argv.slice(2),
|
|
234
261
|
cliName,
|
|
235
262
|
version,
|
|
236
263
|
cwd: process.cwd(),
|
|
237
264
|
});
|
|
238
|
-
|
|
239
|
-
await writeCommandExecutionResult(result);
|
|
240
265
|
`;
|
|
241
266
|
}
|
|
242
267
|
function collectCommandEntryPoints(manifest) {
|
|
@@ -359,10 +384,15 @@ async function runBuildCommand(options) {
|
|
|
359
384
|
buildCliEntry(projectRoot, distDirectory, cliInfo.name, cliInfo.version),
|
|
360
385
|
copyBuiltAssets(sourceDirectory, distDirectory)
|
|
361
386
|
]);
|
|
362
|
-
|
|
387
|
+
await writeStdout(`Built CLI to ${path.join(distDirectory, BUILD_CLI_FILENAME)}\n`);
|
|
388
|
+
return 0;
|
|
363
389
|
} catch (error) {
|
|
364
|
-
if (isBuildFailure(error))
|
|
365
|
-
|
|
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;
|
|
366
396
|
}
|
|
367
397
|
}
|
|
368
398
|
//#endregion
|
|
@@ -394,7 +424,10 @@ async function runDevCommand(options) {
|
|
|
394
424
|
try {
|
|
395
425
|
const projectRoot = resolveProjectPath(options);
|
|
396
426
|
const cliInfo = await readProjectCliInfo(projectRoot);
|
|
397
|
-
if (cliInfo.version && options.rawArgs.length === 1 && isVersionFlag(options.rawArgs[0]))
|
|
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
|
+
}
|
|
398
431
|
const commandsDirectory = resolveCommandsDirectory(projectRoot);
|
|
399
432
|
await assertCommandsDirectoryExists(commandsDirectory);
|
|
400
433
|
const manifest = await generateCommandManifest({ commandsDirectory });
|
|
@@ -407,21 +440,34 @@ async function runDevCommand(options) {
|
|
|
407
440
|
cwd: options.cwd
|
|
408
441
|
});
|
|
409
442
|
} catch (error) {
|
|
410
|
-
|
|
443
|
+
await writeStderrLine(error instanceof Error ? error.message : "Failed to run rune dev");
|
|
444
|
+
return 1;
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
447
|
//#endregion
|
|
414
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
|
+
}
|
|
415
454
|
function tryParseProjectOption(argv, index) {
|
|
416
455
|
const token = argv[index];
|
|
417
456
|
if (token.startsWith("--project=")) return {
|
|
457
|
+
ok: true,
|
|
418
458
|
projectPath: token.slice(10),
|
|
419
459
|
nextIndex: index + 1
|
|
420
460
|
};
|
|
421
461
|
if (token === "--project") {
|
|
422
462
|
const nextToken = argv[index + 1];
|
|
423
|
-
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
|
+
};
|
|
424
469
|
return {
|
|
470
|
+
ok: true,
|
|
425
471
|
projectPath: nextToken,
|
|
426
472
|
nextIndex: index + 2
|
|
427
473
|
};
|
|
@@ -438,25 +484,33 @@ function parseDevArgs(argv) {
|
|
|
438
484
|
if (token === "--") {
|
|
439
485
|
commandArgs.push(...argv.slice(index + 1));
|
|
440
486
|
return {
|
|
487
|
+
ok: true,
|
|
441
488
|
projectPath,
|
|
442
489
|
commandArgs
|
|
443
490
|
};
|
|
444
491
|
}
|
|
445
|
-
if (isHelpFlag(token)) return
|
|
492
|
+
if (isHelpFlag(token)) return {
|
|
493
|
+
ok: false,
|
|
494
|
+
exitCode: 0,
|
|
495
|
+
output: renderRuneDevHelp(),
|
|
496
|
+
stream: "stdout"
|
|
497
|
+
};
|
|
446
498
|
const projectResult = tryParseProjectOption(argv, index);
|
|
447
499
|
if (projectResult) {
|
|
448
|
-
if (
|
|
500
|
+
if (!projectResult.ok) return projectResult;
|
|
449
501
|
projectPath = projectResult.projectPath;
|
|
450
502
|
index = projectResult.nextIndex - 1;
|
|
451
503
|
continue;
|
|
452
504
|
}
|
|
453
505
|
commandArgs.push(token, ...argv.slice(index + 1));
|
|
454
506
|
return {
|
|
507
|
+
ok: true,
|
|
455
508
|
projectPath,
|
|
456
509
|
commandArgs
|
|
457
510
|
};
|
|
458
511
|
}
|
|
459
512
|
return {
|
|
513
|
+
ok: true,
|
|
460
514
|
projectPath,
|
|
461
515
|
commandArgs
|
|
462
516
|
};
|
|
@@ -465,17 +519,30 @@ function parseBuildArgs(argv) {
|
|
|
465
519
|
let projectPath;
|
|
466
520
|
for (let index = 0; index < argv.length; index += 1) {
|
|
467
521
|
const token = argv[index];
|
|
468
|
-
if (isHelpFlag(token)) return
|
|
522
|
+
if (isHelpFlag(token)) return {
|
|
523
|
+
ok: false,
|
|
524
|
+
exitCode: 0,
|
|
525
|
+
output: renderRuneBuildHelp(),
|
|
526
|
+
stream: "stdout"
|
|
527
|
+
};
|
|
469
528
|
const projectResult = tryParseProjectOption(argv, index);
|
|
470
529
|
if (projectResult) {
|
|
471
|
-
if (
|
|
530
|
+
if (!projectResult.ok) return projectResult;
|
|
472
531
|
projectPath = projectResult.projectPath;
|
|
473
532
|
index = projectResult.nextIndex - 1;
|
|
474
533
|
continue;
|
|
475
534
|
}
|
|
476
|
-
return
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
exitCode: 1,
|
|
538
|
+
output: `Unexpected argument for rune build: ${token}`,
|
|
539
|
+
stream: "stderr"
|
|
540
|
+
};
|
|
477
541
|
}
|
|
478
|
-
return {
|
|
542
|
+
return {
|
|
543
|
+
ok: true,
|
|
544
|
+
projectPath
|
|
545
|
+
};
|
|
479
546
|
}
|
|
480
547
|
function renderRuneCliHelp() {
|
|
481
548
|
return `\
|
|
@@ -492,11 +559,17 @@ Options:
|
|
|
492
559
|
}
|
|
493
560
|
async function runRuneCli(options) {
|
|
494
561
|
const [subcommand, ...restArgs] = options.argv;
|
|
495
|
-
if (!subcommand || isHelpFlag(subcommand))
|
|
496
|
-
|
|
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
|
+
}
|
|
497
570
|
if (subcommand === "dev") {
|
|
498
571
|
const parsedDevArgs = parseDevArgs(restArgs);
|
|
499
|
-
if (
|
|
572
|
+
if (!parsedDevArgs.ok) return writeEarlyExit(parsedDevArgs);
|
|
500
573
|
return runDevCommand({
|
|
501
574
|
rawArgs: parsedDevArgs.commandArgs,
|
|
502
575
|
projectPath: parsedDevArgs.projectPath,
|
|
@@ -505,19 +578,20 @@ async function runRuneCli(options) {
|
|
|
505
578
|
}
|
|
506
579
|
if (subcommand === "build") {
|
|
507
580
|
const parsedBuildArgs = parseBuildArgs(restArgs);
|
|
508
|
-
if (
|
|
581
|
+
if (!parsedBuildArgs.ok) return writeEarlyExit(parsedBuildArgs);
|
|
509
582
|
return runBuildCommand({
|
|
510
583
|
projectPath: parsedBuildArgs.projectPath,
|
|
511
584
|
cwd: options.cwd
|
|
512
585
|
});
|
|
513
586
|
}
|
|
514
|
-
|
|
587
|
+
await writeStderrLine(`Unknown command: ${subcommand}. Available commands: build, dev`);
|
|
588
|
+
return 1;
|
|
515
589
|
}
|
|
516
590
|
//#endregion
|
|
517
591
|
//#region src/cli.ts
|
|
518
|
-
|
|
592
|
+
process.exitCode = await runRuneCli({
|
|
519
593
|
argv: process.argv.slice(2),
|
|
520
594
|
cwd: process.cwd()
|
|
521
|
-
})
|
|
595
|
+
});
|
|
522
596
|
//#endregion
|
|
523
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,4 +1,4 @@
|
|
|
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
3
|
//#region src/cli/flags.ts
|
|
4
4
|
function isHelpFlag(token) {
|
|
@@ -8,20 +8,40 @@ function isVersionFlag(token) {
|
|
|
8
8
|
return token === "--version" || token === "-V";
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
|
-
//#region src/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
11
|
+
//#region src/manifest/command-loader.ts
|
|
12
|
+
async function loadCommandFromModule(sourceFilePath) {
|
|
13
|
+
const loadedModule = await import(pathToFileURL(sourceFilePath).href);
|
|
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)}.`);
|
|
16
|
+
return loadedModule.default;
|
|
18
17
|
}
|
|
19
|
-
function
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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";
|
|
29
|
+
}
|
|
30
|
+
const defaultLoadCommand = (node) => loadCommandFromModule(node.sourceFilePath);
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/manifest/damerau-levenshtein.ts
|
|
33
|
+
function damerauLevenshteinDistance(left, right) {
|
|
34
|
+
const rows = left.length + 1;
|
|
35
|
+
const cols = right.length + 1;
|
|
36
|
+
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
|
|
37
|
+
for (let row = 0; row < rows; row += 1) matrix[row][0] = row;
|
|
38
|
+
for (let col = 0; col < cols; col += 1) matrix[0][col] = col;
|
|
39
|
+
for (let row = 1; row < rows; row += 1) for (let col = 1; col < cols; col += 1) {
|
|
40
|
+
const substitutionCost = left[row - 1] === right[col - 1] ? 0 : 1;
|
|
41
|
+
matrix[row][col] = Math.min(matrix[row - 1][col] + 1, matrix[row][col - 1] + 1, matrix[row - 1][col - 1] + substitutionCost);
|
|
42
|
+
if (row > 1 && col > 1 && left[row - 1] === right[col - 2] && left[row - 2] === right[col - 1]) matrix[row][col] = Math.min(matrix[row][col], matrix[row - 2][col - 2] + 1);
|
|
43
|
+
}
|
|
44
|
+
return matrix[left.length][right.length];
|
|
25
45
|
}
|
|
26
46
|
//#endregion
|
|
27
47
|
//#region src/manifest/manifest-map.ts
|
|
@@ -32,24 +52,80 @@ function createCommandManifestNodeMap(manifest) {
|
|
|
32
52
|
return Object.fromEntries(manifest.nodes.map((node) => [commandManifestPathToKey(node.pathSegments), node]));
|
|
33
53
|
}
|
|
34
54
|
//#endregion
|
|
35
|
-
//#region src/manifest/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (loadedModule.default === void 0) throw new Error(`Command module did not export a default command: ${sourceFilePath}`);
|
|
39
|
-
return loadedModule.default;
|
|
55
|
+
//#region src/manifest/resolve-command-path.ts
|
|
56
|
+
function isOptionLikeToken(token) {
|
|
57
|
+
return token === "--" || token.startsWith("-");
|
|
40
58
|
}
|
|
41
|
-
|
|
59
|
+
function getHelpRequested(args) {
|
|
60
|
+
return args.some(isHelpFlag);
|
|
61
|
+
}
|
|
62
|
+
function getSuggestionThreshold(candidate) {
|
|
63
|
+
return Math.max(2, Math.floor(candidate.length / 3));
|
|
64
|
+
}
|
|
65
|
+
function getSuggestedChildNames(unknownSegment, childNames) {
|
|
66
|
+
return [...childNames].map((childName) => ({
|
|
67
|
+
childName,
|
|
68
|
+
distance: damerauLevenshteinDistance(unknownSegment, childName)
|
|
69
|
+
})).filter(({ childName, distance }) => distance <= getSuggestionThreshold(childName)).sort((left, right) => left.distance - right.distance || left.childName.localeCompare(right.childName)).slice(0, 3).map(({ childName }) => childName);
|
|
70
|
+
}
|
|
71
|
+
function resolveCommandPath(manifest, rawArgs) {
|
|
72
|
+
const nodeMap = createCommandManifestNodeMap(manifest);
|
|
73
|
+
const rootNode = nodeMap[""];
|
|
74
|
+
if (rootNode === void 0) throw new Error("Manifest root node is missing");
|
|
75
|
+
let currentNode = rootNode;
|
|
76
|
+
let tokenIndex = 0;
|
|
77
|
+
while (tokenIndex < rawArgs.length) {
|
|
78
|
+
const token = rawArgs[tokenIndex];
|
|
79
|
+
if (isOptionLikeToken(token)) break;
|
|
80
|
+
const childNode = nodeMap[commandManifestPathToKey([...currentNode.pathSegments, token])];
|
|
81
|
+
if (childNode === void 0) {
|
|
82
|
+
const suggestions = getSuggestedChildNames(token, currentNode.childNames);
|
|
83
|
+
if (currentNode.kind === "group" || suggestions.length > 0) return {
|
|
84
|
+
kind: "unknown",
|
|
85
|
+
attemptedPath: [...currentNode.pathSegments, token],
|
|
86
|
+
matchedPath: currentNode.pathSegments,
|
|
87
|
+
unknownSegment: token,
|
|
88
|
+
availableChildNames: currentNode.childNames,
|
|
89
|
+
suggestions
|
|
90
|
+
};
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
currentNode = childNode;
|
|
94
|
+
tokenIndex += 1;
|
|
95
|
+
}
|
|
96
|
+
const remainingArgs = rawArgs.slice(tokenIndex);
|
|
97
|
+
const helpRequested = getHelpRequested(remainingArgs);
|
|
98
|
+
if (currentNode.kind === "group") return {
|
|
99
|
+
kind: "group",
|
|
100
|
+
node: currentNode,
|
|
101
|
+
matchedPath: currentNode.pathSegments,
|
|
102
|
+
remainingArgs,
|
|
103
|
+
helpRequested
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
kind: "command",
|
|
107
|
+
node: currentNode,
|
|
108
|
+
matchedPath: currentNode.pathSegments,
|
|
109
|
+
remainingArgs,
|
|
110
|
+
helpRequested
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/manifest/render-help.ts
|
|
42
115
|
function formatCommandName(cliName, pathSegments) {
|
|
43
116
|
return pathSegments.length === 0 ? cliName : `${cliName} ${pathSegments.join(" ")}`;
|
|
44
117
|
}
|
|
45
118
|
function formatSectionEntries(entries) {
|
|
46
119
|
return entries.map(({ label, description }) => ` ${label}${description ? ` ${description}` : ""}`).join("\n");
|
|
47
120
|
}
|
|
121
|
+
function formatTypeHint(field) {
|
|
122
|
+
return isSchemaField(field) ? "" : ` <${field.type}>`;
|
|
123
|
+
}
|
|
48
124
|
function formatArgumentLabel(field) {
|
|
49
|
-
return `${field.name}${
|
|
125
|
+
return `${field.name}${formatTypeHint(field)}`;
|
|
50
126
|
}
|
|
51
127
|
function formatOptionLabel(field) {
|
|
52
|
-
const longOptionLabel = `--${field.name}${
|
|
128
|
+
const longOptionLabel = `--${field.name}${formatTypeHint(field)}`;
|
|
53
129
|
if (!field.alias) return longOptionLabel;
|
|
54
130
|
return `-${field.alias}, ${longOptionLabel}`;
|
|
55
131
|
}
|
|
@@ -117,6 +193,8 @@ function renderUnknownCommandMessage(route, cliName) {
|
|
|
117
193
|
if (route.suggestions.length > 0) parts.push(`Did you mean?\n${route.suggestions.map((name) => ` ${name}`).join("\n")}`);
|
|
118
194
|
return `${parts.join("\n\n")}\n`;
|
|
119
195
|
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/manifest/resolve-help.ts
|
|
120
198
|
async function renderResolvedHelp(options) {
|
|
121
199
|
if (options.route.kind === "unknown") return renderUnknownCommandMessage(options.route, options.cliName);
|
|
122
200
|
if (options.route.kind === "group") return renderGroupHelp({
|
|
@@ -128,122 +206,55 @@ async function renderResolvedHelp(options) {
|
|
|
128
206
|
return renderCommandHelp(await (options.loadCommand ?? defaultLoadCommand)(options.route.node), options.route.matchedPath, options.cliName);
|
|
129
207
|
}
|
|
130
208
|
//#endregion
|
|
131
|
-
//#region src/manifest/
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
const cols = right.length + 1;
|
|
135
|
-
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
|
|
136
|
-
for (let row = 0; row < rows; row += 1) matrix[row][0] = row;
|
|
137
|
-
for (let col = 0; col < cols; col += 1) matrix[0][col] = col;
|
|
138
|
-
for (let row = 1; row < rows; row += 1) for (let col = 1; col < cols; col += 1) {
|
|
139
|
-
const substitutionCost = left[row - 1] === right[col - 1] ? 0 : 1;
|
|
140
|
-
matrix[row][col] = Math.min(matrix[row - 1][col] + 1, matrix[row][col - 1] + 1, matrix[row - 1][col - 1] + substitutionCost);
|
|
141
|
-
if (row > 1 && col > 1 && left[row - 1] === right[col - 2] && left[row - 2] === right[col - 1]) matrix[row][col] = Math.min(matrix[row][col], matrix[row - 2][col - 2] + 1);
|
|
142
|
-
}
|
|
143
|
-
return matrix[left.length][right.length];
|
|
144
|
-
}
|
|
145
|
-
//#endregion
|
|
146
|
-
//#region src/manifest/resolve-command-path.ts
|
|
147
|
-
function isOptionLikeToken(token) {
|
|
148
|
-
return token === "--" || token.startsWith("-");
|
|
149
|
-
}
|
|
150
|
-
function getHelpRequested(args) {
|
|
151
|
-
return args.some(isHelpFlag);
|
|
152
|
-
}
|
|
153
|
-
function getSuggestionThreshold(candidate) {
|
|
154
|
-
return Math.max(2, Math.floor(candidate.length / 3));
|
|
155
|
-
}
|
|
156
|
-
function getSuggestedChildNames(unknownSegment, childNames) {
|
|
157
|
-
return [...childNames].map((childName) => ({
|
|
158
|
-
childName,
|
|
159
|
-
distance: damerauLevenshteinDistance(unknownSegment, childName)
|
|
160
|
-
})).filter(({ childName, distance }) => distance <= getSuggestionThreshold(childName)).sort((left, right) => left.distance - right.distance || left.childName.localeCompare(right.childName)).slice(0, 3).map(({ childName }) => childName);
|
|
209
|
+
//#region src/manifest/run-manifest-command.ts
|
|
210
|
+
function ensureTrailingNewline(text) {
|
|
211
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
161
212
|
}
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
let currentNode = rootNode;
|
|
167
|
-
let tokenIndex = 0;
|
|
168
|
-
while (tokenIndex < rawArgs.length) {
|
|
169
|
-
const token = rawArgs[tokenIndex];
|
|
170
|
-
if (isOptionLikeToken(token)) break;
|
|
171
|
-
const childNode = nodeMap[commandManifestPathToKey([...currentNode.pathSegments, token])];
|
|
172
|
-
if (childNode === void 0) {
|
|
173
|
-
const suggestions = getSuggestedChildNames(token, currentNode.childNames);
|
|
174
|
-
if (currentNode.kind === "group" || suggestions.length > 0) return {
|
|
175
|
-
kind: "unknown",
|
|
176
|
-
attemptedPath: [...currentNode.pathSegments, token],
|
|
177
|
-
matchedPath: currentNode.pathSegments,
|
|
178
|
-
unknownSegment: token,
|
|
179
|
-
availableChildNames: currentNode.childNames,
|
|
180
|
-
suggestions
|
|
181
|
-
};
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
currentNode = childNode;
|
|
185
|
-
tokenIndex += 1;
|
|
186
|
-
}
|
|
187
|
-
const remainingArgs = rawArgs.slice(tokenIndex);
|
|
188
|
-
const helpRequested = getHelpRequested(remainingArgs);
|
|
189
|
-
if (currentNode.kind === "group") return {
|
|
190
|
-
kind: "group",
|
|
191
|
-
node: currentNode,
|
|
192
|
-
matchedPath: currentNode.pathSegments,
|
|
193
|
-
remainingArgs,
|
|
194
|
-
helpRequested
|
|
195
|
-
};
|
|
196
|
-
return {
|
|
197
|
-
kind: "command",
|
|
198
|
-
node: currentNode,
|
|
199
|
-
matchedPath: currentNode.pathSegments,
|
|
200
|
-
remainingArgs,
|
|
201
|
-
helpRequested
|
|
202
|
-
};
|
|
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";
|
|
203
217
|
}
|
|
204
|
-
//#endregion
|
|
205
|
-
//#region src/manifest/run-manifest-command.ts
|
|
206
218
|
async function runManifestCommand(options) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
args: parsed.value.args,
|
|
225
|
-
cwd: options.cwd,
|
|
226
|
-
rawArgs: parsed.value.rawArgs
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
//#endregion
|
|
230
|
-
//#region src/cli/write-result.ts
|
|
231
|
-
async function writeStream(stream, contents) {
|
|
232
|
-
if (contents.length === 0) return;
|
|
233
|
-
await new Promise((resolve, reject) => {
|
|
234
|
-
stream.write(contents, (error) => {
|
|
235
|
-
if (error) {
|
|
236
|
-
reject(error);
|
|
237
|
-
return;
|
|
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;
|
|
238
236
|
}
|
|
239
|
-
|
|
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
|
|
240
251
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
}
|
|
247
258
|
}
|
|
248
259
|
//#endregion
|
|
249
|
-
export { isHelpFlag as
|
|
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
|
|
@@ -34,9 +34,6 @@ interface RunManifestCommandOptions {
|
|
|
34
34
|
readonly cwd?: string | undefined;
|
|
35
35
|
readonly loadCommand?: LoadCommandFn | undefined;
|
|
36
36
|
}
|
|
37
|
-
declare function runManifestCommand(options: RunManifestCommandOptions): Promise<
|
|
37
|
+
declare function runManifestCommand(options: RunManifestCommandOptions): Promise<number>;
|
|
38
38
|
//#endregion
|
|
39
|
-
|
|
40
|
-
declare function writeCommandExecutionResult(result: CommandExecutionResult): Promise<void>;
|
|
41
|
-
//#endregion
|
|
42
|
-
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