@warlock.js/core 5.32.0 → 5.34.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.
Files changed (173) hide show
  1. package/CHANGELOG.md +64 -46
  2. package/esm/application/application-config-types.d.mts +8 -0
  3. package/esm/application/application-config-types.d.mts.map +1 -1
  4. package/esm/application/boot-signal.d.mts +8 -8
  5. package/esm/application/boot-signal.d.mts.map +1 -1
  6. package/esm/application/boot-signal.mjs +15 -9
  7. package/esm/application/boot-signal.mjs.map +1 -1
  8. package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
  9. package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
  10. package/esm/cli/commands/ai.command.mjs +50 -0
  11. package/esm/cli/commands/ai.command.mjs.map +1 -0
  12. package/esm/cli/commands/check/check-command.action.mjs +4 -0
  13. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/check.command.mjs +6 -1
  15. package/esm/cli/commands/check.command.mjs.map +1 -1
  16. package/esm/cli/commands/e2e.command.mjs +63 -0
  17. package/esm/cli/commands/e2e.command.mjs.map +1 -0
  18. package/esm/cli/commands/load-e2e-spec.mjs +47 -0
  19. package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
  20. package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
  21. package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
  22. package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
  23. package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
  24. package/esm/cli/commands/pm2.command.mjs +53 -0
  25. package/esm/cli/commands/pm2.command.mjs.map +1 -0
  26. package/esm/cli/framework-cli-commands.mjs +6 -0
  27. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  28. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  29. package/esm/connectors/connectors-manager.mjs +1 -4
  30. package/esm/connectors/connectors-manager.mjs.map +1 -1
  31. package/esm/connectors/shutdown-timeout.mjs +7 -0
  32. package/esm/connectors/shutdown-timeout.mjs.map +1 -0
  33. package/esm/connectors/socket-connector.d.mts.map +1 -1
  34. package/esm/connectors/socket-connector.mjs +8 -1
  35. package/esm/connectors/socket-connector.mjs.map +1 -1
  36. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
  37. package/esm/dev-server/health-checker/default-health-checkers.mjs +2 -16
  38. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
  39. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -2
  40. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  41. package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
  42. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  43. package/esm/e2e/browser.d.mts +4 -0
  44. package/esm/e2e/browser.d.mts.map +1 -0
  45. package/esm/e2e/browser.mjs +88 -0
  46. package/esm/e2e/browser.mjs.map +1 -0
  47. package/esm/e2e/cdp.d.mts +152 -0
  48. package/esm/e2e/cdp.d.mts.map +1 -0
  49. package/esm/e2e/cdp.mjs +106 -0
  50. package/esm/e2e/cdp.mjs.map +1 -0
  51. package/esm/e2e/fixture.mjs +195 -0
  52. package/esm/e2e/fixture.mjs.map +1 -0
  53. package/esm/e2e/index.d.mts +5 -0
  54. package/esm/e2e/index.mjs +6 -0
  55. package/esm/e2e/owned-process.mjs +193 -0
  56. package/esm/e2e/owned-process.mjs.map +1 -0
  57. package/esm/e2e/page.d.mts +64 -0
  58. package/esm/e2e/page.d.mts.map +1 -0
  59. package/esm/e2e/page.mjs +376 -0
  60. package/esm/e2e/page.mjs.map +1 -0
  61. package/esm/e2e/run-e2e.d.mts +17 -0
  62. package/esm/e2e/run-e2e.d.mts.map +1 -0
  63. package/esm/e2e/run-e2e.mjs +143 -0
  64. package/esm/e2e/run-e2e.mjs.map +1 -0
  65. package/esm/e2e/types.d.mts +123 -0
  66. package/esm/e2e/types.d.mts.map +1 -0
  67. package/esm/e2e/types.mjs +24 -0
  68. package/esm/e2e/types.mjs.map +1 -0
  69. package/esm/generations/features/index.mjs +2 -0
  70. package/esm/generations/features/index.mjs.map +1 -1
  71. package/esm/generations/features/process.feature.mjs +65 -0
  72. package/esm/generations/features/process.feature.mjs.map +1 -0
  73. package/esm/http/port-preflight.d.mts.map +1 -1
  74. package/esm/http/port-preflight.mjs +2 -0
  75. package/esm/http/port-preflight.mjs.map +1 -1
  76. package/esm/index.d.mts +7 -2
  77. package/esm/index.mjs +7 -2
  78. package/esm/secrets/errors.d.mts +9 -0
  79. package/esm/secrets/errors.d.mts.map +1 -0
  80. package/esm/secrets/errors.mjs +24 -0
  81. package/esm/secrets/errors.mjs.map +1 -0
  82. package/esm/secrets/file-backend.mjs +167 -0
  83. package/esm/secrets/file-backend.mjs.map +1 -0
  84. package/esm/secrets/index.d.mts +6 -0
  85. package/esm/secrets/index.mjs +6 -0
  86. package/esm/secrets/keyring-backend.mjs +50 -0
  87. package/esm/secrets/keyring-backend.mjs.map +1 -0
  88. package/esm/secrets/memory-backend.d.mts +12 -0
  89. package/esm/secrets/memory-backend.d.mts.map +1 -0
  90. package/esm/secrets/memory-backend.mjs +28 -0
  91. package/esm/secrets/memory-backend.mjs.map +1 -0
  92. package/esm/secrets/resolve-driver.mjs +14 -0
  93. package/esm/secrets/resolve-driver.mjs.map +1 -0
  94. package/esm/secrets/secret-store.d.mts +25 -0
  95. package/esm/secrets/secret-store.d.mts.map +1 -0
  96. package/esm/secrets/secret-store.mjs +120 -0
  97. package/esm/secrets/secret-store.mjs.map +1 -0
  98. package/esm/secrets/secrets.d.mts +11 -0
  99. package/esm/secrets/secrets.d.mts.map +1 -0
  100. package/esm/secrets/secrets.mjs +72 -0
  101. package/esm/secrets/secrets.mjs.map +1 -0
  102. package/esm/secrets/types.d.mts +26 -0
  103. package/esm/secrets/types.d.mts.map +1 -0
  104. package/esm/socket/index.d.mts +2 -2
  105. package/esm/socket/index.mjs +2 -2
  106. package/esm/socket/utils.d.mts +7 -0
  107. package/esm/socket/utils.d.mts.map +1 -1
  108. package/esm/socket/utils.mjs +12 -1
  109. package/esm/socket/utils.mjs.map +1 -1
  110. package/esm/tests/fakes/index.d.mts +2 -1
  111. package/esm/tests/fakes/index.mjs +2 -1
  112. package/esm/tests/fakes/secrets-fake.d.mts +13 -0
  113. package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
  114. package/esm/tests/fakes/secrets-fake.mjs +50 -0
  115. package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
  116. package/esm/tests/fakes/test-fakes.d.mts +2 -2
  117. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  118. package/esm/tests/fakes/test-fakes.mjs +5 -2
  119. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  120. package/esm/tests/index.d.mts +2 -1
  121. package/esm/tests/index.mjs +2 -1
  122. package/esm/tests/vitest-setup.d.mts +1 -1
  123. package/esm/tests/vitest-setup.mjs.map +1 -1
  124. package/esm/tooling/check/lint-step.d.mts +2 -8
  125. package/esm/tooling/check/lint-step.d.mts.map +1 -1
  126. package/esm/tooling/check/lint-step.mjs +1 -42
  127. package/esm/tooling/check/lint-step.mjs.map +1 -1
  128. package/esm/tooling/check/run-check.d.mts +2 -0
  129. package/esm/tooling/check/run-check.d.mts.map +1 -1
  130. package/esm/tooling/check/run-check.mjs +3 -1
  131. package/esm/tooling/check/run-check.mjs.map +1 -1
  132. package/esm/tooling/problem.d.mts +1 -1
  133. package/esm/tooling/problem.d.mts.map +1 -1
  134. package/esm/tooling/problem.mjs.map +1 -1
  135. package/esm/updater/package-manager.mjs +5 -1
  136. package/esm/updater/package-manager.mjs.map +1 -1
  137. package/esm/upgrade/codemods/index.mjs +3 -0
  138. package/esm/upgrade/codemods/index.mjs.map +1 -1
  139. package/esm/upgrade/codemods/oxc-toolchain.mjs +191 -0
  140. package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
  141. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
  142. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
  143. package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
  144. package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
  145. package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
  146. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
  147. package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
  148. package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
  149. package/esm/upgrade/notes.mjs +14 -7
  150. package/esm/upgrade/notes.mjs.map +1 -1
  151. package/esm/upgrade/run-upgrade.mjs +25 -6
  152. package/esm/upgrade/run-upgrade.mjs.map +1 -1
  153. package/esm/warlock-config/types.d.mts +1 -1
  154. package/llms-full.txt +538 -29
  155. package/llms.txt +5 -3
  156. package/package.json +29 -20
  157. package/skills/README.md +2 -1
  158. package/skills/create-module/SKILL.md +2 -2
  159. package/skills/index.md +1 -1
  160. package/skills/run-app/SKILL.md +115 -6
  161. package/skills/store-secrets/SKILL.md +141 -0
  162. package/skills/test-browser/SKILL.md +119 -0
  163. package/skills/test-fakes/SKILL.md +13 -11
  164. package/skills/upgrade-from-5-29/SKILL.md +85 -7
  165. package/skills/warlock-check/SKILL.md +7 -3
  166. package/skills/warlock-doctor/SKILL.md +8 -0
  167. package/skills/wire-socket/SKILL.md +19 -0
  168. package/skills/write-cli-command/SKILL.md +21 -0
  169. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -154
  170. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
  171. package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
  172. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -192
  173. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: test-browser
