@warlock.js/core 5.20.0 → 5.21.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 (295) hide show
  1. package/CHANGELOG.md +1124 -1086
  2. package/esm/benchmark/benchmark.mjs +34 -25
  3. package/esm/benchmark/benchmark.mjs.map +1 -1
  4. package/esm/benchmark/profiler.d.mts.map +1 -1
  5. package/esm/benchmark/profiler.mjs +8 -3
  6. package/esm/benchmark/profiler.mjs.map +1 -1
  7. package/esm/bootstrap.d.mts.map +1 -1
  8. package/esm/bootstrap.mjs +3 -0
  9. package/esm/bootstrap.mjs.map +1 -1
  10. package/esm/cache/database-cache-driver.d.mts +2 -2
  11. package/esm/cache/database-cache-driver.d.mts.map +1 -1
  12. package/esm/cache/database-cache-driver.mjs +3 -2
  13. package/esm/cache/database-cache-driver.mjs.map +1 -1
  14. package/esm/cli/cli-commands.manager.mjs +16 -14
  15. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  16. package/esm/cli/commands/add.command.mjs +1 -1
  17. package/esm/cli/commands/add.command.mjs.map +1 -1
  18. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +3 -2
  19. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  20. package/esm/cli/commands/generate/generate.command.mjs +20 -0
  21. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  22. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs +4 -2
  23. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
  24. package/esm/cli/commands/generate/generators/controller.generator.mjs +1 -1
  25. package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
  26. package/esm/cli/commands/generate/generators/migration.generator.mjs +22 -6
  27. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  28. package/esm/cli/commands/generate/generators/model.generator.mjs +22 -7
  29. package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
  30. package/esm/cli/commands/generate/generators/module.generator.mjs +8 -6
  31. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  32. package/esm/cli/commands/generate/generators/repository.generator.mjs +1 -1
  33. package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
  34. package/esm/cli/commands/generate/generators/resource.generator.mjs +1 -1
  35. package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
  36. package/esm/cli/commands/generate/generators/service.generator.mjs +1 -1
  37. package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
  38. package/esm/cli/commands/generate/templates/stubs.mjs +7 -7
  39. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  40. package/esm/cli/commands/generate/utils/writer.mjs +25 -1
  41. package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
  42. package/esm/cli/commands/routes/routes-command.action.mjs +5 -1
  43. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
  44. package/esm/cli/commands/routes/with-console-on-stderr.mjs +24 -0
  45. package/esm/cli/commands/routes/with-console-on-stderr.mjs.map +1 -0
  46. package/esm/cli/commands/routes.command.mjs +3 -4
  47. package/esm/cli/commands/routes.command.mjs.map +1 -1
  48. package/esm/cli/commands/start-production.command.mjs +5 -5
  49. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  50. package/esm/cli/commands/storage-put.action.mjs +2 -1
  51. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  52. package/esm/cli/commands/update.command.mjs +1 -0
  53. package/esm/cli/commands/update.command.mjs.map +1 -1
  54. package/esm/cli/parse-cli-args.mjs +13 -1
  55. package/esm/cli/parse-cli-args.mjs.map +1 -1
  56. package/esm/commands/cli-command.d.mts.map +1 -1
  57. package/esm/commands/cli-command.mjs +4 -2
  58. package/esm/commands/cli-command.mjs.map +1 -1
  59. package/esm/commands/index.mjs +3 -0
  60. package/esm/config/config-key-from-path.mjs +20 -0
  61. package/esm/config/config-key-from-path.mjs.map +1 -0
  62. package/esm/config/config-loader.d.mts.map +1 -1
  63. package/esm/config/config-loader.mjs +2 -3
  64. package/esm/config/config-loader.mjs.map +1 -1
  65. package/esm/connectors/connectors-manager.d.mts +4 -0
  66. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  67. package/esm/connectors/connectors-manager.mjs +30 -2
  68. package/esm/connectors/connectors-manager.mjs.map +1 -1
  69. package/esm/connectors/socket-connector.d.mts +29 -0
  70. package/esm/connectors/socket-connector.d.mts.map +1 -1
  71. package/esm/connectors/socket-connector.mjs +73 -9
  72. package/esm/connectors/socket-connector.mjs.map +1 -1
  73. package/esm/connectors/types.d.mts +1 -1
  74. package/esm/connectors/types.mjs.map +1 -1
  75. package/esm/database/models/database-log/database-log.d.mts +47 -24
  76. package/esm/database/models/database-log/database-log.d.mts.map +1 -1
  77. package/esm/database/models/database-log/database-log.mjs +36 -4
  78. package/esm/database/models/database-log/database-log.mjs.map +1 -1
  79. package/esm/database/seed-command-action.mjs +10 -2
  80. package/esm/database/seed-command-action.mjs.map +1 -1
  81. package/esm/dev-server/development-server.mjs +11 -4
  82. package/esm/dev-server/development-server.mjs.map +1 -1
  83. package/esm/dev-server/file-event-handler.mjs +26 -6
  84. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  85. package/esm/dev-server/file-manager.d.mts.map +1 -1
  86. package/esm/dev-server/file-manager.mjs +2 -2
  87. package/esm/dev-server/file-manager.mjs.map +1 -1
  88. package/esm/dev-server/files-orchestrator.mjs +3 -1
  89. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  90. package/esm/dev-server/files-watcher.mjs +64 -5
  91. package/esm/dev-server/files-watcher.mjs.map +1 -1
  92. package/esm/dev-server/health-checker/files-healthcare.manager.mjs +31 -9
  93. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  94. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +11 -7
  95. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +1 -1
  96. package/esm/dev-server/loader/load-hook.mjs +1 -1
  97. package/esm/dev-server/loader/register-loader.mjs +1 -1
  98. package/esm/dev-server/manifest-manager.d.mts.map +1 -1
  99. package/esm/dev-server/manifest-manager.mjs +12 -6
  100. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  101. package/esm/dev-server/parse-imports.mjs +9 -0
  102. package/esm/dev-server/parse-imports.mjs.map +1 -1
  103. package/esm/dev-server/register-dev-shortcuts.mjs +2 -1
  104. package/esm/dev-server/register-dev-shortcuts.mjs.map +1 -1
  105. package/esm/dev-server/shortcuts.mjs +2 -2
  106. package/esm/dev-server/shortcuts.mjs.map +1 -1
  107. package/esm/dev-server/special-file-patterns.mjs +18 -0
  108. package/esm/dev-server/special-file-patterns.mjs.map +1 -0
  109. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  110. package/esm/dev-server/special-files-collector.mjs +2 -5
  111. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  112. package/esm/dev-server/start-development-server.mjs +1 -2
  113. package/esm/dev-server/start-development-server.mjs.map +1 -1
  114. package/esm/dev-server/supervisor.mjs +23 -3
  115. package/esm/dev-server/supervisor.mjs.map +1 -1
  116. package/esm/dev-server/tsconfig-manager.mjs +22 -12
  117. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  118. package/esm/dev-server/type-generator.mjs +10 -9
  119. package/esm/dev-server/type-generator.mjs.map +1 -1
  120. package/esm/encryption/encrypt.d.mts.map +1 -1
  121. package/esm/encryption/encrypt.mjs +16 -5
  122. package/esm/encryption/encrypt.mjs.map +1 -1
  123. package/esm/encryption/hash.d.mts.map +1 -1
  124. package/esm/encryption/hash.mjs +2 -0
  125. package/esm/encryption/hash.mjs.map +1 -1
  126. package/esm/encryption/password.d.mts.map +1 -1
  127. package/esm/encryption/password.mjs +3 -1
  128. package/esm/encryption/password.mjs.map +1 -1
  129. package/esm/generations/features/index.mjs +1 -1
  130. package/esm/generations/features/mysql.feature.mjs +9 -2
  131. package/esm/generations/features/mysql.feature.mjs.map +1 -1
  132. package/esm/generations/features/test.feature.mjs +2 -1
  133. package/esm/generations/features/test.feature.mjs.map +1 -1
  134. package/esm/generations/features/web.feature.mjs +4 -2
  135. package/esm/generations/features/web.feature.mjs.map +1 -1
  136. package/esm/http/csrf-origin-policy.d.mts.map +1 -1
  137. package/esm/http/csrf-origin-policy.mjs +5 -2
  138. package/esm/http/csrf-origin-policy.mjs.map +1 -1
  139. package/esm/http/flush-pending-cookies.mjs +52 -0
  140. package/esm/http/flush-pending-cookies.mjs.map +1 -0
  141. package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
  142. package/esm/http/middleware/cache-response-middleware.mjs +4 -2
  143. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  144. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  145. package/esm/http/middleware/inject-request-context.mjs +14 -3
  146. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  147. package/esm/http/middleware/utils/cidr-match.d.mts +1 -1
  148. package/esm/http/middleware/utils/cidr-match.d.mts.map +1 -1
  149. package/esm/http/middleware/utils/cidr-match.mjs +11 -2
  150. package/esm/http/middleware/utils/cidr-match.mjs.map +1 -1
  151. package/esm/http/middleware/utils/idempotency-key.mjs +1 -1
  152. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  153. package/esm/http/plugins.d.mts.map +1 -1
  154. package/esm/http/plugins.mjs +10 -1
  155. package/esm/http/plugins.mjs.map +1 -1
  156. package/esm/http/request.d.mts +12 -1
  157. package/esm/http/request.d.mts.map +1 -1
  158. package/esm/http/request.mjs +35 -7
  159. package/esm/http/request.mjs.map +1 -1
  160. package/esm/http/response.d.mts +16 -0
  161. package/esm/http/response.d.mts.map +1 -1
  162. package/esm/http/response.mjs +75 -37
  163. package/esm/http/response.mjs.map +1 -1
  164. package/esm/http/types.d.mts +24 -0
  165. package/esm/http/types.d.mts.map +1 -1
  166. package/esm/http/uploads/uploaded-file.controller.mjs +1 -1
  167. package/esm/http/uploads-config.mjs +2 -1
  168. package/esm/http/uploads-config.mjs.map +1 -1
  169. package/esm/http/uploads-types.d.mts +3 -3
  170. package/esm/image/image.d.mts +1 -1
  171. package/esm/image/image.d.mts.map +1 -1
  172. package/esm/image/image.mjs +25 -15
  173. package/esm/image/image.mjs.map +1 -1
  174. package/esm/index.d.mts +4 -3
  175. package/esm/index.mjs +5 -3
  176. package/esm/logger/logger.mjs +6 -1
  177. package/esm/logger/logger.mjs.map +1 -1
  178. package/esm/mail/config.d.mts.map +1 -1
  179. package/esm/mail/config.mjs +31 -21
  180. package/esm/mail/config.mjs.map +1 -1
  181. package/esm/mail/react-mail.mjs +6 -3
  182. package/esm/mail/react-mail.mjs.map +1 -1
  183. package/esm/mail/send-mail.d.mts.map +1 -1
  184. package/esm/mail/send-mail.mjs +40 -19
  185. package/esm/mail/send-mail.mjs.map +1 -1
  186. package/esm/production/build-contributions.mjs +3 -0
  187. package/esm/production/build-contributions.mjs.map +1 -1
  188. package/esm/production/esbuild-plugins.mjs +3 -2
  189. package/esm/production/esbuild-plugins.mjs.map +1 -1
  190. package/esm/production/production-builder.mjs +32 -36
  191. package/esm/production/production-builder.mjs.map +1 -1
  192. package/esm/production/production-supervisor.mjs +12 -5
  193. package/esm/production/production-supervisor.mjs.map +1 -1
  194. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts +3 -1
  195. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
  196. package/esm/repositories/adapters/cascade/cascade-adapter.mjs +17 -5
  197. package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
  198. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +3 -3
  199. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  200. package/esm/repositories/adapters/cascade/filter-applicator.mjs +88 -61
  201. package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
  202. package/esm/repositories/adapters/cascade/index.d.mts +1 -1
  203. package/esm/repositories/adapters/cascade/index.mjs +1 -1
  204. package/esm/repositories/contracts/query-builder.contract.d.mts +3 -3
  205. package/esm/repositories/contracts/types.d.mts +5 -0
  206. package/esm/repositories/contracts/types.d.mts.map +1 -1
  207. package/esm/repositories/index.d.mts +1 -1
  208. package/esm/repositories/index.mjs +1 -1
  209. package/esm/repositories/repository.manager.d.mts +65 -3
  210. package/esm/repositories/repository.manager.d.mts.map +1 -1
  211. package/esm/repositories/repository.manager.mjs +146 -57
  212. package/esm/repositories/repository.manager.mjs.map +1 -1
  213. package/esm/resource/resource-field-builder.d.mts.map +1 -1
  214. package/esm/resource/resource-field-builder.mjs +1 -0
  215. package/esm/resource/resource-field-builder.mjs.map +1 -1
  216. package/esm/resource/resource.d.mts +10 -3
  217. package/esm/resource/resource.d.mts.map +1 -1
  218. package/esm/resource/resource.mjs +19 -5
  219. package/esm/resource/resource.mjs.map +1 -1
  220. package/esm/restful/restful.d.mts +12 -5
  221. package/esm/restful/restful.d.mts.map +1 -1
  222. package/esm/restful/restful.mjs +47 -16
  223. package/esm/restful/restful.mjs.map +1 -1
  224. package/esm/router/build-route-rate-limit.mjs +22 -0
  225. package/esm/router/build-route-rate-limit.mjs.map +1 -0
  226. package/esm/router/dev-dispatch.mjs +60 -0
  227. package/esm/router/dev-dispatch.mjs.map +1 -0
  228. package/esm/router/route-builder.mjs +4 -1
  229. package/esm/router/route-builder.mjs.map +1 -1
  230. package/esm/router/route-registry.d.mts.map +1 -1
  231. package/esm/router/route-registry.mjs +2 -2
  232. package/esm/router/route-registry.mjs.map +1 -1
  233. package/esm/router/router.d.mts +1 -1
  234. package/esm/router/router.d.mts.map +1 -1
  235. package/esm/router/router.mjs +57 -39
  236. package/esm/router/router.mjs.map +1 -1
  237. package/esm/socket/types.d.mts +9 -2
  238. package/esm/socket/types.d.mts.map +1 -1
  239. package/esm/storage/drivers/cloud-driver.d.mts +17 -0
  240. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  241. package/esm/storage/drivers/cloud-driver.mjs +53 -17
  242. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  243. package/esm/storage/drivers/local-driver.d.mts +7 -5
  244. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  245. package/esm/storage/drivers/local-driver.mjs +31 -26
  246. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  247. package/esm/storage/index.d.mts +2 -1
  248. package/esm/storage/index.mjs +2 -1
  249. package/esm/storage/scoped-storage.d.mts +13 -1
  250. package/esm/storage/scoped-storage.d.mts.map +1 -1
  251. package/esm/storage/scoped-storage.mjs +18 -8
  252. package/esm/storage/scoped-storage.mjs.map +1 -1
  253. package/esm/storage/storage.d.mts +0 -5
  254. package/esm/storage/storage.d.mts.map +1 -1
  255. package/esm/storage/storage.mjs +7 -16
  256. package/esm/storage/storage.mjs.map +1 -1
  257. package/esm/storage/types.d.mts +22 -4
  258. package/esm/storage/types.d.mts.map +1 -1
  259. package/esm/storage/utils/classify-ip-address.d.mts +13 -0
  260. package/esm/storage/utils/classify-ip-address.d.mts.map +1 -0
  261. package/esm/storage/utils/classify-ip-address.mjs +98 -0
  262. package/esm/storage/utils/classify-ip-address.mjs.map +1 -0
  263. package/esm/storage/utils/safe-fetch.d.mts +3 -12
  264. package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
  265. package/esm/storage/utils/safe-fetch.mjs +3 -48
  266. package/esm/storage/utils/safe-fetch.mjs.map +1 -1
  267. package/esm/updater/update-warlock-packages.mjs +11 -1
  268. package/esm/updater/update-warlock-packages.mjs.map +1 -1
  269. package/esm/use-cases/use-case-broadcast.mjs +7 -1
  270. package/esm/use-cases/use-case-broadcast.mjs.map +1 -1
  271. package/esm/use-cases/use-cases-registry.mjs +21 -2
  272. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  273. package/esm/utils/database-log.mjs +0 -3
  274. package/esm/utils/database-log.mjs.map +1 -1
  275. package/esm/utils/queue.d.mts +6 -4
  276. package/esm/utils/queue.d.mts.map +1 -1
  277. package/esm/utils/queue.mjs +20 -9
  278. package/esm/utils/queue.mjs.map +1 -1
  279. package/esm/validation/database/exists-except-current-id.mjs +6 -2
  280. package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
  281. package/esm/validation/database/unique-except-current-id.mjs +6 -2
  282. package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
  283. package/esm/validation/init.mjs +6 -2
  284. package/esm/validation/init.mjs.map +1 -1
  285. package/esm/validation/validateAll.mjs +3 -2
  286. package/esm/validation/validateAll.mjs.map +1 -1
  287. package/esm/warlock-config/types.d.mts +14 -1
  288. package/esm/warlock-config/types.d.mts.map +1 -1
  289. package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
  290. package/esm/warlock-config/warlock-config.manager.mjs +3 -1
  291. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  292. package/esm/warlock-config/warn-deprecated-config.mjs +25 -0
  293. package/esm/warlock-config/warn-deprecated-config.mjs.map +1 -0
  294. package/package.json +11 -11
  295. package/esm/_virtual/_rolldown/runtime.mjs +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"file-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-manager.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { getFileAsync, lastModifiedAsync } from \"@warlock.js/fs\";\nimport crypto from \"crypto\";\nimport { DEV_SERVER_EVENTS } from \"./events\";\nimport { type FileOperations } from \"./file-operations\";\nimport { isTypeOnlyFile, parseImports } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\nimport type { FileManifest, FileState, FileType } from \"./types\";\n\nexport type CleanupFunction = () => void;\n\n/**\n * Whether `error` is a Node `ENOENT` (path does not exist) error. Used to\n * distinguish a file that has genuinely vanished between the watcher event\n * and its read/stat (a rename or move racing the filesystem) from a real\n * failure such as `EACCES` or a parse error, which must still surface.\n */\nfunction isEnoentError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as NodeJS.ErrnoException).code === \"ENOENT\"\n );\n}\n\n/**\n * FileManager — per-file metadata for the dependency graph.\n *\n * The loader hook transpiles and caches on-demand at import time, so this\n * class never touches disk for anything beyond reading source to hash it\n * and parse its imports. Its job is keeping the metadata that the\n * orchestrator needs to invalidate dependents on change.\n */\nexport class FileManager {\n public relativePath = \"\";\n public lastModified = 0;\n public hash = \"\";\n public source = \"\";\n\n /** Files this file imports (relative paths). */\n public dependencies = new Set<string>();\n\n /**\n * Subset of `dependencies` reached only through type-only imports — a single\n * runtime occurrence makes the whole edge runtime.\n */\n public typeOnlyDependencies = new Set<string>();\n\n /** Original specifier → resolved absolute path. */\n public importMap = new Map<string, string>();\n\n /** Files that import this file (relative paths). */\n public dependents = new Set<string>();\n\n public type: FileType | undefined;\n\n /** Cleanup hooks the loaded module registered for itself. */\n public cleanup: CleanupFunction[] = [];\n\n /** Whether the source has zero runtime imports/exports. */\n public isTypeOnlyFile = false;\n\n public state: FileState = \"idle\";\n\n public constructor(\n public readonly absolutePath: string,\n public files: Map<string, FileManager>,\n public fileOperations: FileOperations,\n ) {}\n\n public addCleanup(cleanup: CleanupFunction | CleanupFunction[]) {\n const next = Array.isArray(cleanup) ? cleanup : [cleanup];\n this.cleanup.push(...next);\n this.cleanup = [...new Set(this.cleanup)];\n }\n\n public resetCleanup() {\n this.cleanup = [];\n }\n\n /**\n * Initial setup. Restores from manifest if the hash still matches,\n * otherwise re-processes from disk.\n */\n public async init(fileManifest?: Partial<FileManifest>): Promise<void> {\n this.relativePath = Path.toRelative(this.absolutePath);\n this.detectFileType();\n\n if (fileManifest) {\n await this.initFromManifest(fileManifest);\n } else {\n await this.process();\n }\n }\n\n /**\n * Read source, hash, parse imports, emit ready. The only file-system\n * touch happens here; transpilation is the loader hook's job.\n *\n * Both the read and the stat happen up front, before anything on this\n * instance is mutated. A path that disappears between the watcher event\n * and either of those calls (`ENOENT`) — typically a rename or move\n * racing the filesystem — is treated as a deletion rather than a thrown\n * error, and never leaves `source`/`hash` pointing at content that then\n * vanished. A real failure (`EACCES`, a permissions error, etc.) still\n * throws.\n *\n * @param force - re-parse even when the hash matches.\n * @returns true if the file was (re)parsed.\n */\n public async process({ force = false }: { force?: boolean } = {}): Promise<boolean> {\n if (!this.relativePath) this.relativePath = Path.toRelative(this.absolutePath);\n if (!this.type) this.detectFileType();\n\n this.state = \"loading\";\n\n let newSource: string;\n let modifiedTime: Date;\n\n try {\n newSource = await getFileAsync(this.absolutePath);\n modifiedTime = await lastModifiedAsync(this.absolutePath);\n } catch (error) {\n if (isEnoentError(error)) {\n this.state = \"deleted\";\n return false;\n }\n\n throw error;\n }\n\n const newHash = crypto.createHash(\"sha256\").update(newSource).digest(\"hex\");\n\n if (!force && newHash === this.hash) {\n this.state = \"ready\";\n return false;\n }\n\n this.source = newSource;\n this.hash = newHash;\n this.lastModified = modifiedTime.getTime();\n\n await this.rebuildImportMetadata();\n this.isTypeOnlyFile = isTypeOnlyFile(this.source);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n\n return true;\n }\n\n /**\n * Parse the current source and refresh `importMap`, `dependencies`, and\n * `typeOnlyDependencies`. A dependency is classified type-only iff every\n * import/export statement that resolves to it is type-only — a single\n * runtime reference makes the whole edge runtime.\n */\n protected async rebuildImportMetadata(): Promise<void> {\n const resolved = await parseImports(this.source, this.absolutePath);\n\n this.importMap = new Map();\n this.dependencies = new Set();\n this.typeOnlyDependencies = new Set();\n\n const runtimePaths = new Set<string>();\n\n for (const [originalPath, { absolutePath, isTypeOnly }] of resolved) {\n this.importMap.set(originalPath, absolutePath);\n\n const relativePath = Path.toRelative(absolutePath);\n this.dependencies.add(relativePath);\n\n if (!isTypeOnly) {\n runtimePaths.add(relativePath);\n }\n }\n\n for (const dependency of this.dependencies) {\n if (!runtimePaths.has(dependency)) {\n this.typeOnlyDependencies.add(dependency);\n }\n }\n }\n\n /**\n * Restore from a cached manifest entry. If the on-disk hash matches the\n * manifest hash, dep-graph metadata is restored from the manifest without\n * re-parsing. Otherwise the file is reprocessed.\n */\n protected async initFromManifest(fileManifest: Partial<FileManifest>): Promise<void> {\n this.type = fileManifest.type;\n\n this.state = \"loading\";\n\n try {\n this.source = await getFileAsync(this.absolutePath);\n } catch {\n this.state = \"deleted\";\n return;\n }\n\n const currentHash = crypto.createHash(\"sha256\").update(this.source).digest(\"hex\");\n\n if (currentHash !== fileManifest.hash) {\n await this.process({ force: true });\n return;\n }\n\n this.hash = fileManifest.hash!;\n this.lastModified = fileManifest.lastModified!;\n this.dependencies = new Set(fileManifest.dependencies || []);\n this.typeOnlyDependencies = new Set(fileManifest.typeOnlyDependencies || []);\n this.dependents = new Set(fileManifest.dependents || []);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n }\n\n protected detectFileType(): void {\n const path = this.relativePath;\n\n if (path.includes(\"main.ts\") || path.includes(\"main.tsx\")) {\n this.type = \"main\";\n } else if (path.startsWith(\"src/config/\")) {\n this.type = \"config\";\n } else if (path.endsWith(\"routes.ts\") || path.endsWith(\"routes.tsx\")) {\n this.type = \"route\";\n } else if (path.includes(\"/events/\")) {\n this.type = \"event\";\n } else if (path.includes(\"controller\")) {\n this.type = \"controller\";\n } else if (path.includes(\"service\")) {\n this.type = \"service\";\n } else if (path.endsWith(\".model.ts\")) {\n this.type = \"model\";\n } else {\n this.type = \"other\";\n }\n }\n\n public toManifest(): FileManifest {\n return {\n absolutePath: this.absolutePath,\n relativePath: this.relativePath,\n lastModified: this.lastModified,\n hash: this.hash,\n dependencies: Array.from(this.dependencies),\n typeOnlyDependencies: Array.from(this.typeOnlyDependencies),\n dependents: Array.from(this.dependents),\n type: this.type!,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-manager.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { getFileAsync, lastModifiedAsync } from \"@warlock.js/fs\";\nimport crypto from \"crypto\";\nimport { DEV_SERVER_EVENTS } from \"./events\";\nimport { type FileOperations } from \"./file-operations\";\nimport { isTypeOnlyFile, parseImports } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\nimport type { FileManifest, FileState, FileType } from \"./types\";\n\nexport type CleanupFunction = () => void;\n\n/**\n * Whether `error` is a Node `ENOENT` (path does not exist) error. Used to\n * distinguish a file that has genuinely vanished between the watcher event\n * and its read/stat (a rename or move racing the filesystem) from a real\n * failure such as `EACCES` or a parse error, which must still surface.\n */\nfunction isEnoentError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as NodeJS.ErrnoException).code === \"ENOENT\"\n );\n}\n\n/**\n * FileManager — per-file metadata for the dependency graph.\n *\n * The loader hook transpiles and caches on-demand at import time, so this\n * class never touches disk for anything beyond reading source to hash it\n * and parse its imports. Its job is keeping the metadata that the\n * orchestrator needs to invalidate dependents on change.\n */\nexport class FileManager {\n public relativePath = \"\";\n public lastModified = 0;\n public hash = \"\";\n public source = \"\";\n\n /** Files this file imports (relative paths). */\n public dependencies = new Set<string>();\n\n /**\n * Subset of `dependencies` reached only through type-only imports — a single\n * runtime occurrence makes the whole edge runtime.\n */\n public typeOnlyDependencies = new Set<string>();\n\n /** Original specifier → resolved absolute path. */\n public importMap = new Map<string, string>();\n\n /** Files that import this file (relative paths). */\n public dependents = new Set<string>();\n\n public type: FileType | undefined;\n\n /** Cleanup hooks the loaded module registered for itself. */\n public cleanup: CleanupFunction[] = [];\n\n /** Whether the source has zero runtime imports/exports. */\n public isTypeOnlyFile = false;\n\n public state: FileState = \"idle\";\n\n public constructor(\n public readonly absolutePath: string,\n public files: Map<string, FileManager>,\n public fileOperations: FileOperations,\n ) {}\n\n public addCleanup(cleanup: CleanupFunction | CleanupFunction[]) {\n const next = Array.isArray(cleanup) ? cleanup : [cleanup];\n this.cleanup.push(...next);\n this.cleanup = [...new Set(this.cleanup)];\n }\n\n public resetCleanup() {\n this.cleanup = [];\n }\n\n /**\n * Initial setup. Restores from manifest if the hash still matches,\n * otherwise re-processes from disk.\n */\n public async init(fileManifest?: Partial<FileManifest>): Promise<void> {\n this.relativePath = Path.toRelative(this.absolutePath);\n this.detectFileType();\n\n if (fileManifest) {\n await this.initFromManifest(fileManifest);\n } else {\n await this.process();\n }\n }\n\n /**\n * Read source, hash, parse imports, emit ready. The only file-system\n * touch happens here; transpilation is the loader hook's job.\n *\n * Both the read and the stat happen up front, before anything on this\n * instance is mutated. A path that disappears between the watcher event\n * and either of those calls (`ENOENT`) — typically a rename or move\n * racing the filesystem — is treated as a deletion rather than a thrown\n * error, and never leaves `source`/`hash` pointing at content that then\n * vanished. A real failure (`EACCES`, a permissions error, etc.) still\n * throws.\n *\n * @param force - re-parse even when the hash matches.\n * @returns true if the file was (re)parsed.\n */\n public async process({ force = false }: { force?: boolean } = {}): Promise<boolean> {\n if (!this.relativePath) this.relativePath = Path.toRelative(this.absolutePath);\n if (!this.type) this.detectFileType();\n\n this.state = \"loading\";\n\n let newSource: string;\n let modifiedTime: Date;\n\n try {\n newSource = await getFileAsync(this.absolutePath);\n modifiedTime = await lastModifiedAsync(this.absolutePath);\n } catch (error) {\n if (isEnoentError(error)) {\n this.state = \"deleted\";\n return false;\n }\n\n throw error;\n }\n\n const newHash = crypto.createHash(\"sha256\").update(newSource).digest(\"hex\");\n\n if (!force && newHash === this.hash) {\n this.state = \"ready\";\n return false;\n }\n\n this.source = newSource;\n this.hash = newHash;\n this.lastModified = modifiedTime.getTime();\n\n await this.rebuildImportMetadata();\n this.isTypeOnlyFile = isTypeOnlyFile(this.source);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n\n return true;\n }\n\n /**\n * Parse the current source and refresh `importMap`, `dependencies`, and\n * `typeOnlyDependencies`. A dependency is classified type-only iff every\n * import/export statement that resolves to it is type-only — a single\n * runtime reference makes the whole edge runtime.\n */\n protected async rebuildImportMetadata(): Promise<void> {\n const resolved = await parseImports(this.source, this.absolutePath);\n\n this.importMap = new Map();\n this.dependencies = new Set();\n this.typeOnlyDependencies = new Set();\n\n const runtimePaths = new Set<string>();\n\n for (const [originalPath, { absolutePath, isTypeOnly }] of resolved) {\n this.importMap.set(originalPath, absolutePath);\n\n const relativePath = Path.toRelative(absolutePath);\n this.dependencies.add(relativePath);\n\n if (!isTypeOnly) {\n runtimePaths.add(relativePath);\n }\n }\n\n for (const dependency of this.dependencies) {\n if (!runtimePaths.has(dependency)) {\n this.typeOnlyDependencies.add(dependency);\n }\n }\n }\n\n /**\n * Restore from a cached manifest entry. If the on-disk hash matches the\n * manifest hash, dep-graph metadata is restored from the manifest without\n * re-parsing. Otherwise the file is reprocessed.\n */\n protected async initFromManifest(fileManifest: Partial<FileManifest>): Promise<void> {\n this.type = fileManifest.type;\n\n this.state = \"loading\";\n\n try {\n this.source = await getFileAsync(this.absolutePath);\n } catch {\n this.state = \"deleted\";\n return;\n }\n\n const currentHash = crypto.createHash(\"sha256\").update(this.source).digest(\"hex\");\n\n if (currentHash !== fileManifest.hash) {\n await this.process({ force: true });\n return;\n }\n\n this.hash = fileManifest.hash!;\n this.lastModified = fileManifest.lastModified!;\n this.dependencies = new Set(fileManifest.dependencies || []);\n this.typeOnlyDependencies = new Set(fileManifest.typeOnlyDependencies || []);\n this.dependents = new Set(fileManifest.dependents || []);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n }\n\n protected detectFileType(): void {\n const path = this.relativePath;\n\n if (/(^|\\/)main\\.tsx?$/.test(path)) {\n this.type = \"main\";\n } else if (/\\.model\\.tsx?$/.test(path)) {\n // Before the folder-name checks below: `events/models/x.model.ts` is a model.\n this.type = \"model\";\n } else if (path.startsWith(\"src/config/\")) {\n this.type = \"config\";\n } else if (path.endsWith(\"routes.ts\") || path.endsWith(\"routes.tsx\")) {\n this.type = \"route\";\n } else if (path.includes(\"/events/\")) {\n this.type = \"event\";\n } else if (path.includes(\"controller\")) {\n this.type = \"controller\";\n } else if (path.includes(\"service\")) {\n this.type = \"service\";\n } else {\n this.type = \"other\";\n }\n }\n\n public toManifest(): FileManifest {\n return {\n absolutePath: this.absolutePath,\n relativePath: this.relativePath,\n lastModified: this.lastModified,\n hash: this.hash,\n dependencies: Array.from(this.dependencies),\n typeOnlyDependencies: Array.from(this.typeOnlyDependencies),\n dependents: Array.from(this.dependents),\n type: this.type!,\n };\n }\n}\n"],"mappings":"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"}
