@unotest/core 0.9.1 → 0.10.0

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
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to `@unotest/core` are documented in this file.
4
4
 
5
+ ## [0.10.0] - 2026-08-06
6
+
7
+ ### Patch Changes
8
+
9
+ - 574841f: Dedup pass across the tree (jscpd 0 clones, ratchet threshold 0). New public export in `@unotest/grounder-client`: `intEnvInRange(env, name, def, min, max)` — the `UNOTEST_GROUNDER_*` integer-env parser shared by the consumer side and the grounder server. Everything else is internal refactoring with no behavior change: protocol gains a shared fs-safe-name validator behind `validateCollectionName` / `validateSegmentName` and a shared env-file assignment iterator; core's `RuntimeInspection` becomes a `Pick` of `RuntimeStateFile`; dsl's `ExecutionWalker` threads one `WalkCtx` object instead of an eight-argument clump and AST nodes use constructor parameter properties; web deduplicates the MCP tool scaffolds, action plugins, DSL contracts, perception LRU caches, and semantic-dom grouping passes.
10
+ - Updated dependencies [7ed750c]
11
+ - Updated dependencies [574841f]
12
+ - @unotest/dsl@0.10.0
13
+ - @unotest/protocol@0.10.0
14
+
15
+ ## [0.9.2] - 2026-06-16
16
+
17
+ Released in lockstep with the rest of the ecosystem. No functional changes.
18
+
5
19
  ## [0.9.1] - 2026-06-14
6
20
 
7
21
  - Debug control plane extracted from `@unotest/web` / `@unotest/mobile`: the
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RunArtifact, RunManifest, RunSources, RuntimeState, RuntimeStateFile, DbgCommand, PickerMode, DbgCommandRecord, DbgCommandProbe } from '@unotest/protocol';
1
+ import { RunArtifact, RunManifest, RunSources, RuntimeStateFile, RuntimeState, DbgCommand, PickerMode, DbgCommandRecord, DbgCommandProbe } from '@unotest/protocol';
2
2
  import { ExecutionEvent } from '@unotest/dsl/executor';
3
3
 
4
4
  interface IRunArtifactWriter {
@@ -78,23 +78,8 @@ declare function writeRunSources(input: WriteRunSourcesInput): Promise<void>;
78
78
  type RuntimeExecState = "starting" | "running" | "paused-step" | "paused-failure" | "completed" | "failed" | "aborted";
79
79
  /** Normalized, protocol-shaped runtime view assembled from a manager snapshot.
80
80
  * Feeds straight into `RuntimeStateFile` (the writer adds runId / wsEndpoint /
81
- * wheel / updatedAt). */
82
- interface RuntimeInspection {
83
- scenario: string;
84
- testFn: string;
85
- state: RuntimeState;
86
- current: {
87
- line: number;
88
- col: number;
89
- } | null;
90
- vars: Record<string, string>;
91
- callStack: {
92
- fn: string;
93
- file: string;
94
- line: number;
95
- }[];
96
- lastFailure: RuntimeStateFile["lastFailure"];
97
- }
81
+ * wheel / updatedAt) — hence a Pick, not a re-spelled mirror. */
82
+ type RuntimeInspection = Pick<RuntimeStateFile, "scenario" | "testFn" | "state" | "current" | "vars" | "callStack" | "lastFailure">;
98
83
  /** Raw inputs from a `TestRuntimeManager.inspect()` snapshot, before
99
84
  * protocol-normalization. `lastFailure` is the manager's richer record; only
100
85
  * line/col/message survive into the protocol form. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unotest/core",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Shared runner-side utilities for the @unotest ecosystem: JSONL run-artifact writer (steps.jsonl + heartbeat), atomic run-manifest + run-sources writers, atomic runtime-state writer with a protocol-normalizing runtime-inspection helper, layered .env reader/applier (target + environment axes), idempotent .gitignore updater. Used by @unotest/web and @unotest/mobile; depends on @unotest/protocol plus a type-only import of @unotest/dsl/executor event types (M-20). Unlike protocol (pure data), this package owns the thin filesystem layer both runners need.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -22,14 +22,15 @@
22
22
  },
23
23
  "files": [
24
24
  "dist",
25
+ "!dist/**/*.map",
25
26
  "CHANGELOG.md",
26
27
  "LICENSE",
27
28
  "README.md"
28
29
  ],
29
30
  "dependencies": {
30
31
  "chokidar": "^4.0.3",
31
- "@unotest/protocol": "^0.9.1",
32
- "@unotest/dsl": "^0.9.1"
32
+ "@unotest/dsl": "^0.10.0",
33
+ "@unotest/protocol": "^0.10.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^22.10.0",
@@ -41,7 +42,9 @@
41
42
  "typecheck": "tsc --noEmit",
42
43
  "test": "node --import tsx --test --test-reporter=spec 'src/**/*.test.ts'",
43
44
  "compile": "tsup",
44
- "verify": "pnpm typecheck && pnpm test",
45
- "build": "pnpm verify && pnpm compile"
45
+ "verify": "pnpm typecheck && pnpm check:no-cyrillic && pnpm test",
46
+ "build": "pnpm verify && pnpm compile && pnpm check:tarball",
47
+ "check:no-cyrillic": "node scripts/check-no-cyrillic.mjs",
48
+ "check:tarball": "node scripts/check-tarball.mjs"
46
49
  }