3
+ description: Write or repair browser e2e specs using warlock e2e and @warlock.js/core/e2e, with owned app fixtures, direct CDP interaction, responsive screenshots and run evidence. Use for real browser flows, click races or viewport overflow; use test-http for API-only tests.
4
+ ---
5
+
6
+ # Browser e2e
7
+
8
+ Use `@warlock.js/core/e2e`, a Node-only subpath, and default-export `defineE2E` from
9
+ `e2e/*.e2e.ts`. Run `npx warlock e2e`. The CLI loads TypeScript itself; no Vitest
10
+ bootstrap, Playwright, Puppeteer or external browser server is needed.
11
+
12
+ Every run copies the app into a temporary directory, excludes dotenv files and
13
+ symlinks, links installed dependencies for reads, seeds its own environment and
14
+ owns the app/browser process groups. Keep foreground commands in the fixture;
15
+ never use a shared dev server or daemonize. Map the disposable `dataRoot` into the
16
+ app's actual storage/database config. The harness cannot infer that mapping.
17
+ `prepare` is where seeds and fake executables belong; `app.path` is relative to the
18
+ copied app. Do not install dependencies through the shared `node_modules` link.
19
+
20
+ ## Complete standalone spec
21
+
22
+ This spec creates its own small app; it can be pasted into an existing project
23
+ with core installed and run with `npx warlock e2e`.
24
+
25
+ ```ts
26
+ import { mkdir, writeFile } from "node:fs/promises";
27
+ import { join } from "node:path";
28
+ import { defineE2E } from "@warlock.js/core/e2e";
29
+
30
+ export default defineE2E({
31
+ app: {
32
+ start: [process.execPath, "form-fixture.mjs"],
33
+ portFromFile: true,
34
+ path: ["fake-bin"],
35
+ env: ({ dataRoot }) => ({ FORM_DATA: dataRoot }),
36
+ prepare: async ({ directory, dataRoot }) => {
37
+ await mkdir(join(directory, "fake-bin"));
38
+ await writeFile(join(dataRoot, "prefix.txt"), "Saved");
39
+ await writeFile(
40
+ join(directory, "form-fixture.mjs"),
41
+ `
42
+ import { createServer } from 'node:http';
43
+ import { readFileSync, writeFileSync } from 'node:fs';
44
+ import { join } from 'node:path';
45
+ const prefix = readFileSync(join(process.env.FORM_DATA, 'prefix.txt'), 'utf8');
46
+ const server = createServer((request, response) => {
47
+ if (request.url === '/favicon.ico') { response.writeHead(204); response.end(); return; }
48
+ response.setHeader('Content-Type', 'text/html');
49
+ response.end('<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1">'
50
+ + '<input id="name"><button id="save"><span>Save</span></button><h1 id="result">Ready</h1>'
51
+ + '<script>document.querySelector("#save").onclick=()=>{history.pushState({},"","/saved");document.querySelector("#result").textContent='
52
+ + JSON.stringify(prefix) + '+" "+document.querySelector("#name").value}</script>');
53
+ });
54
+ server.listen(0, '127.0.0.1', () => writeFileSync(process.env.WARLOCK_E2E_PORT_FILE, String(server.address().port)));
55
+ `,
56
+ );
57
+ },
58
+ },
59
+ run: async ({ page, step }) => {
60
+ await page.goto("/");
61
+ await step("save the form", async () => {
62
+ await page.fill("#name", "Ada");
63
+ await page.type("#name", " Lovelace");
64
+ await page.waitForNavigation(() => page.click("#save"), { url: "/saved" });
65
+ await page.waitFor({ selector: "#result", text: "Saved Ada Lovelace" });
66
+ });
67
+ await page.captureViewports("saved");
68
+ },
69
+ });
70
+ ```
71
+
72
+ For an existing app, replace `prepare` and `start`; default start is
73
+ `[process.execPath, "dist/app.js"]`. Optional
74
+ `build: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "build"]`
75
+ builds inside the copy. Supply app-specific test service variables via `env` and
76
+ exclude production data directories with `exclude: ["storage", "data"]`.
77
+ By default the fixture supplies an OS-assigned `PORT`/`HTTP_PORT`. Prefer the
78
+ port-file handshake above when the app supports binding `0` itself.
79
+
80
+ ## Interaction and evidence
81
+
82
+ - Use `page.click` for scrolling, settled boxes and hit testing, including a fresh
83
+ check after hover. Do not replace it with `element.click()` or fixed sleeps.
84
+ - `fill` clears editable content, then sends CDP text input; `type` appends Unicode
85
+ text. Values do not appear in step names. `type` does not simulate shortcuts.
86
+ - `waitFor({ selector, text, url, timeoutMs })` combines conditions. Arm navigation
87
+ **before** clicking with `waitForNavigation(() => page.click(...))`.
88
+ - `setViewport({ name, width, height, mobile })`, `assertViewport()` and
89
+ `screenshot(name)` are available independently. `captureViewports(name)` checks
90
+ and captures 1280×800 desktop and 390×844 phone by default, restoring the
91
+ original viewport. `viewports` overrides the list. Final captures are automatic.
92
+ - Overflow compares document/body scrollWidth **and innerWidth** against the
93
+ requested width, so a phone that expands to 448 px fails. Keep the viewport meta
94
+ tag; inspect the reported widest element IDs, classes and dimensions.
95
+ - Health requires zero console errors, exceptions, HTTP statuses >=400 and failed
96
+ loads (including cancellation). Narrow exceptions use
97
+ `allowConsoleErrors: [/^Expected warning$/]` and
98
+ `allowNetworkFailures: [{ url: "/missing", status: 404 }]`. Canceled-request rules
99
+ can match `errorText`. Strings match substrings; regexps are also accepted.
100
+ - `allowPendingRequests: ["/events"]` exempts an intentional stream from network
101
+ idle, without allowing its errors. `waitForNetworkIdle()` remains available.
102
+ - `page.evaluate<T>(expression)` is an escape hatch for assertions in the main
103
+ document. Selector helpers do not enter frames or closed shadow roots.
104
+
105
+ Use `--browser /path/to/chrome` or `WARLOCK_E2E_BROWSER` if discovery fails.
106
+ `browser: { args: ["--no-sandbox"] }` is an explicit option for controlled root
107
+ containers; it is not a default. On Windows, command arrays should invoke JS tools
108
+ through `process.execPath`, avoiding `.cmd` executables that require a shell.
109
+
110
+ Read `.warlock/e2e/run-*/evidence.json` and the PNGs/logs on failure. Evidence keeps
111
+ allowed errors, step timings, viewport dimensions and recorded process identities.
112
+ `--out` changes the evidence parent; `--path ./fake-bin,./engine-bin` prepends fake
113
+ binary paths. `runE2E(spec, { outputDirectory, browser, path, signal })` is the
114
+ programmatic runner; `E2ERunError.result` exposes evidence on failure.
115
+
116
+ Cleanup verifies the unique supervisor command line before stopping only its
117
+ recorded process group (Windows: verified PID tree). It retains disposable files
118
+ if ownership verification fails and reports the failure in evidence. Do not add
119
+ process-name or port-based kills to a spec.
@@ -1,20 +1,21 @@
1
1
  ---