@@ -8,7 +8,7 @@ import { FileEventHandler } from "./file-event-handler.mjs";
8
8
  import { FileManager } from "./file-manager.mjs";
9
9
  import { ensureWarlockDirectory, getFilesFromDirectory } from "./utils.mjs";
10
10
  import { FileOperations } from "./file-operations.mjs";
11
- import { FilesWatcher } from "./files-watcher.mjs";
11
+ import { FilesWatcher, createIgnoredMatcher } from "./files-watcher.mjs";
12
12
  import { EslintHealthChecker } from "./health-checker/checkers/eslint-health-checker.mjs";
13
13
  import { TypescriptHealthChecker } from "./health-checker/checkers/typescript-health-checker.mjs";
14
14
  import { FilesHealthcareManager } from "./health-checker/files-healthcare.manager.mjs";
@@ -20,6 +20,7 @@ import { packageJsonManager } from "./package-json-manager.mjs";
20
20
  import { SpecialFilesCollector } from "./special-files-collector.mjs";
21
21
  import { colors } from "@mongez/copper";
22
22
  import { init } from "es-module-lexer";
23
+ import { fileURLToPath } from "node:url";
23
24
 
24
25
  //#region ../core/src/dev-server/files-orchestrator.ts
25
26
  /**
@@ -173,6 +174,7 @@ var FilesOrchestrator = class {
173
174
  this.filesWatcher.onFileAdd((p, settleMs) => this.eventHandler.handleFileAdd(p, settleMs));
174
175
  this.filesWatcher.onFileDelete((p, settleMs) => this.eventHandler.handleFileDelete(p, settleMs));
175
176
  const watchConfig = warlockConfigManager.get("devServer")?.watch;
177
+ if (watchConfig?.include?.length) this.eventHandler.setIncludeMatcher(createIgnoredMatcher(watchConfig.include));
176
178
  await this.filesWatcher.watch(watchConfig);
177
179
  }
178
180
  };
@@ -1 +1 @@
1
- {"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { init } from \"es-module-lexer\";\r\nimport type { MessagePort } from \"node:worker_threads\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { DependencyGraph } from \"./dependency-graph\";\r\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\r\nimport { FileEventHandler } from \"./file-event-handler\";\r\nimport { FileManager } from \"./file-manager\";\r\nimport { FileOperations } from \"./file-operations\";\r\nimport { FilesWatcher } from \"./files-watcher\";\r\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\r\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\r\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\r\nimport type { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\r\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\r\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\r\nimport { registerLoader } from \"./loader/register-loader.js\";\r\nimport { ManifestManager } from \"./manifest-manager\";\r\nimport { ModuleLoader } from \"./module-loader\";\r\nimport { packageJsonManager } from \"./package-json-manager\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { SpecialFilesCollector } from \"./special-files-collector\";\r\nimport { tsconfigManager } from \"./tsconfig-manager\";\r\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\r\n\r\n/**\r\n * Register a cleanup callback that fires before the current module is\r\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\r\n * thread the FileManager through.\r\n */\r\nexport function onCleanup(callback: () => any) {\r\n if (globalThis.__currentModuleFile) {\r\n globalThis.__currentModuleFile.addCleanup(callback);\r\n return;\r\n }\r\n\r\n const stack = new Error().stack;\r\n const callerLine = stack?.split(\"\\n\")[2];\r\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\r\n const callerFile = match?.[1];\r\n\r\n if (!callerFile) return;\r\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\r\n fileManager?.addCleanup(callback);\r\n}\r\n\r\n/**\r\n * Top-level coordinator for the dev server's file system.\r\n *\r\n * Owns: file discovery, the dependency graph, the manifest, the special-files\r\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\r\n * provides cache-busting via `?v=N` version tokens).\r\n */\r\nexport class FilesOrchestrator {\r\n public readonly filesWatcher = new FilesWatcher();\r\n public readonly files = new Map<string, FileManager>();\r\n private readonly manifest = new ManifestManager(this.files);\r\n private readonly dependencyGraph = new DependencyGraph();\r\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\r\n public readonly specialFilesCollector = new SpecialFilesCollector();\r\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\r\n public readonly fileOperations: FileOperations;\r\n private readonly eventHandler: FileEventHandler;\r\n\r\n /** Main-thread end of the MessageChannel to the loader hook worker. */\r\n private loaderPort: MessagePort | null = null;\r\n\r\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\r\n private readonly pendingFlushes: Array<() => void> = [];\r\n\r\n public isInitialized = false;\r\n\r\n public constructor() {\r\n this.fileOperations = new FileOperations(\r\n this.files,\r\n this.dependencyGraph,\r\n this.manifest,\r\n this.specialFilesCollector,\r\n );\r\n\r\n this.eventHandler = new FileEventHandler(\r\n this.fileOperations,\r\n this.manifest,\r\n this.dependencyGraph,\r\n this.files,\r\n );\r\n }\r\n\r\n public async add(relativePath: string): Promise<FileManager> {\r\n return this.fileOperations.addFile(relativePath);\r\n }\r\n\r\n public async load<T>(relativePath: string, type = \"other\") {\r\n // Guarantee the ESM loader hook is registered before we import anything.\r\n // `load()` is the dynamic-import entrypoint, so it is the one place that\r\n // must satisfy `init()`'s documented precondition (\"Must be called before\r\n // any user src/ module is dynamically imported\") on its own — it cannot\r\n // assume a caller ran `init()` first.\r\n //\r\n // The CLI command-resolution path proves why: `cliCommandsLoader.load()`\r\n // imports a project command module during command *lookup* (before the\r\n // command's own `execute()` runs), whereas `loadPreloaders()` only calls\r\n // `init()` from *inside* `execute()`. So a `warlock <command>` whose\r\n // command file imports an `app/*` alias or a `.ts` sibling was imported\r\n // with no hook registered and died on `ERR_MODULE_NOT_FOUND` (f9d8f99f).\r\n //\r\n // `init()` is idempotent (guarded by `isInitialized`), so this is a no-op\r\n // on the dev-server path, which already calls it explicitly at startup.\r\n await this.init();\r\n\r\n const fileManager = await this.add(relativePath);\r\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\r\n }\r\n\r\n public getDependencyGraph(): DependencyGraph {\r\n return this.dependencyGraph;\r\n }\r\n\r\n public getInvalidationChain(file: string): string[] {\r\n return this.dependencyGraph.getInvalidationChain(file);\r\n }\r\n\r\n public getFiles(): Map<string, FileManager> {\r\n return this.files;\r\n }\r\n\r\n public getHealthCheckerManager(): FilesHealthcareManager {\r\n return this.healthCheckerManager;\r\n }\r\n\r\n /**\r\n * Initialise managers and register the ESM loader hook. Must be called\r\n * before any user `src/` module is dynamically imported.\r\n */\r\n public async init() {\r\n if (this.isInitialized) return;\r\n this.isInitialized = true;\r\n\r\n await init;\r\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\r\n\r\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\r\n await ensureWarlockDirectory();\r\n\r\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\r\n const transpileInit = buildTranspileInit(\r\n tsconfigManager.tsconfig?.compilerOptions,\r\n devServerConfig?.transpileCacheDebug === true,\r\n );\r\n\r\n this.loaderPort = await registerLoader(transpileInit);\r\n\r\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\r\n if (msg.type === \"sync-ack\") {\r\n this.pendingFlushes.shift()?.();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Tell the hook worker a file changed. The next `import()` of it will get\r\n * a fresh `?v=N` URL → Node cache miss → fresh content.\r\n */\r\n public bumpVersion(absolutePath: string): void {\r\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\r\n }\r\n\r\n /**\r\n * Wait until the hook worker has processed every pending bump.\r\n *\r\n * `postMessage` is async — without this, a follow-up `import()` may\r\n * resolve before the worker increments the counter and Node will return\r\n * the cached old module.\r\n */\r\n public flushVersionBumps(): Promise<void> {\r\n if (!this.loaderPort) return Promise.resolve();\r\n\r\n return new Promise<void>((resolve) => {\r\n this.pendingFlushes.push(resolve);\r\n this.loaderPort!.postMessage({ type: \"sync\" });\r\n });\r\n }\r\n\r\n /**\r\n * Discover files on disk, reconcile against the manifest, build the dep\r\n * graph, and persist the new manifest. Idempotent.\r\n */\r\n public async initializeAll() {\r\n const [filesInFilesystem, manifestExists] = await Promise.all([\r\n this.getAllFilesFromFilesystem(),\r\n this.manifest.init(),\r\n ]);\r\n\r\n if (!manifestExists) {\r\n await this.processFiles(filesInFilesystem);\r\n } else {\r\n await this.reconcileFiles(filesInFilesystem);\r\n }\r\n\r\n this.dependencyGraph.build(this.files);\r\n this.fileOperations.updateFileDependents();\r\n this.fileOperations.syncFilesToManifest();\r\n await this.manifest.save();\r\n }\r\n\r\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\r\n const filesToCheck = [...files.added, ...files.changed]\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n const filesToDelete = files.deleted\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n await this.healthCheckerManager.onFileChanges(filesToCheck);\r\n this.healthCheckerManager.removeFiles(filesToDelete);\r\n this.healthCheckerManager.checkFiles(filesToCheck);\r\n }\r\n\r\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\r\n devLogDim(\"Started File Health Checks in the background.\");\r\n\r\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\r\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\r\n await this.healthCheckerManager.validateAllFiles();\r\n }\r\n\r\n /**\r\n * Glob the src directory, returning relative paths.\r\n */\r\n public async getAllFilesFromFilesystem(): Promise<string[]> {\r\n const absolutePaths = await getFilesFromDirectory();\r\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\r\n }\r\n\r\n /**\r\n * Process every file from scratch — used when no manifest exists.\r\n */\r\n private async processFiles(filePaths: string[]) {\r\n devLogDim(`processing ${filePaths.length} files...`);\r\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.process();\r\n });\r\n devLogSuccess(`processed ${filePaths.length} files`);\r\n }\r\n\r\n private async reconcileFiles(filesInFilesystem: string[]) {\r\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\r\n const filesInFilesystemSet = new Set(filesInFilesystem);\r\n\r\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\r\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\r\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\r\n\r\n if (newFiles.length > 0 || deletedFiles.length > 0) {\r\n devLogDim(\r\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\r\n );\r\n }\r\n\r\n await this.processFiles(newFiles);\r\n\r\n for (const relativePath of deletedFiles) {\r\n this.manifest.removeFile(relativePath);\r\n this.files.delete(relativePath);\r\n }\r\n\r\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.init(this.manifest.getFile(relativePath));\r\n });\r\n }\r\n\r\n public async watchFiles() {\r\n devLogSuccess(\"watching for file changes (not serving yet)\");\r\n\r\n this.filesWatcher.onFileChange((p, settleMs) => this.eventHandler.handleFileChange(p, settleMs));\r\n this.filesWatcher.onFileAdd((p, settleMs) => this.eventHandler.handleFileAdd(p, settleMs));\r\n this.filesWatcher.onFileDelete((p, settleMs) => this.eventHandler.handleFileDelete(p, settleMs));\r\n\r\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\r\n await this.filesWatcher.watch(watchConfig);\r\n }\r\n}\r\n\r\nasync function runInBatches<T>(\r\n items: T[],\r\n size: number,\r\n fn: (item: T) => Promise<unknown>,\r\n): Promise<void> {\r\n if (items.length === 0) return;\r\n for (let i = 0; i < items.length; i += size) {\r\n await Promise.all(items.slice(i, i + size).map(fn));\r\n }\r\n}\r\n\r\nexport const filesOrchestrator = new FilesOrchestrator();\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { init } from \"es-module-lexer\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport type { MessagePort } from \"node:worker_threads\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { DependencyGraph } from \"./dependency-graph\";\r\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\r\nimport { FileEventHandler } from \"./file-event-handler\";\r\nimport { FileManager } from \"./file-manager\";\r\nimport { FileOperations } from \"./file-operations\";\r\nimport { FilesWatcher, createIgnoredMatcher } from \"./files-watcher\";\r\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\r\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\r\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\r\nimport type { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\r\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\r\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\r\nimport { registerLoader } from \"./loader/register-loader.js\";\r\nimport { ManifestManager } from \"./manifest-manager\";\r\nimport { ModuleLoader } from \"./module-loader\";\r\nimport { packageJsonManager } from \"./package-json-manager\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { SpecialFilesCollector } from \"./special-files-collector\";\r\nimport { tsconfigManager } from \"./tsconfig-manager\";\r\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\r\n\r\n/**\r\n * Register a cleanup callback that fires before the current module is\r\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\r\n * thread the FileManager through.\r\n */\r\nexport function onCleanup(callback: () => any) {\r\n if (globalThis.__currentModuleFile) {\r\n globalThis.__currentModuleFile.addCleanup(callback);\r\n return;\r\n }\r\n\r\n const stack = new Error().stack;\r\n const callerLine = stack?.split(\"\\n\")[2];\r\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\r\n const rawCaller = match?.[1];\r\n\r\n // Loaded modules report `file:///…?v=N`; turn that back into a plain path.\r\n const callerFile = rawCaller?.startsWith(\"file://\")\r\n ? fileURLToPath(rawCaller.replace(/\\?.*$/, \"\"))\r\n : rawCaller;\r\n\r\n if (!callerFile) return;\r\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\r\n fileManager?.addCleanup(callback);\r\n}\r\n\r\n/**\r\n * Top-level coordinator for the dev server's file system.\r\n *\r\n * Owns: file discovery, the dependency graph, the manifest, the special-files\r\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\r\n * provides cache-busting via `?v=N` version tokens).\r\n */\r\nexport class FilesOrchestrator {\r\n public readonly filesWatcher = new FilesWatcher();\r\n public readonly files = new Map<string, FileManager>();\r\n private readonly manifest = new ManifestManager(this.files);\r\n private readonly dependencyGraph = new DependencyGraph();\r\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\r\n public readonly specialFilesCollector = new SpecialFilesCollector();\r\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\r\n public readonly fileOperations: FileOperations;\r\n private readonly eventHandler: FileEventHandler;\r\n\r\n /** Main-thread end of the MessageChannel to the loader hook worker. */\r\n private loaderPort: MessagePort | null = null;\r\n\r\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\r\n private readonly pendingFlushes: Array<() => void> = [];\r\n\r\n public isInitialized = false;\r\n\r\n public constructor() {\r\n this.fileOperations = new FileOperations(\r\n this.files,\r\n this.dependencyGraph,\r\n this.manifest,\r\n this.specialFilesCollector,\r\n );\r\n\r\n this.eventHandler = new FileEventHandler(\r\n this.fileOperations,\r\n this.manifest,\r\n this.dependencyGraph,\r\n this.files,\r\n );\r\n }\r\n\r\n public async add(relativePath: string): Promise<FileManager> {\r\n return this.fileOperations.addFile(relativePath);\r\n }\r\n\r\n public async load<T>(relativePath: string, type = \"other\") {\r\n // Guarantee the ESM loader hook is registered before we import anything.\r\n // `load()` is the dynamic-import entrypoint, so it is the one place that\r\n // must satisfy `init()`'s documented precondition (\"Must be called before\r\n // any user src/ module is dynamically imported\") on its own — it cannot\r\n // assume a caller ran `init()` first.\r\n //\r\n // The CLI command-resolution path proves why: `cliCommandsLoader.load()`\r\n // imports a project command module during command *lookup* (before the\r\n // command's own `execute()` runs), whereas `loadPreloaders()` only calls\r\n // `init()` from *inside* `execute()`. So a `warlock <command>` whose\r\n // command file imports an `app/*` alias or a `.ts` sibling was imported\r\n // with no hook registered and died on `ERR_MODULE_NOT_FOUND` (f9d8f99f).\r\n //\r\n // `init()` is idempotent (guarded by `isInitialized`), so this is a no-op\r\n // on the dev-server path, which already calls it explicitly at startup.\r\n await this.init();\r\n\r\n const fileManager = await this.add(relativePath);\r\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\r\n }\r\n\r\n public getDependencyGraph(): DependencyGraph {\r\n return this.dependencyGraph;\r\n }\r\n\r\n public getInvalidationChain(file: string): string[] {\r\n return this.dependencyGraph.getInvalidationChain(file);\r\n }\r\n\r\n public getFiles(): Map<string, FileManager> {\r\n return this.files;\r\n }\r\n\r\n public getHealthCheckerManager(): FilesHealthcareManager {\r\n return this.healthCheckerManager;\r\n }\r\n\r\n /**\r\n * Initialise managers and register the ESM loader hook. Must be called\r\n * before any user `src/` module is dynamically imported.\r\n */\r\n public async init() {\r\n if (this.isInitialized) return;\r\n this.isInitialized = true;\r\n\r\n await init;\r\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\r\n\r\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\r\n await ensureWarlockDirectory();\r\n\r\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\r\n const transpileInit = buildTranspileInit(\r\n tsconfigManager.tsconfig?.compilerOptions,\r\n devServerConfig?.transpileCacheDebug === true,\r\n );\r\n\r\n this.loaderPort = await registerLoader(transpileInit);\r\n\r\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\r\n if (msg.type === \"sync-ack\") {\r\n this.pendingFlushes.shift()?.();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Tell the hook worker a file changed. The next `import()` of it will get\r\n * a fresh `?v=N` URL → Node cache miss → fresh content.\r\n */\r\n public bumpVersion(absolutePath: string): void {\r\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\r\n }\r\n\r\n /**\r\n * Wait until the hook worker has processed every pending bump.\r\n *\r\n * `postMessage` is async — without this, a follow-up `import()` may\r\n * resolve before the worker increments the counter and Node will return\r\n * the cached old module.\r\n */\r\n public flushVersionBumps(): Promise<void> {\r\n if (!this.loaderPort) return Promise.resolve();\r\n\r\n return new Promise<void>((resolve) => {\r\n this.pendingFlushes.push(resolve);\r\n this.loaderPort!.postMessage({ type: \"sync\" });\r\n });\r\n }\r\n\r\n /**\r\n * Discover files on disk, reconcile against the manifest, build the dep\r\n * graph, and persist the new manifest. Idempotent.\r\n */\r\n public async initializeAll() {\r\n const [filesInFilesystem, manifestExists] = await Promise.all([\r\n this.getAllFilesFromFilesystem(),\r\n this.manifest.init(),\r\n ]);\r\n\r\n if (!manifestExists) {\r\n await this.processFiles(filesInFilesystem);\r\n } else {\r\n await this.reconcileFiles(filesInFilesystem);\r\n }\r\n\r\n this.dependencyGraph.build(this.files);\r\n this.fileOperations.updateFileDependents();\r\n this.fileOperations.syncFilesToManifest();\r\n await this.manifest.save();\r\n }\r\n\r\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\r\n const filesToCheck = [...files.added, ...files.changed]\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n const filesToDelete = files.deleted\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n await this.healthCheckerManager.onFileChanges(filesToCheck);\r\n this.healthCheckerManager.removeFiles(filesToDelete);\r\n this.healthCheckerManager.checkFiles(filesToCheck);\r\n }\r\n\r\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\r\n devLogDim(\"Started File Health Checks in the background.\");\r\n\r\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\r\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\r\n await this.healthCheckerManager.validateAllFiles();\r\n }\r\n\r\n /**\r\n * Glob the src directory, returning relative paths.\r\n */\r\n public async getAllFilesFromFilesystem(): Promise<string[]> {\r\n const absolutePaths = await getFilesFromDirectory();\r\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\r\n }\r\n\r\n /**\r\n * Process every file from scratch — used when no manifest exists.\r\n */\r\n private async processFiles(filePaths: string[]) {\r\n devLogDim(`processing ${filePaths.length} files...`);\r\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.process();\r\n });\r\n devLogSuccess(`processed ${filePaths.length} files`);\r\n }\r\n\r\n private async reconcileFiles(filesInFilesystem: string[]) {\r\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\r\n const filesInFilesystemSet = new Set(filesInFilesystem);\r\n\r\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\r\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\r\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\r\n\r\n if (newFiles.length > 0 || deletedFiles.length > 0) {\r\n devLogDim(\r\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\r\n );\r\n }\r\n\r\n await this.processFiles(newFiles);\r\n\r\n for (const relativePath of deletedFiles) {\r\n this.manifest.removeFile(relativePath);\r\n this.files.delete(relativePath);\r\n }\r\n\r\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.init(this.manifest.getFile(relativePath));\r\n });\r\n }\r\n\r\n public async watchFiles() {\r\n devLogSuccess(\"watching for file changes (not serving yet)\");\r\n\r\n this.filesWatcher.onFileChange((p, settleMs) => this.eventHandler.handleFileChange(p, settleMs));\r\n this.filesWatcher.onFileAdd((p, settleMs) => this.eventHandler.handleFileAdd(p, settleMs));\r\n this.filesWatcher.onFileDelete((p, settleMs) => this.eventHandler.handleFileDelete(p, settleMs));\r\n\r\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\r\n if (watchConfig?.include?.length) {\n this.eventHandler.setIncludeMatcher(createIgnoredMatcher(watchConfig.include));\n }\n\n await this.filesWatcher.watch(watchConfig);\r\n }\r\n}\r\n\r\nasync function runInBatches<T>(\r\n items: T[],\r\n size: number,\r\n fn: (item: T) => Promise<unknown>,\r\n): Promise<void> {\r\n if (items.length === 0) return;\r\n for (let i = 0; i < items.length; i += size) {\r\n await Promise.all(items.slice(i, i + size).map(fn));\r\n }\r\n}\r\n\r\nexport const filesOrchestrator = new FilesOrchestrator();\r\n"],"mappings":"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"}
@@ -5,6 +5,8 @@ import { warlockConfigManager } from "../warlock-config/warlock-config.manager.m
5
5
  import path from "node:path";
