nomoreide 0.1.24 → 0.1.26

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.
Files changed (35) hide show
  1. package/dist/cli/git.d.ts +1 -1
  2. package/dist/core/config-files.js.map +1 -1
  3. package/dist/core/config-store.js +2 -0
  4. package/dist/core/config-store.js.map +1 -1
  5. package/dist/core/env-file.js +3 -2
  6. package/dist/core/env-file.js.map +1 -1
  7. package/dist/core/error-inbox.d.ts +20 -0
  8. package/dist/core/error-inbox.js +21 -6
  9. package/dist/core/error-inbox.js.map +1 -1
  10. package/dist/core/git-graph-layout.js +1 -1
  11. package/dist/core/git-graph-layout.js.map +1 -1
  12. package/dist/core/process-manager.js +1 -1
  13. package/dist/core/process-manager.js.map +1 -1
  14. package/dist/core/repro-bundle.d.ts +30 -0
  15. package/dist/core/repro-bundle.js +116 -0
  16. package/dist/core/repro-bundle.js.map +1 -0
  17. package/dist/core/test-runner.d.ts +69 -0
  18. package/dist/core/test-runner.js +199 -0
  19. package/dist/core/test-runner.js.map +1 -0
  20. package/dist/core/types.d.ts +2 -0
  21. package/dist/web/client/assets/{index-DNUDJ5VX.js → index-DQdQ7efQ.js} +17 -17
  22. package/dist/web/client/index.html +1 -1
  23. package/dist/web/dashboard.d.ts +3 -3
  24. package/dist/web/routes/context.d.ts +4 -0
  25. package/dist/web/routes/context.js.map +1 -1
  26. package/dist/web/routes/errors-routes.js +13 -0
  27. package/dist/web/routes/errors-routes.js.map +1 -1
  28. package/dist/web/routes/service-routes.js +39 -0
  29. package/dist/web/routes/service-routes.js.map +1 -1
  30. package/dist/web/server.js +10 -0
  31. package/dist/web/server.js.map +1 -1
  32. package/dist/web/ui-lifecycle.js +0 -1
  33. package/dist/web/ui-lifecycle.js.map +1 -1
  34. package/dist/web/usage-info.js.map +1 -1
  35. package/package.json +4 -1