2
2
  name: test-fakes
3
- description: 'The test fakes Warlock installs by default in the test environment — `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake` — and their assertions (`assertSent`, `assertPushed`, `assertHas`, `assertExists`, `travel`), plus how to opt out (`tests.fakes`, `setupTest({ fakes })`). Triggers: `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `installTestFakes`, `restoreTestFakes`, `tests.fakes`, `FakeAssertionError`, `clock.now`, `ids.uuid`, `setDispatchInterceptor`; "assert an email was sent", "assert a job was dispatched", "freeze time in a test", "deterministic ids", "tests without Redis", "keep the real cache driver in tests". Typical import `import { mailFake, queueFake } from "@warlock.js/core/tests"`. Skip: HTTP request helpers — the `test-http` topic; fixtures — the `model-factories` topic.'
3
+ description: 'The test fakes Warlock installs by default in the test environment — `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `secretsFake` — and their assertions (`assertSent`, `assertPushed`, `assertHas`, `assertExists`, `travel`), plus how to opt out (`tests.fakes`, `setupTest({ fakes })`). Triggers: `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `secretsFake`, `installTestFakes`, `restoreTestFakes`, `tests.fakes`, `FakeAssertionError`, `clock.now`, `ids.uuid`, `setDispatchInterceptor`; "assert an email was sent", "assert a job was dispatched", "freeze time in a test", "deterministic ids", "tests without Redis", "keep the real cache driver in tests". Typical import `import { mailFake, queueFake } from "@warlock.js/core/tests"`. Skip: HTTP request helpers — the `test-http` topic; fixtures — the `model-factories` topic.'
4
4
  ---
5
5
 
6
6
  # Warlock — test fakes
7
7
 
8
- In the test environment six fakes are installed for you: `setupTest()` installs them in every test file once its connectors are up, `startHttpTestServer()` installs them in the shared server, and teardown restores them. Nothing is delivered, pushed to Redis, uploaded to a bucket, and time and ids are deterministic.
8
+ In the test environment seven fakes are installed for you: `setupTest()` installs them in every test file once its connectors are up, `startHttpTestServer()` installs them in the shared server, and teardown restores them. Nothing is delivered, pushed to Redis, uploaded to a bucket, and time and ids are deterministic.
9
9
 