6
6
  import { Random } from "@mongez/reinforcements";
7
7
  import events from "@mongez/events";
8
+ import fs from "node:fs";
9
+ import glob from "fast-glob";
8
10
  import chokidar from "chokidar";
9
11
 
10
12
  //#region ../core/src/dev-server/files-watcher.ts
@@ -17,6 +19,58 @@ const DEFAULT_EXCLUDE = [
17
19
  "**/.warlock/**",
18
20
  "**/.git/**"
19
21
  ];
22
+ const GLOB_CHARS = /[*?{}[\]]/;
23
+ /**
24
+ * Convert a glob (`**`, `*`, `?`, `{a,b}`) to a RegExp over a forward-slash
25
+ * path. chokidar >= 4 has no glob support, so patterns are matched here
26
+ * instead of being handed to it.
27
+ */
28
+ function globToRegExp(glob) {
29
+ let source = "";
30
+ let braceDepth = 0;
31
+ for (let i = 0; i < glob.length; i++) {
32
+ const char = glob[i];
33
+ if (char === "*") if (glob[i + 1] === "*") {
34
+ i++;
35
+ if (glob[i + 1] === "/") {
36
+ i++;
37
+ source += "(?:.*/)?";
38
+ } else source += ".*";
39
+ } else source += "[^/]*";
40
+ else if (char === "?") source += "[^/]";
41
+ else if (char === "{") {
42
+ braceDepth++;
43
+ source += "(?:";
44
+ } else if (char === "}" && braceDepth > 0) {
45
+ braceDepth--;
46
+ source += ")";
47
+ } else if (char === "," && braceDepth > 0) source += "|";
48
+ else source += /[.+^$()|[\]\\]/.test(char) ? `\\${char}` : char;
49
+ }
50
+ return new RegExp(`^${source}$`);
51
+ }
52
+ /**
53
+ * Build a chokidar `ignored` predicate from glob and literal patterns. A
54
+ * pattern matches the absolute path or the project-relative path.
55
+ */
56
+ function createIgnoredMatcher(patterns) {
57
+ const matchers = patterns.map((pattern) => globToRegExp(Path.normalize(pattern)));
58
+ return (path) => {
59
+ const absolute = Path.normalize(path);
60
+ const relative = Path.toRelative(absolute);
61
+ return matchers.some((matcher) => matcher.test(absolute) || matcher.test(relative));
62
+ };
63
+ }
64
+ /**
65
+ * Expand glob `include` entries to concrete files (chokidar watches literal
66
+ * paths only); literal entries pass through unchanged.
67
+ */
68
+ function expandIncludes(include) {
69
+ return include.flatMap((entry) => GLOB_CHARS.test(entry) ? glob.sync(entry.replace(/\\/g, "/"), {
70
+ cwd: rootPath(),
71
+ absolute: true
72
+ }) : [entry]);
73
+ }
20
74
  /**
21
75
  * All .env file variants to watch
22
76
  */
