@mercury-fw/cli 0.46.1 → 0.47.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @mercury-fw/cli
2
2
 
3
+ ## 0.47.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e005ca4: - `mfw e2e`: a REPL that exits before its stderr has been read no longer reports "exited with code N:" without the reason.
8
+ - @mercury-fw/core@0.47.1
9
+ - @mercury-fw/utils@0.47.1
10
+
11
+ ## 0.47.0
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [3d73e59]
16
+ - @mercury-fw/core@0.47.0
17
+ - @mercury-fw/utils@0.47.0
18
+
3
19
  ## 0.46.1
4
20
 
5
21
  ### Patch Changes
@@ -10,6 +10,20 @@ export type ReplSessionOptions = {
10
10
  name: string;
11
11
  /** How long one turn may take. */
12
12
  timeoutMs: number;
13
+ /** Starts the REPL; a test hands in a stand-in process. */
14
+ spawn?: (argv: string[], cwd: string) => ReplProcess;
15
+ };
16
+ /** The parts of a spawned process the session uses. */
17
+ export type ReplProcess = {
18
+ stdin: {
19
+ write: (text: string) => unknown;
20
+ flush: () => unknown;
21
+ end: () => unknown;
22
+ };
23
+ stdout: ReadableStream<Uint8Array>;
24
+ stderr: ReadableStream<Uint8Array>;
25
+ exited: Promise<number>;
26
+ kill: () => void;
13
27
  };
14
28
  /** Starts the REPL and returns the session over it once the REPL is ready
15
29
  * (its first prompt is out), so a turn's time is only the turn's; throws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/cli",
3
- "version": "0.46.1",
3
+ "version": "0.47.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,21 +41,21 @@
41
41
  "comment:shape": "The Mercury CLI (`mfw`). `mfw create <dir>` writes a new Mercury app from the template. The framework packages move in lockstep with it, so a new app gets them at the CLI's own version; plugins and channels at the registry's latest. The plugin and channel packages are devDependencies only for the catalog test. `main(argv)` is exported for create-mercury-agent.",
42
42
  "dependencies": {
43
43
  "@clack/prompts": "^1.8.1",
44
- "@mercury-fw/core": "0.46.1",
45
- "@mercury-fw/utils": "0.46.1",
44
+ "@mercury-fw/core": "0.47.1",
45
+ "@mercury-fw/utils": "0.47.1",
46
46
  "commander": "^15.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@mercury-fw/auth-oidc": "0.1.1",
49
+ "@mercury-fw/auth-oidc": "0.2.0",
50
50
  "@mercury-fw/auth-static": "0.2.1",
51
51
  "@mercury-fw/channel-google-chat": "0.7.0",
52
- "@mercury-fw/channel-http": "0.8.2",
52
+ "@mercury-fw/channel-http": "0.8.3",
53
53
  "@mercury-fw/directory-static": "0.1.1",
54
- "@mercury-fw/formatter": "0.46.1",
54
+ "@mercury-fw/formatter": "0.47.1",
55
55
  "@mercury-fw/plugin-atlassian-admin": "0.3.2",
56
56
  "@mercury-fw/plugin-bitbucket": "0.4.1",
57
57
  "@mercury-fw/plugin-jira": "0.6.2",
58
- "@mercury-fw/plugin-zitadel": "0.3.1",
58
+ "@mercury-fw/plugin-zitadel": "0.3.2",
59
59
  "@mercury-fw/typescript-config": "*",
60
60
  "@types/bun": "^1.4.2",
61
61
  "typescript": "^6.0.3"
@@ -22,13 +22,31 @@ export type ReplSessionOptions = {
22
22
  name: string;
23
23
  /** How long one turn may take. */
24
24
  timeoutMs: number;
25
+ /** Starts the REPL; a test hands in a stand-in process. */
26
+ spawn?: (argv: string[], cwd: string) => ReplProcess;
25
27
  };
26
28
 
29
+ /** The parts of a spawned process the session uses. */
30
+ export type ReplProcess = {
31
+ stdin: { write: (text: string) => unknown; flush: () => unknown; end: () => unknown };
32
+ stdout: ReadableStream<Uint8Array>;
33
+ stderr: ReadableStream<Uint8Array>;
34
+ exited: Promise<number>;
35
+ kill: () => void;
36
+ };
37
+
38
+ /** How long, once the REPL is gone, its stderr may take to be read to its end. */
39
+ const STDERR_DRAIN_MS = 1000;
40
+
41
+ function spawnRepl(argv: string[], cwd: string): ReplProcess {
42
+ return Bun.spawn(argv, { cwd, stdin: "pipe", stdout: "pipe", stderr: "pipe" });
43
+ }
44
+
27
45
  /** Starts the REPL and returns the session over it once the REPL is ready
28
46
  * (its first prompt is out), so a turn's time is only the turn's; throws
29
47
  * when the REPL exits or doesn't get there in time. */
30
48
  export async function openReplSession(opts: ReplSessionOptions): Promise<Session<ReplReply>> {
31
- const proc = Bun.spawn(opts.argv, { cwd: opts.cwd, stdin: "pipe", stdout: "pipe", stderr: "pipe" });
49
+ const proc = (opts.spawn ?? spawnRepl)(opts.argv, opts.cwd);
32
50
  let stdout = "";
33
51
  let stderr = "";
34
52
  let exited: number | undefined;
@@ -42,8 +60,11 @@ export async function openReplSession(opts: ReplSessionOptions): Promise<Session
42
60
  }
43
61
  };
44
62
  void pump(proc.stdout, (t) => (stdout += t));
45
- void pump(proc.stderr, (t) => (stderr += t));
46
- void proc.exited.then((code) => {
63
+ const stderrRead = pump(proc.stderr, (t) => (stderr += t)).catch(() => {});
64
+ // The exit counts once stderr is read to its end, or the reason it gives is
65
+ // lost; a grandchild still holding the pipe open bounds the wait.
66
+ void proc.exited.then(async (code) => {
67
+ await Promise.race([stderrRead, new Promise((r) => setTimeout(r, STDERR_DRAIN_MS))]);
47
68
  exited = code;
48
69
  notify();
49
70
  });