10
- | Fake | Replaces |
11
- | ------------- | --------------------------------------------------------------- |
12
- | `mailFake` | Mail delivery: `sendMail()` / `Mail` sends are captured |
13
- | `queueFake` | `@warlock.js/queue` dispatch: jobs are recorded, not pushed |
14
- | `cacheFake` | The cache's current driver: a fresh in-memory driver |
15
- | `storageFake` | Every storage driver (local, S3, R2, Spaces): a temp directory |
16
- | `clockFake` | `clock` from `@warlock.js/core`: frozen at 2026-01-01T00:00:00Z |
17
- | `idsFake` | `ids` from `@warlock.js/core`: numbered UUIDs and tokens |
10
+ | Fake | Replaces |
11
+ | ------------- | --------------------------------------------------------------------- |
12
+ | `mailFake` | Mail delivery: `sendMail()` / `Mail` sends are captured |
13
+ | `queueFake` | `@warlock.js/queue` dispatch: jobs are recorded, not pushed |
14
+ | `cacheFake` | The cache's current driver: a fresh in-memory driver |
15
+ | `storageFake` | Every storage driver (local, S3, R2, Spaces): a temp directory |
16
+ | `clockFake` | `clock` from `@warlock.js/core`: frozen at 2026-01-01T00:00:00Z |
17
+ | `idsFake` | `ids` from `@warlock.js/core`: numbered UUIDs and tokens |
18
+ | `secretsFake` | The `secrets` facade: namespace-aware memory, no OS/environment/files |
18
19
 
19
20
  ## Assert what your code did
20
21
 
