@v1nvn/agentic-core 0.44.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +10 -15
- package/src/cli.js +194 -0
- package/src/cli.ts +252 -38
- package/src/dev.js +25 -0
- package/src/dev.ts +3 -8
- package/src/draw.js +244 -0
- package/src/draw.ts +332 -0
- package/src/index.js +8 -0
- package/src/index.ts +8 -6
- package/src/input.js +25 -0
- package/src/last-reply.js +75 -0
- package/src/logger.js +50 -0
- package/src/logger.ts +21 -1
- package/src/mcp.js +29 -0
- package/src/mcp.ts +69 -0
- package/src/shutdown.js +23 -0
- package/src/vite.js +63 -0
- package/src/vite.ts +78 -60
- package/dist/index.js +0 -214
- package/dist/index.js.map +0 -1
- package/dist/vite.js +0 -54
- package/dist/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ User-facing docs: [root README](../../README.md).
|
|
|
13
13
|
| ------------------- | ----------------------------------------------------------------------- |
|
|
14
14
|
| `src/last-reply.ts` | finds the previous assistant reply across local Claude Code transcripts |
|
|
15
15
|
| `src/input.ts` | stdin and markdown-file input with the CLI error contracts |
|
|
16
|
-
| `src/cli.ts` |
|
|
16
|
+
| `src/cli.ts` | the runCli/parseCli doors over declarative command specs |
|
|
17
17
|
| `src/logger.ts` | level-filtered stderr logger for the MCP servers |
|
|
18
18
|
| `src/shutdown.ts` | close-then-exit signal handling for the server entrypoints |
|
|
19
19
|
| `src/dev.ts` | the hot-reload watcher wiring the servers' `yarn dev` harnesses share |
|
package/package.json
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v1nvn/agentic-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared
|
|
3
|
+
"version": "0.45.1",
|
|
4
|
+
"description": "Shared source home for the @v1nvn agent tools — runtime helpers and the drawing kit every process-side surface imports.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
"./vite": {
|
|
13
|
-
"types": "./src/vite.ts",
|
|
14
|
-
"default": "./dist/vite.js"
|
|
15
|
-
}
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./draw": "./src/draw.js",
|
|
10
|
+
"./vite": "./src/vite.js"
|
|
16
11
|
},
|
|
17
12
|
"scripts": {
|
|
18
|
-
"build": "vite build",
|
|
19
13
|
"test": "vitest run",
|
|
20
14
|
"test:watch": "vitest"
|
|
21
15
|
},
|
|
@@ -35,11 +29,12 @@
|
|
|
35
29
|
"directory": "packages/core"
|
|
36
30
|
},
|
|
37
31
|
"files": [
|
|
38
|
-
"dist",
|
|
39
32
|
"src"
|
|
40
33
|
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^15.0.0"
|
|
36
|
+
},
|
|
41
37
|
"devDependencies": {
|
|
42
|
-
"commander": "^15.0.0",
|
|
43
38
|
"vite": "^8.1.4",
|
|
44
39
|
"vitest": "^4.1.10"
|
|
45
40
|
},
|
|
@@ -47,6 +42,6 @@
|
|
|
47
42
|
"access": "public"
|
|
48
43
|
},
|
|
49
44
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
45
|
+
"node": ">=23.6"
|
|
51
46
|
}
|
|
52
47
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
2
|
+
import { describeError } from "./mcp.js";
|
|
3
|
+
// A --help ask caught mid-parse: the parse doors return it instead of exiting,
|
|
4
|
+
// and runCli answers it (the help payload) on stdout with 0.
|
|
5
|
+
export class HelpRequested extends Error {
|
|
6
|
+
command;
|
|
7
|
+
constructor(command) {
|
|
8
|
+
super(`${command.name()} --help`);
|
|
9
|
+
this.command = command;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function addOption(cmd, option) {
|
|
13
|
+
const bare = option.env === undefined &&
|
|
14
|
+
option.choices === undefined &&
|
|
15
|
+
option.default === undefined &&
|
|
16
|
+
option.parse === undefined;
|
|
17
|
+
if (bare) {
|
|
18
|
+
cmd.option(option.flags, option.description);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const built = new Option(option.flags, option.description);
|
|
22
|
+
if (option.env !== undefined) {
|
|
23
|
+
built.env(option.env);
|
|
24
|
+
}
|
|
25
|
+
if (option.choices !== undefined) {
|
|
26
|
+
built.choices(option.choices);
|
|
27
|
+
}
|
|
28
|
+
if (option.default !== undefined) {
|
|
29
|
+
built.default(option.default);
|
|
30
|
+
}
|
|
31
|
+
const { parse } = option;
|
|
32
|
+
if (parse !== undefined) {
|
|
33
|
+
// A plain Error from a parse fn skips commander's exitOverride, which is
|
|
34
|
+
// what records the subcommand for the usage print — wrap it so it rides.
|
|
35
|
+
built.argParser((value) => {
|
|
36
|
+
try {
|
|
37
|
+
return parse(value);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err.code === 'commander.invalidArgument') {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
throw new InvalidArgumentError(describeError(err));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
cmd.addOption(built);
|
|
48
|
+
}
|
|
49
|
+
function wire(cmd, spec, program, isRoot, sink) {
|
|
50
|
+
if (spec.description !== undefined) {
|
|
51
|
+
cmd.description(spec.description);
|
|
52
|
+
}
|
|
53
|
+
if (spec.argument !== undefined) {
|
|
54
|
+
cmd.argument(spec.argument.flags, spec.argument.description);
|
|
55
|
+
}
|
|
56
|
+
for (const option of spec.options ?? []) {
|
|
57
|
+
addOption(cmd, option);
|
|
58
|
+
}
|
|
59
|
+
if (isRoot && spec.version !== undefined) {
|
|
60
|
+
cmd.option('-V, --version', 'print the version and exit');
|
|
61
|
+
}
|
|
62
|
+
cmd.action((...all) => {
|
|
63
|
+
const options = all.at(-2);
|
|
64
|
+
const command = all.at(-1);
|
|
65
|
+
sink?.({
|
|
66
|
+
command: isRoot ? undefined : spec.name,
|
|
67
|
+
options,
|
|
68
|
+
args: command.args,
|
|
69
|
+
root: program.opts(),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
for (const sub of spec.commands ?? []) {
|
|
73
|
+
const child = new Command(sub.name);
|
|
74
|
+
wire(child, sub, program, false, sink);
|
|
75
|
+
cmd.addCommand(child);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function materialize(spec, sink) {
|
|
79
|
+
const program = new Command(spec.name);
|
|
80
|
+
wire(program, spec, program, true, sink);
|
|
81
|
+
return program;
|
|
82
|
+
}
|
|
83
|
+
export function buildCli(spec) {
|
|
84
|
+
return materialize(spec);
|
|
85
|
+
}
|
|
86
|
+
function quiet(command, onUsageError) {
|
|
87
|
+
command.configureOutput({
|
|
88
|
+
writeOut: () => undefined,
|
|
89
|
+
writeErr: () => undefined,
|
|
90
|
+
});
|
|
91
|
+
command.exitOverride(err => {
|
|
92
|
+
const code = err.code;
|
|
93
|
+
if (code === 'commander.help' || code === 'commander.helpDisplayed') {
|
|
94
|
+
throw new HelpRequested(command);
|
|
95
|
+
}
|
|
96
|
+
onUsageError(command);
|
|
97
|
+
throw err;
|
|
98
|
+
});
|
|
99
|
+
command.commands.forEach(c => {
|
|
100
|
+
quiet(c, onUsageError);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function parseCli(spec, argv, onHelp, onUsageError) {
|
|
104
|
+
let invocation;
|
|
105
|
+
const program = materialize(spec, found => {
|
|
106
|
+
invocation = found;
|
|
107
|
+
});
|
|
108
|
+
let errored;
|
|
109
|
+
quiet(program, command => {
|
|
110
|
+
errored = command;
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
program.parse([...argv], { from: 'user' });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (!(err instanceof HelpRequested)) {
|
|
117
|
+
if (errored !== undefined) {
|
|
118
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
119
|
+
onUsageError?.(errored, cause);
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return onHelp !== undefined
|
|
124
|
+
? onHelp(err.command.name(), err.command.helpInformation())
|
|
125
|
+
: err;
|
|
126
|
+
}
|
|
127
|
+
return invocation;
|
|
128
|
+
}
|
|
129
|
+
export function helpTextFor(spec, topic) {
|
|
130
|
+
const program = materialize(spec);
|
|
131
|
+
const command = topic === spec.name
|
|
132
|
+
? program
|
|
133
|
+
: program.commands.find(c => c.name() === topic);
|
|
134
|
+
if (command === undefined) {
|
|
135
|
+
throw new Error(`no '${topic}' command to describe`);
|
|
136
|
+
}
|
|
137
|
+
return command.helpInformation();
|
|
138
|
+
}
|
|
139
|
+
function usageError(spec, topic, message) {
|
|
140
|
+
if (message !== undefined) {
|
|
141
|
+
// Commander 15 builds its messages with the 'error: ' prefix already in.
|
|
142
|
+
process.stderr.write(`${message}\n`);
|
|
143
|
+
}
|
|
144
|
+
process.stderr.write(`${helpTextFor(spec, topic === undefined ? spec.name : topic)}\n`);
|
|
145
|
+
return spec.usageExit ?? 1;
|
|
146
|
+
}
|
|
147
|
+
async function call(run, options, args) {
|
|
148
|
+
try {
|
|
149
|
+
return (await run(options, args)) ?? 0;
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.error(err.message);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export async function runCli(spec, argv, onHelp) {
|
|
157
|
+
let asked;
|
|
158
|
+
let usageTopic;
|
|
159
|
+
let usageMessage;
|
|
160
|
+
const parsed = parseCli(spec, argv, (topic, text) => {
|
|
161
|
+
asked = { topic, text };
|
|
162
|
+
return 0;
|
|
163
|
+
}, (command, err) => {
|
|
164
|
+
usageTopic = command.name();
|
|
165
|
+
usageMessage = err.message;
|
|
166
|
+
});
|
|
167
|
+
if (parsed === undefined) {
|
|
168
|
+
return usageError(spec, usageTopic, usageMessage);
|
|
169
|
+
}
|
|
170
|
+
if (asked !== undefined) {
|
|
171
|
+
if (onHelp !== undefined) {
|
|
172
|
+
return onHelp(asked.topic, asked.text) ?? 0;
|
|
173
|
+
}
|
|
174
|
+
process.stdout.write(`${asked.text}\n`);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
const invocation = parsed;
|
|
178
|
+
if (spec.version !== undefined && invocation.root.version === true) {
|
|
179
|
+
process.stdout.write(`${spec.version}\n`);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
if (invocation.command === undefined) {
|
|
183
|
+
if ((spec.commands?.length ?? 0) > 0) {
|
|
184
|
+
return usageError(spec);
|
|
185
|
+
}
|
|
186
|
+
return spec.run === undefined
|
|
187
|
+
? 0
|
|
188
|
+
: await call(spec.run, invocation.options, invocation.args);
|
|
189
|
+
}
|
|
190
|
+
const sub = spec.commands?.find(c => c.name === invocation.command);
|
|
191
|
+
return sub?.run === undefined
|
|
192
|
+
? 0
|
|
193
|
+
: await call(sub.run, invocation.options, invocation.args);
|
|
194
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,60 +1,274 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { describeError } from './mcp.ts';
|
|
4
|
+
|
|
5
|
+
export interface CliOption {
|
|
6
|
+
readonly choices?: readonly string[];
|
|
7
|
+
readonly default?: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly env?: string;
|
|
10
|
+
readonly flags: string;
|
|
11
|
+
readonly parse?: (value: string) => unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CliResult = number | undefined;
|
|
15
|
+
|
|
16
|
+
export type CliRun = (
|
|
17
|
+
options: Record<string, unknown>,
|
|
18
|
+
args: readonly string[],
|
|
19
|
+
) => CliResult | Promise<CliResult>;
|
|
20
|
+
|
|
21
|
+
export interface CliSpec {
|
|
22
|
+
readonly argument?: { readonly description: string; readonly flags: string };
|
|
23
|
+
readonly commands?: readonly CliSpec[];
|
|
24
|
+
readonly description?: string;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly options?: readonly CliOption[];
|
|
27
|
+
readonly run?: CliRun;
|
|
28
|
+
readonly usageExit?: number;
|
|
29
|
+
readonly version?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CliInvocation {
|
|
33
|
+
readonly args: readonly string[];
|
|
34
|
+
readonly command?: string;
|
|
35
|
+
readonly options: Record<string, unknown>;
|
|
36
|
+
readonly root: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A --help ask caught mid-parse: the parse doors return it instead of exiting,
|
|
40
|
+
// and runCli answers it (the help payload) on stdout with 0.
|
|
5
41
|
export class HelpRequested extends Error {
|
|
6
|
-
|
|
42
|
+
readonly command: Command;
|
|
43
|
+
|
|
44
|
+
constructor(command: Command) {
|
|
7
45
|
super(`${command.name()} --help`);
|
|
46
|
+
this.command = command;
|
|
8
47
|
}
|
|
9
48
|
}
|
|
10
49
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
export type CliHelpHook<T> = (topic: string, text: string) => T;
|
|
51
|
+
|
|
52
|
+
function addOption(cmd: Command, option: CliOption): void {
|
|
53
|
+
const bare =
|
|
54
|
+
option.env === undefined &&
|
|
55
|
+
option.choices === undefined &&
|
|
56
|
+
option.default === undefined &&
|
|
57
|
+
option.parse === undefined;
|
|
58
|
+
if (bare) {
|
|
59
|
+
cmd.option(option.flags, option.description);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const built = new Option(option.flags, option.description);
|
|
63
|
+
if (option.env !== undefined) {
|
|
64
|
+
built.env(option.env);
|
|
65
|
+
}
|
|
66
|
+
if (option.choices !== undefined) {
|
|
67
|
+
built.choices(option.choices);
|
|
68
|
+
}
|
|
69
|
+
if (option.default !== undefined) {
|
|
70
|
+
built.default(option.default);
|
|
71
|
+
}
|
|
72
|
+
const { parse } = option;
|
|
73
|
+
if (parse !== undefined) {
|
|
74
|
+
// A plain Error from a parse fn skips commander's exitOverride, which is
|
|
75
|
+
// what records the subcommand for the usage print — wrap it so it rides.
|
|
76
|
+
built.argParser((value: string) => {
|
|
77
|
+
try {
|
|
78
|
+
return parse(value);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if ((err as { code?: string }).code === 'commander.invalidArgument') {
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
throw new InvalidArgumentError(describeError(err));
|
|
24
84
|
}
|
|
25
|
-
throw err;
|
|
26
85
|
});
|
|
27
|
-
command.commands.forEach(quiet);
|
|
28
86
|
}
|
|
87
|
+
cmd.addOption(built);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function wire(
|
|
91
|
+
cmd: Command,
|
|
92
|
+
spec: CliSpec,
|
|
93
|
+
program: Command,
|
|
94
|
+
isRoot: boolean,
|
|
95
|
+
sink?: (invocation: CliInvocation) => void,
|
|
96
|
+
): void {
|
|
97
|
+
if (spec.description !== undefined) {
|
|
98
|
+
cmd.description(spec.description);
|
|
99
|
+
}
|
|
100
|
+
if (spec.argument !== undefined) {
|
|
101
|
+
cmd.argument(spec.argument.flags, spec.argument.description);
|
|
102
|
+
}
|
|
103
|
+
for (const option of spec.options ?? []) {
|
|
104
|
+
addOption(cmd, option);
|
|
105
|
+
}
|
|
106
|
+
if (isRoot && spec.version !== undefined) {
|
|
107
|
+
cmd.option('-V, --version', 'print the version and exit');
|
|
108
|
+
}
|
|
109
|
+
cmd.action((...all: unknown[]) => {
|
|
110
|
+
const options = all.at(-2) as Record<string, unknown>;
|
|
111
|
+
const command = all.at(-1) as Command;
|
|
112
|
+
sink?.({
|
|
113
|
+
command: isRoot ? undefined : spec.name,
|
|
114
|
+
options,
|
|
115
|
+
args: command.args,
|
|
116
|
+
root: program.opts(),
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
for (const sub of spec.commands ?? []) {
|
|
120
|
+
const child = new Command(sub.name);
|
|
121
|
+
wire(child, sub, program, false, sink);
|
|
122
|
+
cmd.addCommand(child);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function materialize(
|
|
127
|
+
spec: CliSpec,
|
|
128
|
+
sink?: (invocation: CliInvocation) => void,
|
|
129
|
+
): Command {
|
|
130
|
+
const program = new Command(spec.name);
|
|
131
|
+
wire(program, spec, program, true, sink);
|
|
132
|
+
return program;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function buildCli(spec: CliSpec): Command {
|
|
136
|
+
return materialize(spec);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function quiet(
|
|
140
|
+
command: Command,
|
|
141
|
+
onUsageError: (command: Command) => void,
|
|
142
|
+
): void {
|
|
143
|
+
command.configureOutput({
|
|
144
|
+
writeOut: () => undefined,
|
|
145
|
+
writeErr: () => undefined,
|
|
146
|
+
});
|
|
147
|
+
command.exitOverride(err => {
|
|
148
|
+
const code = (err as { code?: string }).code;
|
|
149
|
+
if (code === 'commander.help' || code === 'commander.helpDisplayed') {
|
|
150
|
+
throw new HelpRequested(command);
|
|
151
|
+
}
|
|
152
|
+
onUsageError(command);
|
|
153
|
+
throw err;
|
|
154
|
+
});
|
|
155
|
+
command.commands.forEach(c => {
|
|
156
|
+
quiet(c, onUsageError);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function parseCli<T = HelpRequested>(
|
|
161
|
+
spec: CliSpec,
|
|
162
|
+
argv: readonly string[],
|
|
163
|
+
onHelp?: CliHelpHook<T>,
|
|
164
|
+
onUsageError?: (command: Command, err: Error) => void,
|
|
165
|
+
): CliInvocation | HelpRequested | T | undefined {
|
|
166
|
+
let invocation: CliInvocation | undefined;
|
|
167
|
+
const program = materialize(spec, found => {
|
|
168
|
+
invocation = found;
|
|
169
|
+
});
|
|
170
|
+
let errored: Command | undefined;
|
|
171
|
+
quiet(program, command => {
|
|
172
|
+
errored = command;
|
|
173
|
+
});
|
|
29
174
|
try {
|
|
30
|
-
|
|
31
|
-
program.parse([...args], { from: 'user' });
|
|
32
|
-
return program;
|
|
175
|
+
program.parse([...argv], { from: 'user' });
|
|
33
176
|
} catch (err) {
|
|
34
|
-
|
|
177
|
+
if (!(err instanceof HelpRequested)) {
|
|
178
|
+
if (errored !== undefined) {
|
|
179
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
180
|
+
onUsageError?.(errored, cause);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return onHelp !== undefined
|
|
185
|
+
? onHelp(err.command.name(), err.command.helpInformation())
|
|
186
|
+
: err;
|
|
35
187
|
}
|
|
188
|
+
return invocation;
|
|
36
189
|
}
|
|
37
190
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
191
|
+
export function helpTextFor(spec: CliSpec, topic: string): string {
|
|
192
|
+
const program = materialize(spec);
|
|
193
|
+
const command =
|
|
194
|
+
topic === spec.name
|
|
195
|
+
? program
|
|
196
|
+
: program.commands.find(c => c.name() === topic);
|
|
197
|
+
if (command === undefined) {
|
|
198
|
+
throw new Error(`no '${topic}' command to describe`);
|
|
199
|
+
}
|
|
200
|
+
return command.helpInformation();
|
|
41
201
|
}
|
|
42
202
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
203
|
+
function usageError(spec: CliSpec, topic?: string, message?: string): number {
|
|
204
|
+
if (message !== undefined) {
|
|
205
|
+
// Commander 15 builds its messages with the 'error: ' prefix already in.
|
|
206
|
+
process.stderr.write(`${message}\n`);
|
|
207
|
+
}
|
|
208
|
+
process.stderr.write(
|
|
209
|
+
`${helpTextFor(spec, topic === undefined ? spec.name : topic)}\n`,
|
|
210
|
+
);
|
|
211
|
+
return spec.usageExit ?? 1;
|
|
46
212
|
}
|
|
47
213
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
214
|
+
async function call(
|
|
215
|
+
run: CliRun,
|
|
216
|
+
options: Record<string, unknown>,
|
|
217
|
+
args: readonly string[],
|
|
218
|
+
): Promise<number> {
|
|
219
|
+
try {
|
|
220
|
+
return (await run(options, args)) ?? 0;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error((err as Error).message);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
51
225
|
}
|
|
52
226
|
|
|
53
|
-
export async function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
227
|
+
export async function runCli(
|
|
228
|
+
spec: CliSpec,
|
|
229
|
+
argv: readonly string[],
|
|
230
|
+
onHelp?: CliHelpHook<CliResult>,
|
|
231
|
+
): Promise<number> {
|
|
232
|
+
let asked: undefined | { readonly text: string; readonly topic: string };
|
|
233
|
+
let usageTopic: string | undefined;
|
|
234
|
+
let usageMessage: string | undefined;
|
|
235
|
+
const parsed = parseCli(
|
|
236
|
+
spec,
|
|
237
|
+
argv,
|
|
238
|
+
(topic, text) => {
|
|
239
|
+
asked = { topic, text };
|
|
240
|
+
return 0;
|
|
241
|
+
},
|
|
242
|
+
(command, err) => {
|
|
243
|
+
usageTopic = command.name();
|
|
244
|
+
usageMessage = err.message;
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
if (parsed === undefined) {
|
|
248
|
+
return usageError(spec, usageTopic, usageMessage);
|
|
249
|
+
}
|
|
250
|
+
if (asked !== undefined) {
|
|
251
|
+
if (onHelp !== undefined) {
|
|
252
|
+
return onHelp(asked.topic, asked.text) ?? 0;
|
|
253
|
+
}
|
|
254
|
+
process.stdout.write(`${asked.text}\n`);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
const invocation = parsed as CliInvocation;
|
|
258
|
+
if (spec.version !== undefined && invocation.root.version === true) {
|
|
259
|
+
process.stdout.write(`${spec.version}\n`);
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
if (invocation.command === undefined) {
|
|
263
|
+
if ((spec.commands?.length ?? 0) > 0) {
|
|
264
|
+
return usageError(spec);
|
|
265
|
+
}
|
|
266
|
+
return spec.run === undefined
|
|
267
|
+
? 0
|
|
268
|
+
: await call(spec.run, invocation.options, invocation.args);
|
|
59
269
|
}
|
|
270
|
+
const sub = spec.commands?.find(c => c.name === invocation.command);
|
|
271
|
+
return sub?.run === undefined
|
|
272
|
+
? 0
|
|
273
|
+
: await call(sub.run, invocation.options, invocation.args);
|
|
60
274
|
}
|
package/src/dev.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const RELOAD_DEBOUNCE_MS = 150;
|
|
2
|
+
const IGNORED_SEGMENTS = ['/node_modules/', '/dist/', '/.git/', '/coverage/'];
|
|
3
|
+
function shouldReload(file) {
|
|
4
|
+
return IGNORED_SEGMENTS.every(seg => !file.includes(seg));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Debounced file-change reloads, serialized via a promise chain: concurrent
|
|
8
|
+
* saves never overlap, and a mid-reload save still produces a follow-up.
|
|
9
|
+
*/
|
|
10
|
+
export function watchWithReload(vite, reload) {
|
|
11
|
+
let chain = Promise.resolve();
|
|
12
|
+
let timer;
|
|
13
|
+
vite.watcher.on('change', file => {
|
|
14
|
+
if (!shouldReload(file)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (timer) {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
}
|
|
20
|
+
timer = setTimeout(() => {
|
|
21
|
+
timer = undefined;
|
|
22
|
+
chain = chain.then(reload);
|
|
23
|
+
}, RELOAD_DEBOUNCE_MS);
|
|
24
|
+
});
|
|
25
|
+
}
|
package/src/dev.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
export interface ReloadableRunner {
|
|
5
|
-
readonly evaluatedModules: { clear(): void };
|
|
6
|
-
import<T>(entry: string): Promise<T>;
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
// A structural stand-in for vite's dev server — the shared hot-reload wiring
|
|
2
|
+
// below must not pull vite into core's runtime surface; vite.ts passes the
|
|
3
|
+
// real instance through.
|
|
9
4
|
export interface WatchableDevServer {
|
|
10
5
|
close(): Promise<unknown>;
|
|
11
6
|
readonly watcher: {
|