@rune-cli/rune 0.0.9 → 0.0.10
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 +80 -16
- package/dist/{dist-DuisScgY.mjs → dist-CSbOseWZ.mjs} +27 -58
- package/dist/{index-BWxfSwrT.d.mts → index-CHUchkja.d.mts} +28 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{run-manifest-command-BphalAwU.mjs → run-manifest-command-CSsdj02B.mjs} +5 -2
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/test.d.mts +252 -35
- package/dist/test.mjs +85 -39
- package/package.json +3 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./dist-
|
|
3
|
-
import { n as isHelpFlag, r as isVersionFlag, t as runManifestCommand } from "./run-manifest-command-
|
|
2
|
+
import "./dist-CSbOseWZ.mjs";
|
|
3
|
+
import { n as isHelpFlag, r as isVersionFlag, t as runManifestCommand } from "./run-manifest-command-CSsdj02B.mjs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { build } from "esbuild";
|
|
6
6
|
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import ts from "typescript";
|
|
9
9
|
//#region package.json
|
|
10
|
-
var version = "0.0.
|
|
10
|
+
var version = "0.0.10";
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/manifest/generate-manifest.ts
|
|
13
13
|
const COMMAND_ENTRY_FILE = "index.ts";
|
|
14
|
+
const GROUP_META_FILE = "_group.ts";
|
|
15
|
+
const BARE_COMMAND_EXTENSION = ".ts";
|
|
16
|
+
const DECLARATION_FILE_SUFFIXES = [
|
|
17
|
+
".d.ts",
|
|
18
|
+
".d.mts",
|
|
19
|
+
".d.cts"
|
|
20
|
+
];
|
|
14
21
|
function comparePathSegments(left, right) {
|
|
15
22
|
const length = Math.min(left.length, right.length);
|
|
16
23
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -26,13 +33,22 @@ function getStaticDescriptionValue(expression) {
|
|
|
26
33
|
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
|
|
27
34
|
}
|
|
28
35
|
function isDefineCommandExpression(expression) {
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
return isNamedCallExpression(expression, "defineCommand");
|
|
37
|
+
}
|
|
38
|
+
function isDefineGroupExpression(expression) {
|
|
39
|
+
return isNamedCallExpression(expression, "defineGroup");
|
|
40
|
+
}
|
|
41
|
+
function isNamedCallExpression(expression, name) {
|
|
42
|
+
if (ts.isIdentifier(expression)) return expression.text === name;
|
|
43
|
+
if (ts.isPropertyAccessExpression(expression)) return expression.name.text === name;
|
|
31
44
|
return false;
|
|
32
45
|
}
|
|
46
|
+
function isDefineCallExpression(expression) {
|
|
47
|
+
return ts.isCallExpression(expression) && (isDefineCommandExpression(expression.expression) || isDefineGroupExpression(expression.expression));
|
|
48
|
+
}
|
|
33
49
|
function extractDescriptionFromCommandDefinition(expression, knownDescriptions) {
|
|
34
50
|
if (ts.isIdentifier(expression)) return knownDescriptions.get(expression.text);
|
|
35
|
-
if (!ts.isCallExpression(expression) || !
|
|
51
|
+
if (!ts.isCallExpression(expression) || !isDefineCallExpression(expression)) return;
|
|
36
52
|
const [definition] = expression.arguments;
|
|
37
53
|
if (!definition || !ts.isObjectLiteralExpression(definition)) return;
|
|
38
54
|
for (const property of definition.properties) {
|
|
@@ -56,6 +72,26 @@ async function extractDescriptionFromSourceFile(sourceFilePath) {
|
|
|
56
72
|
if (ts.isExportAssignment(statement)) return extractDescriptionFromCommandDefinition(statement.expression, knownDescriptions);
|
|
57
73
|
}
|
|
58
74
|
}
|
|
75
|
+
async function validateGroupMetaFile(sourceFilePath) {
|
|
76
|
+
const sourceText = await readFile(sourceFilePath, "utf8");
|
|
77
|
+
const sourceFile = ts.createSourceFile(sourceFilePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
78
|
+
for (const statement of sourceFile.statements) {
|
|
79
|
+
if (ts.isVariableStatement(statement)) continue;
|
|
80
|
+
if (ts.isImportDeclaration(statement)) continue;
|
|
81
|
+
if (ts.isExportAssignment(statement)) {
|
|
82
|
+
const expression = ts.isIdentifier(statement.expression) ? findVariableInitializer(sourceFile, statement.expression.text) : statement.expression;
|
|
83
|
+
if (expression && ts.isCallExpression(expression) && isDefineGroupExpression(expression.expression)) return;
|
|
84
|
+
throw new Error(`${sourceFilePath}: _group.ts must use "export default defineGroup(...)". Found a default export that is not a defineGroup() call.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`${sourceFilePath}: _group.ts must have a default export using defineGroup().`);
|
|
88
|
+
}
|
|
89
|
+
function findVariableInitializer(sourceFile, name) {
|
|
90
|
+
for (const statement of sourceFile.statements) {
|
|
91
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
92
|
+
for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === name && declaration.initializer) return declaration.initializer;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
59
95
|
async function walkCommandsDirectory(absoluteDirectoryPath, pathSegments, extractDescription) {
|
|
60
96
|
const entries = await readdir(absoluteDirectoryPath, { withFileTypes: true });
|
|
61
97
|
const childDirectoryNames = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
@@ -65,11 +101,28 @@ async function walkCommandsDirectory(absoluteDirectoryPath, pathSegments, extrac
|
|
|
65
101
|
result: await walkCommandsDirectory(path.join(absoluteDirectoryPath, directoryName), [...pathSegments, directoryName], extractDescription)
|
|
66
102
|
};
|
|
67
103
|
}));
|
|
104
|
+
const bareCommandFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(BARE_COMMAND_EXTENSION) && entry.name !== COMMAND_ENTRY_FILE && entry.name !== GROUP_META_FILE && !DECLARATION_FILE_SUFFIXES.some((suffix) => entry.name.endsWith(suffix))).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
105
|
+
const childDirectoriesWithNodes = new Set(childResults.filter(({ result }) => result.hasNode).map(({ directoryName }) => directoryName));
|
|
106
|
+
const bareCommandNodes = await Promise.all(bareCommandFiles.map(async (fileName) => {
|
|
107
|
+
const commandName = fileName.slice(0, -3);
|
|
108
|
+
if (childDirectoriesWithNodes.has(commandName)) throw new Error(`Conflicting command definitions: both "${commandName}${BARE_COMMAND_EXTENSION}" and "${commandName}/" exist. A bare command file cannot coexist with a command directory.`);
|
|
109
|
+
const sourceFilePath = path.join(absoluteDirectoryPath, fileName);
|
|
110
|
+
return {
|
|
111
|
+
pathSegments: [...pathSegments, commandName],
|
|
112
|
+
kind: "command",
|
|
113
|
+
sourceFilePath,
|
|
114
|
+
childNames: [],
|
|
115
|
+
description: await extractDescription(sourceFilePath)
|
|
116
|
+
};
|
|
117
|
+
}));
|
|
68
118
|
const childNodes = childResults.flatMap(({ result }) => result.nodes);
|
|
69
|
-
const childNames = childResults.filter(({ result }) => result.hasNode).map(({ directoryName }) => directoryName);
|
|
119
|
+
const childNames = [...childResults.filter(({ result }) => result.hasNode).map(({ directoryName }) => directoryName), ...bareCommandFiles.map((fileName) => fileName.slice(0, -3))].sort((left, right) => left.localeCompare(right));
|
|
70
120
|
const hasCommandEntry = entries.some((entry) => entry.isFile() && entry.name === COMMAND_ENTRY_FILE);
|
|
71
|
-
|
|
72
|
-
|
|
121
|
+
const hasGroupMeta = entries.some((entry) => entry.isFile() && entry.name === GROUP_META_FILE);
|
|
122
|
+
if (hasGroupMeta && hasCommandEntry) throw new Error(`Conflicting definitions: both "${GROUP_META_FILE}" and "${COMMAND_ENTRY_FILE}" exist in the same directory. A directory is either a group (_group.ts) or an executable command (index.ts), not both.`);
|
|
123
|
+
if (hasGroupMeta && childNames.length === 0) throw new Error(`${path.join(absoluteDirectoryPath, GROUP_META_FILE)}: _group.ts exists but the directory has no subcommands.`);
|
|
124
|
+
if (!hasCommandEntry && !hasGroupMeta && childNames.length === 0) return {
|
|
125
|
+
nodes: [...childNodes, ...bareCommandNodes],
|
|
73
126
|
hasNode: false
|
|
74
127
|
};
|
|
75
128
|
let node;
|
|
@@ -82,20 +135,31 @@ async function walkCommandsDirectory(absoluteDirectoryPath, pathSegments, extrac
|
|
|
82
135
|
childNames,
|
|
83
136
|
description: await extractDescription(sourceFilePath)
|
|
84
137
|
};
|
|
85
|
-
} else
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
138
|
+
} else {
|
|
139
|
+
const groupMetaPath = path.join(absoluteDirectoryPath, GROUP_META_FILE);
|
|
140
|
+
if (hasGroupMeta) await validateGroupMetaFile(groupMetaPath);
|
|
141
|
+
const description = hasGroupMeta ? await extractDescription(groupMetaPath) : void 0;
|
|
142
|
+
if (hasGroupMeta && !description) throw new Error(`${groupMetaPath}: _group.ts must export a defineGroup() call with a non-empty "description" string.`);
|
|
143
|
+
node = {
|
|
144
|
+
pathSegments,
|
|
145
|
+
kind: "group",
|
|
146
|
+
childNames,
|
|
147
|
+
...description !== void 0 ? { description } : {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
90
150
|
return {
|
|
91
|
-
nodes: [
|
|
151
|
+
nodes: [
|
|
152
|
+
node,
|
|
153
|
+
...childNodes,
|
|
154
|
+
...bareCommandNodes
|
|
155
|
+
],
|
|
92
156
|
hasNode: true
|
|
93
157
|
};
|
|
94
158
|
}
|
|
95
159
|
async function generateCommandManifest(options) {
|
|
96
160
|
const extractDescription = options.extractDescription ?? extractDescriptionFromSourceFile;
|
|
97
161
|
const walkResult = await walkCommandsDirectory(options.commandsDirectory, [], extractDescription);
|
|
98
|
-
if (walkResult.nodes.length === 0) throw new Error("No commands found in src/commands/. Create a command file like src/commands/hello/index.ts");
|
|
162
|
+
if (walkResult.nodes.length === 0) throw new Error("No commands found in src/commands/. Create a command file like src/commands/hello.ts or src/commands/hello/index.ts");
|
|
99
163
|
return { nodes: [...walkResult.nodes].sort((left, right) => comparePathSegments(left.pathSegments, right.pathSegments)) };
|
|
100
164
|
}
|
|
101
165
|
function serializeCommandManifest(manifest) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
2
|
//#region ../core/dist/index.mjs
|
|
3
3
|
function isSchemaField(field) {
|
|
4
4
|
return "schema" in field && field.schema !== void 0;
|
|
@@ -124,6 +124,31 @@ function defineCommand(input) {
|
|
|
124
124
|
function isDefinedCommand(value) {
|
|
125
125
|
return typeof value === "object" && value !== null && value[DEFINED_COMMAND_BRAND] === true;
|
|
126
126
|
}
|
|
127
|
+
const DEFINED_GROUP_BRAND = Symbol.for("@rune-cli/defined-group");
|
|
128
|
+
/**
|
|
129
|
+
* Defines metadata for a command group (a directory that only groups
|
|
130
|
+
* subcommands without being executable itself).
|
|
131
|
+
*
|
|
132
|
+
* Place the default export of this function in a `_group.ts` file inside a
|
|
133
|
+
* command directory.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // src/commands/project/_group.ts
|
|
138
|
+
* export default defineGroup({
|
|
139
|
+
* description: "Manage projects",
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
function defineGroup(input) {
|
|
144
|
+
if (typeof input.description !== "string" || input.description.length === 0) throw new Error("defineGroup() requires a non-empty \"description\" string.");
|
|
145
|
+
const group = { description: input.description };
|
|
146
|
+
Object.defineProperty(group, DEFINED_GROUP_BRAND, {
|
|
147
|
+
value: true,
|
|
148
|
+
enumerable: false
|
|
149
|
+
});
|
|
150
|
+
return group;
|
|
151
|
+
}
|
|
127
152
|
function formatExecutionError(error) {
|
|
128
153
|
if (error instanceof Error) return error.message === "" ? "" : error.message || error.name || "Unknown error";
|
|
129
154
|
if (typeof error === "string") return error;
|
|
@@ -148,62 +173,6 @@ async function executeCommand(command, input = {}) {
|
|
|
148
173
|
} : { exitCode: 1 };
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
|
-
async function captureProcessOutput(action) {
|
|
152
|
-
const stdoutChunks = [];
|
|
153
|
-
const stderrChunks = [];
|
|
154
|
-
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
155
|
-
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
156
|
-
const originalConsoleMethods = {
|
|
157
|
-
log: console.log,
|
|
158
|
-
info: console.info,
|
|
159
|
-
debug: console.debug,
|
|
160
|
-
warn: console.warn,
|
|
161
|
-
error: console.error
|
|
162
|
-
};
|
|
163
|
-
const captureChunk = (chunks, chunk, encoding) => {
|
|
164
|
-
if (typeof chunk === "string") {
|
|
165
|
-
chunks.push(chunk);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
chunks.push(Buffer.from(chunk).toString(encoding));
|
|
169
|
-
};
|
|
170
|
-
const captureConsole = (chunks, args) => {
|
|
171
|
-
chunks.push(`${format(...args)}\n`);
|
|
172
|
-
};
|
|
173
|
-
const createWriteCapture = (chunks) => ((chunk, encoding, cb) => {
|
|
174
|
-
captureChunk(chunks, chunk, typeof encoding === "string" ? encoding : void 0);
|
|
175
|
-
if (typeof encoding === "function") encoding(null);
|
|
176
|
-
else cb?.(null);
|
|
177
|
-
return true;
|
|
178
|
-
});
|
|
179
|
-
process.stdout.write = createWriteCapture(stdoutChunks);
|
|
180
|
-
process.stderr.write = createWriteCapture(stderrChunks);
|
|
181
|
-
for (const method of [
|
|
182
|
-
"log",
|
|
183
|
-
"info",
|
|
184
|
-
"debug"
|
|
185
|
-
]) console[method] = (...args) => captureConsole(stdoutChunks, args);
|
|
186
|
-
for (const method of ["warn", "error"]) console[method] = (...args) => captureConsole(stderrChunks, args);
|
|
187
|
-
try {
|
|
188
|
-
return {
|
|
189
|
-
ok: true,
|
|
190
|
-
value: await action(),
|
|
191
|
-
stdout: stdoutChunks.join(""),
|
|
192
|
-
stderr: stderrChunks.join("")
|
|
193
|
-
};
|
|
194
|
-
} catch (error) {
|
|
195
|
-
return {
|
|
196
|
-
ok: false,
|
|
197
|
-
error,
|
|
198
|
-
stdout: stdoutChunks.join(""),
|
|
199
|
-
stderr: stderrChunks.join("")
|
|
200
|
-
};
|
|
201
|
-
} finally {
|
|
202
|
-
process.stdout.write = originalStdoutWrite;
|
|
203
|
-
process.stderr.write = originalStderrWrite;
|
|
204
|
-
Object.assign(console, originalConsoleMethods);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
176
|
function formatTypeHint(field) {
|
|
208
177
|
return isSchemaField(field) ? "" : ` <${field.type}>`;
|
|
209
178
|
}
|
|
@@ -442,4 +411,4 @@ async function parseCommand(command, rawArgs) {
|
|
|
442
411
|
};
|
|
443
412
|
}
|
|
444
413
|
//#endregion
|
|
445
|
-
export { isSchemaField as a, isDefinedCommand as i,
|
|
414
|
+
export { isSchemaField as a, isDefinedCommand as i, defineGroup as n, parseCommand as o, executeCommand as r, defineCommand as t };
|
|
@@ -285,6 +285,33 @@ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readon
|
|
|
285
285
|
*/
|
|
286
286
|
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>>;
|
|
287
287
|
//#endregion
|
|
288
|
+
//#region src/define-group.d.ts
|
|
289
|
+
/** The group definition object accepted by {@link defineGroup}. */
|
|
290
|
+
interface DefineGroupInput {
|
|
291
|
+
/** One-line summary shown in `--help` output. */
|
|
292
|
+
readonly description: string;
|
|
293
|
+
}
|
|
294
|
+
/** The normalized group object returned by `defineGroup`. */
|
|
295
|
+
interface DefinedGroup {
|
|
296
|
+
readonly description: string;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Defines metadata for a command group (a directory that only groups
|
|
300
|
+
* subcommands without being executable itself).
|
|
301
|
+
*
|
|
302
|
+
* Place the default export of this function in a `_group.ts` file inside a
|
|
303
|
+
* command directory.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* // src/commands/project/_group.ts
|
|
308
|
+
* export default defineGroup({
|
|
309
|
+
* description: "Manage projects",
|
|
310
|
+
* });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
declare function defineGroup(input: DefineGroupInput): DefinedGroup;
|
|
314
|
+
//#endregion
|
|
288
315
|
//#region src/execute-command.d.ts
|
|
289
316
|
interface ExecuteCommandInput<TOptions, TArgs> {
|
|
290
317
|
readonly options?: TOptions | undefined;
|
|
@@ -293,4 +320,4 @@ interface ExecuteCommandInput<TOptions, TArgs> {
|
|
|
293
320
|
readonly rawArgs?: readonly string[] | undefined;
|
|
294
321
|
}
|
|
295
322
|
//#endregion
|
|
296
|
-
export {
|
|
323
|
+
export { DefinedCommand as a, InferExecutionFields as c, PrimitiveOptionField as d, SchemaArgField as f, defineGroup as h, DefineGroupInput as i, PrimitiveArgField as l, defineCommand as m, CommandContext as n, DefinedGroup as o, SchemaOptionField as p, CommandOptionField as r, ExecuteCommandInput as s, CommandArgField as t, PrimitiveFieldType as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
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 };
|
|
1
|
+
import { a as DefinedCommand, c as InferExecutionFields, d as PrimitiveOptionField, f as SchemaArgField, h as defineGroup, i as DefineGroupInput, l as PrimitiveArgField, m as defineCommand, n as CommandContext, o as DefinedGroup, p as SchemaOptionField, r as CommandOptionField, s as ExecuteCommandInput, t as CommandArgField, u as PrimitiveFieldType } from "./index-CHUchkja.mjs";
|
|
2
|
+
export { type CommandArgField, type CommandContext, type CommandOptionField, type DefineGroupInput, type DefinedCommand, type DefinedGroup, type ExecuteCommandInput, type InferExecutionFields, type PrimitiveArgField, type PrimitiveFieldType, type PrimitiveOptionField, type SchemaArgField, type SchemaOptionField, defineCommand, defineGroup };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as defineCommand } from "./dist-
|
|
2
|
-
export { defineCommand };
|
|
1
|
+
import { n as defineGroup, t as defineCommand } from "./dist-CSbOseWZ.mjs";
|
|
2
|
+
export { defineCommand, defineGroup };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-
|
|
1
|
+
import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-CSbOseWZ.mjs";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
//#region src/cli/flags.ts
|
|
4
4
|
function isHelpFlag(token) {
|
|
@@ -153,7 +153,10 @@ function renderGroupHelp(options) {
|
|
|
153
153
|
description: nodeMap[commandManifestPathToKey([...node.pathSegments, childName])]?.description
|
|
154
154
|
};
|
|
155
155
|
});
|
|
156
|
-
const
|
|
156
|
+
const commandName = formatCommandName(cliName, node.pathSegments);
|
|
157
|
+
const parts = [];
|
|
158
|
+
if (node.description) parts.push(node.description);
|
|
159
|
+
parts.push(`Usage: ${commandName} <command>`);
|
|
157
160
|
if (entries.length > 0) parts.push(`Subcommands:\n${formatSectionEntries(entries)}`);
|
|
158
161
|
const optionEntries = [{
|
|
159
162
|
label: "-h, --help",
|
package/dist/runtime.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-CHUchkja.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/manifest/manifest-types.d.ts
|
|
4
4
|
type CommandManifestPath = readonly string[];
|
package/dist/runtime.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./dist-
|
|
2
|
-
import { t as runManifestCommand } from "./run-manifest-command-
|
|
1
|
+
import "./dist-CSbOseWZ.mjs";
|
|
2
|
+
import { t as runManifestCommand } from "./run-manifest-command-CSsdj02B.mjs";
|
|
3
3
|
export { runManifestCommand };
|
package/dist/test.d.mts
CHANGED
|
@@ -1,50 +1,267 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//#region
|
|
4
|
-
|
|
5
|
-
interface
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
readonly
|
|
9
|
-
|
|
1
|
+
//#region ../test-utils/dist/index.d.mts
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region ../core/dist/index.d.mts
|
|
4
|
+
//#region ../../node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
|
|
5
|
+
/** The Standard Typed interface. This is a base type extended by other specs. */
|
|
6
|
+
interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
7
|
+
/** The Standard properties. */
|
|
8
|
+
readonly "~standard": StandardTypedV1.Props<Input, Output>;
|
|
9
|
+
}
|
|
10
|
+
declare namespace StandardTypedV1 {
|
|
11
|
+
/** The Standard Typed properties interface. */
|
|
12
|
+
interface Props<Input = unknown, Output = Input> {
|
|
13
|
+
/** The version number of the standard. */
|
|
14
|
+
readonly version: 1;
|
|
15
|
+
/** The vendor name of the schema library. */
|
|
16
|
+
readonly vendor: string;
|
|
17
|
+
/** Inferred types associated with the schema. */
|
|
18
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** The Standard Typed types interface. */
|
|
21
|
+
interface Types<Input = unknown, Output = Input> {
|
|
22
|
+
/** The input type of the schema. */
|
|
23
|
+
readonly input: Input;
|
|
24
|
+
/** The output type of the schema. */
|
|
25
|
+
readonly output: Output;
|
|
26
|
+
}
|
|
27
|
+
/** Infers the input type of a Standard Typed. */
|
|
28
|
+
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
29
|
+
/** Infers the output type of a Standard Typed. */
|
|
30
|
+
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
31
|
+
}
|
|
32
|
+
/** The Standard Schema interface. */
|
|
33
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
34
|
+
/** The Standard Schema properties. */
|
|
35
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
36
|
+
}
|
|
37
|
+
declare namespace StandardSchemaV1 {
|
|
38
|
+
/** The Standard Schema properties interface. */
|
|
39
|
+
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
|
|
40
|
+
/** Validates unknown input values. */
|
|
41
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
|
|
42
|
+
}
|
|
43
|
+
/** The result interface of the validate function. */
|
|
44
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
45
|
+
/** The result interface if validation succeeds. */
|
|
46
|
+
interface SuccessResult<Output> {
|
|
47
|
+
/** The typed output value. */
|
|
48
|
+
readonly value: Output;
|
|
49
|
+
/** A falsy value for `issues` indicates success. */
|
|
50
|
+
readonly issues?: undefined;
|
|
51
|
+
}
|
|
52
|
+
interface Options {
|
|
53
|
+
/** Explicit support for additional vendor-specific parameters, if needed. */
|
|
54
|
+
readonly libraryOptions?: Record<string, unknown> | undefined;
|
|
55
|
+
}
|
|
56
|
+
/** The result interface if validation fails. */
|
|
57
|
+
interface FailureResult {
|
|
58
|
+
/** The issues of failed validation. */
|
|
59
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
60
|
+
}
|
|
61
|
+
/** The issue interface of the failure output. */
|
|
62
|
+
interface Issue {
|
|
63
|
+
/** The error message of the issue. */
|
|
64
|
+
readonly message: string;
|
|
65
|
+
/** The path of the issue, if any. */
|
|
66
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
67
|
+
}
|
|
68
|
+
/** The path segment interface of the issue. */
|
|
69
|
+
interface PathSegment {
|
|
70
|
+
/** The key representing a path segment. */
|
|
71
|
+
readonly key: PropertyKey;
|
|
72
|
+
}
|
|
73
|
+
/** The Standard types interface. */
|
|
74
|
+
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
|
|
75
|
+
/** Infers the input type of a Standard. */
|
|
76
|
+
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
|
77
|
+
/** Infers the output type of a Standard. */
|
|
78
|
+
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
|
79
|
+
}
|
|
80
|
+
/** The Standard JSON Schema interface. */
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/command-types.d.ts
|
|
83
|
+
type PrimitiveFieldType = "string" | "number" | "boolean";
|
|
84
|
+
type PrimitiveFieldValue<TType extends PrimitiveFieldType> = TType extends "string" ? string : TType extends "number" ? number : boolean;
|
|
85
|
+
interface NamedField<TName extends string = string> {
|
|
86
|
+
/**
|
|
87
|
+
* Identifier used as the key in `ctx.args` / `ctx.options`.
|
|
88
|
+
*
|
|
89
|
+
* For args, any non-empty name is allowed.
|
|
90
|
+
* For options, names must start with a letter and may contain only letters,
|
|
91
|
+
* numbers, and internal hyphens (for example: `dry-run`, `dryRun`, `v2`).
|
|
92
|
+
*/
|
|
93
|
+
readonly name: TName;
|
|
94
|
+
/** One-line help text shown in `--help` output. */
|
|
95
|
+
readonly description?: string | undefined;
|
|
96
|
+
}
|
|
97
|
+
interface PrimitiveFieldBase<TName extends string, TType extends PrimitiveFieldType> extends NamedField<TName> {
|
|
98
|
+
/** Primitive type that Rune parses the raw CLI token into (`"string"`, `"number"`, or `"boolean"`). */
|
|
99
|
+
readonly type: TType;
|
|
100
|
+
/**
|
|
101
|
+
* When `true`, the field must be provided by the user.
|
|
102
|
+
* Omitted or `false` makes the field optional. Absent fields are `undefined`
|
|
103
|
+
* in `ctx`, except primitive boolean options, which default to `false`.
|
|
104
|
+
*/
|
|
105
|
+
readonly required?: boolean | undefined;
|
|
106
|
+
/** Value used when the user does not provide this field. Makes the field always present in `ctx`. */
|
|
107
|
+
readonly default?: PrimitiveFieldValue<TType> | undefined;
|
|
108
|
+
readonly schema?: never;
|
|
109
|
+
}
|
|
110
|
+
interface SchemaFieldBase<TName extends string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends NamedField<TName> {
|
|
111
|
+
/**
|
|
112
|
+
* A Standard Schema object (e.g. `z.string()`, `v.number()`) used to
|
|
113
|
+
* validate and transform the raw CLI token. Required/optional and default
|
|
114
|
+
* semantics are derived from the schema itself.
|
|
115
|
+
*/
|
|
116
|
+
readonly schema: TSchema;
|
|
117
|
+
readonly type?: never;
|
|
118
|
+
readonly required?: never;
|
|
119
|
+
readonly default?: never;
|
|
120
|
+
}
|
|
121
|
+
interface PrimitiveArgField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
|
|
122
|
+
readonly alias?: never;
|
|
123
|
+
readonly flag?: never;
|
|
124
|
+
}
|
|
125
|
+
interface SchemaArgField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
|
|
126
|
+
readonly alias?: never;
|
|
127
|
+
readonly flag?: never;
|
|
128
|
+
}
|
|
129
|
+
interface PrimitiveOptionField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
|
|
130
|
+
/** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
|
|
131
|
+
readonly alias?: string | undefined;
|
|
132
|
+
readonly flag?: never;
|
|
133
|
+
}
|
|
134
|
+
interface SchemaOptionField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
|
|
135
|
+
/** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
|
|
136
|
+
readonly alias?: string | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* When `true`, the option is parsed as a boolean flag (no value expected).
|
|
139
|
+
* The schema receives `true` when the flag is present, `undefined` when absent.
|
|
140
|
+
*/
|
|
141
|
+
readonly flag?: true | undefined;
|
|
10
142
|
}
|
|
143
|
+
type CommandArgField = PrimitiveArgField | SchemaArgField;
|
|
144
|
+
type CommandOptionField = PrimitiveOptionField | SchemaOptionField;
|
|
145
|
+
type FieldName<TField> = TField extends {
|
|
146
|
+
readonly name: infer TName extends string;
|
|
147
|
+
} ? TName : never;
|
|
148
|
+
type InferSchemaOutput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TSchema> : never;
|
|
149
|
+
type InferSchemaInput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<TSchema> : never;
|
|
150
|
+
type IsOptionalSchemaOutput<TValue> = undefined extends TValue ? true : false;
|
|
151
|
+
type FieldValue<TField> = TField extends {
|
|
152
|
+
readonly schema: infer TSchema;
|
|
153
|
+
} ? Exclude<InferSchemaOutput<TSchema>, undefined> : TField extends {
|
|
154
|
+
readonly type: infer TType extends PrimitiveFieldType;
|
|
155
|
+
} ? PrimitiveFieldValue<TType> : never;
|
|
156
|
+
type FieldInputValue<TField> = TField extends {
|
|
157
|
+
readonly schema: infer TSchema;
|
|
158
|
+
} ? InferSchemaInput<TSchema> : TField extends {
|
|
159
|
+
readonly type: infer TType extends PrimitiveFieldType;
|
|
160
|
+
} ? PrimitiveFieldValue<TType> : never;
|
|
161
|
+
type HasDefaultValue<TField> = TField extends {
|
|
162
|
+
readonly default: infer TDefault;
|
|
163
|
+
} ? [TDefault] extends [undefined] ? false : true : false;
|
|
164
|
+
type IsRequiredField<TField, TBooleanAlwaysPresent extends boolean = false> = TField extends {
|
|
165
|
+
readonly schema: infer TSchema;
|
|
166
|
+
} ? IsOptionalSchemaOutput<InferSchemaOutput<TSchema>> extends true ? false : true : HasDefaultValue<TField> extends true ? true : TBooleanAlwaysPresent extends true ? TField extends {
|
|
167
|
+
readonly type: "boolean";
|
|
168
|
+
} ? true : TField extends {
|
|
169
|
+
readonly required: true;
|
|
170
|
+
} ? true : false : TField extends {
|
|
171
|
+
readonly required: true;
|
|
172
|
+
} ? true : false;
|
|
173
|
+
type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] };
|
|
174
|
+
type InferNamedFields<TFields extends readonly NamedField[], TBooleanAlwaysPresent extends boolean = false> = Simplify<{ [TField in TFields[number] as IsRequiredField<TField, TBooleanAlwaysPresent> extends true ? FieldName<TField> : never]: FieldValue<TField> } & { [TField in TFields[number] as IsRequiredField<TField, TBooleanAlwaysPresent> extends true ? never : FieldName<TField>]?: FieldValue<TField> }>;
|
|
175
|
+
type InferExecutionFields<TFields extends readonly NamedField[]> = Simplify<{ [TField in TFields[number] as FieldName<TField>]?: FieldInputValue<TField> }>;
|
|
176
|
+
/** Runtime data passed into a command's `run` function. */
|
|
177
|
+
interface CommandContext<TOptions, TArgs> {
|
|
178
|
+
/** Parsed and validated positional argument values keyed by field name. */
|
|
179
|
+
readonly args: TArgs;
|
|
180
|
+
/** Parsed and validated option values keyed by field name. */
|
|
181
|
+
readonly options: TOptions;
|
|
182
|
+
/** Working directory the CLI was invoked from. */
|
|
183
|
+
readonly cwd: string;
|
|
184
|
+
/**
|
|
185
|
+
* Unparsed argv tokens passed to this command, before Rune splits them
|
|
186
|
+
* into `args` and `options`. Useful for forwarding to child processes.
|
|
187
|
+
*/
|
|
188
|
+
readonly rawArgs: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
/** The command definition object accepted by {@link defineCommand}. */
|
|
191
|
+
interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readonly [], TOptionsFields extends readonly CommandOptionField[] = readonly []> {
|
|
192
|
+
readonly description?: string | undefined;
|
|
193
|
+
readonly args: TArgsFields;
|
|
194
|
+
readonly options: TOptionsFields;
|
|
195
|
+
readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields, true>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
|
|
196
|
+
} //#endregion
|
|
197
|
+
//#region src/define-command.d.ts
|
|
11
198
|
/**
|
|
12
|
-
*
|
|
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.
|
|
199
|
+
* Defines a CLI command with a description, positional arguments, options,
|
|
200
|
+
* and a function to execute when the command is invoked.
|
|
20
201
|
*
|
|
21
|
-
*
|
|
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.
|
|
202
|
+
* The command module's default export should be the return value of this function.
|
|
24
203
|
*
|
|
25
204
|
* @example
|
|
26
205
|
* ```ts
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* options: [
|
|
206
|
+
* export default defineCommand({
|
|
207
|
+
* description: "Greet someone",
|
|
208
|
+
* args: [
|
|
209
|
+
* { name: "name", type: "string", required: true },
|
|
210
|
+
* ],
|
|
211
|
+
* options: [
|
|
212
|
+
* { name: "loud", type: "boolean", alias: "l" },
|
|
213
|
+
* ],
|
|
33
214
|
* run(ctx) {
|
|
34
|
-
*
|
|
215
|
+
* const greeting = `Hello, ${ctx.args.name}!`;
|
|
216
|
+
* console.log(ctx.options.loud ? greeting.toUpperCase() : greeting);
|
|
35
217
|
* },
|
|
36
218
|
* });
|
|
219
|
+
* ```
|
|
37
220
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* });
|
|
221
|
+
* Required positional arguments must precede optional ones. This ordering is
|
|
222
|
+
* enforced at the type level for concrete schema types and at runtime for
|
|
223
|
+
* primitive fields:
|
|
42
224
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
225
|
+
* ```ts
|
|
226
|
+
* // Type error — required arg after optional arg
|
|
227
|
+
* defineCommand({
|
|
228
|
+
* args: [
|
|
229
|
+
* { name: "source", type: "string" },
|
|
230
|
+
* { name: "target", type: "string", required: true },
|
|
231
|
+
* ],
|
|
232
|
+
* run() {},
|
|
233
|
+
* });
|
|
234
|
+
*
|
|
235
|
+
* // Type error — required primitive arg after optional schema arg
|
|
236
|
+
* defineCommand({
|
|
237
|
+
* args: [
|
|
238
|
+
* { name: "mode", schema: z.string().optional() },
|
|
239
|
+
* { name: "target", type: "string", required: true },
|
|
240
|
+
* ],
|
|
241
|
+
* run() {},
|
|
45
242
|
* });
|
|
46
243
|
* ```
|
|
244
|
+
*
|
|
245
|
+
* When a schema type is widened to plain `StandardSchemaV1` (e.g. stored in
|
|
246
|
+
* a variable without a concrete type), optionality information is lost and
|
|
247
|
+
* the ordering check is skipped for that field.
|
|
47
248
|
*/
|
|
48
|
-
declare function runCommand<TArgsFields extends readonly CommandArgField[], TOptionsFields extends readonly CommandOptionField[]>(command: DefinedCommand<TArgsFields, TOptionsFields>, options?: RunCommandOptions<InferExecutionFields<TOptionsFields>, InferExecutionFields<TArgsFields>>): Promise<CommandExecutionResult>;
|
|
49
249
|
//#endregion
|
|
50
|
-
|
|
250
|
+
//#region src/execute-command.d.ts
|
|
251
|
+
interface ExecuteCommandInput<TOptions, TArgs> {
|
|
252
|
+
readonly options?: TOptions | undefined;
|
|
253
|
+
readonly args?: TArgs | undefined;
|
|
254
|
+
readonly cwd?: string | undefined;
|
|
255
|
+
readonly rawArgs?: readonly string[] | undefined;
|
|
256
|
+
} //#endregion
|
|
257
|
+
//#region src/run-command.d.ts
|
|
258
|
+
type RunCommandOptions<TOptions, TArgs> = ExecuteCommandInput<TOptions, TArgs>;
|
|
259
|
+
interface CommandExecutionResult {
|
|
260
|
+
readonly exitCode: number;
|
|
261
|
+
readonly stdout: string;
|
|
262
|
+
readonly stderr: string;
|
|
263
|
+
readonly errorMessage?: string | undefined;
|
|
264
|
+
}
|
|
265
|
+
declare function runCommand<TArgsFields extends readonly CommandArgField[], TOptionsFields extends readonly CommandOptionField[]>(command: DefinedCommand<TArgsFields, TOptionsFields>, options?: RunCommandOptions<InferExecutionFields<TOptionsFields>, InferExecutionFields<TArgsFields>>): Promise<CommandExecutionResult>; //#endregion
|
|
266
|
+
//#endregion
|
|
267
|
+
export { type CommandExecutionResult, type RunCommandOptions, runCommand };
|
package/dist/test.mjs
CHANGED
|
@@ -1,42 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
import { format } from "node:util";
|
|
2
|
+
//#region ../test-utils/dist/index.mjs
|
|
3
|
+
async function captureProcessOutput(action) {
|
|
4
|
+
const stdoutChunks = [];
|
|
5
|
+
const stderrChunks = [];
|
|
6
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
7
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
8
|
+
const originalConsoleMethods = {
|
|
9
|
+
log: console.log,
|
|
10
|
+
info: console.info,
|
|
11
|
+
debug: console.debug,
|
|
12
|
+
warn: console.warn,
|
|
13
|
+
error: console.error
|
|
14
|
+
};
|
|
15
|
+
const captureChunk = (chunks, chunk, encoding) => {
|
|
16
|
+
if (typeof chunk === "string") {
|
|
17
|
+
chunks.push(chunk);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
chunks.push(Buffer.from(chunk).toString(encoding));
|
|
21
|
+
};
|
|
22
|
+
const captureConsole = (chunks, args) => {
|
|
23
|
+
chunks.push(`${format(...args)}\n`);
|
|
24
|
+
};
|
|
25
|
+
const createWriteCapture = (chunks) => ((chunk, encoding, cb) => {
|
|
26
|
+
captureChunk(chunks, chunk, typeof encoding === "string" ? encoding : void 0);
|
|
27
|
+
if (typeof encoding === "function") encoding(null);
|
|
28
|
+
else cb?.(null);
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
process.stdout.write = createWriteCapture(stdoutChunks);
|
|
32
|
+
process.stderr.write = createWriteCapture(stderrChunks);
|
|
33
|
+
for (const method of [
|
|
34
|
+
"log",
|
|
35
|
+
"info",
|
|
36
|
+
"debug"
|
|
37
|
+
]) console[method] = (...args) => captureConsole(stdoutChunks, args);
|
|
38
|
+
for (const method of ["warn", "error"]) console[method] = (...args) => captureConsole(stderrChunks, args);
|
|
39
|
+
try {
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
value: await action(),
|
|
43
|
+
stdout: stdoutChunks.join(""),
|
|
44
|
+
stderr: stderrChunks.join("")
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
error,
|
|
50
|
+
stdout: stdoutChunks.join(""),
|
|
51
|
+
stderr: stderrChunks.join("")
|
|
52
|
+
};
|
|
53
|
+
} finally {
|
|
54
|
+
process.stdout.write = originalStdoutWrite;
|
|
55
|
+
process.stderr.write = originalStderrWrite;
|
|
56
|
+
Object.assign(console, originalConsoleMethods);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isSchemaField(field) {
|
|
60
|
+
return "schema" in field && field.schema !== void 0;
|
|
61
|
+
}
|
|
62
|
+
function formatExecutionError(error) {
|
|
63
|
+
if (error instanceof Error) return error.message === "" ? "" : error.message || error.name || "Unknown error";
|
|
64
|
+
if (typeof error === "string") return error;
|
|
65
|
+
return "Unknown error";
|
|
66
|
+
}
|
|
67
|
+
async function executeCommand(command, input = {}) {
|
|
68
|
+
try {
|
|
69
|
+
const options = { ...input.options };
|
|
70
|
+
for (const field of command.options) if (options[field.name] === void 0 && !isSchemaField(field) && field.type === "boolean") options[field.name] = false;
|
|
71
|
+
await command.run({
|
|
72
|
+
options,
|
|
73
|
+
args: input.args ?? {},
|
|
74
|
+
cwd: input.cwd ?? process.cwd(),
|
|
75
|
+
rawArgs: input.rawArgs ?? []
|
|
76
|
+
});
|
|
77
|
+
return { exitCode: 0 };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const message = formatExecutionError(error);
|
|
80
|
+
return message ? {
|
|
81
|
+
exitCode: 1,
|
|
82
|
+
errorMessage: message
|
|
83
|
+
} : { exitCode: 1 };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
40
86
|
async function runCommand(command, options = {}) {
|
|
41
87
|
const captured = await captureProcessOutput(() => executeCommand(command, options));
|
|
42
88
|
if (!captured.ok) throw captured.error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-cli/rune",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
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": {
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"@typescript/native-preview": "7.0.0-dev.20260322.1",
|
|
47
47
|
"typescript": "5.9.3",
|
|
48
48
|
"vite-plus": "v0.1.13",
|
|
49
|
-
"@rune-cli/core": "0.0.0"
|
|
49
|
+
"@rune-cli/core": "0.0.0",
|
|
50
|
+
"@rune-cli/test-utils": "0.0.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"typescript": ">=5.0.0"
|