@@ -42,7 +43,8 @@ it("queues the report, mails the requester and caches the request", async () =>
42
43
  - **queue** — `assertPushed(jobOrName, payloadSubset?)`, `assertNotPushed`, `assertPushedCount(n, job?)`, `assertNothingPushed()`, `pushed(job?, matcher?)`. Each record has `id`, `name`, `queue`, `payload`, `options` (`delay`, `priority`, `jobId`) and `pushedAt`. An explicit `jobId` stays idempotent and `job.find(id)` answers from the record. Jobs are not run — call the handler to test it.
43
44
  - **cache** — `assertHas(key)`, `assertMissing(key)`, `assertStored(key, value)` (deep equality), `driver()`, `clear()`. TTLs, tags, namespaces and locks are the real memory driver's.
44
45
  - **storage** — `assertExists(path, driver?)`, `assertMissing(path, driver?)`, `files(driver?)`, `root()`. Each configured driver name gets a folder under one temp root, shared with the HTTP test server through `WARLOCK_TEST_STORAGE_ROOT`; the process that created it deletes it.
45
- - A failed assertion throws `FakeAssertionError` listing what was recorded.
46
+ - **secrets** — async `assertHas(name)`, `assertMissing(name)`, `assertStored(name, value)`, plus `clear()` and `isInstalled()`. Assertion errors never reveal values. See `store-secrets` for examples and explicit memory stores.
47
+ - A failed assertion throws `FakeAssertionError`; secret assertions omit values. Other fakes list what was recorded.
46
48
 
47
49
  ## Time and ids
48
50
 
@@ -441,14 +441,92 @@ The CLI preserves incremental state and changed-file filtering. Development
441
441
  health checks keep one native compiler session and update its snapshots. Syntax
442
442
  analysis uses OXC. `warlock upgrade` still parses/transforms with ts-morph's own
443
443
  bundled TypeScript, without using it as the app's compiler. New scaffolds default
444
- to Oxlint/Oxfmt (`--lint=oxc`). Explicit `--lint=eslint` pins the app's compiler to
445
- TypeScript **6.0.3** because typescript-eslint requires the old JavaScript API
446
- and supports `>=4.8.4 <6.1.0`. Keep ESLint apps on 6.0.3 until switching tools:
444
+ to Oxlint/Oxfmt (`--lint=oxc`) with TypeScript 7.0.2. ESLint support was
445
+ removed in 5.34.0; migrate legacy apps using the steps below.
446
+
447
+ ## 5.32 to 5.33
448
+
449
+ The original `oxc-toolchain` codemod remains available:
447
450
 
448
451
  ```bash
449
- npm install --save-dev --save-exact typescript@6.0.3
450
- npx tsc --noEmit
452
+ npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
453
+ npx warlock upgrade --from 5.32 --to 5.33.0
454
+ ```
455
+
456
+ ## 5.33 to 5.34 toolchain removal
457
+
458
+ **BREAKING:** ESLint and Prettier support was removed in **5.34.0**. Oxlint/Oxfmt is the only
459
+ supported toolchain. `--lint eslint` and `healthCheckers.eslint` are removed.
460
+ Install the new core release and `ts-morph`, then migrate an existing app:
461
+
462
+ ```bash
463
+ npm install --save-dev ts-morph
464
+ npx warlock upgrade --from 5.33 --to 5.34.0 --dry-run
465
+ npx warlock upgrade --from 5.33 --to 5.34.0
466
+ npm install
467
+ npm run format
468
+ npm run lint:check
469
+ npx warlock check
470
+ ```
471
+
472
+ The `oxc-toolchain-removal` step reapplies the preserved 5.33 `oxc-toolchain`
473
+ migration when legacy configs, scripts or dependencies remain. Apps upgrading
474
+ from 5.32 can still use `npx warlock upgrade --from 5.32 --to 5.33.0` first.
475
+ It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates generated
476
+ editor settings, removes `healthCheckers.eslint` and selects Oxlint. A generated
477
+ `.prettierignore` becomes `.oxfmtignore`; Oxfmt scripts read it explicitly.
478
+
479
+ The `oxc-toolchain` codemod also updates generated VS Code settings and literal
480
+ ESLint/Oxlint health-check switches. Custom editor settings, checker arrays,
481
+ computed options and nonstandard scripts require review. Oxc diagnostics keep
482
+ framework conventions in `warlock check`. Re-run the migration after addressing
483
+ manual findings if needed; an app with only Oxc tooling is a no-op for this codemod.
484
+
485
+ ## 5.33 to 5.34 plain object validation
486
+
487
+ **BREAKING:** Seal `v.object()` rejects arrays, dates, maps, sets, class instances
488
+ and objects with custom prototypes, including under `.allowUnknown()` and
489
+ `.stripUnknown()`. Plain and null-prototype dictionaries remain accepted. There
490
+ is no permissive opt-in: choose an array/instance validator or construct a plain
491
+ DTO explicitly before validation.
492
+
493
+ ```ts
494
+ import { v } from "@warlock.js/seal";
495
+
496
+ class User {
497
+ name = "Ada";
498
+ }
499
+ const instance = new User();
500
+ const user = v.object({ name: v.string() });
501
+ await v.validate(user, { name: instance.name });
502
+ const names = v.array(v.string());
503
+ await v.validate(names, ["Ada"]);
451
504
  ```
452
505
 
453
- The same tsconfig cleanups work with TS6. ESLint mode prints a compatibility
454
- notice and is planned for deprecation in 5.33 and removal in 5.34.
506
+ `v.record(valueValidator)` remains supported. To validate dictionary keys, use
507
+ `v.record(keyValidator, valueValidator)`; malformed arguments now fail while
508
+ defining the schema. Literal-key records infer partial dictionaries because
509
+ only present keys are checked.
510
+ Literal custom rules are translated without executing the config. Per-folder
511
+ `no-restricted-imports` and `no-restricted-exports` become Oxlint overrides with
512
+ file/exclusion patterns. Supported `@typescript-eslint/*` rules become native
513
+ `typescript/*` rules (or native base rules). Options are checked against the
514
+ pinned Oxlint 1.88.0 schema. Type-aware equivalents enable `options.typeAware`
515
+ and add `oxlint-tsgolint@7.0.2004` to the app's dev dependencies.
516
+ `unused-imports/no-unused-imports` uses `no-unused-vars` with safe import fixes;
517
+ Oxlint shares the severity for imports and variables, so differing active
518
+ severities are reported for review.
519
+
520
+ Only unsupported rules/options, computed or imported settings, selectors such
521
+ as `no-restricted-syntax`, and conflicting existing Oxc rules need manual review.
522
+ Each finding names its source `file:line`, rule and affected file patterns.
523
+ Fully ported lint configs are removed; configs with findings remain for reference
524
+ until those findings are addressed. Existing Oxc rules are kept on conflict.
525
+ `--dry-run --json` reports the same plan without writes. Customized formatting,
526
+ editor settings and nonstandard scripts can also require review.
527
+
528
+ **Upgrading:** new and migrated template configs enable
529
+ `"categories": { "correctness": "error" }`. Address the additional diagnostics;
530
+ existing Oxc configs retain their categories. A real lockfile beside a workspace
531
+ marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
532
+ without `pnpm-lock.yaml` now produces `npm install`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: warlock-check
3
- description: 'Verify a change with one command: `warlock check [--changed] [--json]` runs an incremental typecheck, the app''s ESLint/oxlint, the tests affected by the change and framework convention checks, and reports one JSON object per problem `{ code, category, severity, source, file, line, column, message, hint, skill, docs, rule }` with exit 1 for problems in your code and 2 for problems with this machine (missing Redis/DB, busy port, Node version, missing tool). Also `warlock typecheck [--changed]`, `warlock explain <CODE>` and the stable `WL` error codes (`errorCodeOf`, `WarlockError`). Triggers: `warlock check`, `warlock typecheck`, `warlock explain`, `--changed`, `--base`, `runCheck`, `runTypecheck`, `CHECK_EXIT_CODES`, `ToolingProblem`, `WL7101`, `WL8003`, `errorCodeOf`, `explainErrorCode`, `WarlockError`, "verify my change", "run the checks", "is this failure my code or the machine", "what does WL3001 mean", "typecheck only changed files". Skip: app diagnostics at boot — the `warlock-doctor` topic; reading the app''s routes/models — the `warlock-inspect` topic; writing tests — the `test-http` / `test-service` topics.'
3
+ description: 'Verify a change with one command: `warlock check [--changed] [--json]` runs an incremental typecheck, the app''s Oxlint, the tests affected by the change and framework convention checks, and reports one JSON object per problem `{ code, category, severity, source, file, line, column, message, hint, skill, docs, rule }` with exit 1 for problems in your code and 2 for problems with this machine (missing Redis/DB, busy port, Node version, missing tool). Also `warlock typecheck [--changed]`, `warlock explain <CODE>` and the stable `WL` error codes (`errorCodeOf`, `WarlockError`). Triggers: `warlock check`, `warlock typecheck`, `warlock explain`, `--changed`, `--base`, `runCheck`, `runTypecheck`, `CHECK_EXIT_CODES`, `ToolingProblem`, `WL7101`, `WL8003`, `errorCodeOf`, `explainErrorCode`, `WarlockError`, "verify my change", "run the checks", "is this failure my code or the machine", "what does WL3001 mean", "typecheck only changed files". Skip: app diagnostics at boot — the `warlock-doctor` topic; reading the app''s routes/models — the `warlock-inspect` topic; writing tests — the `test-http` / `test-service` topics.'
4
4
  ---
5
5
 
6
6
  # Warlock — `warlock check`, `typecheck`, `explain`
@@ -120,10 +120,10 @@ const types = runTypecheck({ files: ["src/app/posts/services/publish.service.ts"
120
120
  ## Gotchas
121
121
 
122
122
  - `check` boots nothing — no config, no connection, no port — so it runs the same with or without services; only tests that need them fail, and those report `WL8003`/`WL8004`.
123
- - Lint runs only the linters the app configures (`eslint.config.*`; an oxlint config + the `oxlint` package). No config → the step is skipped, not failed.
123
+ - Lint defaults to Oxlint (an app-root config + the app-installed package). No Oxlint config skips lint. A missing package reports `WL8002`. ESLint support was removed in 5.34.0; see `upgrade-from-5-29`.
124
124
  - Tests use the app's own vitest (`vitest run`, or `vitest related <files> --run` with `--changed`). Test files without vitest installed → `WL8002`.
125
125
  - The typecheck runs in a child `warlock typecheck` process; running out of memory is reported as `WL8007` instead of ending the run.
126
- - The convention checks are the framework's own and run the same with ESLint, Oxlint or no linter. A deliberate exception gets `// warlock-check-ignore WL9006 -- why` on the problem's line or the line above (no code = every code on that line).
126
+ - The convention checks are the framework's own and run the same with Oxlint or no linter. A deliberate exception gets `// warlock-check-ignore WL9006 -- why` on the problem's line or the line above (no code = every code on that line).
127
127
 
128
128
  ## See also
129
129
 
@@ -131,3 +131,7 @@ const types = runTypecheck({ files: ["src/app/posts/services/publish.service.ts"
131
131
  - The `warlock-doctor` topic — boot-time diagnostics; `doctor --json` uses the same codes and fields.
132
132
  - The `warlock-inspect` topic — the app's routes, models and config as JSON.
133
133
  - The `warlock-conventions` topic — the rules behind `WL9001`–`WL9012`, with the fix for each.
134
+
135
+ Migrate lint and format tooling with
136
+ `npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run`, then remove `--dry-run`
137
+ and work through the manual-review list. Use `--lint=oxc` for new apps.
@@ -169,3 +169,11 @@ A `⚠ routes: 0 routes registered` line is the tell that a route module threw o
169
169
  - The `warlock-conventions` topic — project layout the checks assume.
170
170
  - The `warlock-check` topic — `warlock check` / `typecheck` / `explain` and the error-code registry the findings use.
171
171
  - The `warlock-inspect` topic — `warlock inspect`, the same boot pass printed as a versioned JSON report of the app.
172
+
173
+ ## Lint and format checks
174
+
175
+ `warlock doctor` checks application wiring and HTTP health probes. Use
176
+ `npx warlock check` for source lint diagnostics and `npm run format:check` for
177
+ formatting. Oxlint/Oxfmt are the only supported tools in 5.34.0. Use the
178
+ `oxc-toolchain-removal` upgrade step to migrate remaining legacy configs.
179
+ Migrate with `npx warlock upgrade --from 5.32 --to 5.33.0` (preview with `--dry-run`).
@@ -113,6 +113,25 @@ Reading `app.socket` (or `getSocketServer()`) at the top level of `main.ts` retu
113
113
 
114
114
  ## Emitting from a controller or service
115
115
 
116
+ Use `broadcastSocketEvent` when a framework integration should tolerate an
117
+ absent connector and broadcast through the configured adapter:
118
+
119
+ ```ts
120
+ import { broadcastSocketEvent } from "@warlock.js/core";
121
+
122
+ const dispatched = broadcastSocketEvent("inventory:changed", { tags: ["products"] });
123
+ // false without a booted socket server; true means dispatched, not delivered.
124
+ ```
125
+
126
+ This broadcasts to all default-namespace clients, so payloads must be public.
127
+ It uses `io.emit` (not `io.local.emit`), allowing `socket.adapter` to forward to
128
+ other app processes. Each publisher, including a queue/CLI process, must boot
129
+ the socket connector with the same adapter. See Socket.IO's official
130
+ [Redis adapter](https://socket.io/docs/v4/redis-adapter/) and
131
+ [multiple-node guidance](https://socket.io/docs/v4/using-multiple-nodes/).
132
+ Web's `invalidatePageCache(tags)` uses this path for opt-in `config.live` pages;
133
+ shared cache storage is also required when evicting across processes.
134
+
116
135
  Once the server is up, anything with access to `app.socket` can emit:
117
136
 
118
137
  ```ts title="src/app/notifications/services/notify-user.service.ts"
@@ -221,6 +221,7 @@ warlock generate (alias: g) — interactive picker
221
221
  | `postman` | `@warlock.js/postman` |
222
222
  | `herald` | `@warlock.js/herald` + `amqplib` + types; ejects `src/config/communicator.ts` |
223
223
  | `test` | `vitest` + `@mongez/vite` + coverage; drops `test-global-setup.ts` / `test-setup.ts` / `vite.config.ts` |
224
+ | `process` | `@warlock.js/process` pinned to installed Core; typed config, supervisor export, recovery/shutdown lifecycle and optional devtools process panel |
224
225
  | `devtools` | `@warlock.js/devtools` as a dev dependency; `warlock dev` auto-loads its development dashboard at `/__warlock` |
225
226
 
226
227
  Run `warlock add --list` to see what's currently registered. Pass `--packageManager <yarn\|pnpm\|npm>` to override auto-detection (defaults to whichever lockfile is present).
@@ -231,6 +232,10 @@ production install with dev dependencies excluded.
231
232
 
232
233
  The Web starter keeps the page's universal non-component setup in `src/web/index.register.ts`, while `src/web/index.page.tsx` re-exports `register`. Warlock still discovers and runs the hook from the page namespace in both SSR and the browser, but editing the page component remains inside React's Fast Refresh boundary. Do not move the `register()` declaration inline beside the component: React Refresh treats that non-component export as incompatible and reloads the page, losing component state.
233
234
 
235
+ ### AI input measurement
236
+
237
+ `warlock ai measure --model <id> [--tools <module>] [--system <file>] [--skills <dir>] [--json]` loads `@warlock.js/ai` lazily without app bootstrap. Model IDs default to OpenAI Responses or use `openai:`, `anthropic:`, `google:` prefixes and the corresponding installed adapter/environment API key. `--model-module <module>` accepts `model`, `default`, or `resolveModel(id)` for custom providers/prices. Tools modules export a `tools` or default array; paths resolve from cwd. The report breaks down messages, system, skills catalogue, and tools, labels native counts versus estimates, and includes uncached input USD only when priced. CLI reports include an explicit `.` user probe and do not preload skill bodies or add skill tools. JSON stdout contains one report. No CLI config registration is required.
238
+
234
239
  ### Misc
235
240
 
236
241
  | Command | Purpose |
@@ -314,3 +319,19 @@ export default command({
314
319
  - The `run-app` topic — the operational commands (`dev` / `build` / `start`) with all flags and config knobs.
315
320
  - The `configure-app` topic — wiring `defineConfig` and `warlock.config.ts`.
316
321
  - The `warlock-conventions` topic — module layout, canonical imports.
322
+
323
+ ## App-owned processes
324
+
325
+ `npx warlock add process` creates typed `src/config/process.ts` (owner, registry,
326
+ default limits), `src/app/processes/index.ts` (the `processes` supervisor export),
327
+ lifecycle hooks, and a read-only devtools list source. It imports lifecycle from
328
+ `src/app/main.ts`. Startup awaits `recover()` using `Application.onValidateBoot`;
329
+ graceful shutdown awaits `stopAll()` using `Application.onShutdown`. Recovery
330
+ failure aborts boot. Repeated installation preserves existing files.
331
+
332
+ Set a unique owner and registry per app instance, then import `processes` from
333
+ the generated module and await `processes.spawn({ kind: "worker", command,
334
+ args, labels })`. Consume output and await `handle.exited`. Use handle stops or
335
+ label filters; never stop processes by command or name. `spawnPty()` lazily
336
+ loads separately installed optional peer `node-pty`. The `supervise-processes`
337
+ skill in `@warlock.js/process` contains complete working examples.
@@ -1,154 +0,0 @@
1
- import { BaseHealthChecker } from "./base-health-checker.mjs";
2
- import path from "path";
3
- import { colors } from "@mongez/copper";
4
- import fs from "fs";
5
- import { ESLint } from "eslint";
6
-
7
- //#region ../../packages/core/src/dev-server/health-checker/checkers/eslint-health-checker.ts
8
- var EslintHealthChecker = class extends BaseHealthChecker {
9
- constructor(..._args) {
10
- super(..._args);
11
- this.eslint = null;
12
- this.name = "ESLint";
13
- this.workerPath = "./workers/eslint-health.worker";
14
- this.initialized = false;
15
- }
16
- /**
17
- * Check if file is a lintable file
18
- */
19
- isLintableFile(filePath) {
20
- const ext = filePath.toLowerCase();
21
- return ext.endsWith(".ts") || ext.endsWith(".tsx") || ext.endsWith(".js") || ext.endsWith(".jsx");
22
- }
23
- /**
24
- * Initialize the health checker
25
- */
26
- initialize() {
27
- try {
28
- const flatConfigPath = path.join(process.cwd(), "eslint.config.js");
29
- const flatConfigMjsPath = path.join(process.cwd(), "eslint.config.mjs");
30
- const flatConfigCjsPath = path.join(process.cwd(), "eslint.config.cjs");
31
- if (!(fs.existsSync(flatConfigPath) || fs.existsSync(flatConfigMjsPath) || fs.existsSync(flatConfigCjsPath))) {
32
- this.initialized = true;
33
- return this;
34
- }
35
- this.eslint = new ESLint({ cwd: process.cwd() });
36
- this.initialized = true;
37
- } catch (error) {
38
- console.warn("ESLint Health Checker: Failed to initialize:", error);
39
- this.initialized = true;
40
- }
41
- return this;
42
- }
43
- /**
44
- * Display health check results in a pretty format
45
- */
46
- displayResults(file, result) {
47
- const stats = result.getStats();
48
- if (stats.errors === 0 && stats.warnings === 0) return;
49
- const fileName = file.relativePath.replace(/\\/g, "/");
50
- const errorCount = stats.errors;
51
- const warningCount = stats.warnings;
52
- const sourceLines = file.source ? file.source.split("\n") : [];
53
- if (errorCount > 0) {
54
- const errorMessages = result.messages.filter((m) => m.type === "error");
55
- for (const error of errorMessages) {
56
- const icon = colors.redBright("✖");
57
- const level = colors.redBright(colors.bold("ERROR"));
58
- console.log(`\n${icon} ${level} ${colors.dim("in")} ${colors.cyanBright(fileName)}${colors.dim(`(${error.lineNumber},${error.columnNumber})`)}`);
59
- const ruleId = error.ruleId || "eslint";
60
- console.log(` ${colors.magentaBright(ruleId)} ${colors.dim("→")} ${colors.red(error.message)}`);
61
- if (sourceLines.length > 0 && error.lineNumber > 0 && error.lineNumber <= sourceLines.length) {
62
- const lineContent = sourceLines[error.lineNumber - 1];
63
- const lineNum = error.lineNumber.toString().padStart(4, " ");
64
- const errorLength = error.length || 1;
65
- const columnIndex = error.columnNumber - 1;
66
- console.log(` ${colors.dim(lineNum)} ${colors.dim("│")} ${lineContent || ""}`);
67
- const prefixPadding = lineNum.length + 3;
68
- const columnPadding = " ".repeat(columnIndex);
69
- const underline = colors.redBright("~".repeat(Math.max(1, errorLength)));
70
- console.log(` ${colors.dim(" ".repeat(prefixPadding))}${columnPadding}${underline}`);
71
- }
72
- }
73
- }
74
- if (warningCount > 0) {
75
- const warningMessages = result.messages.filter((m) => m.type === "warning");
76
- for (const warning of warningMessages) {
77
- const icon = colors.yellowBright("⚠");
78
- const level = colors.yellowBright(colors.bold("WARNING"));
79
- console.log(`\n${icon} ${level} ${colors.dim("in")} ${colors.cyanBright(fileName)}${colors.dim(`(${warning.lineNumber},${warning.columnNumber})`)}`);
80
- const ruleId = warning.ruleId || "eslint";
81
- console.log(` ${colors.magentaBright(ruleId)} ${colors.dim("→")} ${colors.yellow(warning.message)}`);
82
- if (sourceLines.length > 0 && warning.lineNumber > 0 && warning.lineNumber <= sourceLines.length) {
83
- const lineContent = sourceLines[warning.lineNumber - 1];
84
- const lineNum = warning.lineNumber.toString().padStart(4, " ");
85
- const warningLength = warning.length || 1;
86
- const columnIndex = warning.columnNumber - 1;
87
- console.log(` ${colors.dim(lineNum)} ${colors.dim("│")} ${lineContent || ""}`);
88
- const prefixPadding = lineNum.length + 3;
89
- const columnPadding = " ".repeat(columnIndex);
90
- const underline = colors.yellowBright("~".repeat(Math.max(1, warningLength)));
91
- console.log(` ${colors.dim(" ".repeat(prefixPadding))}${columnPadding}${underline}`);
92
- }
93
- }
94
- }
95
- const summary = [];
96
- if (errorCount > 0) summary.push(colors.red(`${errorCount} error${errorCount > 1 ? "s" : ""}`));
97
- if (warningCount > 0) summary.push(colors.yellow(`${warningCount} warning${warningCount > 1 ? "s" : ""}`));
98
- }
99
- /**
100
- * Validate the health of the file
101
- */
102
- async validate(file, result) {
103
- if (!this.isLintableFile(file.absolutePath)) {
104
- result.markAsHealthy();
105
- return result;
106
- }
107
- if (!this.eslint) {
108
- result.markAsHealthy();
109
- return result;
110
- }
111
- try {
112
- const lintResult = (await this.eslint.lintText(file.source, { filePath: file.absolutePath }))[0];
113
- if (!lintResult) {
114
- console.log("No lint results", file.relativePath);
115
- result.markAsHealthy();
116
- return result;
117
- }
118
- const errors = [];
119
- const warnings = [];
120
- for (const message of lintResult.messages) {
121
- const isError = message.severity === 2;
122
- const isWarning = message.severity === 1;
123
- if (isError) errors.push({
124
- message: message.message,
125
- type: "error",
126
- lineNumber: message.line || 1,
127
- columnNumber: message.column || 1,
128
- length: message.endColumn && message.column ? message.endColumn - message.column : 1,
129
- ruleId: message.ruleId || void 0
130
- });
131
- else if (isWarning) warnings.push({
132
- message: message.message,
133
- type: "warning",
134
- lineNumber: message.line || 1,
135
- columnNumber: message.column || 1,
136
- length: message.endColumn && message.column ? message.endColumn - message.column : 1,
137
- ruleId: message.ruleId || void 0
138
- });
139
- }
140
- if (errors.length > 0) result.addErrors(errors);
141
- if (warnings.length > 0) result.addWarnings(warnings);
142
- if (errors.length === 0 && warnings.length === 0) result.markAsHealthy();
143
- else this.displayResults(file, result);
144
- } catch (error) {
145
- console.log("ESlint Error:", error);
146
- result.markAsHealthy();
147
- }
148
- return result;
149
- }
150
- };
151
-
152
- //#endregion
153
- export { EslintHealthChecker };
154
- //# sourceMappingURL=eslint-health-checker.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"eslint-health-checker.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/dev-server/health-checker/checkers/eslint-health-checker.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ESLint } from \"eslint\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { FileManager } from \"../../file-manager\";\nimport type { FileHealthCheckerContract } from \"../file-health-checker.contract\";\nimport { FileHealthResult } from \"../file-health-result\";\nimport { BaseHealthChecker } from \"./base-health-checker\";\n\nexport class EslintHealthChecker extends BaseHealthChecker implements FileHealthCheckerContract {\n /**\n * ESLint instance (using new ESLint v9 API with flat config)\n */\n private eslint: ESLint | null = null;\n\n /**\n * Health checker name\n */\n public name: string = \"ESLint\";\n\n /**\n * Path to dedicated worker file for ESLint checking\n * Runs in a separate thread to avoid blocking the main dev server\n */\n public workerPath: string = \"./workers/eslint-health.worker\";\n\n /**\n * Whether checker is initialized\n */\n private initialized: boolean = false;\n\n /**\n * Check if file is a lintable file\n */\n private isLintableFile(filePath: string): boolean {\n const ext = filePath.toLowerCase();\n return (\n ext.endsWith(\".ts\") || ext.endsWith(\".tsx\") || ext.endsWith(\".js\") || ext.endsWith(\".jsx\")\n );\n }\n\n /**\n * Initialize the health checker\n */\n public initialize(): EslintHealthChecker {\n try {\n // Check if eslint.config.js exists (new flat config format)\n const flatConfigPath = path.join(process.cwd(), \"eslint.config.js\");\n const flatConfigMjsPath = path.join(process.cwd(), \"eslint.config.mjs\");\n const flatConfigCjsPath = path.join(process.cwd(), \"eslint.config.cjs\");\n\n const hasFlatConfig =\n fs.existsSync(flatConfigPath) ||\n fs.existsSync(flatConfigMjsPath) ||\n fs.existsSync(flatConfigCjsPath);\n\n if (!hasFlatConfig) {\n // No flat config found, skip initialization\n this.initialized = true;\n return this;\n }\n\n // Use new ESLint v9 API with flat config\n // ESLint will automatically find eslint.config.js in the project root\n this.eslint = new ESLint({\n cwd: process.cwd(),\n });\n\n this.initialized = true;\n } catch (error) {\n // Handle any errors during initialization gracefully\n console.warn(\"ESLint Health Checker: Failed to initialize:\", error);\n this.initialized = true; // Mark as initialized to prevent retries\n }\n\n return this;\n }\n\n /**\n * Display health check results in a pretty format\n */\n private displayResults(file: FileManager, result: FileHealthResult): void {\n const stats = result.getStats();\n\n // Only display if there are errors or warnings\n if (stats.errors === 0 && stats.warnings === 0) {\n return;\n }\n\n const fileName = file.relativePath.replace(/\\\\/g, \"/\");\n const errorCount = stats.errors;\n const warningCount = stats.warnings;\n const sourceLines = file.source ? file.source.split(\"\\n\") : [];\n\n // Display errors\n if (errorCount > 0) {\n const errorMessages = result.messages.filter((m) => m.type === \"error\");\n for (const error of errorMessages) {\n const icon = colors.redBright(\"✖\");\n const level = colors.redBright(colors.bold(\"ERROR\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${error.lineNumber},${error.columnNumber})`)}`,\n );\n // Get rule ID from the error if available\n const ruleId = error.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.red(error.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n error.lineNumber > 0 &&\n error.lineNumber <= sourceLines.length\n ) {\n const lineIndex = error.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = error.lineNumber.toString().padStart(4, \" \");\n const errorLength = error.length || 1;\n const columnIndex = error.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all red)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.redBright(\"~\".repeat(Math.max(1, errorLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display warnings\n if (warningCount > 0) {\n const warningMessages = result.messages.filter((m) => m.type === \"warning\");\n for (const warning of warningMessages) {\n const icon = colors.yellowBright(\"⚠\");\n const level = colors.yellowBright(colors.bold(\"WARNING\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${warning.lineNumber},${warning.columnNumber})`)}`,\n );\n // Get rule ID from the warning if available\n const ruleId = warning.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n warning.lineNumber > 0 &&\n warning.lineNumber <= sourceLines.length\n ) {\n const lineIndex = warning.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = warning.lineNumber.toString().padStart(4, \" \");\n const warningLength = warning.length || 1;\n const columnIndex = warning.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all yellow)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.yellowBright(\"~\".repeat(Math.max(1, warningLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display summary\n const summary = [];\n if (errorCount > 0) {\n summary.push(colors.red(`${errorCount} error${errorCount > 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n summary.push(colors.yellow(`${warningCount} warning${warningCount > 1 ? \"s\" : \"\"}`));\n }\n }\n\n /**\n * Validate the health of the file\n */\n public async validate(file: FileManager, result: FileHealthResult): Promise<FileHealthResult> {\n // Early exit: skip non-lintable files\n if (!this.isLintableFile(file.absolutePath)) {\n result.markAsHealthy();\n return result;\n }\n\n // Early exit: check if ESLint is available\n if (!this.eslint) {\n result.markAsHealthy();\n return result;\n }\n\n try {\n // Use lintText to lint the source code directly from FileManager\n // This ensures we always use the latest content\n const lintResults = await this.eslint.lintText(file.source, {\n filePath: file.absolutePath,\n });\n\n const lintResult = lintResults[0];\n if (!lintResult) {\n console.log(\"No lint results\", file.relativePath);\n result.markAsHealthy();\n return result;\n }\n\n // Convert ESLint messages to FileHealthResult format\n const errors: Array<{\n message: string;\n type: \"error\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n const warnings: Array<{\n message: string;\n type: \"warning\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n\n for (const message of lintResult.messages) {\n const isError = message.severity === 2;\n const isWarning = message.severity === 1;\n\n if (isError) {\n errors.push({\n message: message.message,\n type: \"error\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n } else if (isWarning) {\n warnings.push({\n message: message.message,\n type: \"warning\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n }\n }\n\n // Add errors and warnings to result\n if (errors.length > 0) {\n result.addErrors(errors);\n }\n\n if (warnings.length > 0) {\n result.addWarnings(warnings);\n }\n\n // If no errors or warnings, mark as healthy\n if (errors.length === 0 && warnings.length === 0) {\n result.markAsHealthy();\n } else {\n // Display results if there are errors or warnings\n this.displayResults(file, result);\n }\n } catch (error) {\n // Handle any errors during validation gracefully\n console.log(\"ESlint Error:\", error);\n result.markAsHealthy(); // Return healthy on error to avoid blocking\n }\n\n return result;\n }\n}\n"],"mappings":"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"}