@@ -0,0 +1,199 @@
1
+ import { spawn } from "node:child_process";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ const DEFAULT_TEST_COMMAND = "npm test";
4
+ /** The log channel a service's test output is appended under. */
5
+ export function testChannel(service) {
6
+ return `${service}:test`;
7
+ }
8
+ /**
9
+ * Runs a service's test command, streaming output into {@link LogStore} under a
10
+ * synthetic `<service>:test` channel so the {@link ErrorInbox} (which subscribes
11
+ * to the log store) turns assertion failures and stack traces into incidents.
12
+ * On a non-zero exit it appends a synthetic summary line so every failed run
13
+ * also surfaces a top-level incident.
14
+ */
15
+ export class TestRunner {
16
+ logStore;
17
+ configStore;
18
+ cwd;
19
+ runs = new Map();
20
+ children = new Map();
21
+ listeners = new Map();
22
+ nextId = 1;
23
+ /** Serialises log appends so the ErrorInbox sees lines in order. */
24
+ appendChain = Promise.resolve();
25
+ constructor(options) {
26
+ this.logStore = options.logStore;
27
+ this.configStore = options.configStore;
28
+ this.cwd = options.cwd ?? process.cwd();
29
+ }
30
+ isRunning(service) {
31
+ return this.runs.get(service)?.status === "running";
32
+ }
33
+ /** The current or most recent run for a service. */
34
+ current(service) {
35
+ return this.runs.get(service);
36
+ }
37
+ subscribe(service, listener) {
38
+ let set = this.listeners.get(service);
39
+ if (!set) {
40
+ set = new Set();
41
+ this.listeners.set(service, set);
42
+ }
43
+ set.add(listener);
44
+ return () => set?.delete(listener);
45
+ }
46
+ /** Spawn the test command for a service. Rejects if a run is already active. */
47
+ async run(service, pattern) {
48
+ if (this.isRunning(service)) {
49
+ throw new Error(`A test run is already in progress for ${service}.`);
50
+ }
51
+ const { command, cwd } = await this.resolveCommand(service, pattern);
52
+ const run = {
53
+ id: this.nextId++,
54
+ service,
55
+ command,
56
+ pattern: pattern || undefined,
57
+ status: "running",
58
+ startedAt: new Date().toISOString(),
59
+ failingCount: 0,
60
+ };
61
+ this.runs.set(service, run);
62
+ this.emit(service, { type: "status", run });
63
+ const child = spawn(command, {
64
+ cwd,
65
+ detached: process.platform !== "win32",
66
+ env: process.env,
67
+ shell: true,
68
+ stdio: ["ignore", "pipe", "pipe"],
69
+ });
70
+ this.children.set(service, child);
71
+ const buffers = { stdout: "", stderr: "" };
72
+ const onChunk = (stream) => (chunk) => {
73
+ buffers[stream] += chunk.toString();
74
+ const lines = buffers[stream].split(/\r?\n/);
75
+ buffers[stream] = lines.pop() ?? "";
76
+ for (const line of lines)
77
+ this.handleLine(service, run, stream, line);
78
+ };
79
+ child.stdout?.on("data", onChunk("stdout"));
80
+ child.stderr?.on("data", onChunk("stderr"));
81
+ child.once("error", (error) => {
82
+ this.flush(service, run, buffers);
83
+ this.finish(service, run, { status: "error", message: error.message });
84
+ });
85
+ child.once("exit", (exitCode, signal) => {
86
+ this.flush(service, run, buffers);
87
+ run.exitCode = exitCode;
88
+ if (exitCode === 0) {
89
+ this.finish(service, run, { status: "passed" });
90
+ }
91
+ else {
92
+ const detail = run.failingCount ? `${run.failingCount} failing` : "non-zero exit";
93
+ this.finish(service, run, {
94
+ status: "failed",
95
+ message: `ERROR: ${service} tests failed (${detail}) — exit ${exitCode ?? signal ?? "?"} — ${command}`,
96
+ });
97
+ }
98
+ });
99
+ return run;
100
+ }
101
+ /** Terminate the active run for a service, if any. */
102
+ stop(service) {
103
+ const child = this.children.get(service);
104
+ if (!child?.pid)
105
+ return;
106
+ try {
107
+ if (process.platform !== "win32")
108
+ process.kill(-child.pid, "SIGTERM");
109
+ else
110
+ process.kill(child.pid, "SIGTERM");
111
+ }
112
+ catch {
113
+ // The process may already have exited.
114
+ }
115
+ }
116
+ handleLine(service, run, stream, text) {
117
+ if (!text.trim())
118
+ return;
119
+ countFailures(run, text);
120
+ this.append(service, stream, text);
121
+ this.emit(service, { type: "output", run, line: { stream, text } });
122
+ }
123
+ flush(service, run, buffers) {
124
+ for (const stream of ["stdout", "stderr"]) {
125
+ const remaining = buffers[stream];
126
+ buffers[stream] = "";
127
+ if (remaining.trim())
128
+ this.handleLine(service, run, stream, remaining);
129
+ }
130
+ }
131
+ finish(service, run, result) {
132
+ if (result.message)
133
+ this.append(service, "stderr", result.message);
134
+ // Emit the terminal status only once all queued log writes have flushed,
135
+ // so a finished run reflects fully-persisted output (no write races).
136
+ void this.appendChain.then(() => {
137
+ run.status = result.status;
138
+ run.endedAt = new Date().toISOString();
139
+ this.children.delete(service);
140
+ this.emit(service, { type: "status", run });
141
+ });
142
+ }
143
+ /** Append to the synthetic test channel, preserving order for the ErrorInbox. */
144
+ append(service, stream, text) {
145
+ const channel = testChannel(service);
146
+ this.appendChain = this.appendChain
147
+ .then(() => this.logStore.append(channel, stream, text))
148
+ .catch(() => { });
149
+ }
150
+ emit(service, event) {
151
+ const set = this.listeners.get(service);
152
+ if (!set)
153
+ return;
154
+ for (const listener of set) {
155
+ try {
156
+ listener(event);
157
+ }
158
+ catch {
159
+ // ignore listener errors
160
+ }
161
+ }
162
+ }
163
+ async resolveCommand(service, pattern) {
164
+ let command = DEFAULT_TEST_COMMAND;
165
+ let serviceCwd = this.cwd;
166
+ try {
167
+ const config = await this.configStore.load();
168
+ const definition = config.services.find((item) => item.name === service);
169
+ if (!definition)
170
+ throw new Error(`Service ${service} is not registered.`);
171
+ command = definition.test?.trim() || DEFAULT_TEST_COMMAND;
172
+ if (definition.cwd) {
173
+ serviceCwd = isAbsolute(definition.cwd)
174
+ ? definition.cwd
175
+ : resolve(this.cwd, definition.cwd);
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (error instanceof Error && error.message.includes("not registered"))
180
+ throw error;
181
+ // Otherwise fall back to defaults rooted at the workspace.
182
+ }
183
+ if (pattern?.trim())
184
+ command = `${command} ${pattern.trim()}`;
185
+ return { command, cwd: serviceCwd };
186
+ }
187
+ }
188
+ const FAIL_LINE = /^\s*(?:FAIL|✗|×)\s/;
189
+ const SUMMARY_FAILED = /(\d+)\s+failed/i;
190
+ function countFailures(run, text) {
191
+ const summary = SUMMARY_FAILED.exec(text);
192
+ if (summary) {
193
+ run.failingCount = Math.max(run.failingCount, Number(summary[1]));
194
+ return;
195
+ }
196
+ if (FAIL_LINE.test(text))
197
+ run.failingCount += 1;
198
+ }
199
+ //# sourceMappingURL=test-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-runner.js","sourceRoot":"","sources":["../../src/core/test-runner.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,8 @@ export interface ServiceDefinition {
6
6
  kind?: ServiceKind;
7
7
  port?: number;
8
8
  description?: string;
9
+ /** Test Runner command; defaults to `npm test` when absent. */
10
+ test?: string;
9
11
  command?: string;
10
12
  cwd?: string;
11
13
  env?: Record<string, string>;