Skip to content

Commit f30192c

Browse files
committed
remove special export handler, fix config types
1 parent 7b942b8 commit f30192c

3 files changed

Lines changed: 84 additions & 80 deletions

File tree

cli/build-run-args.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
export type Runtime = "node" | "deno" | "bun";
1+
import { type RunConfig } from "./config.ts";
22

3-
export interface RunConfig {
4-
inspectPause: boolean;
5-
inspectRecord: string | undefined;
6-
inspectHost: string;
7-
inspectRuntime?: string;
8-
}
3+
export type Runtime = "node" | "deno" | "bun";
94

105
function hasInspectorImport(args: string[]): boolean {
116
for (let index = 0; index < args.length; index++) {

cli/config.ts

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,70 @@
1-
import { commands, field, object, program, help } from "configliere";
1+
import {
2+
commands,
3+
field,
4+
object,
5+
program,
6+
help,
7+
type CommandsParser,
8+
type ObjectParser,
9+
} from "configliere";
210
import packageJSON from "../package.json" with { type: "json" };
311
import { type } from "arktype";
412
import { scope, player } from "../lib/protocols.ts";
513
import { combine } from "../mod.ts";
614

715
export const inspector = combine.protocols(scope.protocol, player.protocol);
16+
17+
const commandBase = object({
18+
out: {
19+
description: "write out the response to a file",
20+
...field(type("string | undefined"), field.default(undefined)),
21+
},
22+
host: {
23+
description: "inspector base URL (overrides default)",
24+
...field(type("string"), field.default("http://localhost:41000")),
25+
},
26+
});
27+
type InspectorProtocolCommandBase = Record<
28+
keyof typeof inspector.methods,
29+
{ description: string } & typeof commandBase
30+
>;
831
const inspectorProtocolEntries = (
932
Object.keys(inspector.methods) as (keyof typeof inspector.methods)[]
10-
).reduce(
11-
(base, current) => {
12-
base[current] = {
13-
description: `/${current} API`,
14-
...object({
15-
out: {
16-
description: "write out the response to a file",
17-
...field(type("string | undefined"), field.default(undefined)),
18-
},
19-
host: {
20-
description: "inspector base URL (overrides default)",
21-
...field(type("string"), field.default("http://localhost:41000")),
22-
},
23-
}),
24-
};
25-
return base;
26-
},
27-
{} as Record<keyof typeof inspector.methods, {}>,
28-
);
33+
).reduce((base, current) => {
34+
base[current] = {
35+
description: `/${current} API`,
36+
...commandBase,
37+
};
38+
return base satisfies InspectorProtocolCommandBase;
39+
}, {} as InspectorProtocolCommandBase);
2940

3041
const protocolCommands = commands(inspectorProtocolEntries);
31-
export type ProtocolCommands = typeof protocolCommands;
42+
export type ParsedConfig<T extends CommandsParser | ObjectParser<any>> = Extract<
43+
ReturnType<T["parse"]>,
44+
{ ok: true }
45+
>["value"];
46+
export type ProtocolCommandConfig = ParsedConfig<typeof protocolCommands>;
47+
48+
const runBase = object({
49+
inspectPause: {
50+
description: "start program paused until resumed by inspector",
51+
...field(type("boolean"), field.default(false)),
52+
},
53+
inspectRecord: {
54+
description: "write inspector recording to the given file",
55+
...field(type("string | undefined"), field.default(undefined)),
56+
},
57+
inspectHost: {
58+
description: "inspector base URL (overrides default)",
59+
...field(type("string.url"), field.default("http://localhost:41000")),
60+
},
61+
inspectRuntime: {
62+
description:
63+
"which JavaScript runtime to launch (node, deno, bun).\n" +
64+
"If omitted we infer from the executable that invoked the CLI",
65+
...field(type("string"), field.default("node")),
66+
},
67+
});
3268

3369
export const config = program({
3470
name: "inspector",
@@ -46,28 +82,11 @@ export const config = program({
4682
},
4783
run: {
4884
description: "inspect a CLI program",
49-
...object({
50-
inspectPause: {
51-
description: "start program paused until resumed by inspector",
52-
...field(type("boolean"), field.default(false)),
53-
},
54-
inspectRecord: {
55-
description: "write inspector recording to the given file",
56-
...field(type("string | undefined"), field.default(undefined)),
57-
},
58-
inspectHost: {
59-
description: "inspector base URL (overrides default)",
60-
...field(type("string.url"), field.default("http://localhost:41000")),
61-
},
62-
inspectRuntime: {
63-
description:
64-
"which JavaScript runtime to launch (node, deno, bun).\n" +
65-
"If omitted we infer from the executable that invoked the CLI",
66-
...field(type("string"), field.default("node")),
67-
},
68-
}),
85+
...runBase,
6986
},
7087
},
7188
{ default: "run" },
7289
),
7390
});
91+
92+
export type RunConfig = ParsedConfig<typeof runBase>;

