@ecopages/core 0.2.0-beta.31 → 0.2.0-beta.32

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 (338) hide show
  1. package/README.md +1 -0
  2. package/package.json +82 -3
  3. package/src/adapters/abstract/application-adapter.d.ts +0 -8
  4. package/src/adapters/abstract/application-adapter.js +252 -261
  5. package/src/adapters/abstract/router-adapter.js +12 -4
  6. package/src/adapters/abstract/segment-path-matcher.js +132 -134
  7. package/src/adapters/abstract/server-adapter.js +22 -14
  8. package/src/adapters/abstract/ws-pattern-matcher.js +24 -26
  9. package/src/adapters/bun/bun-user-websocket-lifecycle.js +83 -86
  10. package/src/adapters/bun/client-bridge.js +51 -50
  11. package/src/adapters/bun/create-app.js +171 -161
  12. package/src/adapters/bun/hmr-manager.js +62 -57
  13. package/src/adapters/bun/index.js +2 -31
  14. package/src/adapters/bun/runtime-host.js +61 -54
  15. package/src/adapters/bun/server-adapter.d.ts +0 -1
  16. package/src/adapters/bun/server-adapter.js +561 -561
  17. package/src/adapters/bun/static-preview-host.js +43 -46
  18. package/src/adapters/create-app.js +11 -14
  19. package/src/adapters/index.js +2 -30
  20. package/src/adapters/node/create-app.js +123 -129
  21. package/src/adapters/node/http-request-bridge.js +147 -106
  22. package/src/adapters/node/node-client-bridge.js +62 -64
  23. package/src/adapters/node/node-hmr-manager.js +58 -48
  24. package/src/adapters/node/runtime-host.js +96 -89
  25. package/src/adapters/node/server-adapter-dependencies.js +18 -21
  26. package/src/adapters/node/server-adapter.d.ts +0 -1
  27. package/src/adapters/node/server-adapter.js +347 -326
  28. package/src/adapters/node/static-content-server.js +172 -191
  29. package/src/adapters/node/static-preview-host.js +87 -78
  30. package/src/adapters/shared/hmr/dev-transform-entrypoint-registry.d.ts +16 -0
  31. package/src/adapters/shared/hmr/dev-transform-entrypoint-registry.js +32 -0
  32. package/src/adapters/shared/hmr/hmr-html-response.js +46 -32
  33. package/src/adapters/shared/hmr/shared-hmr-manager.d.ts +29 -38
  34. package/src/adapters/shared/hmr/shared-hmr-manager.js +439 -425
  35. package/src/adapters/shared/http/api-request-pipeline.js +122 -119
  36. package/src/adapters/shared/http/api-response.js +77 -77
  37. package/src/adapters/shared/http/create-body-response.js +22 -19
  38. package/src/adapters/shared/http/define-api-handler.js +41 -55
  39. package/src/adapters/shared/http/explicit-static-render-preparation.js +26 -23
  40. package/src/adapters/shared/http/explicit-static-route-matcher.js +55 -62
  41. package/src/adapters/shared/http/file-route-middleware-pipeline.js +81 -75
  42. package/src/adapters/shared/http/fs-server-response-factory.js +101 -96
  43. package/src/adapters/shared/http/fs-server-response-matcher.js +239 -257
  44. package/src/adapters/shared/http/run-middleware-chain.js +15 -15
  45. package/src/adapters/shared/runtime/application-adapter.js +72 -87
  46. package/src/adapters/shared/runtime/copy-runtime-public-dir.js +25 -22
  47. package/src/adapters/shared/runtime/port-manager.js +207 -177
  48. package/src/adapters/shared/runtime/render-context.js +94 -79
  49. package/src/adapters/shared/runtime/runtime-app-bootstrap.js +33 -38
  50. package/src/adapters/shared/runtime/runtime-host.js +1 -0
  51. package/src/adapters/shared/runtime/runtime-server-lifecycle.d.ts +2 -3
  52. package/src/adapters/shared/runtime/runtime-server-lifecycle.js +65 -51
  53. package/src/adapters/shared/runtime/server-adapter.d.ts +1 -0
  54. package/src/adapters/shared/runtime/server-adapter.js +233 -217
  55. package/src/adapters/shared/runtime/server-route-handler.js +74 -70
  56. package/src/adapters/shared/runtime/server-static-builder.js +165 -184
  57. package/src/adapters/shared/runtime/static-preview-host.js +1 -0
  58. package/src/adapters/shared/ws/node-http-websocket-upgrades.js +97 -88
  59. package/src/adapters/shared/ws/websocket-lifecycle.js +57 -50
  60. package/src/build/README.md +52 -1
  61. package/src/build/app-build-manifest-runtime.js +129 -108
  62. package/src/build/browser/browser-runtime-manifest.js +47 -50
  63. package/src/build/browser/browser-runtime-plugin-helpers.js +32 -12
  64. package/src/build/browser/browser-runtime-plugin.js +206 -160
  65. package/src/build/browser/jsx-ownership-plugins.js +51 -37
  66. package/src/build/browser/lit-static-render-worker-context.js +3 -5
  67. package/src/build/build-adapter.js +276 -139
  68. package/src/build/cache/build-input-fingerprint.js +28 -26
  69. package/src/build/cache/cache-constants.js +4 -6
  70. package/src/build/cache/cache-keys.js +64 -56
  71. package/src/build/cache/pages-unified-graph-build.js +179 -178
  72. package/src/build/cache/production-build-cache.js +47 -54
  73. package/src/build/cache/server-entry-build-cache.js +134 -146
  74. package/src/build/contracts/build-contracts.d.ts +4 -4
  75. package/src/build/contracts/build-contracts.js +1 -0
  76. package/src/build/contracts/build-manifest.js +58 -43
  77. package/src/build/contracts/build-types.js +1 -0
  78. package/src/build/rolldown/rolldown-adapter-helpers.d.ts +9 -0
  79. package/src/build/rolldown/rolldown-adapter-helpers.js +405 -298
  80. package/src/build/rolldown/rolldown-build-adapter.js +53 -61
  81. package/src/build/rolldown/rolldown-build-invocation-metrics.js +31 -35
  82. package/src/build/rolldown/rolldown-plugin-bridge.js +264 -194
  83. package/src/build/rolldown/rolldown-source-transform-pass.js +59 -50
  84. package/src/build/rolldown/runtime-build-output-normalizer.js +227 -175
  85. package/src/build/rolldown/server-side-css-shim-plugin.js +54 -31
  86. package/src/build/runtime/build-profile-options.js +36 -36
  87. package/src/build/runtime/build-request-identity.js +70 -45
  88. package/src/build/runtime/build-request-policy.js +80 -42
  89. package/src/build/runtime/build-runtime.js +64 -54
  90. package/src/build/runtime/deduping-build-executor.js +38 -32
  91. package/src/build/runtime/parallel-build-executor.js +58 -50
  92. package/src/build/runtime/serialized-build-executor.js +78 -62
  93. package/src/cache/index.js +6 -6
  94. package/src/cache/module-parse-cache.js +99 -68
  95. package/src/client/navigation-scripts/classification.js +45 -37
  96. package/src/client/navigation-scripts/index.js +3 -23
  97. package/src/client/navigation-scripts/navigation-scripts.test.browser.js +58 -62
  98. package/src/client/navigation-scripts/registry.js +15 -11
  99. package/src/client/navigation-scripts/rerun-queue.js +81 -64
  100. package/src/client/scroll.js +32 -24
  101. package/src/client/scroll.test.browser.js +27 -33
  102. package/src/client/view-transitions.js +68 -55
  103. package/src/config/config-builder.d.ts +8 -0
  104. package/src/config/config-builder.js +637 -642
  105. package/src/config/constants.js +44 -24
  106. package/src/declarations.d.ts +1 -0
  107. package/src/dev/README.md +24 -0
  108. package/src/dev/client-bridge-registry.js +7 -12
  109. package/src/dev/dev-client-ownership.js +7 -5
  110. package/src/dev/hmr-manager-registry.js +7 -12
  111. package/src/dev/host-runtime.js +25 -30
  112. package/src/dev/runtime-server-started-message.js +2 -5
  113. package/src/dev/sc-server.js +106 -103
  114. package/src/dev/transform-server/dev-transform-bundler.d.ts +24 -0
  115. package/src/dev/transform-server/dev-transform-bundler.js +79 -0
  116. package/src/dev/transform-server/dev-transform-externalize-plugin.d.ts +12 -0
  117. package/src/dev/transform-server/dev-transform-externalize-plugin.js +29 -0
  118. package/src/dev/transform-server/dev-transform-import-rewriter.d.ts +16 -0
  119. package/src/dev/transform-server/dev-transform-import-rewriter.js +110 -0
  120. package/src/dev/transform-server/dev-transform-module-kind.d.ts +13 -0
  121. package/src/dev/transform-server/dev-transform-module-kind.js +17 -0
  122. package/src/dev/transform-server/dev-transform-runtime-specifiers.d.ts +5 -0
  123. package/src/dev/transform-server/dev-transform-runtime-specifiers.js +16 -0
  124. package/src/dev/transform-server/dev-transform-server.d.ts +41 -0
  125. package/src/dev/transform-server/dev-transform-server.js +191 -0
  126. package/src/dev/transform-server/dev-transform-url.d.ts +13 -0
  127. package/src/dev/transform-server/dev-transform-url.js +58 -0
  128. package/src/dev/transform-server/dev-transform-vendor-registry.d.ts +27 -0
  129. package/src/dev/transform-server/dev-transform-vendor-registry.js +145 -0
  130. package/src/dev/transform-server/index.d.ts +6 -0
  131. package/src/dev/transform-server/index.js +4 -0
  132. package/src/dev/transform-server/types.d.ts +15 -0
  133. package/src/dev/transform-server/types.js +1 -0
  134. package/src/dev-toolbar/README.md +51 -0
  135. package/src/dev-toolbar/define-dev-tool.d.ts +21 -0
  136. package/src/dev-toolbar/define-dev-tool.js +9 -0
  137. package/src/dev-toolbar/dev-toolbar-config.d.ts +25 -0
  138. package/src/dev-toolbar/dev-toolbar-config.js +19 -0
  139. package/src/dev-toolbar/dev-toolbar-host.d.ts +49 -0
  140. package/src/dev-toolbar/dev-toolbar-host.js +117 -0
  141. package/src/dev-toolbar/dev-toolbar-html-response.d.ts +11 -0
  142. package/src/dev-toolbar/dev-toolbar-html-response.js +14 -0
  143. package/src/dev-toolbar/dev-toolbar-manifest.d.ts +15 -0
  144. package/src/dev-toolbar/dev-toolbar-manifest.js +52 -0
  145. package/src/dev-toolbar/dev-toolbar-package.d.ts +31 -0
  146. package/src/dev-toolbar/dev-toolbar-package.js +72 -0
  147. package/src/dev-toolbar/dev-toolbar-runtime-paths.d.ts +10 -0
  148. package/src/dev-toolbar/dev-toolbar-runtime-paths.js +13 -0
  149. package/src/diagnostics/request-build-dedupe.js +34 -28
  150. package/src/diagnostics/startup-trace.d.ts +10 -1
  151. package/src/diagnostics/startup-trace.js +269 -241
  152. package/src/eco/README.md +6 -0
  153. package/src/eco/eco-declared-component.js +19 -14
  154. package/src/eco/eco.browser.js +56 -69
  155. package/src/eco/eco.js +156 -96
  156. package/src/eco/eco.types.js +5 -0
  157. package/src/eco/eco.utils.js +1 -10
  158. package/src/eco/global-injector-map.js +55 -41
  159. package/src/eco/lazy-injector-map.js +90 -60
  160. package/src/eco/module-dependencies.js +52 -44
  161. package/src/eco/page-layout-normalization.js +59 -49
  162. package/src/errors/http-error.js +50 -49
  163. package/src/errors/index.js +3 -12
  164. package/src/errors/locals-access-error.js +5 -8
  165. package/src/errors/undeclared-component-dependency-error.js +15 -15
  166. package/src/global/app-logger.js +3 -6
  167. package/src/hmr/README.md +10 -0
  168. package/src/hmr/client/hmr-runtime.js +117 -113
  169. package/src/hmr/client/module-update.d.ts +13 -0
  170. package/src/hmr/client/module-update.js +39 -0
  171. package/src/hmr/hmr-asset-paths.d.ts +14 -0
  172. package/src/hmr/hmr-asset-paths.js +22 -0
  173. package/src/hmr/hmr-entrypoint-output.d.ts +11 -39
  174. package/src/hmr/hmr-entrypoint-output.js +28 -54
  175. package/src/hmr/hmr-file-change-prep.js +13 -16
  176. package/src/hmr/hmr-registered-entrypoints-context.d.ts +7 -0
  177. package/src/hmr/hmr-registered-entrypoints-context.js +1 -0
  178. package/src/hmr/hmr-runtime-paths.d.ts +10 -0
  179. package/src/hmr/hmr-runtime-paths.js +13 -0
  180. package/src/hmr/hmr-strategy.d.ts +5 -11
  181. package/src/hmr/hmr-strategy.js +113 -61
  182. package/src/hmr/strategies/default-hmr-strategy.js +55 -33
  183. package/src/hmr/strategies/js-hmr-strategy.d.ts +6 -95
  184. package/src/hmr/strategies/js-hmr-strategy.js +104 -217
  185. package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +33 -27
  186. package/src/index.browser.js +1 -4
  187. package/src/index.d.ts +1 -0
  188. package/src/index.js +6 -47
  189. package/src/integrations/ghtml/ghtml-renderer.js +12 -7
  190. package/src/integrations/ghtml/ghtml.constants.js +1 -4
  191. package/src/integrations/ghtml/ghtml.plugin.js +9 -12
  192. package/src/islands/README.md +25 -0
  193. package/src/islands/island-host.d.ts +37 -0
  194. package/src/islands/island-host.js +55 -0
  195. package/src/plugins/alias-resolver-cache.js +114 -97
  196. package/src/plugins/alias-resolver-plugin.js +33 -47
  197. package/src/plugins/define-integration.js +18 -17
  198. package/src/plugins/eco-component-meta-plugin.js +387 -145
  199. package/src/plugins/foreign-jsx-override-plugin.js +49 -35
  200. package/src/plugins/integration-plugin.js +212 -203
  201. package/src/plugins/jsx-import-source.utils.js +16 -21
  202. package/src/plugins/processor.js +159 -149
  203. package/src/plugins/runtime-capability.js +1 -0
  204. package/src/plugins/source-transform.js +109 -79
  205. package/src/plugins/tsconfig-import-resolver.d.ts +17 -0
  206. package/src/plugins/tsconfig-import-resolver.js +218 -142
  207. package/src/route-renderer/README.md +12 -1
  208. package/src/route-renderer/orchestration/document-shell/document-shell-render.service.js +143 -136
  209. package/src/route-renderer/orchestration/document-shell/layout-shell-props.service.js +33 -27
  210. package/src/route-renderer/orchestration/foreign-child/component-render-context.js +173 -126
  211. package/src/route-renderer/orchestration/foreign-child/foreign-child-output.utils.js +165 -154
  212. package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +329 -341
  213. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +33 -32
  214. package/src/route-renderer/orchestration/integration-renderer.d.ts +1 -0
  215. package/src/route-renderer/orchestration/integration-renderer.js +731 -750
  216. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +168 -172
  217. package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +119 -131
  218. package/src/route-renderer/orchestration/ownership-graph/component-graph.js +82 -91
  219. package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.js +82 -76
  220. package/src/route-renderer/orchestration/page-browser-graph/global-injector-assets.service.js +29 -31
  221. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.js +10 -6
  222. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +31 -28
  223. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +3 -0
  224. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +430 -413
  225. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +191 -212
  226. package/src/route-renderer/orchestration/page-browser-graph/processed-asset-dedupe.js +20 -24
  227. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts +4 -0
  228. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.js +24 -24
  229. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +22 -24
  230. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.js +41 -34
  231. package/src/route-renderer/orchestration/route-pipeline/route-html-finalization.service.d.ts +5 -0
  232. package/src/route-renderer/orchestration/route-pipeline/route-html-finalization.service.js +42 -22
  233. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.js +50 -48
  234. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.utils.js +29 -32
  235. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +150 -156
  236. package/src/route-renderer/orchestration/string-markup-renderer.js +48 -51
  237. package/src/route-renderer/page-loading/component-dependency-collection.js +120 -124
  238. package/src/route-renderer/page-loading/declared-asset-collection.js +66 -94
  239. package/src/route-renderer/page-loading/dependency-resolver.js +101 -104
  240. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +68 -54
  241. package/src/route-renderer/page-loading/lazy-entry-collection.js +75 -98
  242. package/src/route-renderer/page-loading/lazy-trigger-planning.js +46 -36
  243. package/src/route-renderer/page-loading/module-declaration-aggregation.js +38 -31
  244. package/src/route-renderer/page-loading/module-declaration-scripts.js +10 -15
  245. package/src/route-renderer/page-loading/page-dependency-bundling.js +156 -113
  246. package/src/route-renderer/page-loading/page-module-loader.js +130 -124
  247. package/src/route-renderer/route-renderer.js +74 -76
  248. package/src/router/client/link-intent.js +55 -39
  249. package/src/router/client/link-intent.test.browser.js +49 -49
  250. package/src/router/client/link-navigation-policy.js +114 -93
  251. package/src/router/client/link-navigation-policy.test.browser.js +70 -80
  252. package/src/router/client/navigation-coordinator.js +235 -211
  253. package/src/router/server/route-registry.js +237 -242
  254. package/src/services/assets/asset-processing-service/asset-dependency-keys.js +64 -53
  255. package/src/services/assets/asset-processing-service/asset-processing.service.js +287 -303
  256. package/src/services/assets/asset-processing-service/asset.factory.js +78 -81
  257. package/src/services/assets/asset-processing-service/assets.types.js +1 -0
  258. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +57 -46
  259. package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.js +106 -69
  260. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +53 -53
  261. package/src/services/assets/asset-processing-service/finalize-processed-asset.js +4 -6
  262. package/src/services/assets/asset-processing-service/grouped-content-bundles.js +79 -80
  263. package/src/services/assets/asset-processing-service/index.js +8 -8
  264. package/src/services/assets/asset-processing-service/inline-content-script-body.js +22 -16
  265. package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +21 -17
  266. package/src/services/assets/asset-processing-service/page-package.js +70 -72
  267. package/src/services/assets/asset-processing-service/processor.interface.js +7 -5
  268. package/src/services/assets/asset-processing-service/processor.registry.js +11 -14
  269. package/src/services/assets/asset-processing-service/processors/base/base-processor.js +59 -62
  270. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +102 -117
  271. package/src/services/assets/asset-processing-service/processors/index.js +5 -5
  272. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +110 -115
  273. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +95 -97
  274. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +136 -135
  275. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +27 -29
  276. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +34 -40
  277. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +36 -35
  278. package/src/services/assets/asset-processing-service/resolve-integration-plugin.js +14 -16
  279. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +27 -39
  280. package/src/services/assets/browser-bundle.service.js +52 -47
  281. package/src/services/cache/cache.types.js +5 -0
  282. package/src/services/cache/index.js +6 -7
  283. package/src/services/cache/memory-cache-store.js +101 -89
  284. package/src/services/cache/page-cache-service.js +156 -145
  285. package/src/services/cache/page-request-cache-coordinator.service.js +102 -102
  286. package/src/services/html/html-rewriter-provider.service.js +61 -61
  287. package/src/services/html/html-transformer.service.js +249 -266
  288. package/src/services/invalidation/development-invalidation.service.js +252 -241
  289. package/src/services/module-loading/app-module-loader.service.js +1 -0
  290. package/src/services/module-loading/app-server-module-transpiler.service.js +107 -101
  291. package/src/services/module-loading/host-module-loader-registry.js +6 -11
  292. package/src/services/module-loading/module-loading-types.js +1 -0
  293. package/src/services/module-loading/page-module-import.service.js +235 -243
  294. package/src/services/module-loading/route-module-build-cache-registry.js +47 -33
  295. package/src/services/module-loading/route-module-build-cache.js +3 -38
  296. package/src/services/module-loading/route-module-build-cache.store.js +195 -200
  297. package/src/services/module-loading/route-module-build-manifest.js +61 -74
  298. package/src/services/module-loading/route-module-dependency-hasher.js +136 -110
  299. package/src/services/module-loading/server-module-transpiler.service.js +68 -62
  300. package/src/services/module-loading/source-module-support.js +15 -7
  301. package/src/services/runtime-state/dev-graph.service.js +165 -157
  302. package/src/services/runtime-state/entrypoint-dependency-graph.service.js +85 -76
  303. package/src/services/runtime-state/server-invalidation-state.service.js +38 -30
  304. package/src/services/validation/schema-validation-service.js +157 -95
  305. package/src/services/validation/standard-schema.types.js +1 -0
  306. package/src/services/validation/validate-standard-schema.js +13 -13
  307. package/src/static-site-generator/production-page-browser-graph-prebuild.js +35 -19
  308. package/src/static-site-generator/sitemap-routes.js +33 -28
  309. package/src/static-site-generator/sitemap.js +38 -29
  310. package/src/static-site-generator/static-build-invalidation.js +34 -56
  311. package/src/static-site-generator/static-export-context.js +1 -0
  312. package/src/static-site-generator/static-site-generator.js +517 -553
  313. package/src/types/internal-types.d.ts +19 -3
  314. package/src/types/internal-types.js +1 -0
  315. package/src/types/public-types.d.ts +16 -46
  316. package/src/types/public-types.js +1 -0
  317. package/src/utils/deep-merge.js +39 -26
  318. package/src/utils/ecopages-route-info.js +27 -23
  319. package/src/utils/hash.js +3 -6
  320. package/src/utils/html-escaping.js +21 -8
  321. package/src/utils/html.js +1 -4
  322. package/src/utils/invariant.js +11 -10
  323. package/src/utils/locals-utils.js +32 -22
  324. package/src/utils/parse-cli-args.js +73 -65
  325. package/src/utils/path-pattern.js +38 -23
  326. package/src/utils/path-utils.module.js +8 -11
  327. package/src/utils/resolve-entry-file.js +70 -38
  328. package/src/utils/resolve-work-dir.js +23 -27
  329. package/src/utils/runtime.js +24 -33
  330. package/src/utils/server-utils.module.js +60 -52
  331. package/src/utils/static-file-extensions.js +52 -46
  332. package/src/watchers/project-watcher-ignore.js +16 -15
  333. package/src/watchers/project-watcher.js +385 -366
  334. package/src/watchers/project-watcher.test-helpers.js +45 -50
  335. package/src/adapters/shared/hmr/hmr-entrypoint-registrar.d.ts +0 -56
  336. package/src/adapters/shared/hmr/hmr-entrypoint-registrar.js +0 -106
  337. package/src/build/cache/dev-browser-script-cache.d.ts +0 -34
  338. package/src/build/cache/dev-browser-script-cache.js +0 -90