47
50
  }
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/run-artifact-writer.ts","../src/run-manifest-writer.ts","../src/run-sources-writer.ts","../src/runtime-inspection.ts","../src/runtime-state-writer.ts","../src/debug-commands-watcher.ts","../src/cli-flags.ts","../src/debugger-file.ts","../src/gitignore-updater.ts","../src/env-layers.ts"],"sourcesContent":["// Append-only JSONL writer for a run dir's `steps.jsonl`. One line per\n// RunArtifact event. Heartbeat file mtime-bumped on a timer so the\n// viewer can detect interrupted runs (design-v0: 30s without bump →\n// interrupted). ONE implementation for every runner (Phase-5 dedup) —\n// web/mobile re-export it; the per-target runs root (`.runs<suffix>`,\n// M-10) is the CALLER's concern via `runDir`.\n//\n// Pure data sink — generic over event source. The \"translate\n// ExecutionEvent → RunArtifact\" concern lives in each runner's tap\n// (SRP: writer just serializes).\n\nimport { HEARTBEAT_FILE, STEPS_FILE, type RunArtifact } from \"@unotest/protocol\";\nimport { appendFile, mkdir, utimes, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface IRunArtifactWriter {\n emit(event: RunArtifact): Promise<void>;\n close(): Promise<void>;\n}\n\n/** Deep-redacts secret values out of a JSON-ish structure. Structural —\n * web's SecretRegistry satisfies it as-is; runners without a secrets\n * mechanism simply pass nothing. */\nexport interface ArtifactRedactor {\n redact<T>(value: T): T;\n}\n\nexport interface JsonlRunArtifactWriterOptions {\n runDir: string;\n /** Interval between heartbeat-file mtime bumps. Default 5s\n * (matches design-v0: viewer flags interrupted at 30s threshold). */\n heartbeatIntervalMs?: number;\n /** Override Date.now for deterministic tests. */\n nowFn?: () => number;\n /** Redacts secret values out of each event before it lands in\n * steps.jsonl (the viewer reads this stream). */\n redactor?: ArtifactRedactor;\n}\n\nexport class JsonlRunArtifactWriter implements IRunArtifactWriter {\n private readonly stepsPath: string;\n private readonly heartbeatPath: string;\n private readonly nowFn: () => number;\n private readonly redactor: ArtifactRedactor | undefined;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private closed = false;\n /** Tracks whether a `run:finished` event has been emitted. close()\n * synthesizes one with outcome='aborted' if not — covers abort-paths\n * where the tap's natural emit never fires (e.g. paused generator\n * closed externally). Without this the viewer would see the run\n * stuck in \"running\" forever after a Stop click. */\n private runFinishedEmitted = false;\n /** If a step:fail was emitted before close, the synthesized\n * run:finished uses outcome='failed' instead of 'aborted' — the run\n * HAD a real failure, the user just gave up at the pause. */\n private sawStepFail = false;\n\n constructor(opts: JsonlRunArtifactWriterOptions) {\n this.stepsPath = join(opts.runDir, STEPS_FILE);\n this.heartbeatPath = join(opts.runDir, HEARTBEAT_FILE);\n this.nowFn = opts.nowFn ?? Date.now;\n this.redactor = opts.redactor;\n // Heartbeat in the background while the writer is open.\n const interval = opts.heartbeatIntervalMs ?? 5_000;\n if (interval > 0) {\n // Initial touch so the file exists immediately; viewer can\n // distinguish \"no heartbeat\" from \"stale heartbeat\".\n void this.touchHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n void this.touchHeartbeat();\n }, interval);\n // Don't keep the event loop alive solely for the heartbeat.\n this.heartbeatTimer.unref();\n }\n }\n\n async emit(event: RunArtifact): Promise<void> {\n if (this.closed) return;\n if (event.kind === \"run:finished\") this.runFinishedEmitted = true;\n if (event.kind === \"step:fail\") this.sawStepFail = true;\n const safe = this.redactor ? this.redactor.redact(event) : event;\n await appendFile(this.stepsPath, JSON.stringify(safe) + \"\\n\", \"utf8\");\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n // Synthesize a terminal `run:finished` if the producer didn't\n // already emit one — covers abort-paths where the tap was\n // suspended and never finalized. Viewer relies on this event to\n // transition the run out of \"running\" state.\n if (!this.runFinishedEmitted) {\n try {\n await appendFile(\n this.stepsPath,\n JSON.stringify({\n kind: \"run:finished\",\n t: this.nowFn(),\n // If a real step failure was recorded, the run conceptually\n // failed — the user just chose to give up at the paused-\n // failure point. Calling that \"aborted\" hides the actual\n // outcome from history; \"failed\" preserves it.\n outcome: this.sawStepFail ? \"failed\" : \"aborted\",\n }) + \"\\n\",\n \"utf8\",\n );\n } catch {\n // Best-effort — disk errors don't block shutdown.\n }\n this.runFinishedEmitted = true;\n }\n this.closed = true;\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n // Mark the run not-live IMMEDIATELY: backdate the heartbeat so attach\n // discovery / list_runtimes / the viewer see it as stale at once, instead\n // of treating a finished or aborted run as alive for the whole stale-window\n // after the runner's last bump. Backdated (not deleted) so \"had a heartbeat,\n // now stale\" stays distinct from \"never started (no heartbeat)\".\n try {\n const stale = new Date(this.nowFn() - 3_600_000);\n await utimes(this.heartbeatPath, stale, stale);\n } catch {\n // Best-effort — a finished run not going stale instantly is non-fatal.\n }\n }\n\n private async touchHeartbeat(): Promise<void> {\n if (this.closed) return; // never resurrect the heartbeat after close()\n const ts = this.nowFn();\n try {\n // Create file if missing, then bump mtime via `utimes`. We do\n // both in case the file was wiped between bumps (manual cleanup).\n await writeFile(this.heartbeatPath, \"\", { flag: \"a\" });\n await utimes(this.heartbeatPath, new Date(ts), new Date(ts));\n } catch {\n // Heartbeat is best-effort — never block a run on a write fail.\n }\n }\n}\n\n/** Convenience: create `runDir`, emit `run:started`, return a writer\n * ready for subsequent events. Callers compute `runDir` themselves —\n * it carries the per-target suffix (`unotest/.runs<suffix>/<runId>`). */\nexport async function createRunArtifactWriter(opts: {\n runDir: string;\n runId: string;\n scenario: string;\n nowFn?: () => number;\n redactor?: ArtifactRedactor;\n}): Promise<IRunArtifactWriter> {\n await mkdir(opts.runDir, { recursive: true });\n const writerOpts: JsonlRunArtifactWriterOptions = { runDir: opts.runDir };\n if (opts.nowFn !== undefined) writerOpts.nowFn = opts.nowFn;\n if (opts.redactor !== undefined) writerOpts.redactor = opts.redactor;\n const writer = new JsonlRunArtifactWriter(writerOpts);\n await writer.emit({\n kind: \"run:started\",\n t: (opts.nowFn ?? Date.now)(),\n runId: opts.runId,\n scenario: opts.scenario,\n });\n return writer;\n}\n","// Writes `manifest.json` into a run directory before any other\n// artifact lands. Lets the viewer's RunsRepository classify the dir\n// (scenario vs collection) without sniffing a possibly-empty\n// `steps.jsonl`. Atomic via tmp + rename so a concurrent reader never\n// sees a half-written JSON. ONE implementation for every runner\n// (Phase-5 dedup).\n\nimport { RUN_MANIFEST_FILE, type RunManifest } from \"@unotest/protocol\";\nimport { mkdir, rename, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface WriteManifestInput {\n /** Absolute path to the run dir (`unotest/.runs<suffix>/<runId>/`).\n * Created if absent. */\n runDir: string;\n manifest: RunManifest;\n}\n\nexport async function writeRunManifest(input: WriteManifestInput): Promise<void> {\n await mkdir(input.runDir, { recursive: true });\n const dest = join(input.runDir, RUN_MANIFEST_FILE);\n const tmp = `${dest}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;\n await writeFile(tmp, JSON.stringify(input.manifest, null, 2), \"utf8\");\n await rename(tmp, dest);\n}\n","// Writes `sources.json` into a scenario run directory, snapshotting the\n// entry + every loaded helper source as they were when the run started.\n// The viewer renders the run against this snapshot, never re-reading the\n// on-disk file (which may have been edited since). Atomic via tmp + rename\n// so a concurrent reader never sees a half-written JSON.\n//\n// Must land BEFORE the run-artifact-writer emits `run:started` — `file`\n// events reference keys that must already exist here. Both web and mobile\n// runners call this; the implementation lives once in core (M-15 dedup).\n\nimport {\n RUN_SOURCES_FILE,\n type RunSources,\n} from \"@unotest/protocol\";\nimport { mkdir, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nexport interface WriteRunSourcesInput {\n /** Absolute path to the scenario run dir (`unotest/.runs/<runId>/` or the\n * per-target equivalent). Created if absent. */\n runDir: string;\n sources: RunSources;\n}\n\nexport async function writeRunSources(input: WriteRunSourcesInput): Promise<void> {\n await mkdir(input.runDir, { recursive: true });\n const dest = join(input.runDir, RUN_SOURCES_FILE);\n const tmp = `${dest}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;\n await mkdir(dirname(tmp), { recursive: true });\n await writeFile(tmp, JSON.stringify(input.sources, null, 2), \"utf8\");\n await rename(tmp, dest);\n}\n","// Normalizes a runner's internal runtime state into the protocol shape the\n// viewer / MCP / CLI read from `runtime.json`. Shared by `@unotest/web` and\n// `@unotest/mobile` (M-20): both walk the SAME `ExecutionEvent` stream\n// (`@unotest/dsl/executor`) and the SAME 7-state runtime machine, so the\n// mapping to protocol `RuntimeState` / `current` / `callStack` lives once.\n//\n// Type-only dependency on `@unotest/dsl/executor` (M-20 widens core's surface\n// from \"protocol-only\" to \"protocol + dsl event types\"). Var STRINGIFICATION\n// stays in the caller — web renders LocatorValue as `<…>`, mobile is plain\n// JSON — so callers pass a `stringifyVar` hook.\n\nimport type { RuntimeState, RuntimeStateFile } from \"@unotest/protocol\";\nimport { isTerminalRuntimeState } from \"@unotest/protocol\";\nimport type { ExecutionEvent } from \"@unotest/dsl/executor\";\n\n/** The runner's internal runtime-state machine — canonical here so the web /\n * mobile `TestRuntimeManager`s share one definition (M-20). Distinct from\n * protocol `RuntimeState` (8 values): this is what the manager tracks; the\n * protocol form additionally discriminates `paused-breakpoint`. */\nexport type RuntimeExecState =\n | \"starting\"\n | \"running\"\n | \"paused-step\"\n | \"paused-failure\"\n | \"completed\"\n | \"failed\"\n | \"aborted\";\n\n/** Normalized, protocol-shaped runtime view assembled from a manager snapshot.\n * Feeds straight into `RuntimeStateFile` (the writer adds runId / wsEndpoint /\n * wheel / updatedAt). */\nexport interface RuntimeInspection {\n scenario: string;\n testFn: string;\n state: RuntimeState;\n current: { line: number; col: number } | null;\n vars: Record<string, string>;\n callStack: { fn: string; file: string; line: number }[];\n lastFailure: RuntimeStateFile[\"lastFailure\"];\n}\n\n/** Raw inputs from a `TestRuntimeManager.inspect()` snapshot, before\n * protocol-normalization. `lastFailure` is the manager's richer record; only\n * line/col/message survive into the protocol form. */\nexport interface RuntimeInspectionInput {\n scenario: string;\n testFn: string;\n state: RuntimeExecState;\n vars: Record<string, unknown>;\n lastEvent: ExecutionEvent | null;\n lastFailure: {\n line: number;\n col: number;\n error: { message: string; name?: string };\n } | null;\n}\n\n/** Per-var byte cap. JSON-encoded var values longer than this are suffixed\n * with `…` and the rest dropped, keeping the file manageable. 4KB fits most\n * useful payloads (small objects, fixture JSON, response bodies) while\n * truncating absurd values (an entire HTML document, etc). */\nconst VAR_TRUNCATE_BYTES = 4096;\n\nexport function buildRuntimeInspection(\n input: RuntimeInspectionInput,\n stringifyVar: (v: unknown) => string,\n): RuntimeInspection {\n return {\n scenario: input.scenario,\n testFn: input.testFn,\n state: toProtocolRuntimeState(input.state, input.lastEvent),\n current: currentLocation(input.state, input.lastEvent),\n vars: serializeVars(input.vars, stringifyVar),\n callStack: extractCallStack(input.lastEvent),\n lastFailure: input.lastFailure\n ? {\n line: input.lastFailure.line,\n col: input.lastFailure.col,\n error: { message: input.lastFailure.error.message },\n }\n : null,\n };\n}\n\nexport function toProtocolRuntimeState(\n state: RuntimeExecState,\n lastEvent: ExecutionEvent | null,\n): RuntimeState {\n switch (state) {\n case \"starting\":\n return \"starting\";\n case \"running\":\n return \"running\";\n case \"paused-failure\":\n return \"paused-failure\";\n case \"paused-step\":\n // Discriminate breakpoint vs explicit step via the pausing event.\n if (\n lastEvent &&\n lastEvent.kind === \"paused\" &&\n lastEvent.reason === \"breakpoint\"\n ) {\n return \"paused-breakpoint\";\n }\n return \"paused-step\";\n case \"completed\":\n return \"completed\";\n case \"failed\":\n return \"failed\";\n case \"aborted\":\n return \"aborted\";\n }\n}\n\nexport function currentLocation(\n state: RuntimeExecState,\n lastEvent: ExecutionEvent | null,\n): { line: number; col: number } | null {\n if (isTerminalRuntimeState(state)) {\n return null;\n }\n const ev = lastEvent;\n if (!ev) return null;\n if (ev.kind === \"step-start\" || ev.kind === \"step-end\" || ev.kind === \"paused\") {\n return { line: ev.line, col: ev.col };\n }\n return null;\n}\n\nexport function extractCallStack(\n ev: ExecutionEvent | null,\n): { fn: string; file: string; line: number }[] {\n // Executor attaches `callStack: readonly string[]` on step-start / step-end.\n // Per-frame file/line aren't plumbed through yet — emit fn names with\n // placeholders (deeper callStack metadata is a follow-up).\n if (!ev) return [];\n if (\n (ev.kind === \"step-start\" || ev.kind === \"step-end\") &&\n Array.isArray(ev.callStack)\n ) {\n return ev.callStack.map((fn) => ({ fn, file: \"\", line: 0 }));\n }\n return [];\n}\n\nfunction serializeVars(\n vars: Record<string, unknown>,\n stringifyVar: (v: unknown) => string,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, value] of Object.entries(vars)) {\n out[name] = truncate(stringifyVar(value));\n }\n return out;\n}\n\nfunction truncate(s: string): string {\n if (s.length <= VAR_TRUNCATE_BYTES) return s;\n return s.slice(0, VAR_TRUNCATE_BYTES) + \"…\";\n}\n","// Atomic-write JSON snapshot of the current runtime state to\n// `<runDir>/runtime.json`. Readers (viewer's `GET /api/runs/:runId/runtime`,\n// MCP `inspect_runtime`, CLI debug REPL) never observe a half-file because we\n// `writeFile(tmp) + rename(tmp, target)` — POSIX rename(2) is atomic on the\n// same filesystem.\n//\n// Generic over the runner (M-20): the caller supplies an `inspect()` closure\n// returning a protocol-normalized `RuntimeInspection` (built via\n// `buildRuntimeInspection`) plus an optional `extra()` for the web-only\n// `wsEndpoint` / `wheel` / `mode` fields and a `redact` hook for secrets. The\n// write cadence (immediate vs throttled) is decided by the caller, which knows\n// its own event stream — `writeNow()` for paused / step-end / terminal,\n// `writeThrottled()` for the running tick.\n\nimport { RUNTIME_FILE, type RuntimeStateFile } from \"@unotest/protocol\";\nimport { rename, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RuntimeInspection } from \"./runtime-inspection.ts\";\n\n/** Runner-specific extras merged into every snapshot. Evaluated on each write\n * so live values (the current wheel owner) stay fresh. */\nexport type RuntimeStateExtra = Partial<\n Pick<RuntimeStateFile, \"wsEndpoint\" | \"deviceSlot\" | \"wheel\" | \"mode\">\n>;\n\nexport interface RuntimeStateWriterDeps {\n /** Directory the file lands in (`unotest/.runs[-<suffix>]/<runId>/`). */\n runDir: string;\n /** Value of the `runId` field inside the file (web passes the manager's\n * runtime id, which differs from the on-disk run-dir id). */\n runId: string;\n /** Current normalized runtime inspection, or null if the runtime is gone\n * (already aborted) — then the write is skipped and the last file on disk\n * stays readable. */\n inspect: () => RuntimeInspection | null;\n /** Extra protocol fields (wsEndpoint / wheel / mode). */\n extra?: () => RuntimeStateExtra;\n /** Redacts secret values out of the serialized file before it hits disk\n * (the viewer reads this file, so a secret var must never land verbatim).\n * Identity if absent. */\n redact?: (file: RuntimeStateFile) => RuntimeStateFile;\n /** Min ms between throttled writes during running. Default 50. */\n throttleMs?: number;\n /** Override Date.now() for deterministic tests. */\n nowFn?: () => number;\n}\n\nexport interface RuntimeStateWriter {\n /** Write immediately — paused / step-end / terminal moments consumers\n * actually observe. */\n writeNow(): Promise<void>;\n /** Write only if at least throttleMs elapsed since the last write — the\n * running tick / step-start, where writing every one balloons disk I/O. */\n writeThrottled(): Promise<void>;\n /** Final flush — emit terminal state once more. Idempotent. */\n close(): Promise<void>;\n}\n\nexport function createRuntimeStateWriter(\n deps: RuntimeStateWriterDeps,\n): RuntimeStateWriter {\n const path = join(deps.runDir, RUNTIME_FILE);\n const tmpPath = `${path}.tmp`;\n const throttleMs = deps.throttleMs ?? 50;\n const now = deps.nowFn ?? Date.now;\n let lastWriteAt = 0;\n let closed = false;\n\n const writeNow = async (): Promise<void> => {\n if (closed) return;\n const insp = deps.inspect();\n if (!insp) return;\n const file: RuntimeStateFile = {\n runId: deps.runId,\n scenario: insp.scenario,\n testFn: insp.testFn,\n state: insp.state,\n current: insp.current,\n vars: insp.vars,\n callStack: insp.callStack,\n lastFailure: insp.lastFailure,\n ...(deps.extra ? deps.extra() : {}),\n updatedAt: now(),\n };\n try {\n const safe = deps.redact ? deps.redact(file) : file;\n await writeFile(tmpPath, JSON.stringify(safe), \"utf8\");\n await rename(tmpPath, path);\n lastWriteAt = now();\n } catch {\n // Best-effort — disk full / permission errors don't block the run.\n }\n };\n\n return {\n writeNow,\n async writeThrottled() {\n if (closed) return;\n if (now() - lastWriteAt >= throttleMs) await writeNow();\n },\n async close() {\n if (closed) return;\n await writeNow();\n closed = true;\n },\n };\n}\n","// Watches a run's `commands.jsonl` for `DbgCommand` lines appended by\n// viewer / MCP / CLI, and dispatches them to a runner-agnostic control\n// surface: a `DebugControlTarget` (step/resume/abort), the in-process\n// breakpoints set, and the pause flag. Shared by `@unotest/web` and\n// `@unotest/mobile` (the file-tail machinery + the common command dispatch\n// are identical); runner-specific commands (web's in-page picker / wheel /\n// record) are routed to optional capability hooks — absent ⇒ logged + ignored.\n//\n// Why a file, not a socket: @unotest/web (runner / CLI / MCP) and\n// @unotest/viewer live in separate repos and run as separate Node processes.\n// Any cross-process control channel that isn't the filesystem would couple\n// them. By tail-watching commands.jsonl we stay file-only.\n//\n// Reading strategy: chokidar emits a `change` event after each `appendFile`.\n// We hold an offset, read [offset, EOF), split by '\\n', parse each line.\n// Partial lines (writer mid-flush) are malformed JSON — we skip them; chokidar\n// fires again on the next write and we see the full line. Append on POSIX is\n// effectively line-atomic below pipe-buffer size (~4KB), which our commands\n// always are.\n\nimport type { DbgCommand, DbgCommandProbe, DbgCommandRecord, PickerMode } from \"@unotest/protocol\";\nimport { mkdir, writeFile, open, type FileHandle } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { watch as chokidarWatch, type FSWatcher } from \"chokidar\";\n\n/** Minimal logger contract — both runners' loggers satisfy it structurally. */\nexport interface DebugWatcherLogger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\n/** The pump-control surface the watcher drives. Runner binds its\n * `TestRuntimeManager` + runtimeId behind this; absent in an authoring-hold\n * session (no executor) — step/resume become no-ops, abort routes to\n * `onAbort`. */\nexport interface DebugControlTarget {\n step(): Promise<void>;\n resume(): Promise<void>;\n abort(): Promise<void>;\n}\n\nexport interface DebugCommandsWatcherDeps {\n /** Full path to `<runDir>/commands.jsonl`. */\n commandsPath: string;\n /** Pump control. Absent ⇒ step/resume no-op, abort ⇒ onAbort. */\n target?: DebugControlTarget;\n /** Same instance the executor reads via `RunOptions.breakpoints`. The\n * watcher mutates this set on `set-bp` / `clear-bp`; the executor checks\n * membership on the next step-start. */\n breakpoints: Set<string>;\n /** Same ref-object the executor reads via `RunOptions.pauseRequested`.\n * `pause`/`step` set `value=true`; `continue` clears it; the executor flips\n * it back to false after emitting the paused event. */\n pauseRequested: { value: boolean };\n logger: DebugWatcherLogger;\n /** Per-command hook — tests assert the watcher saw the writes. */\n onCommand?: (cmd: DbgCommand) => void;\n /** Abort handler for sessions without a `target` (authoring-hold). When set,\n * `abort` routes here instead of `target.abort()`. */\n onAbort?: () => void;\n // ---- Optional runner capabilities (web in-page picker / wheel / record) --\n /** Arm / re-mode the in-page picker over the live paused page. */\n onPickerStart?: (mode: PickerMode, execute: boolean) => Promise<void>;\n /** Tear down the picker. */\n onPickerStop?: () => Promise<void>;\n /** On-demand page-outline capture → `picker-snapshot.txt` (the viewer's\n * Snapshot tab). Mobile (Phase C) wires its tree-outline writer here. */\n onSnapshot?: () => Promise<void>;\n /** Agent-driven ref-safe record. Impl gates on wheel ownership. */\n onRecord?: (cmd: DbgCommandRecord) => Promise<void>;\n /** Evaluate a locator string against the live page and emit a `probe:result`\n * event. READ-ONLY, so it is NOT wheel-gated. Web-vocab specific (needs the\n * DSL parser + page), so it lives in the web runner, not core. */\n onProbe?: (cmd: DbgCommandProbe) => Promise<void>;\n /** Collaborative control-token change. Impl mutates its owner ref +\n * broadcasts. */\n onWheel?: (owner: \"agent\" | \"human\") => void;\n}\n\n/** Start tailing the commands file. The file is created (empty) if it doesn't\n * exist so chokidar has something to watch. Returns a stop function that\n * closes the watcher and releases the file handle. Idempotent stop. */\nexport async function startDebugCommandsWatcher(\n deps: DebugCommandsWatcherDeps,\n): Promise<() => Promise<void>> {\n // Ensure the parent dir + the file exist. The artifact writer normally\n // creates the parent first; this mkdir is a safety net for early-start\n // orderings.\n await mkdir(dirname(deps.commandsPath), { recursive: true });\n // `flag: 'a'` creates the file if absent, no-op if present — chokidar would\n // skip `change` events on a missing file otherwise.\n await writeFile(deps.commandsPath, \"\", { flag: \"a\" });\n\n let handle: FileHandle | null = await open(deps.commandsPath, \"r\");\n let offset = 0;\n let buffer = \"\"; // unflushed trailing partial line, if any\n let stopped = false;\n let inflight: Promise<void> = Promise.resolve();\n\n const readNew = async (): Promise<void> => {\n if (stopped || !handle) return;\n // Serialize reads — chokidar fires multiple `change` events in quick\n // succession for a single appendFile on some platforms.\n inflight = inflight.then(async () => {\n if (stopped || !handle) return;\n const stat = await handle.stat();\n if (stat.size <= offset) return;\n const len = stat.size - offset;\n const buf = Buffer.alloc(len);\n const { bytesRead } = await handle.read(buf, 0, len, offset);\n offset += bytesRead;\n buffer += buf.subarray(0, bytesRead).toString(\"utf8\");\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line.length === 0) continue;\n await dispatchLine(line, deps);\n }\n });\n await inflight;\n };\n\n // Process anything already in the file when we attach (a pre-seeded command).\n await readNew();\n\n const watcher: FSWatcher = chokidarWatch(deps.commandsPath, {\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: false,\n });\n watcher.on(\"change\", () => {\n void readNew();\n });\n watcher.on(\"add\", () => {\n void readNew();\n });\n\n return async function stop(): Promise<void> {\n if (stopped) return;\n stopped = true;\n await watcher.close();\n if (handle) {\n try {\n await handle.close();\n } catch {\n /* already gone */\n }\n handle = null;\n }\n };\n}\n\nasync function dispatchLine(\n line: string,\n deps: DebugCommandsWatcherDeps,\n): Promise<void> {\n let cmd: DbgCommand;\n try {\n cmd = JSON.parse(line) as DbgCommand;\n } catch {\n deps.logger.warn(`debug-commands: malformed line skipped: ${line.slice(0, 120)}`);\n return;\n }\n if (typeof cmd !== \"object\" || cmd === null || typeof cmd.cmd !== \"string\") {\n deps.logger.warn(`debug-commands: malformed command shape: ${line.slice(0, 120)}`);\n return;\n }\n deps.logger.info(`debug-commands: received ${cmd.cmd}`);\n deps.onCommand?.(cmd);\n switch (cmd.cmd) {\n case \"step\":\n // Step Over: execute exactly ONE statement, then halt. In mode='auto'\n // the executor only yields `paused` on BP / failure / explicit\n // pause-flag — so we arm the flag here; the walker checks it BEFORE the\n // next step-start, yields paused:'step', and resets the flag itself.\n deps.pauseRequested.value = true;\n deps.target?.step().catch((e) => {\n deps.logger.warn(`debug-commands: step failed: ${(e as Error).message}`);\n });\n return;\n case \"continue\":\n // Run until next BP / failure / end. Clear any stale pause-flag (e.g. a\n // prior 'pause' that landed during running — Continue overrides).\n deps.pauseRequested.value = false;\n deps.target?.resume().catch((e) => {\n deps.logger.warn(`debug-commands: continue failed: ${(e as Error).message}`);\n });\n return;\n case \"pause\":\n deps.pauseRequested.value = true;\n return;\n case \"set-bp\":\n deps.breakpoints.add(`${cmd.line}:${cmd.col}`);\n return;\n case \"clear-bp\":\n deps.breakpoints.delete(`${cmd.line}:${cmd.col}`);\n return;\n case \"abort\":\n // Authoring-hold session → tear down via onAbort; debug run → target.\n if (deps.onAbort) {\n deps.onAbort();\n return;\n }\n deps.target?.abort().catch((e) => {\n deps.logger.warn(`debug-commands: abort failed: ${(e as Error).message}`);\n });\n return;\n case \"picker:start\":\n runCapability(deps, \"picker:start\", deps.onPickerStart?.(cmd.mode, cmd.execute ?? false));\n return;\n case \"picker:stop\":\n runCapability(deps, \"picker:stop\", deps.onPickerStop?.());\n return;\n case \"snapshot\":\n runCapability(deps, \"snapshot\", deps.onSnapshot?.());\n return;\n case \"record\":\n runCapability(deps, \"record\", deps.onRecord?.(cmd));\n return;\n case \"probe\":\n runCapability(deps, \"probe\", deps.onProbe?.(cmd));\n return;\n case \"wheel\":\n if (deps.onWheel) deps.onWheel(cmd.owner);\n else deps.logger.warn(\"debug-commands: wheel ignored — no capability\");\n return;\n }\n}\n\n/** Run an optional capability handler: when the handler is absent (a runner\n * without this capability — e.g. mobile has no picker), log + ignore; when\n * present, attach an error handler so a throwing capability never crashes the\n * watcher. */\nfunction runCapability(\n deps: DebugCommandsWatcherDeps,\n label: string,\n result: Promise<void> | undefined,\n): void {\n if (result === undefined) {\n deps.logger.warn(`debug-commands: ${label} ignored — no capability`);\n return;\n }\n result.catch((e) => {\n deps.logger.warn(`debug-commands: ${label} failed: ${(e as Error).message}`);\n });\n}\n","// Shared `e2e <name> [--debug] [--break l:c,…]` flag parser. Identical across\n// web and mobile runners (M-20 dedup) — both spawn the same debug flags from\n// their adapter's `buildArgs`.\n\nexport interface E2EFlags {\n positional: string[];\n debug: boolean;\n /** \"line:col\" entries from `--break a:b,c:d` (comma-separated, no spaces).\n * Empty when --break wasn't passed — caller falls back to the\n * `.debugger[-suffix].json` breakpoint store. */\n breakFlag: string[];\n}\n\nexport function parseE2EFlags(args: string[]): E2EFlags {\n const positional: string[] = [];\n let debug = false;\n const breakFlag: string[] = [];\n for (let i = 0; i < args.length; i++) {\n const a = args[i]!;\n if (a === \"--debug\") {\n debug = true;\n } else if (a === \"--break\") {\n const next = args[i + 1];\n if (next === undefined || next.startsWith(\"--\")) {\n // --break without a value is a usage error; surface as no BPs.\n continue;\n }\n pushBreaks(next, breakFlag);\n i++;\n } else if (a.startsWith(\"--break=\")) {\n pushBreaks(a.slice(\"--break=\".length), breakFlag);\n } else {\n positional.push(a);\n }\n }\n return { positional, debug, breakFlag };\n}\n\nfunction pushBreaks(value: string, out: string[]): void {\n for (const tok of value.split(\",\")) {\n const trimmed = tok.trim();\n if (/^\\d+:\\d+$/.test(trimmed)) out.push(trimmed);\n }\n}\n","// Reads the per-project shared breakpoint store\n// (`unotest/.debugger[-suffix].json`) and returns the BP keys for a scenario.\n// Shared by web (suffix \"\") and mobile (suffix \"-mobile\") runners — the format\n// + lookup are identical, only the per-target filename differs (M-10 / M-20).\n//\n// Missing file / missing scenario / parse errors all collapse to an empty list\n// (debug mode then has zero BPs and just pauses on failure).\n\nimport { debuggerFileFor, type BreakpointEntry, type BreakpointsFile } from \"@unotest/protocol\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function readDebuggerBreakpoints(\n cwd: string,\n suffix: string,\n scenarioName: string,\n): string[] {\n const filePath = join(cwd, debuggerFileFor(suffix));\n if (!existsSync(filePath)) return [];\n try {\n const raw = readFileSync(filePath, \"utf8\");\n const parsed = JSON.parse(raw) as BreakpointsFile;\n if (parsed.version !== 1 || typeof parsed.scenarios !== \"object\") return [];\n const entries = parsed.scenarios[scenarioName];\n if (!Array.isArray(entries)) return [];\n return entries\n .filter(\n (e): e is BreakpointEntry =>\n typeof e?.line === \"number\" && typeof e?.col === \"number\",\n )\n .map((e) => `${e.line}:${e.col}`);\n } catch {\n return [];\n }\n}\n","// Idempotent append-unique-lines for .gitignore (or any line-based file).\n// Pure function over content — caller handles FS. Used by both runners'\n// `init` (Phase-5 dedup).\n\nexport interface GitignoreUpdate {\n added: string[];\n alreadyPresent: string[];\n newContent: string;\n}\n\nexport function appendUniqueLines(\n existingContent: string | null,\n linesToAdd: string[],\n): GitignoreUpdate {\n const existing = existingContent ?? \"\";\n const existingLines = new Set(existing.split(\"\\n\").map((l) => l.trim()));\n const added: string[] = [];\n const alreadyPresent: string[] = [];\n\n for (const line of linesToAdd) {\n const trimmed = line.trim();\n if (trimmed === \"\" || existingLines.has(trimmed)) {\n if (trimmed !== \"\") alreadyPresent.push(line);\n continue;\n }\n added.push(line);\n existingLines.add(trimmed);\n }\n\n if (added.length === 0) {\n return { added, alreadyPresent, newContent: existing };\n }\n\n const sep = existing === \"\" ? \"\" : existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n const newContent = existing + sep + added.join(\"\\n\") + \"\\n\";\n return { added, alreadyPresent, newContent };\n}\n","// Layered env loading across the two M-11 axes — target (dash suffix)\n// and environment (dot suffix): `.env → .env-<target> → .env.<env> →\n// .env-<target>.<env>`, later file winning; ambient process.env always\n// beats every file (set-if-absent). ONE implementation for every runner\n// (web suffix \"\", mobile \"-mobile\") — the layer ORDER and the parser\n// come from @unotest/protocol; this module owns only the fs reads.\n\nimport {\n activeEnvName,\n envLayerFilesFor,\n parseEnvContent,\n} from \"@unotest/protocol\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Parse one `KEY=VALUE` env file. Returns `{}` when the file is absent\n * or unreadable. Thin fs wrapper over the protocol's pure parser. */\nexport function readEnvFile(absPath: string): Record<string, string> {\n if (!existsSync(absPath)) return {};\n try {\n return parseEnvContent(readFileSync(absPath, \"utf8\"));\n } catch {\n return {};\n }\n}\n\n/** Merged vars for (suffix, envName): every layer read in low→high\n * precedence order, later keys winning. */\nexport function readEnvLayers(\n projectRoot: string,\n suffix: string,\n envName?: string,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rel of envLayerFilesFor(suffix, envName)) {\n Object.assign(out, readEnvFile(join(projectRoot, rel)));\n }\n return out;\n}\n\n/**\n * Flatten the layered env files into `target` (default `process.env`).\n * Existing values win: genuine shell / MCP-config / launcher-injected\n * vars override the files. The active environment (`UNOTEST_ENV`) is\n * read from `target` itself, so a `--env`-set var already present there\n * selects the overlays. Returns the keys actually set.\n */\nexport function applyEnvLayers(\n projectRoot: string,\n suffix: string,\n target: NodeJS.ProcessEnv = process.env,\n): string[] {\n const merged = readEnvLayers(projectRoot, suffix, activeEnvName(target));\n const applied: string[] = [];\n for (const [key, value] of Object.entries(merged)) {\n if (target[key] !== undefined) continue;\n target[key] = value;\n applied.push(key);\n }\n return applied;\n}\n"],"mappings":"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","names":["mkdir","writeFile","join","mkdir","join","writeFile","mkdir","rename","writeFile","join","mkdir","join","writeFile","rename","rename","writeFile","join","join","writeFile","rename","mkdir","writeFile","dirname","mkdir","dirname","writeFile","join","join","existsSync","readFileSync","join","existsSync","readFileSync","join"]}