@@ -42,18 +96,23 @@ var FilesWatcher = class {
42
96
  async watch(config) {
43
97
  const devServerConfig = await warlockConfigManager.lazyGet("devServer");
44
98
  const userWatchConfig = devServerConfig?.watch;
99
+ const rootEnvFiles = fs.readdirSync(rootPath(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith(".env")).map((entry) => entry.name);
45
100
  const basePaths = [
46
- ...ENV_FILES.map((file) => rootPath(file)),
101
+ ...[...new Set([
102
+ ...ENV_FILES,
103
+ ".env.shared",
104
+ ...rootEnvFiles
105
+ ])].map((file) => rootPath(file)),
47
106
  rootPath("warlock.config.ts"),
48
107
  srcPath()
49
108
  ];
50
- const additionalPaths = userWatchConfig?.include || config?.include || [];
109
+ const additionalPaths = expandIncludes(userWatchConfig?.include || config?.include || []);
51
110
  const paths = [...basePaths, ...additionalPaths].map((path) => Path.normalize(path));
52
- const ignored = [
111
+ const ignored = createIgnoredMatcher([
53
112
  ...DEFAULT_EXCLUDE,
54
113
  ...userWatchConfig?.exclude || [],
55
114
  ...config?.exclude || []
56
- ];
115
+ ]);
57
116
  const timingsEnabled = devServerConfig?.timings === true;
58
117
  const watcher = chokidar.watch(paths, {
59
118
  ignoreInitial: true,
@@ -142,5 +201,5 @@ var FilesWatcher = class {
142
201
  };
143
202
 
144
203
  //#endregion
145
- export { FilesWatcher };
204
+ export { FilesWatcher, createIgnoredMatcher };
146
205
  //# sourceMappingURL=files-watcher.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"files-watcher.mjs","names":["nodePath"],"sources":["../../../../../../../core/src/dev-server/files-watcher.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { Random } from \"@mongez/reinforcements\";\nimport chokidar from \"chokidar\";\nimport nodePath from \"node:path\";\nimport { rootPath, srcPath } from \"../utils\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { Path } from \"../utils/normalized-path\";\n\ntype FileWatcherEvent = \"change\" | \"delete\" | \"add\" | \"error\" | \"addDir\" | \"unlinkDir\";\n\ntype FileChangeCallback = (filePath: string, settleMs?: number) => void;\ntype FileDeleteCallback = (filePath: string, settleMs?: number) => void;\ntype FileAddCallback = (filePath: string, settleMs?: number) => void;\ntype FileErrorCallback = (filePath: string, error: Error) => void;\ntype FileAddDirCallback = (filePath: string) => void;\ntype FileUnlinkDirCallback = (filePath: string) => void;\ntype OnFileEventCallback =\n | FileChangeCallback\n | FileDeleteCallback\n | FileAddCallback\n | FileErrorCallback\n | FileAddDirCallback\n | FileUnlinkDirCallback;\n\n/**\n * Watch configuration options\n */\nexport type WatchConfig = {\n /**\n * Glob patterns to include\n */\n include?: string[];\n /**\n * Glob patterns to exclude\n */\n exclude?: string[];\n};\n\n/**\n * Default patterns to exclude from watching\n */\nconst DEFAULT_EXCLUDE = [\"**/node_modules/**\", \"**/dist/**\", \"**/.warlock/**\", \"**/.git/**\"];\n\n/**\n * All .env file variants to watch\n */\nconst ENV_FILES = [\n \".env\",\n \".env.local\",\n \".env.development\",\n \".env.development.local\",\n \".env.test\",\n \".env.test.local\",\n \".env.production\",\n \".env.production.local\",\n];\n\nexport class FilesWatcher {\n /**\n * File watcher id\n */\n private id = Random.string();\n\n /**\n * First-seen timestamp per path from chokidar's `raw` event, which fires\n * BEFORE `awaitWriteFinish` stabilises the change. Diffing it against the\n * stabilised `add`/`change` timestamp gives the actual watcher-settle\n * duration for the phase-timing line. Only populated when\n * `devServer.timings` is on — see `watch()` — so a disabled flag costs\n * nothing here beyond the `undefined` checks below.\n */\n private readonly rawEventTimestamps = new Map<string, number>();\n\n /**\n * Watch for files changes\n * @param config Optional watch configuration\n */\n public async watch(config?: WatchConfig) {\n // Get user config from warlock.config.ts\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\n const userWatchConfig = devServerConfig?.watch;\n\n // Build paths to watch:\n // 1. All .env variants that exist\n // 2. warlock.config.ts (project-level settings; restart-required on change)\n // 3. src directory\n // 4. Any additional paths from user config\n const envPaths = ENV_FILES.map((file) => rootPath(file));\n const basePaths = [...envPaths, rootPath(\"warlock.config.ts\"), srcPath()];\n const additionalPaths = userWatchConfig?.include || config?.include || [];\n\n const paths = [...basePaths, ...additionalPaths].map((path) => Path.normalize(path));\n\n // Merge default exclude with config exclude\n const ignored = [\n ...DEFAULT_EXCLUDE,\n ...(userWatchConfig?.exclude || []),\n ...(config?.exclude || []),\n ];\n\n const timingsEnabled = devServerConfig?.timings === true;\n\n const watcher = chokidar.watch(paths, {\n ignoreInitial: true,\n ignored,\n persistent: true,\n usePolling: false, // Try native first, will fallback if needed\n interval: 100,\n binaryInterval: 300,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n },\n // On Windows, explicitly enable recursive watching\n depth: 99,\n });\n\n if (timingsEnabled) {\n watcher.on(\"raw\", (_event, evPath, details) => {\n // On the native (non-fsevents) handler chokidar uses on Windows/Linux,\n // `evPath` is only the changed entry's path *relative to the watched\n // directory* (see chokidar's `handler.js` `handleEvent`/`emitRaw`),\n // not the absolute path the later `add`/`change` event carries. Only\n // fsevents (macOS) hands back an already-absolute path. Reconstruct\n // the absolute path from `details.watchedPath` so the timestamp is\n // keyed the same way the stabilised event looks it up below —\n // otherwise the lookup always misses and the phase silently reads 0.\n const watchedPath = (details as { watchedPath?: string } | undefined)?.watchedPath;\n const absolutePath = watchedPath ? nodePath.resolve(watchedPath, evPath) : evPath;\n const normalized = Path.normalize(absolutePath);\n if (!this.rawEventTimestamps.has(normalized)) {\n this.rawEventTimestamps.set(normalized, performance.now());\n }\n });\n }\n\n watcher.on(\"add\", (filePath) => this.triggerEvent(\"add\", filePath));\n watcher.on(\"change\", (filePath) => this.triggerEvent(\"change\", filePath));\n watcher.on(\"unlink\", (filePath) => this.triggerEvent(\"delete\", filePath));\n watcher.on(\"addDir\", (filePath) => this.triggerEvent(\"addDir\", filePath));\n watcher.on(\"unlinkDir\", (filePath) => this.triggerEvent(\"unlinkDir\", filePath));\n // watcher.on(\"error\", (error: Error, filePath: string) =>\n // this.triggerEvent(\"error\", filePath, error),\n // );\n\n // Cleanup on process exit\n process.on(\"SIGINT\", async () => {\n await watcher.close();\n });\n }\n\n /**\n * Trigger event immediately (no debouncing here)\n * Debouncing is handled at the orchestrator level for batch processing\n */\n private triggerEvent(event: FileWatcherEvent, filePath: string, error?: Error) {\n const normalized = Path.normalize(filePath);\n const rawEventAt = this.rawEventTimestamps.get(normalized);\n let settleMs: number | undefined;\n\n if (rawEventAt !== undefined) {\n settleMs = performance.now() - rawEventAt;\n this.rawEventTimestamps.delete(normalized);\n }\n\n // `error` and `settleMs` share the same second-argument slot: an error\n // event's subscriber reads it as `Error`, every other event's subscriber\n // reads it as the watcher-settle duration (see the `On*Callback` types\n // above). Passing both positionally (`normalized, error, settleMs`) used\n // to leave `settleMs` in an unread third slot, so every change/add/delete\n // subscriber always received `undefined` in its `settleMs` parameter —\n // the watcher phase's timing was silently discarded here even when the\n // raw-event timestamp above resolved correctly.\n events.trigger(`file-watcher.${this.id}.${event}`, normalized, error ?? settleMs);\n }\n\n /**\n * On file change event\n */\n public onFileChange(callback: FileChangeCallback) {\n return this.on(\"change\", callback);\n }\n\n /**\n * On file delete event\n */\n public onFileDelete(callback: FileDeleteCallback) {\n return this.on(\"delete\", callback);\n }\n\n /**\n * On file add event\n */\n public onFileAdd(callback: FileAddCallback) {\n return this.on(\"add\", callback);\n }\n\n /**\n * On file error event\n */\n public onFileError(callback: FileErrorCallback) {\n return this.on(\"error\", callback);\n }\n\n /**\n * On file add dir event\n */\n public onDirectoryAdd(callback: FileAddDirCallback) {\n return this.on(\"addDir\", callback);\n }\n\n /**\n * On file unlink dir event\n */\n public onDirectoryRemove(callback: FileUnlinkDirCallback) {\n return this.on(\"unlinkDir\", callback);\n }\n\n /**\n * On file event\n */\n public on(event: FileWatcherEvent, callback: OnFileEventCallback) {\n return events.subscribe(`file-watcher.${this.id}.${event}`, callback);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"files-watcher.mjs","names":["fg","nodePath"],"sources":["../../../../../../../core/src/dev-server/files-watcher.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { Random } from \"@mongez/reinforcements\";\nimport chokidar from \"chokidar\";\nimport fg from \"fast-glob\";\nimport fs from \"node:fs\";\nimport nodePath from \"node:path\";\nimport { rootPath, srcPath } from \"../utils\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { Path } from \"../utils/normalized-path\";\n\ntype FileWatcherEvent = \"change\" | \"delete\" | \"add\" | \"error\" | \"addDir\" | \"unlinkDir\";\n\ntype FileChangeCallback = (filePath: string, settleMs?: number) => void;\ntype FileDeleteCallback = (filePath: string, settleMs?: number) => void;\ntype FileAddCallback = (filePath: string, settleMs?: number) => void;\ntype FileErrorCallback = (filePath: string, error: Error) => void;\ntype FileAddDirCallback = (filePath: string) => void;\ntype FileUnlinkDirCallback = (filePath: string) => void;\ntype OnFileEventCallback =\n | FileChangeCallback\n | FileDeleteCallback\n | FileAddCallback\n | FileErrorCallback\n | FileAddDirCallback\n | FileUnlinkDirCallback;\n\n/**\n * Watch configuration options\n */\nexport type WatchConfig = {\n /**\n * Glob patterns to include\n */\n include?: string[];\n /**\n * Glob patterns to exclude\n */\n exclude?: string[];\n};\n\n/**\n * Default patterns to exclude from watching\n */\nconst DEFAULT_EXCLUDE = [\"**/node_modules/**\", \"**/dist/**\", \"**/.warlock/**\", \"**/.git/**\"];\n\nconst GLOB_CHARS = /[*?{}[\\]]/;\n\n/**\n * Convert a glob (`**`, `*`, `?`, `{a,b}`) to a RegExp over a forward-slash\n * path. chokidar >= 4 has no glob support, so patterns are matched here\n * instead of being handed to it.\n */\nexport function globToRegExp(glob: string): RegExp {\n let source = \"\";\n let braceDepth = 0;\n\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i]!;\n\n if (char === \"*\") {\n if (glob[i + 1] === \"*\") {\n i++;\n if (glob[i + 1] === \"/\") {\n i++;\n source += \"(?:.*/)?\";\n } else {\n source += \".*\";\n }\n } else {\n source += \"[^/]*\";\n }\n } else if (char === \"?\") {\n source += \"[^/]\";\n } else if (char === \"{\") {\n braceDepth++;\n source += \"(?:\";\n } else if (char === \"}\" && braceDepth > 0) {\n braceDepth--;\n source += \")\";\n } else if (char === \",\" && braceDepth > 0) {\n source += \"|\";\n } else {\n source += /[.+^$()|[\\]\\\\]/.test(char) ? `\\\\${char}` : char;\n }\n }\n\n return new RegExp(`^${source}$`);\n}\n\n/**\n * Build a chokidar `ignored` predicate from glob and literal patterns. A\n * pattern matches the absolute path or the project-relative path.\n */\nexport function createIgnoredMatcher(patterns: string[]): (path: string) => boolean {\n const matchers = patterns.map((pattern) => globToRegExp(Path.normalize(pattern)));\n\n return (path: string) => {\n const absolute = Path.normalize(path);\n const relative = Path.toRelative(absolute);\n\n return matchers.some((matcher) => matcher.test(absolute) || matcher.test(relative));\n };\n}\n\n/**\n * Expand glob `include` entries to concrete files (chokidar watches literal\n * paths only); literal entries pass through unchanged.\n */\nfunction expandIncludes(include: string[]): string[] {\n return include.flatMap((entry) =>\n GLOB_CHARS.test(entry)\n ? fg.sync(entry.replace(/\\\\/g, \"/\"), { cwd: rootPath(), absolute: true })\n : [entry],\n );\n}\n\n/**\n * All .env file variants to watch\n */\nconst ENV_FILES = [\n \".env\",\n \".env.local\",\n \".env.development\",\n \".env.development.local\",\n \".env.test\",\n \".env.test.local\",\n \".env.production\",\n \".env.production.local\",\n];\n\nexport class FilesWatcher {\n /**\n * File watcher id\n */\n private id = Random.string();\n\n /**\n * First-seen timestamp per path from chokidar's `raw` event, which fires\n * BEFORE `awaitWriteFinish` stabilises the change. Diffing it against the\n * stabilised `add`/`change` timestamp gives the actual watcher-settle\n * duration for the phase-timing line. Only populated when\n * `devServer.timings` is on — see `watch()` — so a disabled flag costs\n * nothing here beyond the `undefined` checks below.\n */\n private readonly rawEventTimestamps = new Map<string, number>();\n\n /**\n * Watch for files changes\n * @param config Optional watch configuration\n */\n public async watch(config?: WatchConfig) {\n // Get user config from warlock.config.ts\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\n const userWatchConfig = devServerConfig?.watch;\n\n // Build paths to watch:\n // 1. All .env variants that exist\n // 2. warlock.config.ts (project-level settings; restart-required on change)\n // 3. src directory\n // 4. Any additional paths from user config\n // Also any other `.env*` at the root (`.env.shared`, `.env.staging`, …).\n const rootEnvFiles = fs\n .readdirSync(rootPath(), { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.startsWith(\".env\"))\n .map((entry) => entry.name);\n const envPaths = [...new Set([...ENV_FILES, \".env.shared\", ...rootEnvFiles])].map((file) =>\n rootPath(file),\n );\n const basePaths = [...envPaths, rootPath(\"warlock.config.ts\"), srcPath()];\n const additionalPaths = expandIncludes(userWatchConfig?.include || config?.include || []);\n\n const paths = [...basePaths, ...additionalPaths].map((path) => Path.normalize(path));\n\n // Merge default exclude with config exclude\n const ignored = createIgnoredMatcher([\n ...DEFAULT_EXCLUDE,\n ...(userWatchConfig?.exclude || []),\n ...(config?.exclude || []),\n ]);\n\n const timingsEnabled = devServerConfig?.timings === true;\n\n const watcher = chokidar.watch(paths, {\n ignoreInitial: true,\n ignored,\n persistent: true,\n usePolling: false, // Try native first, will fallback if needed\n interval: 100,\n binaryInterval: 300,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n },\n // On Windows, explicitly enable recursive watching\n depth: 99,\n });\n\n if (timingsEnabled) {\n watcher.on(\"raw\", (_event, evPath, details) => {\n // On the native (non-fsevents) handler chokidar uses on Windows/Linux,\n // `evPath` is only the changed entry's path *relative to the watched\n // directory* (see chokidar's `handler.js` `handleEvent`/`emitRaw`),\n // not the absolute path the later `add`/`change` event carries. Only\n // fsevents (macOS) hands back an already-absolute path. Reconstruct\n // the absolute path from `details.watchedPath` so the timestamp is\n // keyed the same way the stabilised event looks it up below —\n // otherwise the lookup always misses and the phase silently reads 0.\n const watchedPath = (details as { watchedPath?: string } | undefined)?.watchedPath;\n const absolutePath = watchedPath ? nodePath.resolve(watchedPath, evPath) : evPath;\n const normalized = Path.normalize(absolutePath);\n if (!this.rawEventTimestamps.has(normalized)) {\n this.rawEventTimestamps.set(normalized, performance.now());\n }\n });\n }\n\n watcher.on(\"add\", (filePath) => this.triggerEvent(\"add\", filePath));\n watcher.on(\"change\", (filePath) => this.triggerEvent(\"change\", filePath));\n watcher.on(\"unlink\", (filePath) => this.triggerEvent(\"delete\", filePath));\n watcher.on(\"addDir\", (filePath) => this.triggerEvent(\"addDir\", filePath));\n watcher.on(\"unlinkDir\", (filePath) => this.triggerEvent(\"unlinkDir\", filePath));\n // watcher.on(\"error\", (error: Error, filePath: string) =>\n // this.triggerEvent(\"error\", filePath, error),\n // );\n\n // Cleanup on process exit\n process.on(\"SIGINT\", async () => {\n await watcher.close();\n });\n }\n\n /**\n * Trigger event immediately (no debouncing here)\n * Debouncing is handled at the orchestrator level for batch processing\n */\n private triggerEvent(event: FileWatcherEvent, filePath: string, error?: Error) {\n const normalized = Path.normalize(filePath);\n const rawEventAt = this.rawEventTimestamps.get(normalized);\n let settleMs: number | undefined;\n\n if (rawEventAt !== undefined) {\n settleMs = performance.now() - rawEventAt;\n this.rawEventTimestamps.delete(normalized);\n }\n\n // `error` and `settleMs` share the same second-argument slot: an error\n // event's subscriber reads it as `Error`, every other event's subscriber\n // reads it as the watcher-settle duration (see the `On*Callback` types\n // above). Passing both positionally (`normalized, error, settleMs`) used\n // to leave `settleMs` in an unread third slot, so every change/add/delete\n // subscriber always received `undefined` in its `settleMs` parameter —\n // the watcher phase's timing was silently discarded here even when the\n // raw-event timestamp above resolved correctly.\n events.trigger(`file-watcher.${this.id}.${event}`, normalized, error ?? settleMs);\n }\n\n /**\n * On file change event\n */\n public onFileChange(callback: FileChangeCallback) {\n return this.on(\"change\", callback);\n }\n\n /**\n * On file delete event\n */\n public onFileDelete(callback: FileDeleteCallback) {\n return this.on(\"delete\", callback);\n }\n\n /**\n * On file add event\n */\n public onFileAdd(callback: FileAddCallback) {\n return this.on(\"add\", callback);\n }\n\n /**\n * On file error event\n */\n public onFileError(callback: FileErrorCallback) {\n return this.on(\"error\", callback);\n }\n\n /**\n * On file add dir event\n */\n public onDirectoryAdd(callback: FileAddDirCallback) {\n return this.on(\"addDir\", callback);\n }\n\n /**\n * On file unlink dir event\n */\n public onDirectoryRemove(callback: FileUnlinkDirCallback) {\n return this.on(\"unlinkDir\", callback);\n }\n\n /**\n * On file event\n */\n public on(event: FileWatcherEvent, callback: OnFileEventCallback) {\n return events.subscribe(`file-watcher.${this.id}.${event}`, callback);\n }\n}\n"],"mappings":"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"}
@@ -35,6 +35,7 @@ var FilesHealthcareManager = class {
35
35
  this.healthCheckers = [];
36
36
  this.workers = /* @__PURE__ */ new Map();
37
37
  this.workerInitPromises = /* @__PURE__ */ new Map();
38
+ this.workerQueues = /* @__PURE__ */ new Map();
38
39
  this.checkerStats = /* @__PURE__ */ new Map();
39
40
  }
40
41
  /**
@@ -152,24 +153,45 @@ var FilesHealthcareManager = class {
152
153
  content: f.source,
153
154
  relativePath: f.relativePath
154
155
  }));
156
+ const current = (this.workerQueues.get(checkerName) ?? Promise.resolve()).then(() => this.postCheck(checkerName, worker, files, serializedFiles));
157
+ this.workerQueues.set(checkerName, current.then(() => void 0, () => void 0));
158
+ return current;
159
+ }
160
+ /**
161
+ * Post one check to the worker and wait for its single reply.
162
+ * Resolves as healthy if the worker errors or exits so callers never hang.
163
+ */
164
+ postCheck(checkerName, worker, files, serializedFiles) {
155
165
  return new Promise((resolve) => {
166
+ const fallback = () => files.map((f) => ({
167
+ path: f.absolutePath,
168
+ relativePath: f.relativePath,
169
+ healthy: true,
170
+ errors: [],
171
+ warnings: []
172
+ }));
173
+ const cleanup = () => {
174
+ worker.off("message", handler);
175
+ worker.off("error", onFailure);
176
+ worker.off("exit", onFailure);
177
+ };
178
+ const onFailure = () => {
179
+ cleanup();
180
+ resolve(fallback());
181
+ };
156
182
  const handler = (response) => {
157
183
  if (response.type === "results") {
158
- worker.off("message", handler);
184
+ cleanup();
159
185
  this.updateStats(checkerName, response.results);
160
186
  resolve(response.results);
161
187
  } else if (response.type === "error") {
162
- worker.off("message", handler);
163
- resolve(files.map((f) => ({
164
- path: f.absolutePath,
165
- relativePath: f.relativePath,
166
- healthy: true,
167
- errors: [],
168
- warnings: []
169
- })));
188
+ cleanup();
189
+ resolve(fallback());
170
190
  }
171
191
  };
172
192
  worker.on("message", handler);
193
+ worker.on("error", onFailure);
194
+ worker.on("exit", onFailure);
173
195
  worker.postMessage({
174
196
  type: "check",
175
197
  files: serializedFiles
@@ -1 +1 @@
1
- {"version":3,"file":"files-healthcare.manager.mjs","names":[],"sources":["../../../../../../../../core/src/dev-server/health-checker/files-healthcare.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport type { Worker } from \"worker_threads\";\nimport { createWorker } from \"../create-worker\";\nimport { devLogInfo } from \"../dev-logger\";\nimport type { FileManager } from \"../file-manager\";\nimport type {\n FileHealthCheckerContract,\n HealthCheckerFilesStats,\n} from \"./file-health-checker.contract\";\n\n/**\n * Serialized file data to send to workers\n */\ntype SerializedFile = {\n path: string;\n content: string;\n relativePath: string;\n};\n\n/**\n * Diagnostic message from worker\n */\ntype DiagnosticMessage = {\n type: \"error\" | \"warning\";\n message: string;\n lineNumber: number;\n columnNumber: number;\n length: number;\n filePath: string;\n relativePath: string;\n ruleId?: string;\n};\n\n/**\n * File check result from worker\n */\ntype FileCheckResult = {\n path: string;\n relativePath: string;\n healthy: boolean;\n errors: DiagnosticMessage[];\n warnings: DiagnosticMessage[];\n};\n\n/**\n * Worker response type\n */\ntype WorkerResponse =\n | { type: \"initialized\"; success: boolean; hasConfig?: boolean; error?: string }\n | { type: \"results\"; results: FileCheckResult[] }\n | { type: \"error\"; message: string };\n\n/**\n * Files Healthcare Manager\n *\n * Manages health checkers for file validation during development.\n * Supports both main-thread and worker-based checkers.\n *\n * Checkers with `workerPath` run in dedicated worker threads for performance.\n * Checkers without `workerPath` run in the main thread.\n *\n * @example\n * ```typescript\n * const manager = new FilesHealthcareManager(files);\n *\n * manager.setHealthCheckers([\n * new TypescriptHealthChecker(), // Has workerPath → runs in worker\n * new CustomChecker(), // No workerPath → runs in main thread\n * ]);\n *\n * await manager.initialize();\n * await manager.validateAllFiles();\n * ```\n */\nexport class FilesHealthcareManager {\n /**\n * Registered health checkers\n */\n private healthCheckers: FileHealthCheckerContract[] = [];\n\n /**\n * Active worker instances (checkerName → Worker)\n */\n private workers = new Map<string, Worker>();\n\n /**\n * Worker initialization promises (for ensuring workers are ready)\n */\n private workerInitPromises = new Map<string, Promise<boolean>>();\n\n /**\n * Aggregated stats for each checker\n */\n private checkerStats = new Map<\n string,\n {\n totalFiles: number;\n healthyFiles: number;\n defectiveFiles: number;\n totalErrors: number;\n totalWarnings: number;\n filesWithErrors: number;\n filesWithWarnings: number;\n }\n >();\n\n /**\n * Constructor\n * @param files - Map of tracked files\n */\n public constructor(private readonly files: Map<string, FileManager>) {}\n\n /**\n * Add health checkers to the manager\n * @param checkers - Checkers to add\n */\n public addHealthCheckers(...checkers: FileHealthCheckerContract[]): FilesHealthcareManager {\n this.healthCheckers.push(...checkers);\n return this;\n }\n\n /**\n * Set (replace) all health checkers\n * @param checkers - New set of checkers\n */\n public setHealthCheckers(checkers: FileHealthCheckerContract[]): FilesHealthcareManager {\n this.healthCheckers = checkers;\n return this;\n }\n\n /**\n * Initialize all health checkers\n *\n * For checkers with `workerPath`, spawns dedicated worker threads.\n * For other checkers, calls their `initialize()` method directly.\n */\n public async initialize(): Promise<void> {\n const initPromises = this.healthCheckers.map(async (checker) => {\n // Initialize stats for this checker\n this.checkerStats.set(checker.name, {\n totalFiles: 0,\n healthyFiles: 0,\n defectiveFiles: 0,\n totalErrors: 0,\n totalWarnings: 0,\n filesWithErrors: 0,\n filesWithWarnings: 0,\n });\n\n if (checker.workerPath) {\n // Spawn dedicated worker for this checker\n await this.spawnWorker(checker);\n } else {\n // Initialize checker on main thread\n checker.initialize();\n }\n });\n\n await Promise.all(initPromises);\n }\n\n /**\n * Spawn a worker for a checker\n */\n private async spawnWorker(checker: FileHealthCheckerContract): Promise<void> {\n if (!checker.workerPath) return;\n\n const worker = createWorker(checker.workerPath, import.meta.url, {\n workerData: { cwd: process.cwd() },\n });\n\n this.workers.set(checker.name, worker);\n\n // Create init promise\n const initPromise = new Promise<boolean>((resolve) => {\n const handler = (response: WorkerResponse) => {\n if (response.type === \"initialized\") {\n worker.off(\"message\", handler);\n resolve(response.success);\n }\n };\n\n worker.on(\"message\", handler);\n\n // Handle worker errors\n worker.on(\"error\", (error) => {\n console.error(`Health Checker Worker Error (${checker.name}):`, error);\n resolve(false);\n });\n\n // Send init message\n worker.postMessage({ type: \"init\", config: { cwd: process.cwd() } });\n });\n\n this.workerInitPromises.set(checker.name, initPromise);\n\n // Wait for initialization\n await initPromise;\n }\n\n /**\n * Validate all tracked files\n */\n public async validateAllFiles(): Promise<void> {\n const allFiles = Array.from(this.files.values());\n await this.checkFiles(allFiles);\n\n // Display stats\n await this.displayStats();\n }\n\n /**\n * Check the given files with all registered checkers\n *\n * All checkers run in parallel for performance.\n * Worker-based checkers communicate via postMessage.\n * Main-thread checkers are called directly.\n */\n public async checkFiles(files: FileManager[]): Promise<void> {\n const checkPromises = this.healthCheckers.map(async (checker) => {\n if (checker.workerPath) {\n return this.runInWorker(checker.name, files);\n } else {\n return this.runInline(checker, files);\n }\n });\n\n const results = await Promise.all(checkPromises);\n\n // Display any errors/warnings\n for (const checkerResults of results) {\n for (const result of checkerResults) {\n if (!result.healthy) {\n this.displayFileResults(result);\n }\n }\n }\n }\n\n /**\n * Run checker in worker thread\n */\n private async runInWorker(checkerName: string, files: FileManager[]): Promise<FileCheckResult[]> {\n const worker = this.workers.get(checkerName);\n\n if (!worker) {\n return files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n\n // Wait for worker to be initialized\n const initPromise = this.workerInitPromises.get(checkerName);\n if (initPromise) {\n const success = await initPromise;\n if (!success) {\n return files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n }\n\n // Serialize files for worker\n const serializedFiles: SerializedFile[] = files.map((f) => ({\n path: f.absolutePath,\n content: f.source,\n relativePath: f.relativePath,\n }));\n\n return new Promise((resolve) => {\n const handler = (response: WorkerResponse) => {\n if (response.type === \"results\") {\n worker.off(\"message\", handler);\n\n // Update stats\n this.updateStats(checkerName, response.results);\n\n resolve(response.results);\n } else if (response.type === \"error\") {\n worker.off(\"message\", handler);\n resolve(\n files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n })),\n );\n }\n };\n\n worker.on(\"message\", handler);\n worker.postMessage({ type: \"check\", files: serializedFiles });\n });\n }\n\n /**\n * Run checker inline (main thread)\n */\n private async runInline(\n checker: FileHealthCheckerContract,\n files: FileManager[],\n ): Promise<FileCheckResult[]> {\n const results: FileCheckResult[] = [];\n\n for (const file of files) {\n const healthResult = await checker.check(file);\n const stats = healthResult.getStats();\n\n results.push({\n path: file.absolutePath,\n relativePath: file.relativePath,\n healthy: stats.state === \"healthy\",\n errors: healthResult.messages\n .filter((m) => m.type === \"error\")\n .map((m) => ({\n type: \"error\" as const,\n message: m.message,\n lineNumber: m.lineNumber,\n columnNumber: m.columnNumber,\n length: m.length || 1,\n filePath: file.absolutePath,\n relativePath: file.relativePath,\n ruleId: m.ruleId,\n })),\n warnings: healthResult.messages\n .filter((m) => m.type === \"warning\")\n .map((m) => ({\n type: \"warning\" as const,\n message: m.message,\n lineNumber: m.lineNumber,\n columnNumber: m.columnNumber,\n length: m.length || 1,\n filePath: file.absolutePath,\n relativePath: file.relativePath,\n ruleId: m.ruleId,\n })),\n });\n }\n\n // Update stats\n const checkerResults = results;\n this.updateStats(checker.name, checkerResults);\n\n return results;\n }\n\n /**\n * Update aggregated stats for a checker\n */\n private updateStats(checkerName: string, results: FileCheckResult[]): void {\n const stats = this.checkerStats.get(checkerName);\n if (!stats) return;\n\n for (const result of results) {\n stats.totalFiles++;\n if (result.healthy) {\n stats.healthyFiles++;\n } else {\n stats.defectiveFiles++;\n }\n stats.totalErrors += result.errors.length;\n stats.totalWarnings += result.warnings.length;\n if (result.errors.length > 0) stats.filesWithErrors++;\n if (result.warnings.length > 0) stats.filesWithWarnings++;\n }\n }\n\n /**\n * Detect when files are changed\n */\n public async onFileChanges(files: FileManager[]): Promise<void> {\n // Notify workers of file changes\n const serializedFiles: SerializedFile[] = files.map((f) => ({\n path: f.absolutePath,\n content: f.source,\n relativePath: f.relativePath,\n }));\n\n for (const [, worker] of this.workers) {\n worker.postMessage({ type: \"fileChanges\", files: serializedFiles });\n }\n\n // Notify inline checkers\n await Promise.all(\n this.healthCheckers.filter((c) => !c.workerPath).map((c) => c.onFileChanges(files)),\n );\n }\n\n /**\n * Remove files from tracking\n * Notifies both inline checkers and worker-based checkers about removed files\n */\n public removeFiles(files: FileManager[]): void {\n // Notify inline checkers\n this.healthCheckers\n .filter((c) => !c.workerPath)\n .forEach((checker) => files.forEach((file) => checker.removeFile(file)));\n\n // Notify workers about deleted files\n if (files.length > 0) {\n const deletedPaths = files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n }));\n\n for (const [, worker] of this.workers) {\n worker.postMessage({ type: \"filesDeleted\", files: deletedPaths });\n }\n }\n }\n\n /**\n * Display file results (errors/warnings)\n */\n private displayFileResults(result: FileCheckResult): void {\n const fileName = result.relativePath.replace(/\\\\/g, \"/\");\n\n // Display errors\n for (const error of result.errors) {\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 if (error.ruleId) {\n console.log(\n ` ${colors.magentaBright(error.ruleId)} ${colors.dim(\"→\")} ${colors.red(error.message)}`,\n );\n } else {\n console.log(` ${colors.dim(\"→\")} ${colors.red(error.message)}`);\n }\n }\n\n // Display warnings\n for (const warning of result.warnings) {\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 if (warning.ruleId) {\n console.log(\n ` ${colors.magentaBright(warning.ruleId)} ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`,\n );\n } else {\n console.log(` ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`);\n }\n }\n }\n\n /**\n * Display stats for all health checkers\n */\n private async displayStats(): Promise<void> {\n const allStats: HealthCheckerFilesStats[] = [];\n\n for (const checker of this.healthCheckers) {\n const stats = this.checkerStats.get(checker.name);\n if (stats) {\n allStats.push({\n name: checker.name,\n files: {\n healthy: stats.healthyFiles,\n defective: stats.defectiveFiles,\n },\n warnings: {\n total: stats.totalWarnings,\n totalFiles: stats.filesWithWarnings,\n },\n errors: {\n total: stats.totalErrors,\n totalFiles: stats.filesWithErrors,\n },\n });\n }\n }\n\n console.log(\"\\n\");\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n devLogInfo(colors.bold(\" 📊 Health Checker Statistics\"));\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n console.log(\"\");\n\n allStats.forEach((stats: HealthCheckerFilesStats, index: number) => {\n const totalFiles = stats.files.healthy + stats.files.defective;\n const hasIssues = stats.files.defective > 0;\n\n // Checker name with icon\n const statusIcon = hasIssues ? \"⚠️\" : \"✅\";\n console.log(colors.bold(` ${statusIcon} ${stats.name}`));\n console.log(colors.dim(\" \" + \"─\".repeat(76)));\n\n // Files summary\n const filesLine = ` Files: ${colors.white(totalFiles.toString())} total │ ${colors.green(\"✓ \" + stats.files.healthy)} healthy │ ${hasIssues ? colors.red(\"✗ \" + stats.files.defective) : colors.dim(\"✗ 0\")} defective`;\n console.log(filesLine);\n\n // Issues summary\n const warningsText =\n stats.warnings.total > 0\n ? colors.yellow(`⚠ ${stats.warnings.total} warnings`)\n : colors.dim(\"⚠ 0 warnings\");\n\n const errorsText =\n stats.errors.total > 0\n ? colors.red(`✗ ${stats.errors.total} errors`)\n : colors.dim(\"✗ 0 errors\");\n\n console.log(` Issues: ${warningsText} │ ${errorsText}`);\n\n // Add spacing between checkers\n if (index < allStats.length - 1) {\n console.log(\"\");\n }\n });\n\n console.log(\"\");\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n console.log(\"\");\n }\n\n /**\n * Shutdown all workers\n */\n public async shutdown(): Promise<void> {\n for (const [name, worker] of this.workers) {\n try {\n worker.postMessage({ type: \"shutdown\" });\n await worker.terminate();\n } catch {\n // Worker may already be terminated\n }\n }\n\n this.workers.clear();\n this.workerInitPromises.clear();\n this.checkerStats.clear();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"files-healthcare.manager.mjs","names":[],"sources":["../../../../../../../../core/src/dev-server/health-checker/files-healthcare.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport type { Worker } from \"worker_threads\";\nimport { createWorker } from \"../create-worker\";\nimport { devLogInfo } from \"../dev-logger\";\nimport type { FileManager } from \"../file-manager\";\nimport type {\n FileHealthCheckerContract,\n HealthCheckerFilesStats,\n} from \"./file-health-checker.contract\";\n\n/**\n * Serialized file data to send to workers\n */\ntype SerializedFile = {\n path: string;\n content: string;\n relativePath: string;\n};\n\n/**\n * Diagnostic message from worker\n */\ntype DiagnosticMessage = {\n type: \"error\" | \"warning\";\n message: string;\n lineNumber: number;\n columnNumber: number;\n length: number;\n filePath: string;\n relativePath: string;\n ruleId?: string;\n};\n\n/**\n * File check result from worker\n */\ntype FileCheckResult = {\n path: string;\n relativePath: string;\n healthy: boolean;\n errors: DiagnosticMessage[];\n warnings: DiagnosticMessage[];\n};\n\n/**\n * Worker response type\n */\ntype WorkerResponse =\n | { type: \"initialized\"; success: boolean; hasConfig?: boolean; error?: string }\n | { type: \"results\"; results: FileCheckResult[] }\n | { type: \"error\"; message: string };\n\n/**\n * Files Healthcare Manager\n *\n * Manages health checkers for file validation during development.\n * Supports both main-thread and worker-based checkers.\n *\n * Checkers with `workerPath` run in dedicated worker threads for performance.\n * Checkers without `workerPath` run in the main thread.\n *\n * @example\n * ```typescript\n * const manager = new FilesHealthcareManager(files);\n *\n * manager.setHealthCheckers([\n * new TypescriptHealthChecker(), // Has workerPath → runs in worker\n * new CustomChecker(), // No workerPath → runs in main thread\n * ]);\n *\n * await manager.initialize();\n * await manager.validateAllFiles();\n * ```\n */\nexport class FilesHealthcareManager {\n /**\n * Registered health checkers\n */\n private healthCheckers: FileHealthCheckerContract[] = [];\n\n /**\n * Active worker instances (checkerName → Worker)\n */\n private workers = new Map<string, Worker>();\n\n /**\n * Worker initialization promises (for ensuring workers are ready)\n */\n private workerInitPromises = new Map<string, Promise<boolean>>();\n\n /**\n * Per-worker queue that serializes check requests\n */\n private workerQueues = new Map<string, Promise<void>>();\n\n /**\n * Aggregated stats for each checker\n */\n private checkerStats = new Map<\n string,\n {\n totalFiles: number;\n healthyFiles: number;\n defectiveFiles: number;\n totalErrors: number;\n totalWarnings: number;\n filesWithErrors: number;\n filesWithWarnings: number;\n }\n >();\n\n /**\n * Constructor\n * @param files - Map of tracked files\n */\n public constructor(private readonly files: Map<string, FileManager>) {}\n\n /**\n * Add health checkers to the manager\n * @param checkers - Checkers to add\n */\n public addHealthCheckers(...checkers: FileHealthCheckerContract[]): FilesHealthcareManager {\n this.healthCheckers.push(...checkers);\n return this;\n }\n\n /**\n * Set (replace) all health checkers\n * @param checkers - New set of checkers\n */\n public setHealthCheckers(checkers: FileHealthCheckerContract[]): FilesHealthcareManager {\n this.healthCheckers = checkers;\n return this;\n }\n\n /**\n * Initialize all health checkers\n *\n * For checkers with `workerPath`, spawns dedicated worker threads.\n * For other checkers, calls their `initialize()` method directly.\n */\n public async initialize(): Promise<void> {\n const initPromises = this.healthCheckers.map(async (checker) => {\n // Initialize stats for this checker\n this.checkerStats.set(checker.name, {\n totalFiles: 0,\n healthyFiles: 0,\n defectiveFiles: 0,\n totalErrors: 0,\n totalWarnings: 0,\n filesWithErrors: 0,\n filesWithWarnings: 0,\n });\n\n if (checker.workerPath) {\n // Spawn dedicated worker for this checker\n await this.spawnWorker(checker);\n } else {\n // Initialize checker on main thread\n checker.initialize();\n }\n });\n\n await Promise.all(initPromises);\n }\n\n /**\n * Spawn a worker for a checker\n */\n private async spawnWorker(checker: FileHealthCheckerContract): Promise<void> {\n if (!checker.workerPath) return;\n\n const worker = createWorker(checker.workerPath, import.meta.url, {\n workerData: { cwd: process.cwd() },\n });\n\n this.workers.set(checker.name, worker);\n\n // Create init promise\n const initPromise = new Promise<boolean>((resolve) => {\n const handler = (response: WorkerResponse) => {\n if (response.type === \"initialized\") {\n worker.off(\"message\", handler);\n resolve(response.success);\n }\n };\n\n worker.on(\"message\", handler);\n\n // Handle worker errors\n worker.on(\"error\", (error) => {\n console.error(`Health Checker Worker Error (${checker.name}):`, error);\n resolve(false);\n });\n\n // Send init message\n worker.postMessage({ type: \"init\", config: { cwd: process.cwd() } });\n });\n\n this.workerInitPromises.set(checker.name, initPromise);\n\n // Wait for initialization\n await initPromise;\n }\n\n /**\n * Validate all tracked files\n */\n public async validateAllFiles(): Promise<void> {\n const allFiles = Array.from(this.files.values());\n await this.checkFiles(allFiles);\n\n // Display stats\n await this.displayStats();\n }\n\n /**\n * Check the given files with all registered checkers\n *\n * All checkers run in parallel for performance.\n * Worker-based checkers communicate via postMessage.\n * Main-thread checkers are called directly.\n */\n public async checkFiles(files: FileManager[]): Promise<void> {\n const checkPromises = this.healthCheckers.map(async (checker) => {\n if (checker.workerPath) {\n return this.runInWorker(checker.name, files);\n } else {\n return this.runInline(checker, files);\n }\n });\n\n const results = await Promise.all(checkPromises);\n\n // Display any errors/warnings\n for (const checkerResults of results) {\n for (const result of checkerResults) {\n if (!result.healthy) {\n this.displayFileResults(result);\n }\n }\n }\n }\n\n /**\n * Run checker in worker thread\n */\n private async runInWorker(checkerName: string, files: FileManager[]): Promise<FileCheckResult[]> {\n const worker = this.workers.get(checkerName);\n\n if (!worker) {\n return files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n\n // Wait for worker to be initialized\n const initPromise = this.workerInitPromises.get(checkerName);\n if (initPromise) {\n const success = await initPromise;\n if (!success) {\n return files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n }\n\n // Serialize files for worker\n const serializedFiles: SerializedFile[] = files.map((f) => ({\n path: f.absolutePath,\n content: f.source,\n relativePath: f.relativePath,\n }));\n\n // Workers reply without a request id, so calls are serialized per worker:\n // exactly one \"check\" is in flight and its reply can only belong to it.\n const previous = this.workerQueues.get(checkerName) ?? Promise.resolve();\n\n const current = previous.then(() =>\n this.postCheck(checkerName, worker, files, serializedFiles),\n );\n\n this.workerQueues.set(\n checkerName,\n current.then(\n () => undefined,\n () => undefined,\n ),\n );\n\n return current;\n }\n\n /**\n * Post one check to the worker and wait for its single reply.\n * Resolves as healthy if the worker errors or exits so callers never hang.\n */\n private postCheck(\n checkerName: string,\n worker: Worker,\n files: FileManager[],\n serializedFiles: SerializedFile[],\n ): Promise<FileCheckResult[]> {\n return new Promise((resolve) => {\n const fallback = () =>\n files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n\n const cleanup = () => {\n worker.off(\"message\", handler);\n worker.off(\"error\", onFailure);\n worker.off(\"exit\", onFailure);\n };\n\n const onFailure = () => {\n cleanup();\n resolve(fallback());\n };\n\n const handler = (response: WorkerResponse) => {\n if (response.type === \"results\") {\n cleanup();\n\n // Update stats\n this.updateStats(checkerName, response.results);\n\n resolve(response.results);\n } else if (response.type === \"error\") {\n cleanup();\n resolve(fallback());\n }\n };\n\n worker.on(\"message\", handler);\n worker.on(\"error\", onFailure);\n worker.on(\"exit\", onFailure);\n worker.postMessage({ type: \"check\", files: serializedFiles });\n });\n }\n\n /**\n * Run checker inline (main thread)\n */\n private async runInline(\n checker: FileHealthCheckerContract,\n files: FileManager[],\n ): Promise<FileCheckResult[]> {\n const results: FileCheckResult[] = [];\n\n for (const file of files) {\n const healthResult = await checker.check(file);\n const stats = healthResult.getStats();\n\n results.push({\n path: file.absolutePath,\n relativePath: file.relativePath,\n healthy: stats.state === \"healthy\",\n errors: healthResult.messages\n .filter((m) => m.type === \"error\")\n .map((m) => ({\n type: \"error\" as const,\n message: m.message,\n lineNumber: m.lineNumber,\n columnNumber: m.columnNumber,\n length: m.length || 1,\n filePath: file.absolutePath,\n relativePath: file.relativePath,\n ruleId: m.ruleId,\n })),\n warnings: healthResult.messages\n .filter((m) => m.type === \"warning\")\n .map((m) => ({\n type: \"warning\" as const,\n message: m.message,\n lineNumber: m.lineNumber,\n columnNumber: m.columnNumber,\n length: m.length || 1,\n filePath: file.absolutePath,\n relativePath: file.relativePath,\n ruleId: m.ruleId,\n })),\n });\n }\n\n // Update stats\n const checkerResults = results;\n this.updateStats(checker.name, checkerResults);\n\n return results;\n }\n\n /**\n * Update aggregated stats for a checker\n */\n private updateStats(checkerName: string, results: FileCheckResult[]): void {\n const stats = this.checkerStats.get(checkerName);\n if (!stats) return;\n\n for (const result of results) {\n stats.totalFiles++;\n if (result.healthy) {\n stats.healthyFiles++;\n } else {\n stats.defectiveFiles++;\n }\n stats.totalErrors += result.errors.length;\n stats.totalWarnings += result.warnings.length;\n if (result.errors.length > 0) stats.filesWithErrors++;\n if (result.warnings.length > 0) stats.filesWithWarnings++;\n }\n }\n\n /**\n * Detect when files are changed\n */\n public async onFileChanges(files: FileManager[]): Promise<void> {\n // Notify workers of file changes\n const serializedFiles: SerializedFile[] = files.map((f) => ({\n path: f.absolutePath,\n content: f.source,\n relativePath: f.relativePath,\n }));\n\n for (const [, worker] of this.workers) {\n worker.postMessage({ type: \"fileChanges\", files: serializedFiles });\n }\n\n // Notify inline checkers\n await Promise.all(\n this.healthCheckers.filter((c) => !c.workerPath).map((c) => c.onFileChanges(files)),\n );\n }\n\n /**\n * Remove files from tracking\n * Notifies both inline checkers and worker-based checkers about removed files\n */\n public removeFiles(files: FileManager[]): void {\n // Notify inline checkers\n this.healthCheckers\n .filter((c) => !c.workerPath)\n .forEach((checker) => files.forEach((file) => checker.removeFile(file)));\n\n // Notify workers about deleted files\n if (files.length > 0) {\n const deletedPaths = files.map((f) => ({\n path: f.absolutePath,\n relativePath: f.relativePath,\n }));\n\n for (const [, worker] of this.workers) {\n worker.postMessage({ type: \"filesDeleted\", files: deletedPaths });\n }\n }\n }\n\n /**\n * Display file results (errors/warnings)\n */\n private displayFileResults(result: FileCheckResult): void {\n const fileName = result.relativePath.replace(/\\\\/g, \"/\");\n\n // Display errors\n for (const error of result.errors) {\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 if (error.ruleId) {\n console.log(\n ` ${colors.magentaBright(error.ruleId)} ${colors.dim(\"→\")} ${colors.red(error.message)}`,\n );\n } else {\n console.log(` ${colors.dim(\"→\")} ${colors.red(error.message)}`);\n }\n }\n\n // Display warnings\n for (const warning of result.warnings) {\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 if (warning.ruleId) {\n console.log(\n ` ${colors.magentaBright(warning.ruleId)} ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`,\n );\n } else {\n console.log(` ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`);\n }\n }\n }\n\n /**\n * Display stats for all health checkers\n */\n private async displayStats(): Promise<void> {\n const allStats: HealthCheckerFilesStats[] = [];\n\n for (const checker of this.healthCheckers) {\n const stats = this.checkerStats.get(checker.name);\n if (stats) {\n allStats.push({\n name: checker.name,\n files: {\n healthy: stats.healthyFiles,\n defective: stats.defectiveFiles,\n },\n warnings: {\n total: stats.totalWarnings,\n totalFiles: stats.filesWithWarnings,\n },\n errors: {\n total: stats.totalErrors,\n totalFiles: stats.filesWithErrors,\n },\n });\n }\n }\n\n console.log(\"\\n\");\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n devLogInfo(colors.bold(\" 📊 Health Checker Statistics\"));\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n console.log(\"\");\n\n allStats.forEach((stats: HealthCheckerFilesStats, index: number) => {\n const totalFiles = stats.files.healthy + stats.files.defective;\n const hasIssues = stats.files.defective > 0;\n\n // Checker name with icon\n const statusIcon = hasIssues ? \"⚠️\" : \"✅\";\n console.log(colors.bold(` ${statusIcon} ${stats.name}`));\n console.log(colors.dim(\" \" + \"─\".repeat(76)));\n\n // Files summary\n const filesLine = ` Files: ${colors.white(totalFiles.toString())} total │ ${colors.green(\"✓ \" + stats.files.healthy)} healthy │ ${hasIssues ? colors.red(\"✗ \" + stats.files.defective) : colors.dim(\"✗ 0\")} defective`;\n console.log(filesLine);\n\n // Issues summary\n const warningsText =\n stats.warnings.total > 0\n ? colors.yellow(`⚠ ${stats.warnings.total} warnings`)\n : colors.dim(\"⚠ 0 warnings\");\n\n const errorsText =\n stats.errors.total > 0\n ? colors.red(`✗ ${stats.errors.total} errors`)\n : colors.dim(\"✗ 0 errors\");\n\n console.log(` Issues: ${warningsText} │ ${errorsText}`);\n\n // Add spacing between checkers\n if (index < allStats.length - 1) {\n console.log(\"\");\n }\n });\n\n console.log(\"\");\n console.log(colors.bold(colors.cyan(\"━\".repeat(80))));\n console.log(\"\");\n }\n\n /**\n * Shutdown all workers\n */\n public async shutdown(): Promise<void> {\n for (const [name, worker] of this.workers) {\n try {\n worker.postMessage({ type: \"shutdown\" });\n await worker.terminate();\n } catch {\n // Worker may already be terminated\n }\n }\n\n this.workers.clear();\n this.workerInitPromises.clear();\n this.checkerStats.clear();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,IAAa,yBAAb,MAAoC;;;;;CAyClC,AAAO,YAAY,AAAiB,OAAiC;EAAjC;wBArCkB,CAAC;iCAKrC,IAAI,IAAoB;4CAKb,IAAI,IAA8B;sCAKxC,IAAI,IAA2B;sCAK/B,IAAI,IAWzB;CAMoE;;;;;CAMtE,AAAO,kBAAkB,GAAG,UAA+D;EACzF,KAAK,eAAe,KAAK,GAAG,QAAQ;EACpC,OAAO;CACT;;;;;CAMA,AAAO,kBAAkB,UAA+D;EACtF,KAAK,iBAAiB;EACtB,OAAO;CACT;;;;;;;CAQA,MAAa,aAA4B;EACvC,MAAM,eAAe,KAAK,eAAe,IAAI,OAAO,YAAY;GAE9D,KAAK,aAAa,IAAI,QAAQ,MAAM;IAClC,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,mBAAmB;GACrB,CAAC;GAED,IAAI,QAAQ,YAEV,MAAM,KAAK,YAAY,OAAO;QAG9B,QAAQ,WAAW;EAEvB,CAAC;EAED,MAAM,QAAQ,IAAI,YAAY;CAChC;;;;CAKA,MAAc,YAAY,SAAmD;EAC3E,IAAI,CAAC,QAAQ,YAAY;EAEzB,MAAM,SAAS,aAAa,QAAQ,YAAY,OAAO,KAAK,KAAK,EAC/D,YAAY,EAAE,KAAK,QAAQ,IAAI,EAAE,EACnC,CAAC;EAED,KAAK,QAAQ,IAAI,QAAQ,MAAM,MAAM;EAGrC,MAAM,cAAc,IAAI,SAAkB,YAAY;GACpD,MAAM,WAAW,aAA6B;IAC5C,IAAI,SAAS,SAAS,eAAe;KACnC,OAAO,IAAI,WAAW,OAAO;KAC7B,QAAQ,SAAS,OAAO;IAC1B;GACF;GAEA,OAAO,GAAG,WAAW,OAAO;GAG5B,OAAO,GAAG,UAAU,UAAU;IAC5B,QAAQ,MAAM,gCAAgC,QAAQ,KAAK,KAAK,KAAK;IACrE,QAAQ,KAAK;GACf,CAAC;GAGD,OAAO,YAAY;IAAE,MAAM;IAAQ,QAAQ,EAAE,KAAK,QAAQ,IAAI,EAAE;GAAE,CAAC;EACrE,CAAC;EAED,KAAK,mBAAmB,IAAI,QAAQ,MAAM,WAAW;EAGrD,MAAM;CACR;;;;CAKA,MAAa,mBAAkC;EAC7C,MAAM,WAAW,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;EAC/C,MAAM,KAAK,WAAW,QAAQ;EAG9B,MAAM,KAAK,aAAa;CAC1B;;;;;;;;CASA,MAAa,WAAW,OAAqC;EAC3D,MAAM,gBAAgB,KAAK,eAAe,IAAI,OAAO,YAAY;GAC/D,IAAI,QAAQ,YACV,OAAO,KAAK,YAAY,QAAQ,MAAM,KAAK;QAE3C,OAAO,KAAK,UAAU,SAAS,KAAK;EAExC,CAAC;EAED,MAAM,UAAU,MAAM,QAAQ,IAAI,aAAa;EAG/C,KAAK,MAAM,kBAAkB,SAC3B,KAAK,MAAM,UAAU,gBACnB,IAAI,CAAC,OAAO,SACV,KAAK,mBAAmB,MAAM;CAItC;;;;CAKA,MAAc,YAAY,aAAqB,OAAkD;EAC/F,MAAM,SAAS,KAAK,QAAQ,IAAI,WAAW;EAE3C,IAAI,CAAC,QACH,OAAO,MAAM,KAAK,OAAO;GACvB,MAAM,EAAE;GACR,cAAc,EAAE;GAChB,SAAS;GACT,QAAQ,CAAC;GACT,UAAU,CAAC;EACb,EAAE;EAIJ,MAAM,cAAc,KAAK,mBAAmB,IAAI,WAAW;EAC3D,IAAI,aAEF;OAAI,CAAC,MADiB,aAEpB,OAAO,MAAM,KAAK,OAAO;IACvB,MAAM,EAAE;IACR,cAAc,EAAE;IAChB,SAAS;IACT,QAAQ,CAAC;IACT,UAAU,CAAC;GACb,EAAE;EACJ;EAIF,MAAM,kBAAoC,MAAM,KAAK,OAAO;GAC1D,MAAM,EAAE;GACR,SAAS,EAAE;GACX,cAAc,EAAE;EAClB,EAAE;EAMF,MAAM,WAFW,KAAK,aAAa,IAAI,WAAW,KAAK,QAAQ,QAAQ,EAE/C,CAAC,WACvB,KAAK,UAAU,aAAa,QAAQ,OAAO,eAAe,CAC5D;EAEA,KAAK,aAAa,IAChB,aACA,QAAQ,WACA,cACA,MACR,CACF;EAEA,OAAO;CACT;;;;;CAMA,AAAQ,UACN,aACA,QACA,OACA,iBAC4B;EAC5B,OAAO,IAAI,SAAS,YAAY;GAC9B,MAAM,iBACJ,MAAM,KAAK,OAAO;IAChB,MAAM,EAAE;IACR,cAAc,EAAE;IAChB,SAAS;IACT,QAAQ,CAAC;IACT,UAAU,CAAC;GACb,EAAE;GAEJ,MAAM,gBAAgB;IACpB,OAAO,IAAI,WAAW,OAAO;IAC7B,OAAO,IAAI,SAAS,SAAS;IAC7B,OAAO,IAAI,QAAQ,SAAS;GAC9B;GAEA,MAAM,kBAAkB;IACtB,QAAQ;IACR,QAAQ,SAAS,CAAC;GACpB;GAEA,MAAM,WAAW,aAA6B;IAC5C,IAAI,SAAS,SAAS,WAAW;KAC/B,QAAQ;KAGR,KAAK,YAAY,aAAa,SAAS,OAAO;KAE9C,QAAQ,SAAS,OAAO;IAC1B,OAAO,IAAI,SAAS,SAAS,SAAS;KACpC,QAAQ;KACR,QAAQ,SAAS,CAAC;IACpB;GACF;GAEA,OAAO,GAAG,WAAW,OAAO;GAC5B,OAAO,GAAG,SAAS,SAAS;GAC5B,OAAO,GAAG,QAAQ,SAAS;GAC3B,OAAO,YAAY;IAAE,MAAM;IAAS,OAAO;GAAgB,CAAC;EAC9D,CAAC;CACH;;;;CAKA,MAAc,UACZ,SACA,OAC4B;EAC5B,MAAM,UAA6B,CAAC;EAEpC,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,eAAe,MAAM,QAAQ,MAAM,IAAI;GAC7C,MAAM,QAAQ,aAAa,SAAS;GAEpC,QAAQ,KAAK;IACX,MAAM,KAAK;IACX,cAAc,KAAK;IACnB,SAAS,MAAM,UAAU;IACzB,QAAQ,aAAa,SAClB,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,CACjC,KAAK,OAAO;KACX,MAAM;KACN,SAAS,EAAE;KACX,YAAY,EAAE;KACd,cAAc,EAAE;KAChB,QAAQ,EAAE,UAAU;KACpB,UAAU,KAAK;KACf,cAAc,KAAK;KACnB,QAAQ,EAAE;IACZ,EAAE;IACJ,UAAU,aAAa,SACpB,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC,CACnC,KAAK,OAAO;KACX,MAAM;KACN,SAAS,EAAE;KACX,YAAY,EAAE;KACd,cAAc,EAAE;KAChB,QAAQ,EAAE,UAAU;KACpB,UAAU,KAAK;KACf,cAAc,KAAK;KACnB,QAAQ,EAAE;IACZ,EAAE;GACN,CAAC;EACH;EAGA,MAAM,iBAAiB;EACvB,KAAK,YAAY,QAAQ,MAAM,cAAc;EAE7C,OAAO;CACT;;;;CAKA,AAAQ,YAAY,aAAqB,SAAkC;EACzE,MAAM,QAAQ,KAAK,aAAa,IAAI,WAAW;EAC/C,IAAI,CAAC,OAAO;EAEZ,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM;GACN,IAAI,OAAO,SACT,MAAM;QAEN,MAAM;GAER,MAAM,eAAe,OAAO,OAAO;GACnC,MAAM,iBAAiB,OAAO,SAAS;GACvC,IAAI,OAAO,OAAO,SAAS,GAAG,MAAM;GACpC,IAAI,OAAO,SAAS,SAAS,GAAG,MAAM;EACxC;CACF;;;;CAKA,MAAa,cAAc,OAAqC;EAE9D,MAAM,kBAAoC,MAAM,KAAK,OAAO;GAC1D,MAAM,EAAE;GACR,SAAS,EAAE;GACX,cAAc,EAAE;EAClB,EAAE;EAEF,KAAK,MAAM,GAAG,WAAW,KAAK,SAC5B,OAAO,YAAY;GAAE,MAAM;GAAe,OAAO;EAAgB,CAAC;EAIpE,MAAM,QAAQ,IACZ,KAAK,eAAe,QAAQ,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,KAAK,MAAM,EAAE,cAAc,KAAK,CAAC,CACpF;CACF;;;;;CAMA,AAAO,YAAY,OAA4B;EAE7C,KAAK,eACF,QAAQ,MAAM,CAAC,EAAE,UAAU,CAAC,CAC5B,SAAS,YAAY,MAAM,SAAS,SAAS,QAAQ,WAAW,IAAI,CAAC,CAAC;EAGzE,IAAI,MAAM,SAAS,GAAG;GACpB,MAAM,eAAe,MAAM,KAAK,OAAO;IACrC,MAAM,EAAE;IACR,cAAc,EAAE;GAClB,EAAE;GAEF,KAAK,MAAM,GAAG,WAAW,KAAK,SAC5B,OAAO,YAAY;IAAE,MAAM;IAAgB,OAAO;GAAa,CAAC;EAEpE;CACF;;;;CAKA,AAAQ,mBAAmB,QAA+B;EACxD,MAAM,WAAW,OAAO,aAAa,QAAQ,OAAO,GAAG;EAGvD,KAAK,MAAM,SAAS,OAAO,QAAQ;GACjC,MAAM,OAAO,OAAO,UAAU,GAAG;GACjC,MAAM,QAAQ,OAAO,UAAU,OAAO,KAAK,OAAO,CAAC;GACnD,QAAQ,IACN,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,IAAI,IAAI,EAAE,GAAG,OAAO,WAAW,QAAQ,IAAI,OAAO,IAAI,IAAI,MAAM,WAAW,GAAG,MAAM,aAAa,EAAE,GAClI;GACA,IAAI,MAAM,QACR,QAAQ,IACN,KAAK,OAAO,cAAc,MAAM,MAAM,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,IAAI,MAAM,OAAO,GACxF;QAEA,QAAQ,IAAI,KAAK,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,IAAI,MAAM,OAAO,GAAG;EAEnE;EAGA,KAAK,MAAM,WAAW,OAAO,UAAU;GACrC,MAAM,OAAO,OAAO,aAAa,GAAG;GACpC,MAAM,QAAQ,OAAO,aAAa,OAAO,KAAK,SAAS,CAAC;GACxD,QAAQ,IACN,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,IAAI,IAAI,EAAE,GAAG,OAAO,WAAW,QAAQ,IAAI,OAAO,IAAI,IAAI,QAAQ,WAAW,GAAG,QAAQ,aAAa,EAAE,GACtI;GACA,IAAI,QAAQ,QACV,QAAQ,IACN,KAAK,OAAO,cAAc,QAAQ,MAAM,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,OAAO,QAAQ,OAAO,GAC/F;QAEA,QAAQ,IAAI,KAAK,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,OAAO,QAAQ,OAAO,GAAG;EAExE;CACF;;;;CAKA,MAAc,eAA8B;EAC1C,MAAM,WAAsC,CAAC;EAE7C,KAAK,MAAM,WAAW,KAAK,gBAAgB;GACzC,MAAM,QAAQ,KAAK,aAAa,IAAI,QAAQ,IAAI;GAChD,IAAI,OACF,SAAS,KAAK;IACZ,MAAM,QAAQ;IACd,OAAO;KACL,SAAS,MAAM;KACf,WAAW,MAAM;IACnB;IACA,UAAU;KACR,OAAO,MAAM;KACb,YAAY,MAAM;IACpB;IACA,QAAQ;KACN,OAAO,MAAM;KACb,YAAY,MAAM;IACpB;GACF,CAAC;EAEL;EAEA,QAAQ,IAAI,IAAI;EAChB,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC;EACpD,WAAW,OAAO,KAAK,gCAAgC,CAAC;EACxD,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC;EACpD,QAAQ,IAAI,EAAE;EAEd,SAAS,SAAS,OAAgC,UAAkB;GAClE,MAAM,aAAa,MAAM,MAAM,UAAU,MAAM,MAAM;GACrD,MAAM,YAAY,MAAM,MAAM,YAAY;GAG1C,MAAM,aAAa,YAAY,OAAO;GACtC,QAAQ,IAAI,OAAO,KAAK,KAAK,WAAW,IAAI,MAAM,MAAM,CAAC;GACzD,QAAQ,IAAI,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC,CAAC;GAG7C,MAAM,YAAY,eAAe,OAAO,MAAM,WAAW,SAAS,CAAC,EAAE,aAAa,OAAO,MAAM,OAAO,MAAM,MAAM,OAAO,EAAE,eAAe,YAAY,OAAO,IAAI,OAAO,MAAM,MAAM,SAAS,IAAI,OAAO,IAAI,KAAK,EAAE;GACnN,QAAQ,IAAI,SAAS;GAGrB,MAAM,eACJ,MAAM,SAAS,QAAQ,IACnB,OAAO,OAAO,KAAK,MAAM,SAAS,MAAM,UAAU,IAClD,OAAO,IAAI,cAAc;GAE/B,MAAM,aACJ,MAAM,OAAO,QAAQ,IACjB,OAAO,IAAI,KAAK,MAAM,OAAO,MAAM,QAAQ,IAC3C,OAAO,IAAI,YAAY;GAE7B,QAAQ,IAAI,gBAAgB,aAAa,OAAO,YAAY;GAG5D,IAAI,QAAQ,SAAS,SAAS,GAC5B,QAAQ,IAAI,EAAE;EAElB,CAAC;EAED,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC;EACpD,QAAQ,IAAI,EAAE;CAChB;;;;CAKA,MAAa,WAA0B;EACrC,KAAK,MAAM,CAAC,MAAM,WAAW,KAAK,SAChC,IAAI;GACF,OAAO,YAAY,EAAE,MAAM,WAAW,CAAC;GACvC,MAAM,OAAO,UAAU;EACzB,QAAQ,CAER;EAGF,KAAK,QAAQ,MAAM;EACnB,KAAK,mBAAmB,MAAM;EAC9B,KAAK,aAAa,MAAM;CAC1B;AACF"}
@@ -32,10 +32,15 @@ var ESLintHealthWorker = class {
32
32
  initialize(config) {
33
33
  try {
34
34
  this.cwd = config.cwd || process.cwd();
35
- const flatConfigPath = path.join(this.cwd, "eslint.config.js");
36
- const flatConfigMjsPath = path.join(this.cwd, "eslint.config.mjs");
37
- const flatConfigCjsPath = path.join(this.cwd, "eslint.config.cjs");
38
- this.hasConfig = fs.existsSync(flatConfigPath) || fs.existsSync(flatConfigMjsPath) || fs.existsSync(flatConfigCjsPath);
35
+ const flatConfigExtensions = [
36
+ "js",
37
+ "mjs",
38
+ "cjs",
39
+ "ts",
40
+ "mts",
41
+ "cts"
42
+ ];
43
+ this.hasConfig = flatConfigExtensions.some((extension) => fs.existsSync(path.join(this.cwd, `eslint.config.${extension}`)));
39
44
  if (!this.hasConfig) {
40
45
  this.initialized = true;
41
46
  return {
@@ -107,15 +112,14 @@ var ESLintHealthWorker = class {
107
112
  errors: [],
108
113
  warnings: []
109
114
  };
110
- const lintResults = await this.eslint.lintText(file.content, { filePath: file.path });
111
- if (lintResults.length === 0) return {
115
+ const lintResult = (await this.eslint.lintText(file.content, { filePath: file.path }))[0];
116
+ if (!lintResult) return {
112
117
  path: file.path,
113
118
  relativePath: file.relativePath,
114
119
  healthy: true,
115
120
  errors: [],
116
121
  warnings: []
117
122
  };
118
- const lintResult = lintResults[0];
119
123
  const errors = [];
120
124
  const warnings = [];
121
125
  for (const message of lintResult.messages) {
@@ -1 +1 @@
1
- {"version":3,"file":"eslint-health.worker.mjs","names":[],"sources":["../../../../../../../../../core/src/dev-server/health-checker/workers/eslint-health.worker.ts"],"sourcesContent":["/**\n * ESLint Health Check Worker\n *\n * This worker runs in a dedicated thread to perform ESLint linting\n * without blocking the main dev server thread. It maintains a persistent\n * ESLint instance for efficient linting.\n *\n * Communication:\n * - Receives: { type: 'init' | 'check' | 'shutdown', ... }\n * - Sends: { type: 'results' | 'initialized' | 'error', ... }\n */\nimport { ESLint } from \"eslint\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { parentPort, workerData } from \"worker_threads\";\n\n/**\n * Serialized file data received from main thread\n */\ntype SerializedFile = {\n /** Absolute file path */\n path: string;\n /** File content (source code) */\n content: string;\n /** Relative path for display */\n relativePath: string;\n};\n\n/**\n * Lint message sent back to main thread\n */\ntype LintMessage = {\n type: \"error\" | \"warning\";\n message: string;\n lineNumber: number;\n columnNumber: number;\n length: number;\n filePath: string;\n relativePath: string;\n ruleId?: string;\n};\n\n/**\n * Health check result for a single file\n */\ntype FileCheckResult = {\n path: string;\n relativePath: string;\n healthy: boolean;\n errors: LintMessage[];\n warnings: LintMessage[];\n};\n\n/**\n * Messages received from main thread\n */\n/**\n * Deleted file info\n */\ntype DeletedFile = {\n path: string;\n relativePath: string;\n};\n\n/**\n * Messages received from main thread\n */\ntype WorkerMessage =\n | { type: \"init\"; config: { cwd: string } }\n | { type: \"check\"; files: SerializedFile[] }\n | { type: \"filesDeleted\"; files: DeletedFile[] }\n | { type: \"shutdown\" };\n\n/**\n * Messages sent to main thread\n */\ntype WorkerResponse =\n | { type: \"initialized\"; success: boolean; hasConfig: boolean; error?: string }\n | { type: \"results\"; results: FileCheckResult[] }\n | { type: \"error\"; message: string };\n\n/**\n * ESLint Health Worker class\n * Maintains persistent ESLint instance for efficient linting\n */\nclass ESLintHealthWorker {\n /**\n * ESLint instance\n */\n private eslint: ESLint | null = null;\n\n /**\n * Whether the worker is initialized\n */\n private initialized = false;\n\n /**\n * Whether ESLint config was found\n */\n private hasConfig = false;\n\n /**\n * Current working directory\n */\n private cwd: string = process.cwd();\n\n /**\n * Initialize the worker with ESLint configuration\n */\n public initialize(config: { cwd: string }): { success: boolean; hasConfig: boolean } {\n try {\n this.cwd = config.cwd || process.cwd();\n\n // Check if ESLint flat config exists\n const flatConfigPath = path.join(this.cwd, \"eslint.config.js\");\n const flatConfigMjsPath = path.join(this.cwd, \"eslint.config.mjs\");\n const flatConfigCjsPath = path.join(this.cwd, \"eslint.config.cjs\");\n\n this.hasConfig =\n fs.existsSync(flatConfigPath) ||\n fs.existsSync(flatConfigMjsPath) ||\n fs.existsSync(flatConfigCjsPath);\n\n if (!this.hasConfig) {\n this.initialized = true;\n return { success: true, hasConfig: false };\n }\n\n // Create ESLint instance with flat config\n this.eslint = new ESLint({\n cwd: this.cwd,\n });\n\n this.initialized = true;\n return { success: true, hasConfig: true };\n } catch (error) {\n console.error(\"ESLint Worker: Failed to initialize:\", error);\n this.initialized = true;\n return { success: false, hasConfig: false };\n }\n }\n\n /**\n * Check files for ESLint errors\n */\n public async checkFiles(files: SerializedFile[]): Promise<FileCheckResult[]> {\n if (!this.eslint || !this.hasConfig) {\n // No ESLint config, return all files as healthy\n return files.map((file) => ({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n\n const results: FileCheckResult[] = [];\n\n for (const file of files) {\n // Only lint lintable files\n if (!this.isLintableFile(file.path)) {\n results.push({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n });\n continue;\n }\n\n try {\n const result = await this.checkSingleFile(file);\n results.push(result);\n } catch (error) {\n // On error, mark as healthy to avoid blocking\n results.push({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n });\n }\n }\n\n return results;\n }\n\n /**\n * Check a single file for lint issues\n */\n private async checkSingleFile(file: SerializedFile): Promise<FileCheckResult> {\n if (!this.eslint) {\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n };\n }\n\n // Use lintText to lint from memory (not disk)\n const lintResults = await this.eslint.lintText(file.content, {\n filePath: file.path,\n });\n\n if (lintResults.length === 0) {\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n };\n }\n\n const lintResult = lintResults[0];\n const errors: LintMessage[] = [];\n const warnings: LintMessage[] = [];\n\n for (const message of lintResult.messages) {\n const lintMessage: LintMessage = {\n type: message.severity === 2 ? \"error\" : \"warning\",\n message: message.message,\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n filePath: file.path,\n relativePath: file.relativePath,\n ruleId: message.ruleId || undefined,\n };\n\n if (message.severity === 2) {\n errors.push(lintMessage);\n } else if (message.severity === 1) {\n warnings.push(lintMessage);\n }\n }\n\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: errors.length === 0 && warnings.length === 0,\n errors,\n warnings,\n };\n }\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// Create worker instance\nconst worker = new ESLintHealthWorker();\n\n// Handle messages from main thread\nparentPort?.on(\"message\", async (message: WorkerMessage) => {\n try {\n switch (message.type) {\n case \"init\": {\n const initResult = worker.initialize(message.config);\n const response: WorkerResponse = {\n type: \"initialized\",\n success: initResult.success,\n hasConfig: initResult.hasConfig,\n };\n parentPort?.postMessage(response);\n break;\n }\n\n case \"check\": {\n const results = await worker.checkFiles(message.files);\n const response: WorkerResponse = { type: \"results\", results };\n parentPort?.postMessage(response);\n break;\n }\n\n case \"filesDeleted\": {\n // ESLint doesn't maintain internal file cache (lints from memory)\n // No cleanup needed, just acknowledge\n break;\n }\n\n case \"shutdown\": {\n process.exit(0);\n }\n }\n } catch (error) {\n const response: WorkerResponse = {\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n };\n parentPort?.postMessage(response);\n }\n});\n\n// Handle initialization from workerData if provided\nif (workerData?.autoInit) {\n worker.initialize({ cwd: workerData.cwd || process.cwd() });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"eslint-health.worker.mjs","names":[],"sources":["../../../../../../../../../core/src/dev-server/health-checker/workers/eslint-health.worker.ts"],"sourcesContent":["/**\n * ESLint Health Check Worker\n *\n * This worker runs in a dedicated thread to perform ESLint linting\n * without blocking the main dev server thread. It maintains a persistent\n * ESLint instance for efficient linting.\n *\n * Communication:\n * - Receives: { type: 'init' | 'check' | 'shutdown', ... }\n * - Sends: { type: 'results' | 'initialized' | 'error', ... }\n */\nimport { ESLint } from \"eslint\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { parentPort, workerData } from \"worker_threads\";\n\n/**\n * Serialized file data received from main thread\n */\ntype SerializedFile = {\n /** Absolute file path */\n path: string;\n /** File content (source code) */\n content: string;\n /** Relative path for display */\n relativePath: string;\n};\n\n/**\n * Lint message sent back to main thread\n */\ntype LintMessage = {\n type: \"error\" | \"warning\";\n message: string;\n lineNumber: number;\n columnNumber: number;\n length: number;\n filePath: string;\n relativePath: string;\n ruleId?: string;\n};\n\n/**\n * Health check result for a single file\n */\ntype FileCheckResult = {\n path: string;\n relativePath: string;\n healthy: boolean;\n errors: LintMessage[];\n warnings: LintMessage[];\n};\n\n/**\n * Messages received from main thread\n */\n/**\n * Deleted file info\n */\ntype DeletedFile = {\n path: string;\n relativePath: string;\n};\n\n/**\n * Messages received from main thread\n */\ntype WorkerMessage =\n | { type: \"init\"; config: { cwd: string } }\n | { type: \"check\"; files: SerializedFile[] }\n | { type: \"filesDeleted\"; files: DeletedFile[] }\n | { type: \"shutdown\" };\n\n/**\n * Messages sent to main thread\n */\ntype WorkerResponse =\n | { type: \"initialized\"; success: boolean; hasConfig: boolean; error?: string }\n | { type: \"results\"; results: FileCheckResult[] }\n | { type: \"error\"; message: string };\n\n/**\n * ESLint Health Worker class\n * Maintains persistent ESLint instance for efficient linting\n */\nclass ESLintHealthWorker {\n /**\n * ESLint instance\n */\n private eslint: ESLint | null = null;\n\n /**\n * Whether the worker is initialized\n */\n private initialized = false;\n\n /**\n * Whether ESLint config was found\n */\n private hasConfig = false;\n\n /**\n * Current working directory\n */\n private cwd: string = process.cwd();\n\n /**\n * Initialize the worker with ESLint configuration\n */\n public initialize(config: { cwd: string }): { success: boolean; hasConfig: boolean } {\n try {\n this.cwd = config.cwd || process.cwd();\n\n // Check if ESLint flat config exists\n // (.ts/.mts/.cts are supported since ESLint 9.18)\n const flatConfigExtensions = [\"js\", \"mjs\", \"cjs\", \"ts\", \"mts\", \"cts\"];\n\n this.hasConfig = flatConfigExtensions.some((extension) =>\n fs.existsSync(path.join(this.cwd, `eslint.config.${extension}`)),\n );\n\n if (!this.hasConfig) {\n this.initialized = true;\n return { success: true, hasConfig: false };\n }\n\n // Create ESLint instance with flat config\n this.eslint = new ESLint({\n cwd: this.cwd,\n });\n\n this.initialized = true;\n return { success: true, hasConfig: true };\n } catch (error) {\n console.error(\"ESLint Worker: Failed to initialize:\", error);\n this.initialized = true;\n return { success: false, hasConfig: false };\n }\n }\n\n /**\n * Check files for ESLint errors\n */\n public async checkFiles(files: SerializedFile[]): Promise<FileCheckResult[]> {\n if (!this.eslint || !this.hasConfig) {\n // No ESLint config, return all files as healthy\n return files.map((file) => ({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n }));\n }\n\n const results: FileCheckResult[] = [];\n\n for (const file of files) {\n // Only lint lintable files\n if (!this.isLintableFile(file.path)) {\n results.push({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n });\n continue;\n }\n\n try {\n const result = await this.checkSingleFile(file);\n results.push(result);\n } catch (error) {\n // On error, mark as healthy to avoid blocking\n results.push({\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n });\n }\n }\n\n return results;\n }\n\n /**\n * Check a single file for lint issues\n */\n private async checkSingleFile(file: SerializedFile): Promise<FileCheckResult> {\n if (!this.eslint) {\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n };\n }\n\n // Use lintText to lint from memory (not disk)\n const lintResults = await this.eslint.lintText(file.content, {\n filePath: file.path,\n });\n\n const lintResult = lintResults[0];\n\n if (!lintResult) {\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: true,\n errors: [],\n warnings: [],\n };\n }\n\n const errors: LintMessage[] = [];\n const warnings: LintMessage[] = [];\n\n for (const message of lintResult.messages) {\n const lintMessage: LintMessage = {\n type: message.severity === 2 ? \"error\" : \"warning\",\n message: message.message,\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n filePath: file.path,\n relativePath: file.relativePath,\n ruleId: message.ruleId || undefined,\n };\n\n if (message.severity === 2) {\n errors.push(lintMessage);\n } else if (message.severity === 1) {\n warnings.push(lintMessage);\n }\n }\n\n return {\n path: file.path,\n relativePath: file.relativePath,\n healthy: errors.length === 0 && warnings.length === 0,\n errors,\n warnings,\n };\n }\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// Create worker instance\nconst worker = new ESLintHealthWorker();\n\n// Handle messages from main thread\nparentPort?.on(\"message\", async (message: WorkerMessage) => {\n try {\n switch (message.type) {\n case \"init\": {\n const initResult = worker.initialize(message.config);\n const response: WorkerResponse = {\n type: \"initialized\",\n success: initResult.success,\n hasConfig: initResult.hasConfig,\n };\n parentPort?.postMessage(response);\n break;\n }\n\n case \"check\": {\n const results = await worker.checkFiles(message.files);\n const response: WorkerResponse = { type: \"results\", results };\n parentPort?.postMessage(response);\n break;\n }\n\n case \"filesDeleted\": {\n // ESLint doesn't maintain internal file cache (lints from memory)\n // No cleanup needed, just acknowledge\n break;\n }\n\n case \"shutdown\": {\n process.exit(0);\n }\n }\n } catch (error) {\n const response: WorkerResponse = {\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n };\n parentPort?.postMessage(response);\n }\n});\n\n// Handle initialization from workerData if provided\nif (workerData?.autoInit) {\n worker.initialize({ cwd: workerData.cwd || process.cwd() });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqFA,IAAM,qBAAN,MAAyB;;gBAIS;qBAKV;mBAKF;aAKE,QAAQ,IAAI;;;;;CAKlC,AAAO,WAAW,QAAmE;EACnF,IAAI;GACF,KAAK,MAAM,OAAO,OAAO,QAAQ,IAAI;GAIrC,MAAM,uBAAuB;IAAC;IAAM;IAAO;IAAO;IAAM;IAAO;GAAK;GAEpE,KAAK,YAAY,qBAAqB,MAAM,cAC1C,GAAG,WAAW,KAAK,KAAK,KAAK,KAAK,iBAAiB,WAAW,CAAC,CACjE;GAEA,IAAI,CAAC,KAAK,WAAW;IACnB,KAAK,cAAc;IACnB,OAAO;KAAE,SAAS;KAAM,WAAW;IAAM;GAC3C;GAGA,KAAK,SAAS,IAAI,OAAO,EACvB,KAAK,KAAK,IACZ,CAAC;GAED,KAAK,cAAc;GACnB,OAAO;IAAE,SAAS;IAAM,WAAW;GAAK;EAC1C,SAAS,OAAO;GACd,QAAQ,MAAM,wCAAwC,KAAK;GAC3D,KAAK,cAAc;GACnB,OAAO;IAAE,SAAS;IAAO,WAAW;GAAM;EAC5C;CACF;;;;CAKA,MAAa,WAAW,OAAqD;EAC3E,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,WAExB,OAAO,MAAM,KAAK,UAAU;GAC1B,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,SAAS;GACT,QAAQ,CAAC;GACT,UAAU,CAAC;EACb,EAAE;EAGJ,MAAM,UAA6B,CAAC;EAEpC,KAAK,MAAM,QAAQ,OAAO;GAExB,IAAI,CAAC,KAAK,eAAe,KAAK,IAAI,GAAG;IACnC,QAAQ,KAAK;KACX,MAAM,KAAK;KACX,cAAc,KAAK;KACnB,SAAS;KACT,QAAQ,CAAC;KACT,UAAU,CAAC;IACb,CAAC;IACD;GACF;GAEA,IAAI;IACF,MAAM,SAAS,MAAM,KAAK,gBAAgB,IAAI;IAC9C,QAAQ,KAAK,MAAM;GACrB,SAAS,OAAO;IAEd,QAAQ,KAAK;KACX,MAAM,KAAK;KACX,cAAc,KAAK;KACnB,SAAS;KACT,QAAQ,CAAC;KACT,UAAU,CAAC;IACb,CAAC;GACH;EACF;EAEA,OAAO;CACT;;;;CAKA,MAAc,gBAAgB,MAAgD;EAC5E,IAAI,CAAC,KAAK,QACR,OAAO;GACL,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,SAAS;GACT,QAAQ,CAAC;GACT,UAAU,CAAC;EACb;EAQF,MAAM,cAAa,MAJO,KAAK,OAAO,SAAS,KAAK,SAAS,EAC3D,UAAU,KAAK,KACjB,CAAC,EAE6B,CAAC;EAE/B,IAAI,CAAC,YACH,OAAO;GACL,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,SAAS;GACT,QAAQ,CAAC;GACT,UAAU,CAAC;EACb;EAGF,MAAM,SAAwB,CAAC;EAC/B,MAAM,WAA0B,CAAC;EAEjC,KAAK,MAAM,WAAW,WAAW,UAAU;GACzC,MAAM,cAA2B;IAC/B,MAAM,QAAQ,aAAa,IAAI,UAAU;IACzC,SAAS,QAAQ;IACjB,YAAY,QAAQ,QAAQ;IAC5B,cAAc,QAAQ,UAAU;IAChC,QAAQ,QAAQ,aAAa,QAAQ,SAAS,QAAQ,YAAY,QAAQ,SAAS;IACnF,UAAU,KAAK;IACf,cAAc,KAAK;IACnB,QAAQ,QAAQ,UAAU;GAC5B;GAEA,IAAI,QAAQ,aAAa,GACvB,OAAO,KAAK,WAAW;QAClB,IAAI,QAAQ,aAAa,GAC9B,SAAS,KAAK,WAAW;EAE7B;EAEA,OAAO;GACL,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,SAAS,OAAO,WAAW,KAAK,SAAS,WAAW;GACpD;GACA;EACF;CACF;;;;CAKA,AAAQ,eAAe,UAA2B;EAChD,MAAM,MAAM,SAAS,YAAY;EACjC,OACE,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,MAAM,KAAK,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,MAAM;CAE7F;AACF;AAGA,MAAM,SAAS,IAAI,mBAAmB;AAGtC,YAAY,GAAG,WAAW,OAAO,YAA2B;CAC1D,IAAI;EACF,QAAQ,QAAQ,MAAhB;GACE,KAAK,QAAQ;IACX,MAAM,aAAa,OAAO,WAAW,QAAQ,MAAM;IACnD,MAAM,WAA2B;KAC/B,MAAM;KACN,SAAS,WAAW;KACpB,WAAW,WAAW;IACxB;IACA,YAAY,YAAY,QAAQ;IAChC;GACF;GAEA,KAAK,SAAS;IAEZ,MAAM,WAA2B;KAAE,MAAM;KAAW,eAD9B,OAAO,WAAW,QAAQ,KAAK;IACO;IAC5D,YAAY,YAAY,QAAQ;IAChC;GACF;GAEA,KAAK,gBAGH;GAGF,KAAK,YACH,QAAQ,KAAK,CAAC;EAElB;CACF,SAAS,OAAO;EACd,MAAM,WAA2B;GAC/B,MAAM;GACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAChE;EACA,YAAY,YAAY,QAAQ;CAClC;AACF,CAAC;AAGD,IAAI,YAAY,UACd,OAAO,WAAW,EAAE,KAAK,WAAW,OAAO,QAAQ,IAAI,EAAE,CAAC"}