cli/index.ts

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
#!/usr/bin/env node
22
import { type Operation, each, main, sleep, spawn, suspend, until } from "effection";
3-
import type { Program } from "configliere";
4-
import { config, inspector, type ProtocolCommands } from "./config.ts";
3+
import { config, inspector, type RunConfig, type ProtocolCommandConfig } from "./config.ts";
54
import { exec } from "@effectionx/process";
65
import { writeFile } from "node:fs/promises";
76
import { createSSEClient } from "../lib/sse-client.ts";
87
import { useSSEServer } from "../lib/sse-server.ts";
98
import { log } from "./logger.ts";
10-
import {
11-
type RunConfig,
12-
buildRuntimeArguments,
13-
buildRunEnvironment,
14-
resolveRuntime,
15-
} from "./build-run-args.ts";
9+
import { buildRuntimeArguments, buildRunEnvironment, resolveRuntime } from "./build-run-args.ts";
1610

1711
export function* invokeWithRetry(name: "watchScopes" | "recordNodeMap", host: string) {
1812
let handle = createSSEClient(inspector, { url: host });
@@ -86,10 +80,9 @@ function* recordNodeMapToFile(host: string, filePath: string): Operation<void> {
8680
}
8781
}
8882

89-
function* callMethod(config: Program<ProtocolCommands>) {
83+
function* callMethod(config: ProtocolCommandConfig) {
9084
const {
9185
name,
92-
// @ts-expect-error types presume config is boolean
9386
config: { out, host },
9487
} = config;
9588
const argsList = [] as never[];
@@ -100,7 +93,6 @@ function* callMethod(config: Program<ProtocolCommands>) {
10093
}
10194
let results: unknown[] = [];
10295
let subscription = yield* handle.invoke({
103-
// @ts-expect-error TODO still not refined enough, only a string
10496
name,
10597
args: argsList,
10698
});
@@ -122,53 +114,51 @@ function* callMethod(config: Program<ProtocolCommands>) {
122114
}
123115
}
124116

125-
// return values represent process exit codes for use in testing
126-
export function* cliOp(argv: string[]): Operation<number> {
127-
let parser = config.createParser({
128-
args: argv,
117+
await main(function* () {
118+
const parser = config.createParser({
119+
args: process.argv.slice(2),
129120
envs: [{ name: "ENV", value: process.env as Record<string, string> }],
130121
});
131122

132123
switch (parser.type) {
133124
case "help":
134125
case "version":
135126
yield* log.info(parser.print());
136-
return 0;
127+
break;
137128
case "main": {
138-
let result = parser.parse();
129+
const result = parser.parse();
139130
if (result.ok) {
140131
let { value: command, remainder } = result;
141132
switch (command.name) {
142133
case "help":
143134
yield* log.info(command.config.text);
144-
return 0;
135+
break;
145136
case "ui": {
146137
let port = 41000;
147138
let address = yield* useSSEServer({ protocol: { methods: {} } } as any, { port });
148139
yield* log.info(`serving inspector UI at ${address}`);
149140
yield* suspend();
150-
return 0;
141+
break;
151142
}
152143
case "call":
153-
// @ts-expect-error TODO what type should this actually be?
154144
yield* callMethod(command.config);
155-
return 0;
145+
break;
156146
case "run":
157147
yield* runProgram(command.config, remainder.args ?? []);
158-
return 0;
148+
break;
149+
default:
150+
// An exhaustiveness check using 'never' can be added here
151+
const _exhaustiveCheck: never = command;
152+
break;
159153
}
160154
} else {
161155
yield* log.error(result.error.message);
162-
return 1;
163156
}
157+
break;
164158
}
159+
default:
160+
// An exhaustiveness check using 'never' can be added here
161+
const _exhaustiveCheck: never = parser;
162+
break;
165163
}
166-
}
167-
168-
// when executed as a script we delegate directly to Effection's `main`
169-
// which allows the code above to be imported for testing
170-
if (import.meta.url === `file://${process.argv[1]}`) {
171-
await main(function* (args) {
172-
yield* cliOp(args);
173-
});
174-
}
164+
});

0 commit comments

Comments
 (0)