@@ -1,387 +1,406 @@
1
- import path from "node:path";
2
- import chokidar, {} from "chokidar";
3
- import { fileSystem } from "@ecopages/file-system";
4
- import { appLogger } from "../global/app-logger.js";
5
- import {
6
- DevelopmentInvalidationService
7
- } from "../services/invalidation/development-invalidation.service.js";
8
- import { prepareHmrFileChange } from "../hmr/hmr-file-change-prep.js";
9
- import { getAppPageBrowserGraphSession } from "../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js";
10
- import { isRegisteredScriptEntrypoint } from "../hmr/hmr-entrypoint-output.js";
11
- import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js";
12
- import { createProjectWatcherIgnorePredicate } from "./project-watcher-ignore.js";
13
- class ProjectWatcher {
14
- /**
15
- * Duplicate identical watcher events within this window are ignored.
16
- *
17
- * Some editors or save pipelines emit two near-identical filesystem change
18
- * notifications for the same file. Ecopages should treat those as one logical
19
- * update so HMR and route refresh work are not repeated unnecessarily.
20
- */
21
- static duplicateChangeWindowMs = 150;
22
- appConfig;
23
- refreshRouterRoutesCallback;
24
- hmrManager;
25
- bridge;
26
- hostOwnsDevClient;
27
- invalidationService;
28
- changeDebounceMs;
29
- watcher = null;
30
- closed = false;
31
- pendingChangeEvents = /* @__PURE__ */ new Map();
32
- changeQueue = Promise.resolve();
33
- constructor({
34
- config,
35
- refreshRouterRoutesCallback,
36
- hmrManager,
37
- bridge,
38
- hostOwnsDevClient,
39
- changeDebounceMs
40
- }) {
41
- this.appConfig = config;
42
- this.refreshRouterRoutesCallback = refreshRouterRoutesCallback;
43
- this.hmrManager = hmrManager;
44
- this.bridge = bridge;
45
- this.hostOwnsDevClient = hostOwnsDevClient === true;
46
- const envDebounceMs = process.env.ECOPAGES_WATCH_CHANGE_DEBOUNCE_MS;
47
- this.changeDebounceMs = changeDebounceMs ?? (envDebounceMs !== void 0 && envDebounceMs !== "" ? Number(envDebounceMs) : void 0) ?? ProjectWatcher.duplicateChangeWindowMs;
48
- this.invalidationService = new DevelopmentInvalidationService(config);
49
- this.triggerRouterRefresh = this.triggerRouterRefresh.bind(this);
50
- this.handleError = this.handleError.bind(this);
51
- this.handleFileChange = this.handleFileChange.bind(this);
52
- this.processFileChange = this.processFileChange.bind(this);
53
- }
54
- /**
55
- * Uncaches modules in the source directory to ensure fresh imports.
56
- * This is necessary for hot module replacement to work correctly.
57
- * @private
58
- */
59
- uncacheModules() {
60
- if (typeof require === "undefined") return;
61
- const { srcDir, rootDir } = this.appConfig;
62
- const regex = new RegExp(`${rootDir}/${srcDir}/.*`);
63
- for (const key in require.cache) {
64
- if (regex.test(key)) {
65
- delete require.cache[key];
66
- }
1
+ import path from 'node:path';
2
+ import chokidar, {} from 'chokidar';
3
+ import { fileSystem } from '@ecopages/file-system';
4
+ import { appLogger } from '../global/app-logger.js';
5
+ import { DevelopmentInvalidationService, } from '../services/invalidation/development-invalidation.service.js';
6
+ import { prepareHmrFileChange } from '../hmr/hmr-file-change-prep.js';
7
+ import { getAppPageBrowserGraphSession } from '../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js';
8
+ import { isRegisteredDevTransformEntrypoint } from '../hmr/hmr-entrypoint-output.js';
9
+ import { resolveInternalExecutionDir } from '../utils/resolve-work-dir.js';
10
+ import { createProjectWatcherIgnorePredicate } from './project-watcher-ignore.js';
11
+ /**
12
+ * ProjectWatcher handles file system changes for hot module replacement (HMR).
13
+ * It uses chokidar to watch for file changes and triggers appropriate actions:
14
+ * - Uncaches modules when files change
15
+ * - Refreshes router routes for page files
16
+ * - Triggers HMR server reload
17
+ * - Handles processor-specific file changes
18
+ *
19
+ * The watcher uses chokidar's built-in debouncing through `awaitWriteFinish`
20
+ * to handle rapid file changes efficiently:
21
+ * - stabilityThreshold: 50ms - Time to wait for writes to stabilize
22
+ * - pollInterval: 50ms - Interval to poll for file changes
23
+ *
24
+ * @class ProjectWatcher
25
+ */
26
+ export class ProjectWatcher {
27
+ /**
28
+ * Duplicate identical watcher events within this window are ignored.
29
+ *
30
+ * Some editors or save pipelines emit two near-identical filesystem change
31
+ * notifications for the same file. Ecopages should treat those as one logical
32
+ * update so HMR and route refresh work are not repeated unnecessarily.
33
+ */
34
+ static duplicateChangeWindowMs = 150;
35
+ appConfig;
36
+ refreshRouterRoutesCallback;
37
+ hmrManager;
38
+ bridge;
39
+ hostOwnsDevClient;
40
+ invalidationService;
41
+ changeDebounceMs;
42
+ watcher = null;
43
+ closed = false;
44
+ pendingChangeEvents = new Map();
45
+ changeQueue = Promise.resolve();
46
+ constructor({ config, refreshRouterRoutesCallback, hmrManager, bridge, hostOwnsDevClient, changeDebounceMs, }) {
47
+ this.appConfig = config;
48
+ this.refreshRouterRoutesCallback = refreshRouterRoutesCallback;
49
+ this.hmrManager = hmrManager;
50
+ this.bridge = bridge;
51
+ this.hostOwnsDevClient = hostOwnsDevClient === true;
52
+ const envDebounceMs = process.env.ECOPAGES_WATCH_CHANGE_DEBOUNCE_MS;
53
+ this.changeDebounceMs =
54
+ changeDebounceMs ??
55
+ (envDebounceMs !== undefined && envDebounceMs !== '' ? Number(envDebounceMs) : undefined) ??
56
+ ProjectWatcher.duplicateChangeWindowMs;
57
+ this.invalidationService = new DevelopmentInvalidationService(config);
58
+ this.triggerRouterRefresh = this.triggerRouterRefresh.bind(this);
59
+ this.handleError = this.handleError.bind(this);
60
+ this.handleFileChange = this.handleFileChange.bind(this);
61
+ this.processFileChange = this.processFileChange.bind(this);
67
62
  }
68
- }
69
- isRouteSourceFile(filePath) {
70
- return this.invalidationService.isRouteSourceFile(filePath);
71
- }
72
- isIncludeSourceFile(filePath) {
73
- return this.invalidationService.isIncludeSourceFile(filePath);
74
- }
75
- requestBrowserReload() {
76
- if (this.hostOwnsDevClient) {
77
- return;
63
+ /**
64
+ * Uncaches modules in the source directory to ensure fresh imports.
65
+ * This is necessary for hot module replacement to work correctly.
66
+ * @private
67
+ */
68
+ uncacheModules() {
69
+ if (typeof require === 'undefined')
70
+ return;
71
+ const { srcDir, rootDir } = this.appConfig;
72
+ const regex = new RegExp(`${rootDir}/${srcDir}/.*`);
73
+ for (const key in require.cache) {
74
+ if (regex.test(key)) {
75
+ delete require.cache[key];
76
+ }
77
+ }
78
78
  }
79
- this.bridge.reload();
80
- }
81
- /**
82
- * Handles public directory file changes by copying only the changed file.
83
- * @param filePath - Absolute path of the changed file
84
- */
85
- async handlePublicDirFileChange(filePath) {
86
- try {
87
- const relativePath = path.relative(this.appConfig.absolutePaths.publicDir, filePath);
88
- const destPath = path.join(this.appConfig.absolutePaths.distDir, relativePath);
89
- if (fileSystem.exists(filePath)) {
90
- const destDir = path.dirname(destPath);
91
- fileSystem.ensureDir(destDir);
92
- await fileSystem.copyFileAsync(filePath, destPath);
93
- }
94
- this.requestBrowserReload();
95
- } catch (error) {
96
- appLogger.error(`Failed to copy public file: ${error instanceof Error ? error.message : String(error)}`);
97
- this.requestBrowserReload();
79
+ isRouteSourceFile(filePath) {
80
+ return this.invalidationService.isRouteSourceFile(filePath);
98
81
  }
99
- }
100
- /**
101
- * Serializes file change handling so that concurrent chokidar events are
102
- * processed one at a time, preventing overlapping builds and race conditions.
103
- */
104
- enqueueChange(task) {
105
- const queuedTask = this.changeQueue.then(task, task);
106
- this.changeQueue = queuedTask.catch(() => void 0);
107
- return queuedTask;
108
- }
109
- /**
110
- * Handles file changes by uncaching modules, refreshing routes, and delegating appropriately.
111
- * Follows 5-rule priority:
112
- * 0. Public directory match? -> copy file and reload
113
- * 1. additionalWatchPaths match? -> reload
114
- * 2. Include template source? -> current-page refresh via HMR after processor notifications are deferred
115
- * 3. Processor-owned asset? -> processor already handled it via notification, skip HMR
116
- * 4. Otherwise -> HMR strategies
117
- *
118
- * Processors that watch a file extension as a dependency (e.g. PostCSS watching
119
- * .tsx for Tailwind class scanning) are always notified first, but do not
120
- * prevent the file from flowing through the normal HMR strategy pipeline.
121
- *
122
- * Duplicate identical watcher events for the same file are coalesced within a
123
- * short window before any of the priority rules run.
124
- * @param rawPath - Path of the changed file
125
- * @param event - The type of file system event
126
- */
127
- handleFileChange(rawPath, event = "change") {
128
- if (this.closed) {
129
- return Promise.resolve();
82
+ isIncludeSourceFile(filePath) {
83
+ return this.invalidationService.isIncludeSourceFile(filePath);
130
84
  }
131
- const filePath = path.resolve(rawPath);
132
- if (this.changeDebounceMs === 0) {
133
- return this.enqueueChange(() => this.processFileChange(filePath, event));
85
+ requestBrowserReload() {
86
+ if (this.hostOwnsDevClient) {
87
+ return;
88
+ }
89
+ this.bridge.reload();
134
90
  }
135
- const existing = this.pendingChangeEvents.get(filePath);
136
- if (existing) {
137
- clearTimeout(existing.timer);
91
+ /**
92
+ * Handles public directory file changes by copying only the changed file.
93
+ * @param filePath - Absolute path of the changed file
94
+ */
95
+ async handlePublicDirFileChange(filePath) {
96
+ try {
97
+ const relativePath = path.relative(this.appConfig.absolutePaths.publicDir, filePath);
98
+ const destPath = path.join(this.appConfig.absolutePaths.distDir, relativePath);
99
+ if (fileSystem.exists(filePath)) {
100
+ const destDir = path.dirname(destPath);
101
+ fileSystem.ensureDir(destDir);
102
+ await fileSystem.copyFileAsync(filePath, destPath);
103
+ }
104
+ this.requestBrowserReload();
105
+ }
106
+ catch (error) {
107
+ appLogger.error(`Failed to copy public file: ${error instanceof Error ? error.message : String(error)}`);
108
+ this.requestBrowserReload();
109
+ }
138
110
  }
139
- const timer = setTimeout(() => {
140
- this.pendingChangeEvents.delete(filePath);
141
- void this.enqueueChange(() => this.processFileChange(filePath, event));
142
- }, this.changeDebounceMs);
143
- this.pendingChangeEvents.set(filePath, { event, timer });
144
- return Promise.resolve();
145
- }
146
- async processFileChange(filePath, event) {
147
- try {
148
- const plan = this.invalidationService.planFileChange(filePath);
149
- if (plan.category === "public-asset") {
150
- await this.handlePublicDirFileChange(filePath);
151
- return;
152
- }
153
- this.uncacheModules();
154
- const resolvedFilePath = path.resolve(filePath);
155
- const graphPreparation = this.hmrManager.isEnabled() ? prepareHmrFileChange(this.appConfig, resolvedFilePath) : void 0;
156
- if (plan.refreshRoutes && (event === "unlink" || event === "add")) {
157
- getAppPageBrowserGraphSession(this.appConfig).invalidateByRouteFile(resolvedFilePath);
158
- }
159
- const isRegisteredScriptEdit = isRegisteredScriptEntrypoint(
160
- this.hmrManager.getWatchedFiles(),
161
- resolvedFilePath
162
- );
163
- if (plan.invalidateServerModules && !isRegisteredScriptEdit) {
164
- this.invalidationService.invalidateServerModules([filePath]);
165
- }
166
- if (plan.refreshRoutes) {
167
- await this.refreshRouterRoutesCallback();
168
- }
169
- if (plan.reloadBrowser) {
170
- this.requestBrowserReload();
171
- return;
172
- }
173
- const deferProcessorNotifications = plan.category === "include-source" || plan.category === "explicit-server-view" || isRegisteredScriptEdit;
174
- if (deferProcessorNotifications && plan.delegateToHmr) {
175
- await this.prewarmBeforeHmr(resolvedFilePath, plan);
176
- await this.hmrManager.handleFileChange(filePath, {
177
- graphIdentities: graphPreparation?.affectedGraphIdentities
178
- });
179
- await this.notifyProcessors(filePath, event);
180
- return;
181
- }
182
- await this.notifyProcessors(filePath, event);
183
- if (plan.processorHandledAsset) {
184
- return;
185
- }
186
- if (plan.delegateToHmr) {
187
- await this.hmrManager.handleFileChange(filePath, {
188
- graphIdentities: graphPreparation?.affectedGraphIdentities
189
- });
190
- }
191
- } catch (error) {
192
- if (error instanceof Error) {
193
- this.bridge.error(error.message);
194
- this.handleError(error);
195
- }
111
+ /**
112
+ * Serializes file change handling so that concurrent chokidar events are
113
+ * processed one at a time, preventing overlapping builds and race conditions.
114
+ */
115
+ enqueueChange(task) {
116
+ const queuedTask = this.changeQueue.then(task, task);
117
+ this.changeQueue = queuedTask.catch(() => undefined);
118
+ return queuedTask;
196
119
  }
197
- }
198
- /**
199
- * Re-imports server modules before HMR broadcast so reload/refetch does not
200
- * race stale in-memory imports or custom-element registry state.
201
- */
202
- async prewarmBeforeHmr(filePath, plan) {
203
- if (plan.category !== "include-source" && plan.category !== "explicit-server-view") {
204
- return;
120
+ /**
121
+ * Handles file changes by uncaching modules, refreshing routes, and delegating appropriately.
122
+ * Follows 5-rule priority:
123
+ * 0. Public directory match? -> copy file and reload
124
+ * 1. additionalWatchPaths match? -> reload
125
+ * 2. Include template source? -> current-page refresh via HMR after processor notifications are deferred
126
+ * 3. Processor-owned asset? -> processor already handled it via notification, skip HMR
127
+ * 4. Otherwise -> HMR strategies
128
+ *
129
+ * Processors that watch a file extension as a dependency (e.g. PostCSS watching
130
+ * .tsx for Tailwind class scanning) are always notified first, but do not
131
+ * prevent the file from flowing through the normal HMR strategy pipeline.
132
+ *
133
+ * Duplicate identical watcher events for the same file are coalesced within a
134
+ * short window before any of the priority rules run.
135
+ * @param rawPath - Path of the changed file
136
+ * @param event - The type of file system event
137
+ */
138
+ handleFileChange(rawPath, event = 'change') {
139
+ if (this.closed) {
140
+ return Promise.resolve();
141
+ }
142
+ const filePath = path.resolve(rawPath);
143
+ if (this.changeDebounceMs === 0) {
144
+ return this.enqueueChange(() => this.processFileChange(filePath, event));
145
+ }
146
+ const existing = this.pendingChangeEvents.get(filePath);
147
+ if (existing) {
148
+ clearTimeout(existing.timer);
149
+ }
150
+ const timer = setTimeout(() => {
151
+ this.pendingChangeEvents.delete(filePath);
152
+ void this.enqueueChange(() => this.processFileChange(filePath, event));
153
+ }, this.changeDebounceMs);
154
+ this.pendingChangeEvents.set(filePath, { event, timer });
155
+ return Promise.resolve();
205
156
  }
206
- const modulePaths = plan.category === "include-source" ? [this.appConfig.absolutePaths.htmlTemplatePath] : [path.resolve(filePath)];
207
- await this.prewarmServerModuleImports(modulePaths, { scope: "server template" });
208
- }
209
- async prewarmServerModuleImports(modulePaths, options) {
210
- const appModuleLoader = this.appConfig.runtime?.appModuleLoader;
211
- if (!appModuleLoader) {
212
- return;
213
- }
214
- const outdir = path.join(resolveInternalExecutionDir(this.appConfig), ".server-modules");
215
- for (const modulePath of modulePaths) {
216
- if (!modulePath) {
217
- continue;
218
- }
219
- try {
220
- await appModuleLoader.importModule({
221
- filePath: modulePath,
222
- rootDir: this.appConfig.rootDir,
223
- outdir,
224
- externalPackages: true,
225
- bypassCache: options.bypassCache
226
- });
227
- } catch (error) {
228
- appLogger.error(
229
- `Failed to prewarm ${options.scope} ${modulePath}: ${error instanceof Error ? error.message : String(error)}`
230
- );
231
- }
157
+ async processFileChange(filePath, event) {
158
+ try {
159
+ const plan = this.invalidationService.planFileChange(filePath);
160
+ if (plan.category === 'public-asset') {
161
+ await this.handlePublicDirFileChange(filePath);
162
+ return;
163
+ }
164
+ this.uncacheModules();
165
+ const resolvedFilePath = path.resolve(filePath);
166
+ const graphPreparation = this.hmrManager.isEnabled()
167
+ ? prepareHmrFileChange(this.appConfig, resolvedFilePath)
168
+ : undefined;
169
+ if (plan.refreshRoutes && (event === 'unlink' || event === 'add')) {
170
+ getAppPageBrowserGraphSession(this.appConfig).invalidateByRouteFile(resolvedFilePath);
171
+ }
172
+ const isRegisteredDevTransformEdit = isRegisteredDevTransformEntrypoint(this.hmrManager.getRegisteredEntrypoints(), resolvedFilePath);
173
+ if (plan.invalidateServerModules && !isRegisteredDevTransformEdit) {
174
+ this.invalidationService.invalidateServerModules([filePath]);
175
+ }
176
+ if (plan.refreshRoutes) {
177
+ await this.refreshRouterRoutesCallback();
178
+ }
179
+ if (plan.reloadBrowser) {
180
+ this.requestBrowserReload();
181
+ return;
182
+ }
183
+ const deferProcessorNotifications = plan.category === 'include-source' ||
184
+ plan.category === 'explicit-server-view' ||
185
+ isRegisteredDevTransformEdit;
186
+ if (deferProcessorNotifications && plan.delegateToHmr) {
187
+ await this.prewarmBeforeHmr(resolvedFilePath, plan);
188
+ await this.hmrManager.handleFileChange(filePath, {
189
+ graphIdentities: graphPreparation?.affectedGraphIdentities,
190
+ });
191
+ await this.notifyProcessors(filePath, event);
192
+ return;
193
+ }
194
+ await this.notifyProcessors(filePath, event);
195
+ if (plan.processorHandledAsset) {
196
+ return;
197
+ }
198
+ if (plan.delegateToHmr) {
199
+ await this.hmrManager.handleFileChange(filePath, {
200
+ graphIdentities: graphPreparation?.affectedGraphIdentities,
201
+ });
202
+ }
203
+ }
204
+ catch (error) {
205
+ if (error instanceof Error) {
206
+ this.bridge.error(error.message);
207
+ this.handleError(error);
208
+ }
209
+ }
232
210
  }
233
- }
234
- /**
235
- * Notifies all processors whose watch config matches the given file extension.
236
- * This is called before checking processor ownership so that dependency-only
237
- * processors (e.g. PostCSS watching .tsx for class scanning) receive their
238
- * notifications regardless of whether they own the file.
239
- */
240
- async notifyProcessors(filePath, event) {
241
- const ctx = { path: filePath, bridge: this.bridge };
242
- for (const processor of this.appConfig.processors.values()) {
243
- const watchConfig = processor.getWatchConfig();
244
- if (!watchConfig) continue;
245
- const { extensions = [] } = watchConfig;
246
- if (extensions.length && !extensions.some((ext) => filePath.endsWith(ext))) {
247
- continue;
248
- }
249
- const handler = this.getProcessorHandler(watchConfig, event);
250
- if (handler) {
251
- await handler(ctx);
252
- }
211
+ /**
212
+ * Re-imports server modules before HMR broadcast so reload/refetch does not
213
+ * race stale in-memory imports or custom-element registry state.
214
+ */
215
+ async prewarmBeforeHmr(filePath, plan) {
216
+ if (plan.category !== 'include-source' && plan.category !== 'explicit-server-view') {
217
+ return;
218
+ }
219
+ const modulePaths = plan.category === 'include-source'
220
+ ? [this.appConfig.absolutePaths.htmlTemplatePath]
221
+ : [path.resolve(filePath)];
222
+ await this.prewarmServerModuleImports(modulePaths, { scope: 'server template' });
253
223
  }
254
- }
255
- getProcessorHandler(watchConfig, event) {
256
- switch (event) {
257
- case "change":
258
- return watchConfig.onChange;
259
- case "add":
260
- return watchConfig.onCreate;
261
- case "unlink":
262
- return watchConfig.onDelete;
224
+ async prewarmServerModuleImports(modulePaths, options) {
225
+ const appModuleLoader = this.appConfig.runtime?.appModuleLoader;
226
+ if (!appModuleLoader) {
227
+ return;
228
+ }
229
+ const outdir = path.join(resolveInternalExecutionDir(this.appConfig), '.server-modules');
230
+ for (const modulePath of modulePaths) {
231
+ if (!modulePath) {
232
+ continue;
233
+ }
234
+ try {
235
+ await appModuleLoader.importModule({
236
+ filePath: modulePath,
237
+ rootDir: this.appConfig.rootDir,
238
+ outdir,
239
+ externalPackages: true,
240
+ bypassCache: options.bypassCache,
241
+ });
242
+ }
243
+ catch (error) {
244
+ appLogger.error(`Failed to prewarm ${options.scope} ${modulePath}: ${error instanceof Error ? error.message : String(error)}`);
245
+ }
246
+ }
263
247
  }
264
- }
265
- /**
266
- * Checks if a file is in the public directory.
267
- */
268
- isPublicDirFile(filePath) {
269
- return this.invalidationService.isPublicDirFile(filePath);
270
- }
271
- /**
272
- * Checks if file path matches any additionalWatchPaths patterns.
273
- */
274
- matchesAdditionalWatchPaths(filePath) {
275
- return this.invalidationService.matchesAdditionalWatchPaths(filePath);
276
- }
277
- /**
278
- * Checks if a file is owned by a processor as an asset input.
279
- * Ownership requires declared asset capabilities; watch config only drives
280
- * {@link notifyProcessors} notifications.
281
- */
282
- isHandledByProcessor(filePath) {
283
- return this.invalidationService.isProcessorOwnedAsset(filePath);
284
- }
285
- /**
286
- * Triggers router refresh for page directory changes.
287
- * This ensures the router is updated when pages are added or removed.
288
- *
289
- * @param {string} path - Path of the changed directory
290
- */
291
- async triggerRouterRefresh(changedPath) {
292
- const resolvedPath = path.resolve(changedPath);
293
- const isPageDir = resolvedPath.startsWith(this.appConfig.absolutePaths.pagesDir) && path.extname(resolvedPath) === "";
294
- if (isPageDir || this.isRouteSourceFile(resolvedPath)) {
295
- await this.refreshRouterRoutesCallback();
248
+ /**
249
+ * Notifies all processors whose watch config matches the given file extension.
250
+ * This is called before checking processor ownership so that dependency-only
251
+ * processors (e.g. PostCSS watching .tsx for class scanning) receive their
252
+ * notifications regardless of whether they own the file.
253
+ */
254
+ async notifyProcessors(filePath, event) {
255
+ const ctx = { path: filePath, bridge: this.bridge };
256
+ for (const processor of this.appConfig.processors.values()) {
257
+ const watchConfig = processor.getWatchConfig();
258
+ if (!watchConfig)
259
+ continue;
260
+ const { extensions = [] } = watchConfig;
261
+ if (extensions.length && !extensions.some((ext) => filePath.endsWith(ext))) {
262
+ continue;
263
+ }
264
+ const handler = this.getProcessorHandler(watchConfig, event);
265
+ if (handler) {
266
+ await handler(ctx);
267
+ }
268
+ }
296
269
  }
297
- }
298
- /**
299
- * Handles and logs errors that occur during file watching.
300
- *
301
- * @param {unknown} error - The error to handle
302
- */
303
- handleError(error) {
304
- if (error instanceof Error) {
305
- this.hmrManager.broadcast({ type: "error", message: error.message });
270
+ getProcessorHandler(watchConfig, event) {
271
+ switch (event) {
272
+ case 'change':
273
+ return watchConfig.onChange;
274
+ case 'add':
275
+ return watchConfig.onCreate;
276
+ case 'unlink':
277
+ return watchConfig.onDelete;
278
+ }
306
279
  }
307
- appLogger.error(`Watcher error: ${error}`);
308
- }
309
- /**
310
- * Creates and configures the file system watcher.
311
- * This sets up:
312
- * 1. Page file watching
313
- * 2. Directory watching
314
- * 3. Error handling
315
- *
316
- * Processor notifications are dispatched inside handleFileChange, ensuring
317
- * a single unified event pipeline with no parallel chokidar bindings.
318
- *
319
- * Uses chokidar's built-in debouncing through `awaitWriteFinish` to handle
320
- * rapid file changes efficiently.
321
- */
322
- async createWatcherSubscription() {
323
- if (this.watcher) {
324
- return this.watcher;
280
+ /**
281
+ * Checks if a file is in the public directory.
282
+ */
283
+ isPublicDirFile(filePath) {
284
+ return this.invalidationService.isPublicDirFile(filePath);
325
285
  }
326
- const processorPaths = /* @__PURE__ */ new Set();
327
- for (const processor of this.appConfig.processors.values()) {
328
- const watchConfig = processor.getWatchConfig();
329
- if (!watchConfig) continue;
330
- for (const watchPath of watchConfig.paths) {
331
- processorPaths.add(watchPath);
332
- }
286
+ /**
287
+ * Checks if file path matches any additionalWatchPaths patterns.
288
+ */
289
+ matchesAdditionalWatchPaths(filePath) {
290
+ return this.invalidationService.matchesAdditionalWatchPaths(filePath);
333
291
  }
334
- if (fileSystem.exists(this.appConfig.absolutePaths.includesDir)) {
335
- processorPaths.add(this.appConfig.absolutePaths.includesDir);
292
+ /**
293
+ * Checks if a file is owned by a processor as an asset input.
294
+ * Ownership requires declared asset capabilities; watch config only drives
295
+ * {@link notifyProcessors} notifications.
296
+ */
297
+ isHandledByProcessor(filePath) {
298
+ return this.invalidationService.isProcessorOwnedAsset(filePath);
336
299
  }
337
- if (fileSystem.exists(this.appConfig.absolutePaths.srcDir)) {
338
- processorPaths.add(this.appConfig.absolutePaths.srcDir);
300
+ /**
301
+ * Triggers router refresh for page directory changes.
302
+ * This ensures the router is updated when pages are added or removed.
303
+ *
304
+ * @param {string} path - Path of the changed directory
305
+ */
306
+ async triggerRouterRefresh(changedPath) {
307
+ const resolvedPath = path.resolve(changedPath);
308
+ const isPageDir = resolvedPath.startsWith(this.appConfig.absolutePaths.pagesDir) && path.extname(resolvedPath) === '';
309
+ if (isPageDir || this.isRouteSourceFile(resolvedPath)) {
310
+ await this.refreshRouterRoutesCallback();
311
+ }
339
312
  }
340
- if (fileSystem.exists(this.appConfig.absolutePaths.publicDir)) {
341
- processorPaths.add(this.appConfig.absolutePaths.publicDir);
313
+ /**
314
+ * Handles and logs errors that occur during file watching.
315
+ *
316
+ * @param {unknown} error - The error to handle
317
+ */
318
+ handleError(error) {
319
+ if (error instanceof Error) {
320
+ this.hmrManager.broadcast({ type: 'error', message: error.message });
321
+ }
322
+ appLogger.error(`Watcher error: ${error}`);
342
323
  }
343
- for (const watchPath of this.appConfig.additionalWatchPaths) {
344
- processorPaths.add(watchPath);
345
- }
346
- const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
347
- this.watcher = chokidar.watch(Array.from(processorPaths), {
348
- ignoreInitial: true,
349
- ignorePermissionErrors: true,
350
- ignored,
351
- awaitWriteFinish: {
352
- stabilityThreshold: 50,
353
- pollInterval: 50
354
- }
355
- });
356
- this.watcher.on("change", (p) => this.handleFileChange(p, "change")).on("add", (p) => this.handleFileChange(p, "add")).on("addDir", (p) => this.enqueueChange(() => this.triggerRouterRefresh(p))).on("unlink", (p) => this.handleFileChange(p, "unlink")).on("unlinkDir", (p) => this.enqueueChange(() => this.triggerRouterRefresh(p))).on("error", (error) => this.handleError(error));
357
- for (const processor of this.appConfig.processors.values()) {
358
- const watchConfig = processor.getWatchConfig();
359
- if (watchConfig?.onError) {
360
- this.watcher.on("error", watchConfig.onError);
361
- }
362
- }
363
- return this.watcher;
364
- }
365
- /**
366
- * Closes the active filesystem watcher subscription.
367
- *
368
- * @remarks
369
- * Safe to call multiple times. Used when tearing down dev servers in tests
370
- * and other short-lived Ecopages runtimes.
371
- */
372
- async close() {
373
- this.closed = true;
374
- for (const pending of this.pendingChangeEvents.values()) {
375
- clearTimeout(pending.timer);
324
+ /**
325
+ * Creates and configures the file system watcher.
326
+ * This sets up:
327
+ * 1. Page file watching
328
+ * 2. Directory watching
329
+ * 3. Error handling
330
+ *
331
+ * Processor notifications are dispatched inside handleFileChange, ensuring
332
+ * a single unified event pipeline with no parallel chokidar bindings.
333
+ *
334
+ * Uses chokidar's built-in debouncing through `awaitWriteFinish` to handle
335
+ * rapid file changes efficiently.
336
+ */
337
+ async createWatcherSubscription() {
338
+ if (this.watcher) {
339
+ return this.watcher;
340
+ }
341
+ const processorPaths = new Set();
342
+ for (const processor of this.appConfig.processors.values()) {
343
+ const watchConfig = processor.getWatchConfig();
344
+ if (!watchConfig)
345
+ continue;
346
+ for (const watchPath of watchConfig.paths) {
347
+ processorPaths.add(watchPath);
348
+ }
349
+ }
350
+ if (fileSystem.exists(this.appConfig.absolutePaths.includesDir)) {
351
+ processorPaths.add(this.appConfig.absolutePaths.includesDir);
352
+ }
353
+ if (fileSystem.exists(this.appConfig.absolutePaths.srcDir)) {
354
+ processorPaths.add(this.appConfig.absolutePaths.srcDir);
355
+ }
356
+ if (fileSystem.exists(this.appConfig.absolutePaths.publicDir)) {
357
+ processorPaths.add(this.appConfig.absolutePaths.publicDir);
358
+ }
359
+ for (const watchPath of this.appConfig.additionalWatchPaths) {
360
+ processorPaths.add(watchPath);
361
+ }
362
+ const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
363
+ this.watcher = chokidar.watch(Array.from(processorPaths), {
364
+ ignoreInitial: true,
365
+ ignorePermissionErrors: true,
366
+ ignored,
367
+ awaitWriteFinish: {
368
+ stabilityThreshold: 50,
369
+ pollInterval: 50,
370
+ },
371
+ });
372
+ this.watcher
373
+ .on('change', (p) => this.handleFileChange(p, 'change'))
374
+ .on('add', (p) => this.handleFileChange(p, 'add'))
375
+ .on('addDir', (p) => this.enqueueChange(() => this.triggerRouterRefresh(p)))
376
+ .on('unlink', (p) => this.handleFileChange(p, 'unlink'))
377
+ .on('unlinkDir', (p) => this.enqueueChange(() => this.triggerRouterRefresh(p)))
378
+ .on('error', (error) => this.handleError(error));
379
+ for (const processor of this.appConfig.processors.values()) {
380
+ const watchConfig = processor.getWatchConfig();
381
+ if (watchConfig?.onError) {
382
+ this.watcher.on('error', watchConfig.onError);
383
+ }
384
+ }
385
+ return this.watcher;
376
386
  }
377
- this.pendingChangeEvents.clear();
378
- if (this.watcher) {
379
- await this.watcher.close();
380
- this.watcher = null;
387
+ /**
388
+ * Closes the active filesystem watcher subscription.
389
+ *
390
+ * @remarks
391
+ * Safe to call multiple times. Used when tearing down dev servers in tests
392
+ * and other short-lived Ecopages runtimes.
393
+ */
394
+ async close() {
395
+ this.closed = true;
396
+ for (const pending of this.pendingChangeEvents.values()) {
397
+ clearTimeout(pending.timer);
398
+ }
399
+ this.pendingChangeEvents.clear();
400
+ if (this.watcher) {
401
+ await this.watcher.close();
402
+ this.watcher = null;
403
+ }
404
+ await this.changeQueue.catch(() => undefined);
381
405
  }
382
- await this.changeQueue.catch(() => void 0);
383
- }
384
406
  }
385
- export {
386
- ProjectWatcher
387
- };