@ldlework/workmark 1.1.0 → 1.2.0
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.js +2 -37
- package/dist/lib/helpers.d.ts +2 -0
- package/dist/lib/helpers.js +22 -16
- package/dist/lib/parse.d.ts +13 -0
- package/dist/lib/parse.js +113 -0
- package/package.json +1 -1
- package/src/cli.ts +2 -42
- package/src/lib/helpers.ts +27 -18
- package/src/lib/parse.ts +155 -0
package/dist/cli.js
CHANGED
|
@@ -1,42 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { loadCommands } from "./lib/load.js";
|
|
3
3
|
import { loadWorkspace } from "./lib/workspace.js";
|
|
4
|
-
|
|
5
|
-
// Arg parsing
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
function parseValue(raw) {
|
|
8
|
-
if (raw === "true")
|
|
9
|
-
return true;
|
|
10
|
-
if (raw === "false")
|
|
11
|
-
return false;
|
|
12
|
-
const n = Number(raw);
|
|
13
|
-
if (!Number.isNaN(n) && raw !== "")
|
|
14
|
-
return n;
|
|
15
|
-
return raw;
|
|
16
|
-
}
|
|
17
|
-
function parseArgs(argv, positionalNames) {
|
|
18
|
-
const args = {};
|
|
19
|
-
let posIdx = 0;
|
|
20
|
-
for (let i = 0; i < argv.length; i++) {
|
|
21
|
-
const token = argv[i];
|
|
22
|
-
if (token.startsWith("--")) {
|
|
23
|
-
const key = token.slice(2);
|
|
24
|
-
const next = argv[i + 1];
|
|
25
|
-
if (next === undefined || next.startsWith("--")) {
|
|
26
|
-
args[key] = true;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
args[key] = parseValue(next);
|
|
30
|
-
i++;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else if (posIdx < positionalNames.length) {
|
|
34
|
-
args[positionalNames[posIdx]] = parseValue(token);
|
|
35
|
-
posIdx++;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return args;
|
|
39
|
-
}
|
|
4
|
+
import { parseArgs } from "./lib/parse.js";
|
|
40
5
|
// ---------------------------------------------------------------------------
|
|
41
6
|
// Help
|
|
42
7
|
// ---------------------------------------------------------------------------
|
|
@@ -131,7 +96,7 @@ async function main() {
|
|
|
131
96
|
printCommandHelp(cmd);
|
|
132
97
|
return;
|
|
133
98
|
}
|
|
134
|
-
const args = parseArgs(rest, cmd.positional);
|
|
99
|
+
const args = parseArgs(rest, cmd.positional, cmd.inputSchema);
|
|
135
100
|
const result = await cmd.handler(args);
|
|
136
101
|
for (const content of result.content) {
|
|
137
102
|
if (content.type === "text") {
|
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export interface ExecOptions {
|
|
|
5
5
|
cwd: string;
|
|
6
6
|
timeout?: number;
|
|
7
7
|
}
|
|
8
|
+
export declare function execRaw(command: string, opts: ExecOptions): string;
|
|
8
9
|
export declare function exec(command: string, opts: ExecOptions): CallToolResult;
|
|
10
|
+
export declare function execAsyncRaw(command: string, opts: ExecOptions): Promise<string>;
|
|
9
11
|
export declare function execAsync(command: string, opts: ExecOptions): Promise<CallToolResult>;
|
package/dist/lib/helpers.js
CHANGED
|
@@ -25,32 +25,38 @@ export function fail(error) {
|
|
|
25
25
|
}
|
|
26
26
|
return { content: [{ type: "text", text: msg }], isError: true };
|
|
27
27
|
}
|
|
28
|
-
export function
|
|
28
|
+
export function execRaw(command, opts) {
|
|
29
29
|
const { cwd, timeout = 120_000 } = opts;
|
|
30
|
+
return execSync(command, {
|
|
31
|
+
cwd,
|
|
32
|
+
timeout,
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
35
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function exec(command, opts) {
|
|
30
39
|
try {
|
|
31
|
-
return ok(
|
|
32
|
-
cwd,
|
|
33
|
-
timeout,
|
|
34
|
-
encoding: "utf-8",
|
|
35
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
36
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
37
|
-
}));
|
|
40
|
+
return ok(execRaw(command, opts));
|
|
38
41
|
}
|
|
39
42
|
catch (e) {
|
|
40
43
|
return fail(e);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
const execPromise = promisify(cpExec);
|
|
44
|
-
export async function
|
|
47
|
+
export async function execAsyncRaw(command, opts) {
|
|
45
48
|
const { cwd, timeout = 120_000 } = opts;
|
|
49
|
+
const { stdout } = await execPromise(command, {
|
|
50
|
+
cwd,
|
|
51
|
+
timeout,
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
54
|
+
});
|
|
55
|
+
return stdout;
|
|
56
|
+
}
|
|
57
|
+
export async function execAsync(command, opts) {
|
|
46
58
|
try {
|
|
47
|
-
|
|
48
|
-
cwd,
|
|
49
|
-
timeout,
|
|
50
|
-
encoding: "utf-8",
|
|
51
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
52
|
-
});
|
|
53
|
-
return ok(stdout);
|
|
59
|
+
return ok(await execAsyncRaw(command, opts));
|
|
54
60
|
}
|
|
55
61
|
catch (e) {
|
|
56
62
|
return fail(e);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing, in three phases:
|
|
3
|
+
*
|
|
4
|
+
* 1. tokenize — turn argv into a structured token stream (flags vs. positionals)
|
|
5
|
+
* 2. dispatch — bind tokens to parameter names using positional order and
|
|
6
|
+
* schema-declared array flags (repeats accumulate)
|
|
7
|
+
* 3. coerce — convert raw strings to JSON Schema-declared types
|
|
8
|
+
*
|
|
9
|
+
* Each phase is independently testable and has one job. Coercion is driven by
|
|
10
|
+
* the command's JSON Schema rather than heuristics, so `z.string()` fields
|
|
11
|
+
* always stay strings and `z.array(...)` fields always come out as arrays.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseArgs(argv: string[], positional: string[], schema: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing, in three phases:
|
|
3
|
+
*
|
|
4
|
+
* 1. tokenize — turn argv into a structured token stream (flags vs. positionals)
|
|
5
|
+
* 2. dispatch — bind tokens to parameter names using positional order and
|
|
6
|
+
* schema-declared array flags (repeats accumulate)
|
|
7
|
+
* 3. coerce — convert raw strings to JSON Schema-declared types
|
|
8
|
+
*
|
|
9
|
+
* Each phase is independently testable and has one job. Coercion is driven by
|
|
10
|
+
* the command's JSON Schema rather than heuristics, so `z.string()` fields
|
|
11
|
+
* always stay strings and `z.array(...)` fields always come out as arrays.
|
|
12
|
+
*/
|
|
13
|
+
// --- 1. tokenize ---------------------------------------------------------
|
|
14
|
+
function tokenize(argv) {
|
|
15
|
+
const tokens = [];
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
const tok = argv[i];
|
|
18
|
+
if (!tok.startsWith("--")) {
|
|
19
|
+
tokens.push({ kind: "positional", value: tok });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const eq = tok.indexOf("=");
|
|
23
|
+
if (eq >= 0) {
|
|
24
|
+
tokens.push({ kind: "flag", key: tok.slice(2, eq), value: tok.slice(eq + 1) });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const key = tok.slice(2);
|
|
28
|
+
const next = argv[i + 1];
|
|
29
|
+
if (next === undefined || next.startsWith("--")) {
|
|
30
|
+
tokens.push({ kind: "flag", key });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
tokens.push({ kind: "flag", key, value: next });
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return tokens;
|
|
38
|
+
}
|
|
39
|
+
// --- 2. dispatch ---------------------------------------------------------
|
|
40
|
+
function isArrayField(schema, name) {
|
|
41
|
+
return schema.properties?.[name]?.type === "array";
|
|
42
|
+
}
|
|
43
|
+
function appendToArray(out, key, value) {
|
|
44
|
+
const prev = out[key];
|
|
45
|
+
out[key] = Array.isArray(prev) ? [...prev, value] : [value];
|
|
46
|
+
}
|
|
47
|
+
function assignFlag(out, schema, key, value) {
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
out[key] = true;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (isArrayField(schema, key)) {
|
|
53
|
+
appendToArray(out, key, value);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
out[key] = value;
|
|
57
|
+
}
|
|
58
|
+
function dispatch(tokens, positional, schema) {
|
|
59
|
+
const out = {};
|
|
60
|
+
let posIdx = 0;
|
|
61
|
+
for (const tok of tokens) {
|
|
62
|
+
if (tok.kind === "flag") {
|
|
63
|
+
assignFlag(out, schema, tok.key, tok.value);
|
|
64
|
+
}
|
|
65
|
+
else if (posIdx < positional.length) {
|
|
66
|
+
out[positional[posIdx]] = tok.value;
|
|
67
|
+
posIdx++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
// --- 3. coerce -----------------------------------------------------------
|
|
73
|
+
function coerceScalar(raw, type) {
|
|
74
|
+
switch (type) {
|
|
75
|
+
case "number":
|
|
76
|
+
case "integer": {
|
|
77
|
+
const n = Number(raw);
|
|
78
|
+
return Number.isNaN(n) ? raw : n;
|
|
79
|
+
}
|
|
80
|
+
case "boolean":
|
|
81
|
+
if (raw === "true")
|
|
82
|
+
return true;
|
|
83
|
+
if (raw === "false")
|
|
84
|
+
return false;
|
|
85
|
+
return raw;
|
|
86
|
+
default:
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function coerceField(value, prop) {
|
|
91
|
+
if (prop === undefined)
|
|
92
|
+
return value;
|
|
93
|
+
if (typeof value === "boolean")
|
|
94
|
+
return value;
|
|
95
|
+
if (prop.type === "array") {
|
|
96
|
+
const items = Array.isArray(value) ? value : [value];
|
|
97
|
+
return items.map((item) => coerceScalar(String(item), prop.items?.type));
|
|
98
|
+
}
|
|
99
|
+
return coerceScalar(String(value), prop.type);
|
|
100
|
+
}
|
|
101
|
+
function coerce(raw, schema) {
|
|
102
|
+
const out = {};
|
|
103
|
+
const props = schema.properties ?? {};
|
|
104
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
105
|
+
out[key] = coerceField(value, props[key]);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
// --- orchestrator --------------------------------------------------------
|
|
110
|
+
export function parseArgs(argv, positional, schema) {
|
|
111
|
+
const s = schema;
|
|
112
|
+
return coerce(dispatch(tokenize(argv), positional, s), s);
|
|
113
|
+
}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -2,49 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { loadCommands } from "./lib/load.js";
|
|
4
4
|
import { loadWorkspace } from "./lib/workspace.js";
|
|
5
|
+
import { parseArgs } from "./lib/parse.js";
|
|
5
6
|
import type { ResolvedCommand } from "./lib/types.js";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Arg parsing
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
function parseValue(raw: string): unknown {
|
|
13
|
-
if (raw === "true") return true;
|
|
14
|
-
if (raw === "false") return false;
|
|
15
|
-
const n = Number(raw);
|
|
16
|
-
if (!Number.isNaN(n) && raw !== "") return n;
|
|
17
|
-
return raw;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function parseArgs(
|
|
21
|
-
argv: string[],
|
|
22
|
-
positionalNames: string[],
|
|
23
|
-
): Record<string, unknown> {
|
|
24
|
-
const args: Record<string, unknown> = {};
|
|
25
|
-
let posIdx = 0;
|
|
26
|
-
|
|
27
|
-
for (let i = 0; i < argv.length; i++) {
|
|
28
|
-
const token = argv[i];
|
|
29
|
-
|
|
30
|
-
if (token.startsWith("--")) {
|
|
31
|
-
const key = token.slice(2);
|
|
32
|
-
const next = argv[i + 1];
|
|
33
|
-
if (next === undefined || next.startsWith("--")) {
|
|
34
|
-
args[key] = true;
|
|
35
|
-
} else {
|
|
36
|
-
args[key] = parseValue(next);
|
|
37
|
-
i++;
|
|
38
|
-
}
|
|
39
|
-
} else if (posIdx < positionalNames.length) {
|
|
40
|
-
args[positionalNames[posIdx]] = parseValue(token);
|
|
41
|
-
posIdx++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return args;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
8
|
// ---------------------------------------------------------------------------
|
|
49
9
|
// Help
|
|
50
10
|
// ---------------------------------------------------------------------------
|
|
@@ -153,7 +113,7 @@ async function main(): Promise<void> {
|
|
|
153
113
|
return;
|
|
154
114
|
}
|
|
155
115
|
|
|
156
|
-
const args = parseArgs(rest, cmd.positional);
|
|
116
|
+
const args = parseArgs(rest, cmd.positional, cmd.inputSchema);
|
|
157
117
|
const result = await cmd.handler(args);
|
|
158
118
|
|
|
159
119
|
for (const content of result.content) {
|
package/src/lib/helpers.ts
CHANGED
|
@@ -37,18 +37,20 @@ export interface ExecOptions {
|
|
|
37
37
|
timeout?: number; // ms, default 120_000
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function
|
|
40
|
+
export function execRaw(command: string, opts: ExecOptions): string {
|
|
41
41
|
const { cwd, timeout = 120_000 } = opts;
|
|
42
|
+
return execSync(command, {
|
|
43
|
+
cwd,
|
|
44
|
+
timeout,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function exec(command: string, opts: ExecOptions): CallToolResult {
|
|
42
52
|
try {
|
|
43
|
-
return ok(
|
|
44
|
-
execSync(command, {
|
|
45
|
-
cwd,
|
|
46
|
-
timeout,
|
|
47
|
-
encoding: "utf-8",
|
|
48
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
50
|
-
}),
|
|
51
|
-
);
|
|
53
|
+
return ok(execRaw(command, opts));
|
|
52
54
|
} catch (e) {
|
|
53
55
|
return fail(e);
|
|
54
56
|
}
|
|
@@ -56,19 +58,26 @@ export function exec(command: string, opts: ExecOptions): CallToolResult {
|
|
|
56
58
|
|
|
57
59
|
const execPromise = promisify(cpExec);
|
|
58
60
|
|
|
61
|
+
export async function execAsyncRaw(
|
|
62
|
+
command: string,
|
|
63
|
+
opts: ExecOptions,
|
|
64
|
+
): Promise<string> {
|
|
65
|
+
const { cwd, timeout = 120_000 } = opts;
|
|
66
|
+
const { stdout } = await execPromise(command, {
|
|
67
|
+
cwd,
|
|
68
|
+
timeout,
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
71
|
+
});
|
|
72
|
+
return stdout;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
export async function execAsync(
|
|
60
76
|
command: string,
|
|
61
77
|
opts: ExecOptions,
|
|
62
78
|
): Promise<CallToolResult> {
|
|
63
|
-
const { cwd, timeout = 120_000 } = opts;
|
|
64
79
|
try {
|
|
65
|
-
|
|
66
|
-
cwd,
|
|
67
|
-
timeout,
|
|
68
|
-
encoding: "utf-8",
|
|
69
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
70
|
-
});
|
|
71
|
-
return ok(stdout);
|
|
80
|
+
return ok(await execAsyncRaw(command, opts));
|
|
72
81
|
} catch (e) {
|
|
73
82
|
return fail(e);
|
|
74
83
|
}
|
package/src/lib/parse.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing, in three phases:
|
|
3
|
+
*
|
|
4
|
+
* 1. tokenize — turn argv into a structured token stream (flags vs. positionals)
|
|
5
|
+
* 2. dispatch — bind tokens to parameter names using positional order and
|
|
6
|
+
* schema-declared array flags (repeats accumulate)
|
|
7
|
+
* 3. coerce — convert raw strings to JSON Schema-declared types
|
|
8
|
+
*
|
|
9
|
+
* Each phase is independently testable and has one job. Coercion is driven by
|
|
10
|
+
* the command's JSON Schema rather than heuristics, so `z.string()` fields
|
|
11
|
+
* always stay strings and `z.array(...)` fields always come out as arrays.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type Token =
|
|
15
|
+
| { kind: "flag"; key: string; value?: string }
|
|
16
|
+
| { kind: "positional"; value: string };
|
|
17
|
+
|
|
18
|
+
type SchemaProp = {
|
|
19
|
+
type?: string;
|
|
20
|
+
items?: { type?: string; enum?: unknown[] };
|
|
21
|
+
enum?: unknown[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type SchemaObject = {
|
|
25
|
+
properties?: Record<string, SchemaProp>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// --- 1. tokenize ---------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
function tokenize(argv: string[]): Token[] {
|
|
31
|
+
const tokens: Token[] = [];
|
|
32
|
+
for (let i = 0; i < argv.length; i++) {
|
|
33
|
+
const tok = argv[i];
|
|
34
|
+
if (!tok.startsWith("--")) {
|
|
35
|
+
tokens.push({ kind: "positional", value: tok });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const eq = tok.indexOf("=");
|
|
40
|
+
if (eq >= 0) {
|
|
41
|
+
tokens.push({ kind: "flag", key: tok.slice(2, eq), value: tok.slice(eq + 1) });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const key = tok.slice(2);
|
|
46
|
+
const next = argv[i + 1];
|
|
47
|
+
if (next === undefined || next.startsWith("--")) {
|
|
48
|
+
tokens.push({ kind: "flag", key });
|
|
49
|
+
} else {
|
|
50
|
+
tokens.push({ kind: "flag", key, value: next });
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return tokens;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- 2. dispatch ---------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function isArrayField(schema: SchemaObject, name: string): boolean {
|
|
60
|
+
return schema.properties?.[name]?.type === "array";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function appendToArray(out: Record<string, unknown>, key: string, value: string): void {
|
|
64
|
+
const prev = out[key];
|
|
65
|
+
out[key] = Array.isArray(prev) ? [...prev, value] : [value];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function assignFlag(
|
|
69
|
+
out: Record<string, unknown>,
|
|
70
|
+
schema: SchemaObject,
|
|
71
|
+
key: string,
|
|
72
|
+
value: string | undefined,
|
|
73
|
+
): void {
|
|
74
|
+
if (value === undefined) {
|
|
75
|
+
out[key] = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (isArrayField(schema, key)) {
|
|
79
|
+
appendToArray(out, key, value);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
out[key] = value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function dispatch(
|
|
86
|
+
tokens: Token[],
|
|
87
|
+
positional: string[],
|
|
88
|
+
schema: SchemaObject,
|
|
89
|
+
): Record<string, unknown> {
|
|
90
|
+
const out: Record<string, unknown> = {};
|
|
91
|
+
let posIdx = 0;
|
|
92
|
+
|
|
93
|
+
for (const tok of tokens) {
|
|
94
|
+
if (tok.kind === "flag") {
|
|
95
|
+
assignFlag(out, schema, tok.key, tok.value);
|
|
96
|
+
} else if (posIdx < positional.length) {
|
|
97
|
+
out[positional[posIdx]] = tok.value;
|
|
98
|
+
posIdx++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- 3. coerce -----------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
function coerceScalar(raw: string, type?: string): unknown {
|
|
107
|
+
switch (type) {
|
|
108
|
+
case "number":
|
|
109
|
+
case "integer": {
|
|
110
|
+
const n = Number(raw);
|
|
111
|
+
return Number.isNaN(n) ? raw : n;
|
|
112
|
+
}
|
|
113
|
+
case "boolean":
|
|
114
|
+
if (raw === "true") return true;
|
|
115
|
+
if (raw === "false") return false;
|
|
116
|
+
return raw;
|
|
117
|
+
default:
|
|
118
|
+
return raw;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function coerceField(value: unknown, prop: SchemaProp | undefined): unknown {
|
|
123
|
+
if (prop === undefined) return value;
|
|
124
|
+
if (typeof value === "boolean") return value;
|
|
125
|
+
|
|
126
|
+
if (prop.type === "array") {
|
|
127
|
+
const items = Array.isArray(value) ? value : [value];
|
|
128
|
+
return items.map((item) => coerceScalar(String(item), prop.items?.type));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return coerceScalar(String(value), prop.type);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function coerce(
|
|
135
|
+
raw: Record<string, unknown>,
|
|
136
|
+
schema: SchemaObject,
|
|
137
|
+
): Record<string, unknown> {
|
|
138
|
+
const out: Record<string, unknown> = {};
|
|
139
|
+
const props = schema.properties ?? {};
|
|
140
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
141
|
+
out[key] = coerceField(value, props[key]);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- orchestrator --------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
export function parseArgs(
|
|
149
|
+
argv: string[],
|
|
150
|
+
positional: string[],
|
|
151
|
+
schema: Record<string, unknown>,
|
|
152
|
+
): Record<string, unknown> {
|
|
153
|
+
const s = schema as SchemaObject;
|
|
154
|
+
return coerce(dispatch(tokenize(argv), positional, s), s);
|
|
155
|
+
}
|