@procedures/shell 0.34.0 → 0.35.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/CHANGELOG.md +31 -0
- package/README.md +23 -14
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/argv.d.ts +29 -0
- package/dist/cli/argv.d.ts.map +1 -0
- package/dist/cli/argv.js +71 -0
- package/dist/cli/argv.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +17 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +38 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +27 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/program.d.ts +29 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +41 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/tool.d.ts +75 -0
- package/dist/cli/tool.d.ts.map +1 -0
- package/dist/cli/tool.js +104 -0
- package/dist/cli/tool.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +2 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -9
- package/dist/module.js.map +1 -1
- package/dist/testing.d.ts +67 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +114 -0
- package/dist/testing.js.map +1 -0
- package/package.json +11 -3
- package/src/cli/argv.ts +71 -0
- package/src/cli/index.ts +32 -0
- package/src/cli/output.ts +59 -0
- package/src/cli/program.ts +47 -0
- package/src/cli/tool.ts +164 -0
- package/src/index.ts +6 -1
- package/src/module.ts +9 -9
- package/src/testing.ts +163 -0
package/src/module.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Procedure Registration for shell operations
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { definePackage, createProcedure, type Effects, type ProcedureModule, type PackageDefinition } from "@procedures/client";
|
|
8
8
|
import { shellRun } from "./procedures/shell/run.js";
|
|
9
9
|
import { shellExec } from "./procedures/shell/exec.js";
|
|
10
10
|
import { shellWhich } from "./procedures/shell/which.js";
|
|
@@ -105,17 +105,19 @@ const shellStreamProcedure = createProcedure()
|
|
|
105
105
|
.handler(shellStream)
|
|
106
106
|
.build();
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
* The module of this package, for `createRegistry()`: its procedures as data. Importing this
|
|
110
|
-
* file registers nothing. (`register.ts` registers the same procedures in the global registry.)
|
|
111
|
-
*/
|
|
112
|
-
export const shellModule: ProcedureModule = defineModule("@procedures/shell", [
|
|
108
|
+
const shellPackage: PackageDefinition = definePackage("@procedures/shell", [
|
|
113
109
|
shellRunProcedure,
|
|
114
110
|
shellExecProcedure,
|
|
115
111
|
shellWhichProcedure,
|
|
116
112
|
shellStreamProcedure,
|
|
117
113
|
]);
|
|
118
114
|
|
|
115
|
+
/**
|
|
116
|
+
* The module of this package, for `createRegistry()`: its procedures as data. Importing this
|
|
117
|
+
* file registers nothing. (`register.ts` registers the same procedures in the global registry.)
|
|
118
|
+
*/
|
|
119
|
+
export const shellModule: ProcedureModule = shellPackage.module;
|
|
120
|
+
|
|
119
121
|
export default shellModule;
|
|
120
122
|
|
|
121
123
|
/**
|
|
@@ -123,6 +125,4 @@ export default shellModule;
|
|
|
123
125
|
* a registry. The default is the global registry. Importing this file registers nothing: the
|
|
124
126
|
* `./register` entry starts this function (ARCHITECTURE-PROPOSALS-2026-10, P9).
|
|
125
127
|
*/
|
|
126
|
-
export
|
|
127
|
-
registerModules(registry, [shellModule]);
|
|
128
|
-
}
|
|
128
|
+
export const registerShellProcedures: PackageDefinition["register"] = shellPackage.register;
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@procedures/shell/testing`: a fake shell for the tests of the wrapper packages (dedupe review F2).
|
|
3
|
+
*
|
|
4
|
+
* `fakeShell(results)` gives the shell procedures with their real schemas, effects and rules of
|
|
5
|
+
* the root, and handlers that record each input and answer with a scripted result. No program
|
|
6
|
+
* starts. It gives the same fake in two forms:
|
|
7
|
+
* - `module`: put it before the module of the wrapper in `createRegistry()`. The first module of
|
|
8
|
+
* a name counts, so the fake takes the place of the real shell.
|
|
9
|
+
* - `client`: give it as `ctx.client` to a handler that a test calls directly.
|
|
10
|
+
*
|
|
11
|
+
* This entry point has no side effects: it registers no procedures.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { defineModule, type AnyProcedure, type ProcedureContext, type ProcedureModule } from "@procedures/client";
|
|
15
|
+
import { shellModule } from "./module.js";
|
|
16
|
+
import type { ShellRunOutput, ShellStreamItem } from "./types.js";
|
|
17
|
+
|
|
18
|
+
/** The scripted result of one call of the fake shell. Each field has a default. */
|
|
19
|
+
export interface FakeShellResult {
|
|
20
|
+
/** The exit code (default: 0) */
|
|
21
|
+
exitCode?: number | undefined;
|
|
22
|
+
/** The standard output (default: empty) */
|
|
23
|
+
stdout?: string | undefined;
|
|
24
|
+
/** The standard error (default: empty) */
|
|
25
|
+
stderr?: string | undefined;
|
|
26
|
+
/** The signal that ended the program (default: none) */
|
|
27
|
+
signal?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The input of a call of the fake shell, as the fake got it. */
|
|
31
|
+
export interface FakeShellInput {
|
|
32
|
+
command: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
cwd?: string | undefined;
|
|
35
|
+
timeout?: number | undefined;
|
|
36
|
+
env?: Record<string, string> | undefined;
|
|
37
|
+
[field: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The results of the fake shell:
|
|
42
|
+
* - one result for each call;
|
|
43
|
+
* - a list with the result of each call, in order (after the list, the `fallback` of the options);
|
|
44
|
+
* - a function of the input and of the index of the call.
|
|
45
|
+
*/
|
|
46
|
+
export type FakeShellResults = FakeShellResult | readonly FakeShellResult[] | ((input: FakeShellInput, index: number) => FakeShellResult);
|
|
47
|
+
|
|
48
|
+
/** The options of `fakeShell()`. */
|
|
49
|
+
export interface FakeShellOptions {
|
|
50
|
+
/** The result after the end of a list of results (default: exit code 0 and no output) */
|
|
51
|
+
fallback?: FakeShellResult | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The fake shell that `fakeShell()` gives. */
|
|
55
|
+
export interface FakeShell {
|
|
56
|
+
/** The input of each `shell.run` call, in order */
|
|
57
|
+
readonly calls: FakeShellInput[];
|
|
58
|
+
/** The input of each `shell.stream` call, in order */
|
|
59
|
+
readonly streams: FakeShellInput[];
|
|
60
|
+
/** The shell procedures with the fake handlers. `shell.which` is the real one. */
|
|
61
|
+
readonly procedures: AnyProcedure[];
|
|
62
|
+
/** The module `@procedures/shell` with the fake procedures */
|
|
63
|
+
readonly module: ProcedureModule;
|
|
64
|
+
/** A client for `ctx.client`: it answers `shell.run` and `shell.stream`, and it refuses other paths. */
|
|
65
|
+
readonly client: ProcedureContext["client"];
|
|
66
|
+
/** This function gives the argument list of a `shell.run` call (default: the first). It throws when the call is missing. */
|
|
67
|
+
args(index?: number): string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The full output of a scripted result. */
|
|
71
|
+
function outputOf(result: FakeShellResult): ShellRunOutput {
|
|
72
|
+
const exitCode = result.exitCode ?? 0;
|
|
73
|
+
const output: ShellRunOutput = { exitCode, stdout: result.stdout ?? "", stderr: result.stderr ?? "", success: exitCode === 0, duration: 1 };
|
|
74
|
+
if (result.signal !== undefined) output.signal = result.signal;
|
|
75
|
+
return output;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The lines of a text, without the line ends and without a last empty line. */
|
|
79
|
+
function linesOf(text: string): string[] {
|
|
80
|
+
const lines = text.split(/\r?\n/);
|
|
81
|
+
if (lines.at(-1) === "") lines.pop();
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* This function gives a fake shell. Each `shell.run` call records its input in `calls` and gives
|
|
87
|
+
* the next scripted result. Each `shell.stream` call records its input in `streams` and gives the
|
|
88
|
+
* lines of the result, then the exit item. `shell.exec` throws: a wrapper never runs a shell string.
|
|
89
|
+
*/
|
|
90
|
+
export function fakeShell(results: FakeShellResults = {}, options: FakeShellOptions = {}): FakeShell {
|
|
91
|
+
const calls: FakeShellInput[] = [];
|
|
92
|
+
const streams: FakeShellInput[] = [];
|
|
93
|
+
let index = 0;
|
|
94
|
+
const next = (input: FakeShellInput): FakeShellResult => {
|
|
95
|
+
const at = index++;
|
|
96
|
+
if (typeof results === "function") return results(input, at);
|
|
97
|
+
if (Array.isArray(results)) return (results as readonly FakeShellResult[])[at] ?? options.fallback ?? {};
|
|
98
|
+
return results as FakeShellResult;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const run = (input: FakeShellInput): ShellRunOutput => {
|
|
102
|
+
calls.push(input);
|
|
103
|
+
return outputOf(next(input));
|
|
104
|
+
};
|
|
105
|
+
async function* stream(input: FakeShellInput): AsyncGenerator<ShellStreamItem> {
|
|
106
|
+
streams.push(input);
|
|
107
|
+
const output = outputOf(next(input));
|
|
108
|
+
for (const line of linesOf(output.stdout)) yield { type: "stdout", line };
|
|
109
|
+
for (const line of linesOf(output.stderr)) yield { type: "stderr", line };
|
|
110
|
+
const exit: Extract<ShellStreamItem, { type: "exit" }> = { type: "exit", exitCode: output.exitCode, duration: output.duration };
|
|
111
|
+
if (output.signal !== undefined) exit.signal = output.signal;
|
|
112
|
+
yield exit;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const procedures = shellModule.procedures.map((procedure): AnyProcedure => {
|
|
116
|
+
switch (procedure.path.join(".")) {
|
|
117
|
+
case "shell.run":
|
|
118
|
+
return { ...procedure, handler: async (input: FakeShellInput) => run(input) } as AnyProcedure;
|
|
119
|
+
case "shell.stream":
|
|
120
|
+
return { ...procedure, handler: stream } as AnyProcedure;
|
|
121
|
+
case "shell.exec":
|
|
122
|
+
return {
|
|
123
|
+
...procedure,
|
|
124
|
+
handler: async () => {
|
|
125
|
+
throw new Error("fakeShell: shell.exec runs a shell string, and a wrapper must call shell.run with argv");
|
|
126
|
+
},
|
|
127
|
+
} as AnyProcedure;
|
|
128
|
+
default:
|
|
129
|
+
return procedure;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const refuse = (path: readonly string[]): Error => new Error(`fakeShell: the fake client answers only shell.run and shell.stream, not ${path.join(".")}`);
|
|
134
|
+
const client = {
|
|
135
|
+
async call(path: readonly string[], input: unknown): Promise<unknown> {
|
|
136
|
+
const key = path.join(".");
|
|
137
|
+
if (key === "shell.run") return run(input as FakeShellInput);
|
|
138
|
+
if (key === "shell.stream") {
|
|
139
|
+
let last: ShellStreamItem | undefined;
|
|
140
|
+
for await (const item of stream(input as FakeShellInput)) last = item;
|
|
141
|
+
return last;
|
|
142
|
+
}
|
|
143
|
+
throw refuse(path);
|
|
144
|
+
},
|
|
145
|
+
stream(path: readonly string[], input: unknown): AsyncIterable<unknown> {
|
|
146
|
+
if (path.join(".") === "shell.stream") return stream(input as FakeShellInput);
|
|
147
|
+
throw refuse(path);
|
|
148
|
+
},
|
|
149
|
+
} as unknown as ProcedureContext["client"];
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
calls,
|
|
153
|
+
streams,
|
|
154
|
+
procedures,
|
|
155
|
+
module: defineModule("@procedures/shell", procedures),
|
|
156
|
+
client,
|
|
157
|
+
args(at = 0): string[] {
|
|
158
|
+
const call = calls[at];
|
|
159
|
+
if (call === undefined) throw new Error(`fakeShell: there is no shell.run call ${at} (${calls.length} calls)`);
|
|
160
|
+
return call.args;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|