@warlock.js/core 5.30.0 → 5.31.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 (797) hide show
  1. package/CHANGELOG.md +142 -36
  2. package/README.md +28 -23
  3. package/bin/source-load-hook.mjs +63 -0
  4. package/bin/warlock.js +102 -42
  5. package/esm/cli/commands/add.command.mjs +4 -2
  6. package/esm/cli/commands/add.command.mjs.map +1 -1
  7. package/esm/cli/commands/agents-md/agents-md-command.action.mjs +69 -0
  8. package/esm/cli/commands/agents-md/agents-md-command.action.mjs.map +1 -0
  9. package/esm/cli/commands/agents-md.command.mjs +38 -0
  10. package/esm/cli/commands/agents-md.command.mjs.map +1 -0
  11. package/esm/cli/commands/board/board-command.action.mjs +279 -0
  12. package/esm/cli/commands/board/board-command.action.mjs.map +1 -0
  13. package/esm/cli/commands/board.command.mjs +78 -0
  14. package/esm/cli/commands/board.command.mjs.map +1 -0
  15. package/esm/cli/commands/build.command.mjs +6 -1
  16. package/esm/cli/commands/build.command.mjs.map +1 -1
  17. package/esm/cli/commands/check/check-command.action.mjs +57 -0
  18. package/esm/cli/commands/check/check-command.action.mjs.map +1 -0
  19. package/esm/cli/commands/check/child-typecheck.mjs +66 -0
  20. package/esm/cli/commands/check/child-typecheck.mjs.map +1 -0
  21. package/esm/cli/commands/check/format-problems.mjs +43 -0
  22. package/esm/cli/commands/check/format-problems.mjs.map +1 -0
  23. package/esm/cli/commands/check/typecheck-command.action.mjs +61 -0
  24. package/esm/cli/commands/check/typecheck-command.action.mjs.map +1 -0
  25. package/esm/cli/commands/check.command.mjs +50 -0
  26. package/esm/cli/commands/check.command.mjs.map +1 -0
  27. package/esm/cli/commands/dev-server.command.mjs +9 -1
  28. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  29. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs +8 -3
  30. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  31. package/esm/cli/commands/doctor/check.types.d.mts +76 -0
  32. package/esm/cli/commands/doctor/check.types.d.mts.map +1 -0
  33. package/esm/cli/commands/doctor/checks/config.check.mjs +1 -0
  34. package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -1
  35. package/esm/cli/commands/doctor/checks/connectors.check.mjs +2 -0
  36. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
  37. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +1 -0
  38. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
  39. package/esm/cli/commands/doctor/checks/health.check.mjs +3 -0
  40. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  41. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +1 -0
  42. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -1
  43. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +1 -0
  44. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -1
  45. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +1 -0
  46. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  47. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +4 -0
  48. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -1
  49. package/esm/cli/commands/doctor/checks/routes.check.mjs +3 -0
  50. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
  51. package/esm/cli/commands/doctor/doctor-command.action.mjs +14 -6
  52. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  53. package/esm/cli/commands/doctor/format-report.mjs +30 -2
  54. package/esm/cli/commands/doctor/format-report.mjs.map +1 -1
  55. package/esm/cli/commands/doctor.command.mjs +4 -0
  56. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  57. package/esm/cli/commands/explain/explain-command.action.mjs +64 -0
  58. package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -0
  59. package/esm/cli/commands/explain.command.mjs +23 -0
  60. package/esm/cli/commands/explain.command.mjs.map +1 -0
  61. package/esm/cli/commands/generate/generate.command.mjs +124 -124
  62. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  63. package/esm/cli/commands/generate/generators/endpoint.generator.mjs +311 -0
  64. package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -0
  65. package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
  66. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  67. package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
  68. package/esm/cli/commands/generate/templates/endpoint-stubs.mjs +341 -0
  69. package/esm/cli/commands/generate/templates/endpoint-stubs.mjs.map +1 -0
  70. package/esm/cli/commands/generate/templates/stubs.mjs +19 -6
  71. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  72. package/esm/cli/commands/generate/utils/endpoint-schema.mjs +137 -0
  73. package/esm/cli/commands/generate/utils/endpoint-schema.mjs.map +1 -0
  74. package/esm/cli/commands/generate/utils/name-parser.mjs +1 -1
  75. package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
  76. package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
  77. package/esm/cli/commands/generate/utils/writer.mjs +38 -13
  78. package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
  79. package/esm/cli/commands/inspect/inspect-command.action.mjs +72 -0
  80. package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -0
  81. package/esm/cli/commands/inspect.command.mjs +39 -0
  82. package/esm/cli/commands/inspect.command.mjs.map +1 -0
  83. package/esm/cli/commands/mcp/mcp-command.action.mjs +103 -0
  84. package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -0
  85. package/esm/cli/commands/mcp.command.mjs +76 -0
  86. package/esm/cli/commands/mcp.command.mjs.map +1 -0
  87. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -1
  88. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -1
  89. package/esm/cli/commands/postman-generator.action.mjs.map +1 -1
  90. package/esm/cli/commands/postman-generator.command.mjs.map +1 -1
  91. package/esm/cli/commands/routes/routes-diff.action.mjs +1 -1
  92. package/esm/cli/commands/test.command.mjs +131 -0
  93. package/esm/cli/commands/test.command.mjs.map +1 -0
  94. package/esm/cli/commands/trace/trace-command.action.mjs +145 -0
  95. package/esm/cli/commands/trace/trace-command.action.mjs.map +1 -0
  96. package/esm/cli/commands/trace.command.mjs +60 -0
  97. package/esm/cli/commands/trace.command.mjs.map +1 -0
  98. package/esm/cli/commands/typecheck.command.mjs +40 -0
  99. package/esm/cli/commands/typecheck.command.mjs.map +1 -0
  100. package/esm/cli/commands/upgrade.command.mjs +46 -0
  101. package/esm/cli/commands/upgrade.command.mjs.map +1 -0
  102. package/esm/cli/flush-and-exit.mjs +34 -0
  103. package/esm/cli/flush-and-exit.mjs.map +1 -0
  104. package/esm/cli/framework-cli-commands.mjs +23 -1
  105. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  106. package/esm/cli/parse-cli-args.mjs +1 -0
  107. package/esm/cli/parse-cli-args.mjs.map +1 -1
  108. package/esm/config/config-loader.d.mts.map +1 -1
  109. package/esm/config/config-loader.mjs +34 -13
  110. package/esm/config/config-loader.mjs.map +1 -1
  111. package/esm/config/config-schema.d.mts +67 -0
  112. package/esm/config/config-schema.d.mts.map +1 -0
  113. package/esm/config/config-schema.mjs +77 -0
  114. package/esm/config/config-schema.mjs.map +1 -0
  115. package/esm/config/index.d.mts +6 -0
  116. package/esm/config/index.mjs +1 -0
  117. package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -1
  118. package/esm/connectors/assert-no-reserved-connector-names.mjs +2 -1
  119. package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
  120. package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -1
  121. package/esm/connectors/assert-unique-connector-names.mjs +26 -2
  122. package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
  123. package/esm/connectors/http-connector.d.mts.map +1 -1
  124. package/esm/connectors/http-connector.mjs +3 -2
  125. package/esm/connectors/http-connector.mjs.map +1 -1
  126. package/esm/connectors/single-server-warnings.mjs.map +1 -1
  127. package/esm/connectors/socket-connector.d.mts.map +1 -1
  128. package/esm/connectors/socket-connector.mjs.map +1 -1
  129. package/esm/container/index.d.mts.map +1 -1
  130. package/esm/container/index.mjs.map +1 -1
  131. package/esm/database/migrate-action.mjs.map +1 -1
  132. package/esm/database/models/database-log/database-log.d.mts +0 -28
  133. package/esm/database/models/database-log/database-log.d.mts.map +1 -1
  134. package/esm/database/seeds/seeder.errors.d.mts +4 -0
  135. package/esm/database/seeds/seeder.errors.d.mts.map +1 -1
  136. package/esm/database/seeds/seeder.errors.mjs +2 -0
  137. package/esm/database/seeds/seeder.errors.mjs.map +1 -1
  138. package/esm/dev-server/boot-precondition-error.mjs +1 -0
  139. package/esm/dev-server/boot-precondition-error.mjs.map +1 -1
  140. package/esm/dev-server/create-worker.mjs +15 -9
  141. package/esm/dev-server/create-worker.mjs.map +1 -1
  142. package/esm/dev-server/dev-agent-tooling.mjs +135 -0
  143. package/esm/dev-server/dev-agent-tooling.mjs.map +1 -0
  144. package/esm/dev-server/dev-logger.mjs.map +1 -1
  145. package/esm/dev-server/development-server.mjs +8 -0
  146. package/esm/dev-server/development-server.mjs.map +1 -1
  147. package/esm/dev-server/files-orchestrator.mjs +4 -5
  148. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  149. package/esm/dev-server/files-watcher.mjs +1 -1
  150. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +2 -3
  151. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
  152. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +33 -0
  153. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs.map +1 -0
  154. package/esm/dev-server/health-checker/default-health-checkers.mjs +31 -0
  155. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -0
  156. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +9 -3
  157. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  158. package/esm/dev-server/health-checker/files-healthcare.manager.mjs +15 -4
  159. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  160. package/esm/dev-server/health-checker/oxlint-runner.mjs +302 -0
  161. package/esm/dev-server/health-checker/oxlint-runner.mjs.map +1 -0
  162. package/esm/dev-server/health-checker/workers/oxlint-health.worker.d.mts +1 -0
  163. package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs +42 -0
  164. package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs.map +1 -0
  165. package/esm/dev-server/loader/loader-port-acks.mjs +1 -1
  166. package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
  167. package/esm/dev-server/loader/register-loader.mjs +1 -1
  168. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  169. package/esm/dev-server/loader/resolve-hook.mjs +1 -1
  170. package/esm/dev-server/loader/transpile-cache.mjs +1 -1
  171. package/esm/dev-server/model-resource-typings.mjs.map +1 -1
  172. package/esm/dev-server/module-loader.mjs +1 -0
  173. package/esm/dev-server/module-loader.mjs.map +1 -1
  174. package/esm/dev-server/parse-imports.mjs +1 -1
  175. package/esm/dev-server/parse-imports.mjs.map +1 -1
  176. package/esm/dev-server/route-types-publisher.mjs +1 -1
  177. package/esm/dev-server/start-development-server.mjs.map +1 -1
  178. package/esm/dev-server/translation-keys-sources.mjs +1 -1
  179. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  180. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  181. package/esm/dev-server/type-generator.mjs +3 -3
  182. package/esm/dev-server/type-generator.mjs.map +1 -1
  183. package/esm/dev-server/types.d.mts.map +1 -1
  184. package/esm/errors/container-key-missing-error.mjs +1 -0
  185. package/esm/errors/container-key-missing-error.mjs.map +1 -1
  186. package/esm/errors/error-codes.d.mts +653 -0
  187. package/esm/errors/error-codes.d.mts.map +1 -0
  188. package/esm/errors/error-codes.mjs +634 -0
  189. package/esm/errors/error-codes.mjs.map +1 -0
  190. package/esm/errors/esbuild-binary-missing-error.mjs +1 -0
  191. package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -1
  192. package/esm/errors/index.d.mts +3 -1
  193. package/esm/errors/index.mjs +3 -1
  194. package/esm/errors/unknown-locale-error.mjs +1 -0
  195. package/esm/errors/unknown-locale-error.mjs.map +1 -1
  196. package/esm/errors/warlock-error.d.mts +23 -0
  197. package/esm/errors/warlock-error.d.mts.map +1 -0
  198. package/esm/errors/warlock-error.mjs +22 -0
  199. package/esm/errors/warlock-error.mjs.map +1 -0
  200. package/esm/generations/add-command.action.mjs +19 -3
  201. package/esm/generations/add-command.action.mjs.map +1 -1
  202. package/esm/generations/features/access.feature.mjs +1 -1
  203. package/esm/generations/features/access.feature.mjs.map +1 -1
  204. package/esm/generations/features/bull-board.feature.mjs +1 -1
  205. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  206. package/esm/generations/features/mysql.feature.mjs.map +1 -1
  207. package/esm/generations/features/notifications.feature.mjs +5 -2
  208. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  209. package/esm/generations/features/queue.feature.mjs +1 -1
  210. package/esm/generations/features/queue.feature.mjs.map +1 -1
  211. package/esm/generations/features/react-email.feature.mjs +1 -1
  212. package/esm/generations/features/react-email.feature.mjs.map +1 -1
  213. package/esm/generations/features/shadcn.feature.mjs +1 -1
  214. package/esm/generations/features/shadcn.feature.mjs.map +1 -1
  215. package/esm/generations/features/shared/link-ai-package-import.mjs +1 -1
  216. package/esm/generations/features/shared/link-ai-package-import.mjs.map +1 -1
  217. package/esm/generations/features/shared/merge-web-sitemap-config.mjs.map +1 -1
  218. package/esm/generations/features/shared/patch-tsconfig-include.mjs +1 -1
  219. package/esm/generations/features/shared/patch-tsconfig-include.mjs.map +1 -1
  220. package/esm/generations/features/shared/relocate-conflicting-home-route.mjs +1 -1
  221. package/esm/generations/features/shared/relocate-conflicting-home-route.mjs.map +1 -1
  222. package/esm/generations/features/sitemap.feature.mjs +1 -1
  223. package/esm/generations/features/sitemap.feature.mjs.map +1 -1
  224. package/esm/generations/features/tailwind.feature.mjs +1 -1
  225. package/esm/generations/features/tailwind.feature.mjs.map +1 -1
  226. package/esm/generations/features/test.feature.mjs +11 -6
  227. package/esm/generations/features/test.feature.mjs.map +1 -1
  228. package/esm/generations/features/web.feature.mjs +1 -1
  229. package/esm/generations/features/web.feature.mjs.map +1 -1
  230. package/esm/generations/generation-fs.mjs +73 -0
  231. package/esm/generations/generation-fs.mjs.map +1 -0
  232. package/esm/generations/generation-run.mjs +185 -0
  233. package/esm/generations/generation-run.mjs.map +1 -0
  234. package/esm/generations/stubs.mjs +155 -13
  235. package/esm/generations/stubs.mjs.map +1 -1
  236. package/esm/http/bounded-stream-queue.d.mts +10 -0
  237. package/esm/http/bounded-stream-queue.d.mts.map +1 -0
  238. package/esm/http/bounded-stream-queue.mjs +97 -0
  239. package/esm/http/bounded-stream-queue.mjs.map +1 -0
  240. package/esm/http/build-rate-limit-options.d.mts +13 -0
  241. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  242. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  243. package/esm/http/conditional-request.d.mts +86 -0
  244. package/esm/http/conditional-request.d.mts.map +1 -0
  245. package/esm/http/conditional-request.mjs +121 -0
  246. package/esm/http/conditional-request.mjs.map +1 -0
  247. package/esm/http/csp.d.mts +2 -0
  248. package/esm/http/csp.d.mts.map +1 -1
  249. package/esm/http/csp.mjs +1 -0
  250. package/esm/http/csp.mjs.map +1 -1
  251. package/esm/http/csrf-origin-policy.mjs.map +1 -1
  252. package/esm/http/database/RequestLog.d.mts +24 -84
  253. package/esm/http/database/RequestLog.d.mts.map +1 -1
  254. package/esm/http/errors/errors.d.mts +2 -0
  255. package/esm/http/errors/errors.d.mts.map +1 -1
  256. package/esm/http/errors/errors.mjs +1 -0
  257. package/esm/http/errors/errors.mjs.map +1 -1
  258. package/esm/http/escape-header-value.d.mts +18 -0
  259. package/esm/http/escape-header-value.d.mts.map +1 -0
  260. package/esm/http/escape-header-value.mjs +27 -0
  261. package/esm/http/escape-header-value.mjs.map +1 -0
  262. package/esm/http/flush-pending-cookies.mjs.map +1 -1
  263. package/esm/http/index.d.mts +5 -2
  264. package/esm/http/index.mjs +4 -1
  265. package/esm/http/lazy-static.mjs +194 -0
  266. package/esm/http/lazy-static.mjs.map +1 -0
  267. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  268. package/esm/http/plugins.d.mts.map +1 -1
  269. package/esm/http/plugins.mjs +7 -4
  270. package/esm/http/plugins.mjs.map +1 -1
  271. package/esm/http/rate-limit-cache-store.mjs +2 -1
  272. package/esm/http/rate-limit-cache-store.mjs.map +1 -1
  273. package/esm/http/request-log-snapshot.d.mts +12 -5
  274. package/esm/http/request-log-snapshot.d.mts.map +1 -1
  275. package/esm/http/request-log-snapshot.mjs +83 -4
  276. package/esm/http/request-log-snapshot.mjs.map +1 -1
  277. package/esm/http/request.d.mts +2 -2
  278. package/esm/http/request.d.mts.map +1 -1
  279. package/esm/http/request.mjs +5 -4
  280. package/esm/http/request.mjs.map +1 -1
  281. package/esm/http/response.d.mts +11 -1
  282. package/esm/http/response.d.mts.map +1 -1
  283. package/esm/http/response.mjs +164 -76
  284. package/esm/http/response.mjs.map +1 -1
  285. package/esm/http/safe-redirect.d.mts +51 -0
  286. package/esm/http/safe-redirect.d.mts.map +1 -0
  287. package/esm/http/safe-redirect.mjs +80 -0
  288. package/esm/http/safe-redirect.mjs.map +1 -0
  289. package/esm/http/serialized.d.mts.map +1 -1
  290. package/esm/http/server.d.mts +3 -1
  291. package/esm/http/server.d.mts.map +1 -1
  292. package/esm/http/server.mjs +14 -4
  293. package/esm/http/server.mjs.map +1 -1
  294. package/esm/http/stream-transport.mjs +4 -2
  295. package/esm/http/stream-transport.mjs.map +1 -1
  296. package/esm/http/types.d.mts +44 -11
  297. package/esm/http/types.d.mts.map +1 -1
  298. package/esm/http/uploads/generate-image-variant.mjs +1 -1
  299. package/esm/http/uploads/generate-image-variants.d.mts +10 -7
  300. package/esm/http/uploads/generate-image-variants.d.mts.map +1 -1
  301. package/esm/http/uploads/generate-image-variants.mjs +61 -24
  302. package/esm/http/uploads/generate-image-variants.mjs.map +1 -1
  303. package/esm/http/uploads/image-variant-cache-entries.mjs +78 -0
  304. package/esm/http/uploads/image-variant-cache-entries.mjs.map +1 -0
  305. package/esm/http/uploads/image-variant-cache.mjs +72 -30
  306. package/esm/http/uploads/image-variant-cache.mjs.map +1 -1
  307. package/esm/http/uploads/image-variant-types.d.mts +2 -0
  308. package/esm/http/uploads/image-variant-types.d.mts.map +1 -1
  309. package/esm/http/uploads/image-variants-config-error.d.mts +3 -1
  310. package/esm/http/uploads/image-variants-config-error.d.mts.map +1 -1
  311. package/esm/http/uploads/image-variants-config-error.mjs +3 -2
  312. package/esm/http/uploads/image-variants-config-error.mjs.map +1 -1
  313. package/esm/http/uploads/load-variant-source.mjs +1 -1
  314. package/esm/http/uploads/load-variant-source.mjs.map +1 -1
  315. package/esm/http/uploads/purge-image-variants.d.mts +7 -2
  316. package/esm/http/uploads/purge-image-variants.d.mts.map +1 -1
  317. package/esm/http/uploads/purge-image-variants.mjs +16 -16
  318. package/esm/http/uploads/purge-image-variants.mjs.map +1 -1
  319. package/esm/http/uploads/remote-variant-source.mjs +115 -0
  320. package/esm/http/uploads/remote-variant-source.mjs.map +1 -0
  321. package/esm/http/uploads/resolve-image-variant.d.mts +3 -4
  322. package/esm/http/uploads/resolve-image-variant.d.mts.map +1 -1
  323. package/esm/http/uploads/resolve-image-variant.mjs +10 -18
  324. package/esm/http/uploads/resolve-image-variant.mjs.map +1 -1
  325. package/esm/http/uploads/resolve-image-variants-config.mjs +5 -3
  326. package/esm/http/uploads/resolve-image-variants-config.mjs.map +1 -1
  327. package/esm/http/uploads/resolve-remote-image-variant.mjs +89 -0
  328. package/esm/http/uploads/resolve-remote-image-variant.mjs.map +1 -0
  329. package/esm/http/uploads/trim-image-variant-cache.mjs +193 -0
  330. package/esm/http/uploads/trim-image-variant-cache.mjs.map +1 -0
  331. package/esm/http/uploads/uploaded-file.controller.d.mts +5 -3
  332. package/esm/http/uploads/uploaded-file.controller.d.mts.map +1 -1
  333. package/esm/http/uploads/uploaded-file.controller.mjs +16 -8
  334. package/esm/http/uploads/uploaded-file.controller.mjs.map +1 -1
  335. package/esm/http/uploads/variant-cache-key.mjs +1 -1
  336. package/esm/http/uploads-types.d.mts +6 -2
  337. package/esm/http/uploads-types.d.mts.map +1 -1
  338. package/esm/index.d.mts +17 -6
  339. package/esm/index.mjs +14 -3
  340. package/esm/localization/parse-locale-dictionary.d.mts +2 -0
  341. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -1
  342. package/esm/localization/parse-locale-dictionary.mjs +1 -0
  343. package/esm/localization/parse-locale-dictionary.mjs.map +1 -1
  344. package/esm/mail/config.d.mts +12 -1
  345. package/esm/mail/config.d.mts.map +1 -1
  346. package/esm/mail/config.mjs +16 -1
  347. package/esm/mail/config.mjs.map +1 -1
  348. package/esm/mail/index.d.mts +1 -1
  349. package/esm/mail/index.mjs +1 -1
  350. package/esm/net/index.d.mts +2 -0
  351. package/esm/net/index.mjs +3 -0
  352. package/esm/openapi/build-openapi-document.d.mts.map +1 -1
  353. package/esm/openapi/build-openapi-document.mjs +8 -5
  354. package/esm/openapi/build-openapi-document.mjs.map +1 -1
  355. package/esm/openapi/error-responses.mjs +28 -2
  356. package/esm/openapi/error-responses.mjs.map +1 -1
  357. package/esm/openapi/postman/build-request-body.mjs.map +1 -1
  358. package/esm/openapi/postman/build-request-url.mjs.map +1 -1
  359. package/esm/openapi/postman/build-saved-responses.mjs.map +1 -1
  360. package/esm/openapi/postman/example-from-schema.d.mts.map +1 -1
  361. package/esm/openapi/postman/example-from-schema.mjs.map +1 -1
  362. package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -1
  363. package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -1
  364. package/esm/openapi/postman/schema-refs.mjs.map +1 -1
  365. package/esm/openapi/postman/uuid-v5.mjs.map +1 -1
  366. package/esm/openapi/request-schemas.mjs.map +1 -1
  367. package/esm/openapi/response-schemas.mjs.map +1 -1
  368. package/esm/openapi/schema-registry.d.mts +1 -0
  369. package/esm/openapi/schema-registry.mjs.map +1 -1
  370. package/esm/openapi/security.mjs.map +1 -1
  371. package/esm/openapi/types.d.mts +3 -2
  372. package/esm/openapi/types.d.mts.map +1 -1
  373. package/esm/production/assert-generated-imports.mjs +1 -0
  374. package/esm/production/assert-generated-imports.mjs.map +1 -1
  375. package/esm/production/production-builder.mjs +12 -4
  376. package/esm/production/production-builder.mjs.map +1 -1
  377. package/esm/production/resolve-build-config.mjs +1 -0
  378. package/esm/production/resolve-build-config.mjs.map +1 -1
  379. package/esm/production/route-registration-child.mjs.map +1 -1
  380. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  381. package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
  382. package/esm/repositories/contracts/types.d.mts.map +1 -1
  383. package/esm/repositories/repository.manager.mjs.map +1 -1
  384. package/esm/resource/resource.d.mts.map +1 -1
  385. package/esm/resource/resource.mjs.map +1 -1
  386. package/esm/resource/types.d.mts.map +1 -1
  387. package/esm/restful/restful.d.mts +5 -5
  388. package/esm/restful/restful.d.mts.map +1 -1
  389. package/esm/restful/restful.mjs.map +1 -1
  390. package/esm/router/build-route-rate-limit.mjs.map +1 -1
  391. package/esm/router/dev-dispatch.mjs +31 -12
  392. package/esm/router/dev-dispatch.mjs.map +1 -1
  393. package/esm/router/index.d.mts +4 -2
  394. package/esm/router/index.mjs +2 -0
  395. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  396. package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
  397. package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
  398. package/esm/router/resolve-route-rate-limit.mjs +47 -0
  399. package/esm/router/resolve-route-rate-limit.mjs.map +1 -0
  400. package/esm/router/resolve-route-validation.mjs +19 -0
  401. package/esm/router/resolve-route-validation.mjs.map +1 -0
  402. package/esm/router/route-guard-names.mjs +30 -0
  403. package/esm/router/route-guard-names.mjs.map +1 -0
  404. package/esm/router/route-name-taken-error.d.mts +14 -0
  405. package/esm/router/route-name-taken-error.d.mts.map +1 -0
  406. package/esm/router/route-name-taken-error.mjs +18 -0
  407. package/esm/router/route-name-taken-error.mjs.map +1 -0
  408. package/esm/router/router.d.mts +32 -13
  409. package/esm/router/router.d.mts.map +1 -1
  410. package/esm/router/router.mjs +71 -38
  411. package/esm/router/router.mjs.map +1 -1
  412. package/esm/router/types.d.mts +137 -17
  413. package/esm/router/types.d.mts.map +1 -1
  414. package/esm/router/unguarded-route-diagnostics.d.mts +40 -0
  415. package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -0
  416. package/esm/router/unguarded-route-diagnostics.mjs +95 -0
  417. package/esm/router/unguarded-route-diagnostics.mjs.map +1 -0
  418. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  419. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  420. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  421. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  422. package/esm/storage/local-disk-roots.mjs +24 -0
  423. package/esm/storage/local-disk-roots.mjs.map +1 -0
  424. package/esm/storage/storage.d.mts +14 -0
  425. package/esm/storage/storage.d.mts.map +1 -1
  426. package/esm/storage/storage.mjs +22 -0
  427. package/esm/storage/storage.mjs.map +1 -1
  428. package/esm/storage/types.d.mts.map +1 -1
  429. package/esm/storage/utils/storage-not-initialized-error.d.mts +2 -0
  430. package/esm/storage/utils/storage-not-initialized-error.d.mts.map +1 -1
  431. package/esm/storage/utils/storage-not-initialized-error.mjs +1 -0
  432. package/esm/storage/utils/storage-not-initialized-error.mjs.map +1 -1
  433. package/esm/tests/authenticated-test-client.d.mts +32 -0
  434. package/esm/tests/authenticated-test-client.d.mts.map +1 -0
  435. package/esm/tests/authenticated-test-client.mjs +40 -0
  436. package/esm/tests/authenticated-test-client.mjs.map +1 -0
  437. package/esm/tests/factories.d.mts +59 -0
  438. package/esm/tests/factories.d.mts.map +1 -0
  439. package/esm/tests/factories.mjs +105 -0
  440. package/esm/tests/factories.mjs.map +1 -0
  441. package/esm/tests/fakes/cache-fake.d.mts +29 -0
  442. package/esm/tests/fakes/cache-fake.d.mts.map +1 -0
  443. package/esm/tests/fakes/cache-fake.mjs +79 -0
  444. package/esm/tests/fakes/cache-fake.mjs.map +1 -0
  445. package/esm/tests/fakes/clock-fake.d.mts +32 -0
  446. package/esm/tests/fakes/clock-fake.d.mts.map +1 -0
  447. package/esm/tests/fakes/clock-fake.mjs +69 -0
  448. package/esm/tests/fakes/clock-fake.mjs.map +1 -0
  449. package/esm/tests/fakes/fake-assertion-error.d.mts +13 -0
  450. package/esm/tests/fakes/fake-assertion-error.d.mts.map +1 -0
  451. package/esm/tests/fakes/fake-assertion-error.mjs +20 -0
  452. package/esm/tests/fakes/fake-assertion-error.mjs.map +1 -0
  453. package/esm/tests/fakes/ids-fake.d.mts +22 -0
  454. package/esm/tests/fakes/ids-fake.d.mts.map +1 -0
  455. package/esm/tests/fakes/ids-fake.mjs +57 -0
  456. package/esm/tests/fakes/ids-fake.mjs.map +1 -0
  457. package/esm/tests/fakes/index.d.mts +8 -0
  458. package/esm/tests/fakes/index.mjs +10 -0
  459. package/esm/tests/fakes/mail-fake.d.mts +41 -0
  460. package/esm/tests/fakes/mail-fake.d.mts.map +1 -0
  461. package/esm/tests/fakes/mail-fake.mjs +111 -0
  462. package/esm/tests/fakes/mail-fake.mjs.map +1 -0
  463. package/esm/tests/fakes/queue-fake.d.mts +48 -0
  464. package/esm/tests/fakes/queue-fake.d.mts.map +1 -0
  465. package/esm/tests/fakes/queue-fake.mjs +134 -0
  466. package/esm/tests/fakes/queue-fake.mjs.map +1 -0
  467. package/esm/tests/fakes/storage-fake.d.mts +31 -0
  468. package/esm/tests/fakes/storage-fake.d.mts.map +1 -0
  469. package/esm/tests/fakes/storage-fake.mjs +122 -0
  470. package/esm/tests/fakes/storage-fake.mjs.map +1 -0
  471. package/esm/tests/fakes/test-fakes.d.mts +39 -0
  472. package/esm/tests/fakes/test-fakes.d.mts.map +1 -0
  473. package/esm/tests/fakes/test-fakes.mjs +90 -0
  474. package/esm/tests/fakes/test-fakes.mjs.map +1 -0
  475. package/esm/tests/index.d.mts +14 -2
  476. package/esm/tests/index.mjs +15 -2
  477. package/esm/tests/snapshot-response.d.mts +34 -0
  478. package/esm/tests/snapshot-response.d.mts.map +1 -0
  479. package/esm/tests/snapshot-response.mjs +42 -0
  480. package/esm/tests/snapshot-response.mjs.map +1 -0
  481. package/esm/tests/start-http-development-server.d.mts +22 -0
  482. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  483. package/esm/tests/start-http-development-server.mjs +36 -3
  484. package/esm/tests/start-http-development-server.mjs.map +1 -1
  485. package/esm/tests/test-helpers.d.mts +20 -1
  486. package/esm/tests/test-helpers.d.mts.map +1 -1
  487. package/esm/tests/test-helpers.mjs +40 -27
  488. package/esm/tests/test-helpers.mjs.map +1 -1
  489. package/esm/tests/test-http-server.d.mts +62 -0
  490. package/esm/tests/test-http-server.d.mts.map +1 -0
  491. package/esm/tests/test-http-server.mjs +119 -0
  492. package/esm/tests/test-http-server.mjs.map +1 -0
  493. package/esm/tests/vitest-setup.d.mts +11 -0
  494. package/esm/tests/vitest-setup.d.mts.map +1 -1
  495. package/esm/tests/vitest-setup.mjs +12 -4
  496. package/esm/tests/vitest-setup.mjs.map +1 -1
  497. package/esm/tooling/agents-md/agents-md-markers.d.mts +27 -0
  498. package/esm/tooling/agents-md/agents-md-markers.d.mts.map +1 -0
  499. package/esm/tooling/agents-md/agents-md-markers.mjs +59 -0
  500. package/esm/tooling/agents-md/agents-md-markers.mjs.map +1 -0
  501. package/esm/tooling/agents-md/agents-md-refresher.d.mts +36 -0
  502. package/esm/tooling/agents-md/agents-md-refresher.d.mts.map +1 -0
  503. package/esm/tooling/agents-md/agents-md-refresher.mjs +72 -0
  504. package/esm/tooling/agents-md/agents-md-refresher.mjs.map +1 -0
  505. package/esm/tooling/agents-md/index.mjs +7 -0
  506. package/esm/tooling/agents-md/installed-skills.d.mts +30 -0
  507. package/esm/tooling/agents-md/installed-skills.d.mts.map +1 -0
  508. package/esm/tooling/agents-md/installed-skills.mjs +74 -0
  509. package/esm/tooling/agents-md/installed-skills.mjs.map +1 -0
  510. package/esm/tooling/agents-md/render-agents-md.d.mts +33 -0
  511. package/esm/tooling/agents-md/render-agents-md.d.mts.map +1 -0
  512. package/esm/tooling/agents-md/render-agents-md.mjs +195 -0
  513. package/esm/tooling/agents-md/render-agents-md.mjs.map +1 -0
  514. package/esm/tooling/agents-md/write-agents-md.d.mts +33 -0
  515. package/esm/tooling/agents-md/write-agents-md.d.mts.map +1 -0
  516. package/esm/tooling/agents-md/write-agents-md.mjs +49 -0
  517. package/esm/tooling/agents-md/write-agents-md.mjs.map +1 -0
  518. package/esm/tooling/board/board-message.d.mts +31 -0
  519. package/esm/tooling/board/board-message.d.mts.map +1 -0
  520. package/esm/tooling/board/board-message.mjs +86 -0
  521. package/esm/tooling/board/board-message.mjs.map +1 -0
  522. package/esm/tooling/board/board.d.mts +109 -0
  523. package/esm/tooling/board/board.d.mts.map +1 -0
  524. package/esm/tooling/board/board.mjs +406 -0
  525. package/esm/tooling/board/board.mjs.map +1 -0
  526. package/esm/tooling/board/board.types.d.mts +72 -0
  527. package/esm/tooling/board/board.types.d.mts.map +1 -0
  528. package/esm/tooling/board/board.types.mjs +54 -0
  529. package/esm/tooling/board/board.types.mjs.map +1 -0
  530. package/esm/tooling/check/changed-files.d.mts +33 -0
  531. package/esm/tooling/check/changed-files.d.mts.map +1 -0
  532. package/esm/tooling/check/changed-files.mjs +73 -0
  533. package/esm/tooling/check/changed-files.mjs.map +1 -0
  534. package/esm/tooling/check/check-report.types.d.mts +62 -0
  535. package/esm/tooling/check/check-report.types.d.mts.map +1 -0
  536. package/esm/tooling/check/check-report.types.mjs +37 -0
  537. package/esm/tooling/check/check-report.types.mjs.map +1 -0
  538. package/esm/tooling/check/code-style-conventions.mjs +106 -0
  539. package/esm/tooling/check/code-style-conventions.mjs.map +1 -0
  540. package/esm/tooling/check/conventions-step.d.mts +34 -0
  541. package/esm/tooling/check/conventions-step.d.mts.map +1 -0
  542. package/esm/tooling/check/conventions-step.mjs +184 -0
  543. package/esm/tooling/check/conventions-step.mjs.map +1 -0
  544. package/esm/tooling/check/environment-step.d.mts +18 -0
  545. package/esm/tooling/check/environment-step.d.mts.map +1 -0
  546. package/esm/tooling/check/environment-step.mjs +78 -0
  547. package/esm/tooling/check/environment-step.mjs.map +1 -0
  548. package/esm/tooling/check/index.d.mts +8 -0
  549. package/esm/tooling/check/index.mjs +10 -0
  550. package/esm/tooling/check/lint-step.d.mts +19 -0
  551. package/esm/tooling/check/lint-step.d.mts.map +1 -0
  552. package/esm/tooling/check/lint-step.mjs +111 -0
  553. package/esm/tooling/check/lint-step.mjs.map +1 -0
  554. package/esm/tooling/check/request-input.mjs +132 -0
  555. package/esm/tooling/check/request-input.mjs.map +1 -0
  556. package/esm/tooling/check/run-check.d.mts +35 -0
  557. package/esm/tooling/check/run-check.d.mts.map +1 -0
  558. package/esm/tooling/check/run-check.mjs +192 -0
  559. package/esm/tooling/check/run-check.mjs.map +1 -0
  560. package/esm/tooling/check/run-tests.d.mts +48 -0
  561. package/esm/tooling/check/run-tests.d.mts.map +1 -0
  562. package/esm/tooling/check/run-tests.mjs +93 -0
  563. package/esm/tooling/check/run-tests.mjs.map +1 -0
  564. package/esm/tooling/check/security-conventions.mjs +648 -0
  565. package/esm/tooling/check/security-conventions.mjs.map +1 -0
  566. package/esm/tooling/check/source-ast.mjs +195 -0
  567. package/esm/tooling/check/source-ast.mjs.map +1 -0
  568. package/esm/tooling/check/source-files.mjs +27 -0
  569. package/esm/tooling/check/source-files.mjs.map +1 -0
  570. package/esm/tooling/check/tests-step.d.mts +34 -0
  571. package/esm/tooling/check/tests-step.d.mts.map +1 -0
  572. package/esm/tooling/check/tests-step.mjs +177 -0
  573. package/esm/tooling/check/tests-step.mjs.map +1 -0
  574. package/esm/tooling/dev-state/dev-server-state.d.mts +39 -0
  575. package/esm/tooling/dev-state/dev-server-state.d.mts.map +1 -0
  576. package/esm/tooling/dev-state/dev-server-state.mjs +67 -0
  577. package/esm/tooling/dev-state/dev-server-state.mjs.map +1 -0
  578. package/esm/tooling/devtools/devtools-client.d.mts +91 -0
  579. package/esm/tooling/devtools/devtools-client.d.mts.map +1 -0
  580. package/esm/tooling/devtools/devtools-client.mjs +155 -0
  581. package/esm/tooling/devtools/devtools-client.mjs.map +1 -0
  582. package/esm/tooling/generate/generation.d.mts +78 -0
  583. package/esm/tooling/generate/generation.d.mts.map +1 -0
  584. package/esm/tooling/generate/generation.mjs +123 -0
  585. package/esm/tooling/generate/generation.mjs.map +1 -0
  586. package/esm/tooling/generate/generator-spec.d.mts +57 -0
  587. package/esm/tooling/generate/generator-spec.d.mts.map +1 -0
  588. package/esm/tooling/generate/generator-spec.mjs +102 -0
  589. package/esm/tooling/generate/generator-spec.mjs.map +1 -0
  590. package/esm/tooling/generate/index.mjs +6 -0
  591. package/esm/tooling/generate/project-files.mjs +121 -0
  592. package/esm/tooling/generate/project-files.mjs.map +1 -0
  593. package/esm/tooling/generate/run-cli.d.mts +40 -0
  594. package/esm/tooling/generate/run-cli.d.mts.map +1 -0
  595. package/esm/tooling/generate/run-cli.mjs +93 -0
  596. package/esm/tooling/generate/run-cli.mjs.map +1 -0
  597. package/esm/tooling/generate/unified-diff.d.mts +37 -0
  598. package/esm/tooling/generate/unified-diff.d.mts.map +1 -0
  599. package/esm/tooling/generate/unified-diff.mjs +161 -0
  600. package/esm/tooling/generate/unified-diff.mjs.map +1 -0
  601. package/esm/tooling/index.d.mts +41 -0
  602. package/esm/tooling/index.mjs +45 -0
  603. package/esm/tooling/inspect/boot-for-inspect.d.mts +23 -0
  604. package/esm/tooling/inspect/boot-for-inspect.d.mts.map +1 -0
  605. package/esm/tooling/inspect/boot-for-inspect.mjs +63 -0
  606. package/esm/tooling/inspect/boot-for-inspect.mjs.map +1 -0
  607. package/esm/tooling/inspect/collect-config.mjs +77 -0
  608. package/esm/tooling/inspect/collect-config.mjs.map +1 -0
  609. package/esm/tooling/inspect/collect-models.d.mts +9 -0
  610. package/esm/tooling/inspect/collect-models.d.mts.map +1 -0
  611. package/esm/tooling/inspect/collect-models.mjs +154 -0
  612. package/esm/tooling/inspect/collect-models.mjs.map +1 -0
  613. package/esm/tooling/inspect/collect-packages.d.mts +10 -0
  614. package/esm/tooling/inspect/collect-packages.d.mts.map +1 -0
  615. package/esm/tooling/inspect/collect-packages.mjs +157 -0
  616. package/esm/tooling/inspect/collect-packages.mjs.map +1 -0
  617. package/esm/tooling/inspect/collect-pages.mjs +108 -0
  618. package/esm/tooling/inspect/collect-pages.mjs.map +1 -0
  619. package/esm/tooling/inspect/collect-routes.mjs +168 -0
  620. package/esm/tooling/inspect/collect-routes.mjs.map +1 -0
  621. package/esm/tooling/inspect/event-recorder.d.mts +37 -0
  622. package/esm/tooling/inspect/event-recorder.d.mts.map +1 -0
  623. package/esm/tooling/inspect/event-recorder.mjs +91 -0
  624. package/esm/tooling/inspect/event-recorder.mjs.map +1 -0
  625. package/esm/tooling/inspect/index.d.mts +7 -0
  626. package/esm/tooling/inspect/index.mjs +7 -0
  627. package/esm/tooling/inspect/inspect-app.d.mts +53 -0
  628. package/esm/tooling/inspect/inspect-app.d.mts.map +1 -0
  629. package/esm/tooling/inspect/inspect-app.mjs +92 -0
  630. package/esm/tooling/inspect/inspect-app.mjs.map +1 -0
  631. package/esm/tooling/inspect/inspect-report.schema.d.mts +15 -0
  632. package/esm/tooling/inspect/inspect-report.schema.d.mts.map +1 -0
  633. package/esm/tooling/inspect/inspect-report.schema.mjs +463 -0
  634. package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -0
  635. package/esm/tooling/inspect/inspect-report.types.d.mts +196 -0
  636. package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -0
  637. package/esm/tooling/inspect/inspect-report.types.mjs +33 -0
  638. package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -0
  639. package/esm/tooling/mcp/create-mcp-context.d.mts +26 -0
  640. package/esm/tooling/mcp/create-mcp-context.d.mts.map +1 -0
  641. package/esm/tooling/mcp/create-mcp-context.mjs +75 -0
  642. package/esm/tooling/mcp/create-mcp-context.mjs.map +1 -0
  643. package/esm/tooling/mcp/db-schema.d.mts +49 -0
  644. package/esm/tooling/mcp/db-schema.d.mts.map +1 -0
  645. package/esm/tooling/mcp/db-schema.mjs +56 -0
  646. package/esm/tooling/mcp/db-schema.mjs.map +1 -0
  647. package/esm/tooling/mcp/docs-search.d.mts +72 -0
  648. package/esm/tooling/mcp/docs-search.d.mts.map +1 -0
  649. package/esm/tooling/mcp/docs-search.mjs +178 -0
  650. package/esm/tooling/mcp/docs-search.mjs.map +1 -0
  651. package/esm/tooling/mcp/index.mjs +9 -0
  652. package/esm/tooling/mcp/load-ai-tools.d.mts +62 -0
  653. package/esm/tooling/mcp/load-ai-tools.d.mts.map +1 -0
  654. package/esm/tooling/mcp/load-ai-tools.mjs +44 -0
  655. package/esm/tooling/mcp/load-ai-tools.mjs.map +1 -0
  656. package/esm/tooling/mcp/mcp-tools.d.mts +60 -0
  657. package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -0
  658. package/esm/tooling/mcp/mcp-tools.mjs +513 -0
  659. package/esm/tooling/mcp/mcp-tools.mjs.map +1 -0
  660. package/esm/tooling/mcp/start-mcp-server.d.mts +56 -0
  661. package/esm/tooling/mcp/start-mcp-server.d.mts.map +1 -0
  662. package/esm/tooling/mcp/start-mcp-server.mjs +126 -0
  663. package/esm/tooling/mcp/start-mcp-server.mjs.map +1 -0
  664. package/esm/tooling/mcp/tool-input.d.mts +75 -0
  665. package/esm/tooling/mcp/tool-input.d.mts.map +1 -0
  666. package/esm/tooling/mcp/tool-input.mjs +72 -0
  667. package/esm/tooling/mcp/tool-input.mjs.map +1 -0
  668. package/esm/tooling/problem.d.mts +45 -0
  669. package/esm/tooling/problem.d.mts.map +1 -0
  670. package/esm/tooling/problem.mjs +28 -0
  671. package/esm/tooling/problem.mjs.map +1 -0
  672. package/esm/tooling/trace/trace-report.types.d.mts +71 -0
  673. package/esm/tooling/trace/trace-report.types.d.mts.map +1 -0
  674. package/esm/tooling/trace/trace-report.types.mjs +7 -0
  675. package/esm/tooling/trace/trace-report.types.mjs.map +1 -0
  676. package/esm/tooling/trace/trace-request.d.mts +83 -0
  677. package/esm/tooling/trace/trace-request.d.mts.map +1 -0
  678. package/esm/tooling/trace/trace-request.mjs +241 -0
  679. package/esm/tooling/trace/trace-request.mjs.map +1 -0
  680. package/esm/tooling/typecheck/index.d.mts +1 -0
  681. package/esm/tooling/typecheck/index.mjs +3 -0
  682. package/esm/tooling/typecheck/run-typecheck.d.mts +59 -0
  683. package/esm/tooling/typecheck/run-typecheck.d.mts.map +1 -0
  684. package/esm/tooling/typecheck/run-typecheck.mjs +153 -0
  685. package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -0
  686. package/esm/upgrade/codemods/head-viewport.mjs +46 -0
  687. package/esm/upgrade/codemods/head-viewport.mjs.map +1 -0
  688. package/esm/upgrade/codemods/helpers.mjs +81 -0
  689. package/esm/upgrade/codemods/helpers.mjs.map +1 -0
  690. package/esm/upgrade/codemods/index.mjs +34 -0
  691. package/esm/upgrade/codemods/index.mjs.map +1 -0
  692. package/esm/upgrade/codemods/logger-sync-calls.mjs +57 -0
  693. package/esm/upgrade/codemods/logger-sync-calls.mjs.map +1 -0
  694. package/esm/upgrade/codemods/package-json.mjs +122 -0
  695. package/esm/upgrade/codemods/package-json.mjs.map +1 -0
  696. package/esm/upgrade/codemods/request-log-snapshot.mjs +82 -0
  697. package/esm/upgrade/codemods/request-log-snapshot.mjs.map +1 -0
  698. package/esm/upgrade/codemods/review-sites.mjs +262 -0
  699. package/esm/upgrade/codemods/review-sites.mjs.map +1 -0
  700. package/esm/upgrade/diff.mjs +119 -0
  701. package/esm/upgrade/diff.mjs.map +1 -0
  702. package/esm/upgrade/format-upgrade-report.mjs +32 -0
  703. package/esm/upgrade/format-upgrade-report.mjs.map +1 -0
  704. package/esm/upgrade/load-ts-morph.mjs +48 -0
  705. package/esm/upgrade/load-ts-morph.mjs.map +1 -0
  706. package/esm/upgrade/notes.mjs +16 -0
  707. package/esm/upgrade/notes.mjs.map +1 -0
  708. package/esm/upgrade/run-upgrade.mjs +246 -0
  709. package/esm/upgrade/run-upgrade.mjs.map +1 -0
  710. package/esm/upgrade/upgrade-command.action.mjs +37 -0
  711. package/esm/upgrade/upgrade-command.action.mjs.map +1 -0
  712. package/esm/upgrade/version.mjs +39 -0
  713. package/esm/upgrade/version.mjs.map +1 -0
  714. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  715. package/esm/utils/clock.d.mts +33 -0
  716. package/esm/utils/clock.d.mts.map +1 -0
  717. package/esm/utils/clock.mjs +27 -0
  718. package/esm/utils/clock.mjs.map +1 -0
  719. package/esm/utils/framework-vesion.mjs +1 -1
  720. package/esm/utils/framework-vesion.mjs.map +1 -1
  721. package/esm/utils/ids.d.mts +32 -0
  722. package/esm/utils/ids.d.mts.map +1 -0
  723. package/esm/utils/ids.mjs +33 -0
  724. package/esm/utils/ids.mjs.map +1 -0
  725. package/esm/utils/index.d.mts +3 -0
  726. package/esm/utils/index.mjs +3 -0
  727. package/esm/utils/runtime.d.mts +59 -0
  728. package/esm/utils/runtime.d.mts.map +1 -0
  729. package/esm/utils/runtime.mjs +85 -0
  730. package/esm/utils/runtime.mjs.map +1 -0
  731. package/esm/validation/index.d.mts +1 -1
  732. package/esm/validation/types.d.mts +57 -2
  733. package/esm/validation/types.d.mts.map +1 -1
  734. package/esm/validation/validateAll.mjs +19 -2
  735. package/esm/validation/validateAll.mjs.map +1 -1
  736. package/esm/validation/validation-failure-status.mjs +15 -0
  737. package/esm/validation/validation-failure-status.mjs.map +1 -0
  738. package/esm/validation/validators/file-validator.d.mts +0 -16
  739. package/esm/validation/validators/file-validator.d.mts.map +1 -1
  740. package/esm/warlock-config/bundle-config.mjs +1 -1
  741. package/esm/warlock-config/types.d.mts +27 -3
  742. package/esm/warlock-config/types.d.mts.map +1 -1
  743. package/llms-full.txt +3166 -736
  744. package/llms.txt +16 -6
  745. package/package.json +43 -13
  746. package/schemas/inspect-report.schema.json +554 -0
  747. package/skills/README.md +9 -0
  748. package/skills/add-connector/SKILL.md +26 -38
  749. package/skills/benchmark-code/SKILL.md +30 -24
  750. package/skills/build-restful/SKILL.md +14 -14
  751. package/skills/build-url/SKILL.md +18 -18
  752. package/skills/configure-app/SKILL.md +107 -14
  753. package/skills/create-controller/SKILL.md +25 -12
  754. package/skills/create-module/SKILL.md +3 -3
  755. package/skills/define-resource/SKILL.md +32 -29
  756. package/skills/encrypt-data/SKILL.md +21 -19
  757. package/skills/generate-openapi/SKILL.md +50 -24
  758. package/skills/hash-password/SKILL.md +2 -2
  759. package/skills/health-checks/SKILL.md +24 -7
  760. package/skills/index.md +9 -4
  761. package/skills/lower-stage3-decorators/SKILL.md +4 -4
  762. package/skills/make-endpoint/SKILL.md +110 -0
  763. package/skills/model-factories/SKILL.md +92 -0
  764. package/skills/process-image/SKILL.md +32 -30
  765. package/skills/register-route/SKILL.md +109 -10
  766. package/skills/request-memo/SKILL.md +1 -1
  767. package/skills/request-tracing/SKILL.md +4 -1
  768. package/skills/resolve-path/SKILL.md +45 -44
  769. package/skills/retry-operation/SKILL.md +20 -11
  770. package/skills/run-app/SKILL.md +106 -22
  771. package/skills/send-response/SKILL.md +179 -14
  772. package/skills/store-file/SKILL.md +93 -53
  773. package/skills/test-fakes/SKILL.md +123 -0
  774. package/skills/test-http/SKILL.md +125 -21
  775. package/skills/test-service/SKILL.md +41 -1
  776. package/skills/update-packages/SKILL.md +29 -18
  777. package/skills/upgrade-from-5-29/SKILL.md +413 -0
  778. package/skills/upload-file/SKILL.md +31 -33
  779. package/skills/use-app-context/SKILL.md +38 -31
  780. package/skills/use-middleware/SKILL.md +45 -25
  781. package/skills/use-model-transformers/SKILL.md +37 -28
  782. package/skills/use-repository/SKILL.md +2 -2
  783. package/skills/validate-input/SKILL.md +72 -28
  784. package/skills/warlock-agents-md/SKILL.md +96 -0
  785. package/skills/warlock-board/SKILL.md +123 -0
  786. package/skills/warlock-check/SKILL.md +132 -0
  787. package/skills/warlock-conventions/SKILL.md +115 -0
  788. package/skills/warlock-doctor/SKILL.md +55 -22
  789. package/skills/warlock-inspect/SKILL.md +134 -0
  790. package/skills/warlock-mcp/SKILL.md +120 -0
  791. package/skills/warlock-routes/SKILL.md +18 -11
  792. package/skills/warlock-trace/SKILL.md +81 -0
  793. package/skills/wire-socket/SKILL.md +8 -8
  794. package/skills/write-seeder/SKILL.md +46 -32
  795. package/skills/write-use-case/SKILL.md +40 -27
  796. package/esm/http/uploads/matches-if-none-match.mjs +0 -13
  797. package/esm/http/uploads/matches-if-none-match.mjs.map +0 -1
@@ -0,0 +1,78 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+
4
+ //#region ../../packages/core/src/http/uploads/image-variant-cache-entries.ts
5
+ const SHARD = /^[a-f0-9]{2}$/;
6
+ const SOURCE = /^[a-f0-9]{64}$/;
7
+ const GENERATION = /^(?:\d+--?\d+|[a-f0-9]{64})$/;
8
+ const DERIVATIVE = /^[a-f0-9]{64}\.(?:jpg|png|webp|avif)$/;
9
+ const TEMPORARY = /^(?:[a-f0-9]{64}\.(?:jpg|png|webp|avif)\.tmp-[a-f0-9]{16}|\.source\.json\.[a-f0-9]{12}\.tmp)$/;
10
+ async function entries(directory) {
11
+ try {
12
+ return await fs.readdir(directory, { withFileTypes: true });
13
+ } catch (error) {
14
+ if (error.code === "ENOENT") return [];
15
+ throw error;
16
+ }
17
+ }
18
+ /** Recognize only generation directories and files emitted by the image writers; never traverse symlinks. */
19
+ async function* variantSourceCacheGenerations(sourceDirectory, keepGeneration) {
20
+ try {
21
+ for (const directory of [path.dirname(sourceDirectory), sourceDirectory]) if (!(await fs.lstat(directory)).isDirectory()) return;
22
+ } catch (error) {
23
+ if (error.code === "ENOENT") return;
24
+ throw error;
25
+ }
26
+ for (const generation of await entries(sourceDirectory)) {
27
+ if (generation.name === keepGeneration || !generation.isDirectory() || !GENERATION.test(generation.name)) continue;
28
+ const directory = path.join(sourceDirectory, generation.name);
29
+ const found = [];
30
+ for (const file of await entries(directory)) {
31
+ if (!file.isFile()) continue;
32
+ const type = DERIVATIVE.test(file.name) ? "derivative" : file.name === "source.json" ? "manifest" : TEMPORARY.test(file.name) ? "temporary" : void 0;
33
+ if (type) found.push({
34
+ target: path.join(directory, file.name),
35
+ type
36
+ });
37
+ }
38
+ yield {
39
+ directory,
40
+ entries: found
41
+ };
42
+ }
43
+ }
44
+ /** Every directory component must match the generated cache layout. */
45
+ async function* variantCacheGenerations(cacheDirectory) {
46
+ for (const shard of await entries(cacheDirectory)) {
47
+ if (!shard.isDirectory() || !SHARD.test(shard.name)) continue;
48
+ const directory = path.join(cacheDirectory, shard.name);
49
+ for (const source of await entries(directory)) {
50
+ if (!source.isDirectory() || !SOURCE.test(source.name) || !source.name.startsWith(shard.name)) continue;
51
+ yield* variantSourceCacheGenerations(path.join(directory, source.name));
52
+ }
53
+ }
54
+ }
55
+ async function* flatten(generations) {
56
+ for await (const generation of generations) yield* generation.entries;
57
+ }
58
+ /** Recognized files of one source's generations, optionally skipping the `keepGeneration` directory. */
59
+ function variantSourceCacheEntries(sourceDirectory, keepGeneration) {
60
+ return flatten(variantSourceCacheGenerations(sourceDirectory, keepGeneration));
61
+ }
62
+ /** Recognized files of every source in the cache. */
63
+ function variantCacheEntries(cacheDirectory) {
64
+ return flatten(variantCacheGenerations(cacheDirectory));
65
+ }
66
+ /** Keep directories in place so removing cache data cannot remove user folders. */
67
+ async function purgeVariantCacheEntries(entries) {
68
+ let removed = false;
69
+ for await (const entry of entries) {
70
+ await fs.rm(entry.target, { force: true });
71
+ removed = true;
72
+ }
73
+ return removed;
74
+ }
75
+
76
+ //#endregion
77
+ export { purgeVariantCacheEntries, variantCacheEntries, variantCacheGenerations, variantSourceCacheEntries };
78
+ //# sourceMappingURL=image-variant-cache-entries.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-variant-cache-entries.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variant-cache-entries.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nconst SHARD = /^[a-f0-9]{2}$/;\nconst SOURCE = /^[a-f0-9]{64}$/;\nconst GENERATION = /^(?:\\d+--?\\d+|[a-f0-9]{64})$/;\nconst DERIVATIVE = /^[a-f0-9]{64}\\.(?:jpg|png|webp|avif)$/;\nconst TEMPORARY =\n /^(?:[a-f0-9]{64}\\.(?:jpg|png|webp|avif)\\.tmp-[a-f0-9]{16}|\\.source\\.json\\.[a-f0-9]{12}\\.tmp)$/;\n\nexport type VariantCacheEntry = {\n target: string;\n type: \"derivative\" | \"manifest\" | \"temporary\";\n};\n\n/** One generation-shaped directory (a real directory, never a symlink) and its recognized files. */\nexport type VariantCacheGeneration = {\n directory: string;\n entries: VariantCacheEntry[];\n};\n\nasync function entries(directory: string) {\n try {\n return await fs.readdir(directory, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n}\n\n/** Recognize only generation directories and files emitted by the image writers; never traverse symlinks. */\nexport async function* variantSourceCacheGenerations(\n sourceDirectory: string,\n keepGeneration?: string,\n): AsyncGenerator<VariantCacheGeneration> {\n try {\n // Checking the final component alone still follows a symlinked shard.\n for (const directory of [path.dirname(sourceDirectory), sourceDirectory]) {\n if (!(await fs.lstat(directory)).isDirectory()) return;\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw error;\n }\n for (const generation of await entries(sourceDirectory)) {\n if (\n generation.name === keepGeneration ||\n !generation.isDirectory() ||\n !GENERATION.test(generation.name)\n )\n continue;\n const directory = path.join(sourceDirectory, generation.name);\n const found: VariantCacheEntry[] = [];\n for (const file of await entries(directory)) {\n if (!file.isFile()) continue;\n const type = DERIVATIVE.test(file.name)\n ? \"derivative\"\n : file.name === \"source.json\"\n ? \"manifest\"\n : TEMPORARY.test(file.name)\n ? \"temporary\"\n : undefined;\n if (type) found.push({ target: path.join(directory, file.name), type });\n }\n yield { directory, entries: found };\n }\n}\n\n/** Every directory component must match the generated cache layout. */\nexport async function* variantCacheGenerations(\n cacheDirectory: string,\n): AsyncGenerator<VariantCacheGeneration> {\n for (const shard of await entries(cacheDirectory)) {\n if (!shard.isDirectory() || !SHARD.test(shard.name)) continue;\n const directory = path.join(cacheDirectory, shard.name);\n for (const source of await entries(directory)) {\n if (!source.isDirectory() || !SOURCE.test(source.name) || !source.name.startsWith(shard.name))\n continue;\n yield* variantSourceCacheGenerations(path.join(directory, source.name));\n }\n }\n}\n\nasync function* flatten(\n generations: AsyncIterable<VariantCacheGeneration>,\n): AsyncGenerator<VariantCacheEntry> {\n for await (const generation of generations) yield* generation.entries;\n}\n\n/** Recognized files of one source's generations, optionally skipping the `keepGeneration` directory. */\nexport function variantSourceCacheEntries(\n sourceDirectory: string,\n keepGeneration?: string,\n): AsyncGenerator<VariantCacheEntry> {\n return flatten(variantSourceCacheGenerations(sourceDirectory, keepGeneration));\n}\n\n/** Recognized files of every source in the cache. */\nexport function variantCacheEntries(cacheDirectory: string): AsyncGenerator<VariantCacheEntry> {\n return flatten(variantCacheGenerations(cacheDirectory));\n}\n\n/** Keep directories in place so removing cache data cannot remove user folders. */\nexport async function purgeVariantCacheEntries(\n entries: AsyncIterable<VariantCacheEntry>,\n): Promise<boolean> {\n let removed = false;\n for await (const entry of entries) {\n await fs.rm(entry.target, { force: true });\n removed = true;\n }\n return removed;\n}\n"],"mappings":";;;;AAGA,MAAM,QAAQ;AACd,MAAM,SAAS;AACf,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,YACJ;AAaF,eAAe,QAAQ,WAAmB;CACxC,IAAI;EACF,OAAO,MAAM,GAAG,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;CAC5D,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO,CAAC;EAChE,MAAM;CACR;AACF;;AAGA,gBAAuB,8BACrB,iBACA,gBACwC;CACxC,IAAI;EAEF,KAAK,MAAM,aAAa,CAAC,KAAK,QAAQ,eAAe,GAAG,eAAe,GACrE,IAAI,EAAE,MAAM,GAAG,MAAM,SAAS,EAAC,CAAE,YAAY,GAAG;CAEpD,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU;EACxD,MAAM;CACR;CACA,KAAK,MAAM,cAAc,MAAM,QAAQ,eAAe,GAAG;EACvD,IACE,WAAW,SAAS,kBACpB,CAAC,WAAW,YAAY,KACxB,CAAC,WAAW,KAAK,WAAW,IAAI,GAEhC;EACF,MAAM,YAAY,KAAK,KAAK,iBAAiB,WAAW,IAAI;EAC5D,MAAM,QAA6B,CAAC;EACpC,KAAK,MAAM,QAAQ,MAAM,QAAQ,SAAS,GAAG;GAC3C,IAAI,CAAC,KAAK,OAAO,GAAG;GACpB,MAAM,OAAO,WAAW,KAAK,KAAK,IAAI,IAClC,eACA,KAAK,SAAS,gBACZ,aACA,UAAU,KAAK,KAAK,IAAI,IACtB,cACA;GACR,IAAI,MAAM,MAAM,KAAK;IAAE,QAAQ,KAAK,KAAK,WAAW,KAAK,IAAI;IAAG;GAAK,CAAC;EACxE;EACA,MAAM;GAAE;GAAW,SAAS;EAAM;CACpC;AACF;;AAGA,gBAAuB,wBACrB,gBACwC;CACxC,KAAK,MAAM,SAAS,MAAM,QAAQ,cAAc,GAAG;EACjD,IAAI,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG;EACrD,MAAM,YAAY,KAAK,KAAK,gBAAgB,MAAM,IAAI;EACtD,KAAK,MAAM,UAAU,MAAM,QAAQ,SAAS,GAAG;GAC7C,IAAI,CAAC,OAAO,YAAY,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,KAAK,WAAW,MAAM,IAAI,GAC1F;GACF,OAAO,8BAA8B,KAAK,KAAK,WAAW,OAAO,IAAI,CAAC;EACxE;CACF;AACF;AAEA,gBAAgB,QACd,aACmC;CACnC,WAAW,MAAM,cAAc,aAAa,OAAO,WAAW;AAChE;;AAGA,SAAgB,0BACd,iBACA,gBACmC;CACnC,OAAO,QAAQ,8BAA8B,iBAAiB,cAAc,CAAC;AAC/E;;AAGA,SAAgB,oBAAoB,gBAA2D;CAC7F,OAAO,QAAQ,wBAAwB,cAAc,CAAC;AACxD;;AAGA,eAAsB,yBACpB,SACkB;CAClB,IAAI,UAAU;CACd,WAAW,MAAM,SAAS,SAAS;EACjC,MAAM,GAAG,GAAG,MAAM,QAAQ,EAAE,OAAO,KAAK,CAAC;EACzC,UAAU;CACZ;CACA,OAAO;AACT"}
@@ -1,30 +1,88 @@
1
1
  import { rootPath } from "../../utils/paths.mjs";
2
+ import { configuredLocalDiskRoots } from "../../storage/local-disk-roots.mjs";
3
+ import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
4
+ import { purgeVariantCacheEntries, variantSourceCacheEntries } from "./image-variant-cache-entries.mjs";
2
5
  import { uploadsConfig } from "../uploads-config.mjs";
3
- import { isPathInside } from "./is-path-inside.mjs";
6
+ import { isPathInside, isPathWithin } from "./is-path-inside.mjs";
4
7
  import { variantGenerationTag, variantSourceDirectory, variantSourceKey } from "./variant-cache-key.mjs";
5
8
  import config from "@mongez/config";
6
9
  import { log } from "@warlock.js/logger";
7
10
  import path from "node:path";
8
- import fs from "node:fs/promises";
11
+ import fs from "node:fs";
12
+ import fs$1 from "node:fs/promises";
9
13
 
10
14
  //#region ../../packages/core/src/http/uploads/image-variant-cache.ts
11
15
  async function realpathOrSelf(target) {
12
16
  try {
13
- return await fs.realpath(target);
17
+ return await fs$1.realpath(target);
14
18
  } catch {
15
19
  return target;
16
20
  }
17
21
  }
22
+ function canonicalDirectory(target) {
23
+ const resolved = path.resolve(target);
24
+ try {
25
+ return fs.realpathSync(resolved);
26
+ } catch (error) {
27
+ const code = error.code;
28
+ if (code !== "ENOENT") throw new ImageVariantsConfigError(`uploads.images.cacheDirectory (or a local disk root) could not be resolved: ${resolved} (${code ?? "unknown error"})`, { cause: error });
29
+ const parent = path.dirname(resolved);
30
+ if (parent === resolved) throw error;
31
+ return path.join(canonicalDirectory(parent), path.basename(resolved));
32
+ }
33
+ }
34
+ /** Most distinct cache/disk-root combinations whose overlap check is remembered. */
35
+ const OVERLAP_CHECKS_LIMIT = 64;
36
+ /**
37
+ * Overlap check outcomes, keyed by the cache directory, the disk root and
38
+ * every configured local root. A disk registered later changes the key, so
39
+ * it is checked again; the stored error is rethrown for a refused layout.
40
+ */
41
+ const overlapChecks = /* @__PURE__ */ new Map();
42
+ function checkCacheOverlap(directory, diskRoot) {
43
+ const roots = [diskRoot, ...configuredLocalDiskRoots()];
44
+ const key = [directory, ...roots].join("\0");
45
+ if (overlapChecks.has(key)) {
46
+ const failure = overlapChecks.get(key);
47
+ if (failure) throw failure;
48
+ return;
49
+ }
50
+ let failure;
51
+ try {
52
+ const realCache = canonicalDirectory(directory);
53
+ for (const root of roots) if (isPathWithin(canonicalDirectory(root), realCache)) {
54
+ failure = new ImageVariantsConfigError("uploads.images.cacheDirectory must not equal or contain the storage root of any local disk (including symlinks).");
55
+ break;
56
+ }
57
+ } catch (error) {
58
+ if (!(error instanceof ImageVariantsConfigError)) throw error;
59
+ failure = error;
60
+ }
61
+ if (overlapChecks.size >= OVERLAP_CHECKS_LIMIT) {
62
+ const oldest = overlapChecks.keys().next();
63
+ if (!oldest.done) overlapChecks.delete(oldest.value);
64
+ }
65
+ overlapChecks.set(key, failure);
66
+ if (failure) throw failure;
67
+ }
18
68
  /**
19
69
  * The derivative cache directory for a local disk: `uploads.images.cacheDirectory`
20
70
  * when the app sets one (shared by every disk; derivatives are keyed by the
21
71
  * disk's real root, so they never collide), otherwise
22
72
  * `<diskRoot>/.cache/image-variants`. The same rule as
23
- * `resolveImageVariantsConfig`.
73
+ * `resolveImageVariantsConfig`. Refuses any cache that equals or contains
74
+ * any configured local root, resolving symlinks before comparison. The
75
+ * outcome is remembered per cache, disk root and configured local roots, so
76
+ * repeated requests do not resolve the same paths again.
77
+ *
78
+ * @throws ImageVariantsConfigError when a cache overlaps a local disk root, or
79
+ * when the cache or a root cannot be resolved (for example `ENOTDIR`, `EACCES`)
24
80
  */
25
- function variantCacheDirectoryFor(diskRoot) {
26
- const configured = uploadsConfig("images")?.cacheDirectory;
27
- return configured ? path.resolve(configured) : path.join(path.resolve(diskRoot), ".cache", "image-variants");
81
+ function variantCacheDirectoryFor(diskRoot, images = uploadsConfig("images")) {
82
+ const configured = images?.cacheDirectory;
83
+ const directory = configured ? path.resolve(configured) : path.join(path.resolve(diskRoot), ".cache", "image-variants");
84
+ checkCacheOverlap(directory, diskRoot);
85
+ return directory;
28
86
  }
29
87
  /**
30
88
  * The directory holding every derivative of one file on a local disk, or
@@ -44,39 +102,23 @@ async function variantSourceDirectoryForFile(diskRoot, absolutePath) {
44
102
  /**
45
103
  * Remove every cached derivative of one file on a local disk.
46
104
  *
47
- * @returns `true` when a cache directory for the file existed and was removed
105
+ * @returns `true` when recognized cache files were removed
48
106
  */
49
107
  async function purgeImageVariantsOfFile(diskRoot, absolutePath) {
50
108
  const directory = await variantSourceDirectoryForFile(diskRoot, absolutePath);
51
109
  if (!directory) return false;
52
- try {
53
- await fs.access(directory);
54
- } catch {
55
- return false;
56
- }
57
- await fs.rm(directory, {
58
- recursive: true,
59
- force: true
60
- });
61
- return true;
110
+ return purgeVariantCacheEntries(variantSourceCacheEntries(directory));
62
111
  }
63
112
  /**
64
113
  * Remove derivatives made from older versions of a source (a different size
65
114
  * or mtime), keeping only the `keep` generation. Called after a derivative of
66
115
  * the current version has been written, so a source rewritten in place does
67
- * not leave its old derivatives behind.
116
+ * not leave its old derivatives behind. Only recognized cache files in
117
+ * generation-shaped directories are removed; unrelated entries and all
118
+ * directories and symlinks stay in place.
68
119
  */
69
120
  async function pruneStaleVariantGenerations(sourceDirectory, keep) {
70
- let entries;
71
- try {
72
- entries = await fs.readdir(sourceDirectory);
73
- } catch {
74
- return;
75
- }
76
- await Promise.all(entries.filter((entry) => entry !== keep).map((entry) => fs.rm(path.join(sourceDirectory, entry), {
77
- recursive: true,
78
- force: true
79
- })));
121
+ await purgeVariantCacheEntries(variantSourceCacheEntries(sourceDirectory, keep));
80
122
  }
81
123
  /**
82
124
  * {@link pruneStaleVariantGenerations}, but only while `keep` is still the
@@ -90,7 +132,7 @@ async function pruneStaleVariantGenerations(sourceDirectory, keep) {
90
132
  async function pruneStaleVariantGenerationsIfCurrent(sourceAbsolutePath, sourceDirectory, keep) {
91
133
  let current;
92
134
  try {
93
- const stats = await fs.stat(sourceAbsolutePath);
135
+ const stats = await fs$1.stat(sourceAbsolutePath);
94
136
  current = variantGenerationTag(stats.size, stats.mtimeMs);
95
137
  } catch {
96
138
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"image-variant-cache.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variant-cache.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { rootPath } from \"../../utils/paths\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport { isPathInside } from \"./is-path-inside\";\nimport {\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\n\n/*\n * Cache housekeeping for image derivatives. Deliberately free of any\n * `storage` import: the local storage driver calls into this module when it\n * deletes or moves a file, and storage must not import back into the HTTP\n * layer that already imports storage.\n */\n\nasync function realpathOrSelf(target: string): Promise<string> {\n try {\n return await fs.realpath(target);\n } catch {\n return target;\n }\n}\n\n/**\n * The derivative cache directory for a local disk: `uploads.images.cacheDirectory`\n * when the app sets one (shared by every disk; derivatives are keyed by the\n * disk's real root, so they never collide), otherwise\n * `<diskRoot>/.cache/image-variants`. The same rule as\n * `resolveImageVariantsConfig`.\n */\nexport function variantCacheDirectoryFor(diskRoot: string): string {\n const configured = uploadsConfig(\"images\")?.cacheDirectory;\n\n return configured\n ? path.resolve(configured)\n : path.join(path.resolve(diskRoot), \".cache\", \"image-variants\");\n}\n\n/**\n * The directory holding every derivative of one file on a local disk, or\n * `undefined` when the file is not inside the disk root. The file does not\n * need to exist any more: only its parent directory is resolved through\n * symlinks, exactly as `resolveUploadPath` resolved it when the derivative\n * was made.\n */\nexport async function variantSourceDirectoryForFile(\n diskRoot: string,\n absolutePath: string,\n): Promise<string | undefined> {\n const realRoot = await realpathOrSelf(path.resolve(diskRoot));\n const realParent = await realpathOrSelf(path.dirname(path.resolve(absolutePath)));\n const realPath = path.join(realParent, path.basename(absolutePath));\n\n if (!isPathInside(realPath, realRoot)) return undefined;\n\n const relativePath = path.relative(realRoot, realPath).split(path.sep).join(\"/\");\n\n return variantSourceDirectory(\n variantCacheDirectoryFor(diskRoot),\n variantSourceKey(realRoot, relativePath),\n );\n}\n\n/**\n * Remove every cached derivative of one file on a local disk.\n *\n * @returns `true` when a cache directory for the file existed and was removed\n */\nexport async function purgeImageVariantsOfFile(\n diskRoot: string,\n absolutePath: string,\n): Promise<boolean> {\n const directory = await variantSourceDirectoryForFile(diskRoot, absolutePath);\n\n if (!directory) return false;\n\n try {\n await fs.access(directory);\n } catch {\n return false;\n }\n\n await fs.rm(directory, { recursive: true, force: true });\n\n return true;\n}\n\n/**\n * Remove derivatives made from older versions of a source (a different size\n * or mtime), keeping only the `keep` generation. Called after a derivative of\n * the current version has been written, so a source rewritten in place does\n * not leave its old derivatives behind.\n */\nexport async function pruneStaleVariantGenerations(\n sourceDirectory: string,\n keep: string,\n): Promise<void> {\n let entries: string[];\n\n try {\n entries = await fs.readdir(sourceDirectory);\n } catch {\n return;\n }\n\n await Promise.all(\n entries\n .filter((entry) => entry !== keep)\n .map((entry) => fs.rm(path.join(sourceDirectory, entry), { recursive: true, force: true })),\n );\n}\n\n/**\n * {@link pruneStaleVariantGenerations}, but only while `keep` is still the\n * source's current generation.\n *\n * A request that read the source before it was rewritten renders an old\n * generation; if it pruned afterwards it would delete the derivatives of the\n * newer version another request had just written. Re-reading the source's\n * size and mtime right before pruning keeps a stale request from doing that.\n */\nexport async function pruneStaleVariantGenerationsIfCurrent(\n sourceAbsolutePath: string,\n sourceDirectory: string,\n keep: string,\n): Promise<void> {\n let current: string;\n\n try {\n const stats = await fs.stat(sourceAbsolutePath);\n current = variantGenerationTag(stats.size, stats.mtimeMs);\n } catch {\n // the source is gone; its derivatives are purged by the delete itself\n return;\n }\n\n if (current !== keep) return;\n\n await pruneStaleVariantGenerations(sourceDirectory, keep);\n}\n\n/**\n * True for \"no such file or directory\"\n */\nexport function isMissingFileError(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === \"ENOENT\";\n}\n\n/**\n * Run a derivative write, once more if it fails because its directory\n * vanished: a concurrent request for a newer version of the source may prune\n * the generation directory between `mkdir` and the atomic rename.\n */\nexport async function retryOnVanishedDirectory(write: () => Promise<void>): Promise<void> {\n try {\n await write();\n } catch (error) {\n if (!isMissingFileError(error)) throw error;\n\n await write();\n }\n}\n\nlet warnedPublicCache = false;\n\n/**\n * Warn once when the derivative cache sits inside the public folder.\n *\n * A configured `uploads.images.cacheDirectory` is shared by every local disk,\n * so derivatives of a private disk land beside public ones. Inside\n * `public/` (or `storage.publicRoot`) they are served to anyone, bypassing\n * whatever guards the private disk's own route. The paths are hashes, but\n * they are derived from the disk root and the file path, which are not\n * secret.\n */\nexport function warnIfVariantCacheIsPublic(cacheDirectory: string): void {\n if (warnedPublicCache) return;\n\n const configured = config.get(\"storage.publicRoot\", rootPath(\"public\")) as unknown;\n const publicRoots = (Array.isArray(configured) ? configured : [configured])\n .filter((root): root is string => typeof root === \"string\" && root.length > 0)\n .map((root) => path.resolve(root));\n const resolved = path.resolve(cacheDirectory);\n\n if (!publicRoots.some((root) => resolved === root || isPathInside(resolved, root))) return;\n\n warnedPublicCache = true;\n log.warn(\n \"uploads\",\n \"image-variants\",\n `The image variant cache (${resolved}) is inside the public folder, so every cached derivative, private disks included, is publicly served. Move uploads.images.cacheDirectory outside public/.`,\n );\n}\n\n/**\n * Forget that the public-cache warning was shown (tests).\n */\nexport function resetVariantCacheWarnings(): void {\n warnedPublicCache = false;\n}\n"],"mappings":";;;;;;;;;;AAoBA,eAAe,eAAe,QAAiC;CAC7D,IAAI;EACF,OAAO,MAAM,GAAG,SAAS,MAAM;CACjC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;AASA,SAAgB,yBAAyB,UAA0B;CACjE,MAAM,aAAa,cAAc,QAAQ,CAAC,EAAE;CAE5C,OAAO,aACH,KAAK,QAAQ,UAAU,IACvB,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,UAAU,gBAAgB;AAClE;;;;;;;;AASA,eAAsB,8BACpB,UACA,cAC6B;CAC7B,MAAM,WAAW,MAAM,eAAe,KAAK,QAAQ,QAAQ,CAAC;CAC5D,MAAM,aAAa,MAAM,eAAe,KAAK,QAAQ,KAAK,QAAQ,YAAY,CAAC,CAAC;CAChF,MAAM,WAAW,KAAK,KAAK,YAAY,KAAK,SAAS,YAAY,CAAC;CAElE,IAAI,CAAC,aAAa,UAAU,QAAQ,GAAG,OAAO;CAE9C,MAAM,eAAe,KAAK,SAAS,UAAU,QAAQ,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CAE/E,OAAO,uBACL,yBAAyB,QAAQ,GACjC,iBAAiB,UAAU,YAAY,CACzC;AACF;;;;;;AAOA,eAAsB,yBACpB,UACA,cACkB;CAClB,MAAM,YAAY,MAAM,8BAA8B,UAAU,YAAY;CAE5E,IAAI,CAAC,WAAW,OAAO;CAEvB,IAAI;EACF,MAAM,GAAG,OAAO,SAAS;CAC3B,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,GAAG,GAAG,WAAW;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CAEvD,OAAO;AACT;;;;;;;AAQA,eAAsB,6BACpB,iBACA,MACe;CACf,IAAI;CAEJ,IAAI;EACF,UAAU,MAAM,GAAG,QAAQ,eAAe;CAC5C,QAAQ;EACN;CACF;CAEA,MAAM,QAAQ,IACZ,QACG,QAAQ,UAAU,UAAU,IAAI,CAAC,CACjC,KAAK,UAAU,GAAG,GAAG,KAAK,KAAK,iBAAiB,KAAK,GAAG;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC,CAAC,CAC9F;AACF;;;;;;;;;;AAWA,eAAsB,sCACpB,oBACA,iBACA,MACe;CACf,IAAI;CAEJ,IAAI;EACF,MAAM,QAAQ,MAAM,GAAG,KAAK,kBAAkB;EAC9C,UAAU,qBAAqB,MAAM,MAAM,MAAM,OAAO;CAC1D,QAAQ;EAEN;CACF;CAEA,IAAI,YAAY,MAAM;CAEtB,MAAM,6BAA6B,iBAAiB,IAAI;AAC1D;;;;AAKA,SAAgB,mBAAmB,OAAyB;CAC1D,OAAQ,OAA6C,SAAS;AAChE;;;;;;AAOA,eAAsB,yBAAyB,OAA2C;CACxF,IAAI;EACF,MAAM,MAAM;CACd,SAAS,OAAO;EACd,IAAI,CAAC,mBAAmB,KAAK,GAAG,MAAM;EAEtC,MAAM,MAAM;CACd;AACF;AAEA,IAAI,oBAAoB;;;;;;;;;;;AAYxB,SAAgB,2BAA2B,gBAA8B;CACvE,IAAI,mBAAmB;CAEvB,MAAM,aAAa,OAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;CACtE,MAAM,eAAe,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU,EAAC,CACvE,QAAQ,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CAAC,CAAC,CAC7E,KAAK,SAAS,KAAK,QAAQ,IAAI,CAAC;CACnC,MAAM,WAAW,KAAK,QAAQ,cAAc;CAE5C,IAAI,CAAC,YAAY,MAAM,SAAS,aAAa,QAAQ,aAAa,UAAU,IAAI,CAAC,GAAG;CAEpF,oBAAoB;CACpB,IAAI,KACF,WACA,kBACA,4BAA4B,SAAS,2JACvC;AACF"}
1
+ {"version":3,"file":"image-variant-cache.mjs","names":["fs","fsSync"],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variant-cache.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport fs from \"node:fs/promises\";\nimport fsSync from \"node:fs\";\nimport { configuredLocalDiskRoots } from \"../../storage/local-disk-roots\";\nimport type { UploadsImagesConfigurations } from \"../uploads-types\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport { purgeVariantCacheEntries, variantSourceCacheEntries } from \"./image-variant-cache-entries\";\nimport path from \"node:path\";\nimport { rootPath } from \"../../utils/paths\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport { isPathInside, isPathWithin } from \"./is-path-inside\";\nimport {\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\n\n/*\n * Cache housekeeping for image derivatives. Deliberately free of any\n * import of the storage manager or drivers: the local driver calls into this module when it\n * deletes or moves a file, and storage must not import back into the HTTP\n * layer that already imports storage.\n */\n\nasync function realpathOrSelf(target: string): Promise<string> {\n try {\n return await fs.realpath(target);\n } catch {\n return target;\n }\n}\n\nfunction canonicalDirectory(target: string): string {\n const resolved = path.resolve(target);\n try {\n return fsSync.realpathSync(resolved);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n // ENOTDIR, EACCES, ELOOP, ...: a configuration problem, reported as one.\n throw new ImageVariantsConfigError(\n `uploads.images.cacheDirectory (or a local disk root) could not be resolved: ${resolved} (${code ?? \"unknown error\"})`,\n { cause: error },\n );\n }\n const parent = path.dirname(resolved);\n if (parent === resolved) throw error;\n // Resolve the nearest existing ancestor too, so a symlink followed by a\n // not-yet-created directory cannot hide an overlap with a future disk root.\n return path.join(canonicalDirectory(parent), path.basename(resolved));\n }\n}\n\n/** Most distinct cache/disk-root combinations whose overlap check is remembered. */\nconst OVERLAP_CHECKS_LIMIT = 64;\n\n/**\n * Overlap check outcomes, keyed by the cache directory, the disk root and\n * every configured local root. A disk registered later changes the key, so\n * it is checked again; the stored error is rethrown for a refused layout.\n */\nconst overlapChecks = new Map<string, ImageVariantsConfigError | undefined>();\n\nfunction checkCacheOverlap(directory: string, diskRoot: string): void {\n const roots = [diskRoot, ...configuredLocalDiskRoots()];\n const key = [directory, ...roots].join(\"\\0\");\n if (overlapChecks.has(key)) {\n const failure = overlapChecks.get(key);\n if (failure) throw failure;\n return;\n }\n let failure: ImageVariantsConfigError | undefined;\n try {\n const realCache = canonicalDirectory(directory);\n for (const root of roots) {\n if (isPathWithin(canonicalDirectory(root), realCache)) {\n failure = new ImageVariantsConfigError(\n \"uploads.images.cacheDirectory must not equal or contain the storage root of any local disk (including symlinks).\",\n );\n break;\n }\n }\n } catch (error) {\n // Only configuration errors are remembered; anything else is retried next time.\n if (!(error instanceof ImageVariantsConfigError)) throw error;\n failure = error;\n }\n if (overlapChecks.size >= OVERLAP_CHECKS_LIMIT) {\n const oldest = overlapChecks.keys().next();\n if (!oldest.done) overlapChecks.delete(oldest.value);\n }\n overlapChecks.set(key, failure);\n if (failure) throw failure;\n}\n\n/**\n * Forget remembered cache overlap checks, so the next check resolves every\n * path again (tests). Changes to the configured disks are detected without it;\n * a directory replaced by a symlink at runtime is not.\n */\nexport function resetVariantCacheOverlapChecks(): void {\n overlapChecks.clear();\n}\n\n/**\n * The derivative cache directory for a local disk: `uploads.images.cacheDirectory`\n * when the app sets one (shared by every disk; derivatives are keyed by the\n * disk's real root, so they never collide), otherwise\n * `<diskRoot>/.cache/image-variants`. The same rule as\n * `resolveImageVariantsConfig`. Refuses any cache that equals or contains\n * any configured local root, resolving symlinks before comparison. The\n * outcome is remembered per cache, disk root and configured local roots, so\n * repeated requests do not resolve the same paths again.\n *\n * @throws ImageVariantsConfigError when a cache overlaps a local disk root, or\n * when the cache or a root cannot be resolved (for example `ENOTDIR`, `EACCES`)\n */\nexport function variantCacheDirectoryFor(\n diskRoot: string,\n images: Pick<UploadsImagesConfigurations, \"cacheDirectory\"> | undefined = uploadsConfig(\"images\"),\n): string {\n const configured = images?.cacheDirectory;\n const directory = configured\n ? path.resolve(configured)\n : path.join(path.resolve(diskRoot), \".cache\", \"image-variants\");\n checkCacheOverlap(directory, diskRoot);\n return directory;\n}\n\n/**\n * The directory holding every derivative of one file on a local disk, or\n * `undefined` when the file is not inside the disk root. The file does not\n * need to exist any more: only its parent directory is resolved through\n * symlinks, exactly as `resolveUploadPath` resolved it when the derivative\n * was made.\n */\nexport async function variantSourceDirectoryForFile(\n diskRoot: string,\n absolutePath: string,\n): Promise<string | undefined> {\n const realRoot = await realpathOrSelf(path.resolve(diskRoot));\n const realParent = await realpathOrSelf(path.dirname(path.resolve(absolutePath)));\n const realPath = path.join(realParent, path.basename(absolutePath));\n\n if (!isPathInside(realPath, realRoot)) return undefined;\n\n const relativePath = path.relative(realRoot, realPath).split(path.sep).join(\"/\");\n\n return variantSourceDirectory(\n variantCacheDirectoryFor(diskRoot),\n variantSourceKey(realRoot, relativePath),\n );\n}\n\n/**\n * Remove every cached derivative of one file on a local disk.\n *\n * @returns `true` when recognized cache files were removed\n */\nexport async function purgeImageVariantsOfFile(\n diskRoot: string,\n absolutePath: string,\n): Promise<boolean> {\n const directory = await variantSourceDirectoryForFile(diskRoot, absolutePath);\n\n if (!directory) return false;\n\n return purgeVariantCacheEntries(variantSourceCacheEntries(directory));\n}\n\n/**\n * Remove derivatives made from older versions of a source (a different size\n * or mtime), keeping only the `keep` generation. Called after a derivative of\n * the current version has been written, so a source rewritten in place does\n * not leave its old derivatives behind. Only recognized cache files in\n * generation-shaped directories are removed; unrelated entries and all\n * directories and symlinks stay in place.\n */\nexport async function pruneStaleVariantGenerations(\n sourceDirectory: string,\n keep: string,\n): Promise<void> {\n await purgeVariantCacheEntries(variantSourceCacheEntries(sourceDirectory, keep));\n}\n\n/**\n * {@link pruneStaleVariantGenerations}, but only while `keep` is still the\n * source's current generation.\n *\n * A request that read the source before it was rewritten renders an old\n * generation; if it pruned afterwards it would delete the derivatives of the\n * newer version another request had just written. Re-reading the source's\n * size and mtime right before pruning keeps a stale request from doing that.\n */\nexport async function pruneStaleVariantGenerationsIfCurrent(\n sourceAbsolutePath: string,\n sourceDirectory: string,\n keep: string,\n): Promise<void> {\n let current: string;\n\n try {\n const stats = await fs.stat(sourceAbsolutePath);\n current = variantGenerationTag(stats.size, stats.mtimeMs);\n } catch {\n // the source is gone; its derivatives are purged by the delete itself\n return;\n }\n\n if (current !== keep) return;\n\n await pruneStaleVariantGenerations(sourceDirectory, keep);\n}\n\n/**\n * True for \"no such file or directory\"\n */\nexport function isMissingFileError(error: unknown): boolean {\n return (error as NodeJS.ErrnoException | undefined)?.code === \"ENOENT\";\n}\n\n/**\n * Run a derivative write, once more if it fails because its directory\n * vanished: a concurrent request for a newer version of the source may prune\n * the generation directory between `mkdir` and the atomic rename.\n */\nexport async function retryOnVanishedDirectory(write: () => Promise<void>): Promise<void> {\n try {\n await write();\n } catch (error) {\n if (!isMissingFileError(error)) throw error;\n\n await write();\n }\n}\n\nlet warnedPublicCache = false;\n\n/**\n * Warn once when the derivative cache sits inside the public folder.\n *\n * A configured `uploads.images.cacheDirectory` is shared by every local disk,\n * so derivatives of a private disk land beside public ones. Inside\n * `public/` (or `storage.publicRoot`) they are served to anyone, bypassing\n * whatever guards the private disk's own route. The paths are hashes, but\n * they are derived from the disk root and the file path, which are not\n * secret.\n */\nexport function warnIfVariantCacheIsPublic(cacheDirectory: string): void {\n if (warnedPublicCache) return;\n\n const configured = config.get(\"storage.publicRoot\", rootPath(\"public\")) as unknown;\n const publicRoots = (Array.isArray(configured) ? configured : [configured])\n .filter((root): root is string => typeof root === \"string\" && root.length > 0)\n .map((root) => path.resolve(root));\n const resolved = path.resolve(cacheDirectory);\n\n if (!publicRoots.some((root) => resolved === root || isPathInside(resolved, root))) return;\n\n warnedPublicCache = true;\n log.warn(\n \"uploads\",\n \"image-variants\",\n `The image variant cache (${resolved}) is inside the public folder, so every cached derivative, private disks included, is publicly served. Move uploads.images.cacheDirectory outside public/.`,\n );\n}\n\n/**\n * Forget that the public-cache warning was shown (tests).\n */\nexport function resetVariantCacheWarnings(): void {\n warnedPublicCache = false;\n}\n"],"mappings":";;;;;;;;;;;;;;AAyBA,eAAe,eAAe,QAAiC;CAC7D,IAAI;EACF,OAAO,MAAMA,KAAG,SAAS,MAAM;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,QAAwB;CAClD,MAAM,WAAW,KAAK,QAAQ,MAAM;CACpC,IAAI;EACF,OAAOC,GAAO,aAAa,QAAQ;CACrC,SAAS,OAAO;EACd,MAAM,OAAQ,MAAgC;EAC9C,IAAI,SAAS,UAEX,MAAM,IAAI,yBACR,+EAA+E,SAAS,IAAI,QAAQ,gBAAgB,IACpH,EAAE,OAAO,MAAM,CACjB;EAEF,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,IAAI,WAAW,UAAU,MAAM;EAG/B,OAAO,KAAK,KAAK,mBAAmB,MAAM,GAAG,KAAK,SAAS,QAAQ,CAAC;CACtE;AACF;;AAGA,MAAM,uBAAuB;;;;;;AAO7B,MAAM,gCAAgB,IAAI,IAAkD;AAE5E,SAAS,kBAAkB,WAAmB,UAAwB;CACpE,MAAM,QAAQ,CAAC,UAAU,GAAG,yBAAyB,CAAC;CACtD,MAAM,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI;CAC3C,IAAI,cAAc,IAAI,GAAG,GAAG;EAC1B,MAAM,UAAU,cAAc,IAAI,GAAG;EACrC,IAAI,SAAS,MAAM;EACnB;CACF;CACA,IAAI;CACJ,IAAI;EACF,MAAM,YAAY,mBAAmB,SAAS;EAC9C,KAAK,MAAM,QAAQ,OACjB,IAAI,aAAa,mBAAmB,IAAI,GAAG,SAAS,GAAG;GACrD,UAAU,IAAI,yBACZ,kHACF;GACA;EACF;CAEJ,SAAS,OAAO;EAEd,IAAI,EAAE,iBAAiB,2BAA2B,MAAM;EACxD,UAAU;CACZ;CACA,IAAI,cAAc,QAAQ,sBAAsB;EAC9C,MAAM,SAAS,cAAc,KAAK,CAAC,CAAC,KAAK;EACzC,IAAI,CAAC,OAAO,MAAM,cAAc,OAAO,OAAO,KAAK;CACrD;CACA,cAAc,IAAI,KAAK,OAAO;CAC9B,IAAI,SAAS,MAAM;AACrB;;;;;;;;;;;;;;AAwBA,SAAgB,yBACd,UACA,SAA0E,cAAc,QAAQ,GACxF;CACR,MAAM,aAAa,QAAQ;CAC3B,MAAM,YAAY,aACd,KAAK,QAAQ,UAAU,IACvB,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,UAAU,gBAAgB;CAChE,kBAAkB,WAAW,QAAQ;CACrC,OAAO;AACT;;;;;;;;AASA,eAAsB,8BACpB,UACA,cAC6B;CAC7B,MAAM,WAAW,MAAM,eAAe,KAAK,QAAQ,QAAQ,CAAC;CAC5D,MAAM,aAAa,MAAM,eAAe,KAAK,QAAQ,KAAK,QAAQ,YAAY,CAAC,CAAC;CAChF,MAAM,WAAW,KAAK,KAAK,YAAY,KAAK,SAAS,YAAY,CAAC;CAElE,IAAI,CAAC,aAAa,UAAU,QAAQ,GAAG,OAAO;CAE9C,MAAM,eAAe,KAAK,SAAS,UAAU,QAAQ,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CAE/E,OAAO,uBACL,yBAAyB,QAAQ,GACjC,iBAAiB,UAAU,YAAY,CACzC;AACF;;;;;;AAOA,eAAsB,yBACpB,UACA,cACkB;CAClB,MAAM,YAAY,MAAM,8BAA8B,UAAU,YAAY;CAE5E,IAAI,CAAC,WAAW,OAAO;CAEvB,OAAO,yBAAyB,0BAA0B,SAAS,CAAC;AACtE;;;;;;;;;AAUA,eAAsB,6BACpB,iBACA,MACe;CACf,MAAM,yBAAyB,0BAA0B,iBAAiB,IAAI,CAAC;AACjF;;;;;;;;;;AAWA,eAAsB,sCACpB,oBACA,iBACA,MACe;CACf,IAAI;CAEJ,IAAI;EACF,MAAM,QAAQ,MAAMD,KAAG,KAAK,kBAAkB;EAC9C,UAAU,qBAAqB,MAAM,MAAM,MAAM,OAAO;CAC1D,QAAQ;EAEN;CACF;CAEA,IAAI,YAAY,MAAM;CAEtB,MAAM,6BAA6B,iBAAiB,IAAI;AAC1D;;;;AAKA,SAAgB,mBAAmB,OAAyB;CAC1D,OAAQ,OAA6C,SAAS;AAChE;;;;;;AAOA,eAAsB,yBAAyB,OAA2C;CACxF,IAAI;EACF,MAAM,MAAM;CACd,SAAS,OAAO;EACd,IAAI,CAAC,mBAAmB,KAAK,GAAG,MAAM;EAEtC,MAAM,MAAM;CACd;AACF;AAEA,IAAI,oBAAoB;;;;;;;;;;;AAYxB,SAAgB,2BAA2B,gBAA8B;CACvE,IAAI,mBAAmB;CAEvB,MAAM,aAAa,OAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;CACtE,MAAM,eAAe,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU,EAAC,CACvE,QAAQ,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CAAC,CAAC,CAC7E,KAAK,SAAS,KAAK,QAAQ,IAAI,CAAC;CACnC,MAAM,WAAW,KAAK,QAAQ,cAAc;CAE5C,IAAI,CAAC,YAAY,MAAM,SAAS,aAAa,QAAQ,aAAa,UAAU,IAAI,CAAC,GAAG;CAEpF,oBAAoB;CACpB,IAAI,KACF,WACA,kBACA,4BAA4B,SAAS,2JACvC;AACF"}
@@ -10,6 +10,8 @@ type ResolvedImageVariantsConfig = {
10
10
  maxSourceBytes: number;
11
11
  maxSourcePixels: number;
12
12
  cacheDirectory: string;
13
+ maxCacheBytes: number;
14
+ maxCacheAge: number;
13
15
  };
14
16
  /**
15
17
  * Raster formats a variant can be generated from, detected by magic bytes
@@ -1 +1 @@
1
- {"version":3,"file":"image-variant-types.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variant-types.ts"],"mappings":";;;;;AAKA;KAAY,2BAAA;EACV,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,sBAAA;EAClC,OAAA,WAAkB,wBAAA;EAClB,cAAA;EACA,eAAA;EACA,cAAA;AAAA;;;;KAMU,mBAAA;;;;KAKA,mBAAA,GAAsB,mBAAmB;;;;KAKzC,mBAAA,GAAsB,mBAAmB;AAVrD;;;AAAA,KAeY,iBAAA;EACN,IAAA;AAAA;EACA,IAAA;EAAiB,OAAA;EAAiB,MAAA;AAAA;EAClC,IAAA;EAAiB,MAAA;AAAA;;AAR8B;AAKrD;;;;;;KAaY,qBAAA;EAX4B;;;EAetC,KAAA;EAd2B;AAU7B;;EASE,MAAA;EAUsB;;;EALtB,GAAA;EAKc;;;EAAd,IAAA,GAAO,OAAA,CAAQ,MAAA,CAAO,wBAAA;AAAA;;;;;KAOZ,wBAAA;EAAA;;;EAIV,GAAA;EAemB;;;EAVnB,KAAA;EAe2C;;;EAV3C,MAAA;EAKA;;;EAAA,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,qBAAA;EAKlC;;;EAAA,OAAA,YAAmB,wBAAA;AAAA"}
1
+ {"version":3,"file":"image-variant-types.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variant-types.ts"],"mappings":";;;;;AAKA;KAAY,2BAAA;EACV,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,sBAAA;EAClC,OAAA,WAAkB,wBAAA;EAClB,cAAA;EACA,eAAA;EACA,cAAA;EACA,aAAA;EACA,WAAA;AAAA;;;;KAMU,mBAAA;;;;KAKA,mBAAA,GAAsB,mBAAmB;;;;KAKzC,mBAAA,GAAsB,mBAAmB;AAVrD;;;AAAA,KAeY,iBAAA;EACN,IAAA;AAAA;EACA,IAAA;EAAiB,OAAA;EAAiB,MAAA;AAAA;EAClC,IAAA;EAAiB,MAAA;AAAA;;AAR8B;AAKrD;;;;;;KAaY,qBAAA;EAX4B;;;EAetC,KAAA;EAd2B;AAU7B;;EASE,MAAA;EAUsB;;;EALtB,GAAA;EAKc;;;EAAd,IAAA,GAAO,OAAA,CAAQ,MAAA,CAAO,wBAAA;AAAA;;;;;KAOZ,wBAAA;EAAA;;;EAIV,GAAA;EAemB;;;EAVnB,KAAA;EAe2C;;;EAV3C,MAAA;EAKA;;;EAAA,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,qBAAA;EAKlC;;;EAAA,OAAA,YAAmB,wBAAA;AAAA"}
@@ -6,7 +6,9 @@
6
6
  * on the first image instead of producing unbounded or surprising derivatives.
7
7
  */
8
8
  declare class ImageVariantsConfigError extends Error {
9
- constructor(message: string);
9
+ /** Stable Warlock error code; `warlock explain WL1005` prints the fix. */
10
+ readonly code = "WL1005";
11
+ constructor(message: string, options?: ErrorOptions);
10
12
  }
11
13
  //#endregion
12
14
  export { ImageVariantsConfigError };
@@ -1 +1 @@
1
- {"version":3,"file":"image-variants-config-error.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variants-config-error.ts"],"mappings":";;AAMA;;;;;cAAa,wBAAA,SAAiC,KAAK;cAC9B,OAAA;AAAA"}
1
+ {"version":3,"file":"image-variants-config-error.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variants-config-error.ts"],"mappings":";;AAMA;;;;;cAAa,wBAAA,SAAiC,KAAK;;WAEjC,IAAA;cAEG,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA"}
@@ -6,8 +6,9 @@
6
6
  * on the first image instead of producing unbounded or surprising derivatives.
7
7
  */
8
8
  var ImageVariantsConfigError = class extends Error {
9
- constructor(message) {
10
- super(`Invalid uploads.images configuration: ${message}`);
9
+ constructor(message, options) {
10
+ super(`Invalid uploads.images configuration: ${message}`, options);
11
+ this.code = "WL1005";
11
12
  this.name = "ImageVariantsConfigError";
12
13
  }
13
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"image-variants-config-error.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variants-config-error.ts"],"sourcesContent":["/**\n * Thrown when the `uploads.images` configuration is invalid.\n *\n * Raised the first time a variant is requested, so a bad config fails loudly\n * on the first image instead of producing unbounded or surprising derivatives.\n */\nexport class ImageVariantsConfigError extends Error {\n public constructor(message: string) {\n super(`Invalid uploads.images configuration: ${message}`);\n\n this.name = \"ImageVariantsConfigError\";\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,2BAAb,cAA8C,MAAM;CAClD,AAAO,YAAY,SAAiB;EAClC,MAAM,yCAAyC,SAAS;EAExD,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"image-variants-config-error.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/image-variants-config-error.ts"],"sourcesContent":["/**\n * Thrown when the `uploads.images` configuration is invalid.\n *\n * Raised the first time a variant is requested, so a bad config fails loudly\n * on the first image instead of producing unbounded or surprising derivatives.\n */\nexport class ImageVariantsConfigError extends Error {\n /** Stable Warlock error code; `warlock explain WL1005` prints the fix. */\n public readonly code = \"WL1005\";\n\n public constructor(message: string, options?: ErrorOptions) {\n super(`Invalid uploads.images configuration: ${message}`, options);\n\n this.name = \"ImageVariantsConfigError\";\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,2BAAb,cAA8C,MAAM;CAIlD,AAAO,YAAY,SAAiB,SAAwB;EAC1D,MAAM,yCAAyC,WAAW,OAAO;cAH5C;EAKrB,KAAK,OAAO;CACd;AACF"}
@@ -21,7 +21,7 @@ import fs from "node:fs/promises";
21
21
  * I/O) propagates unchanged.
22
22
  */
23
23
  async function loadVariantSource(absolutePath, { maxSourceBytes, maxSourcePixels, expectedFormat }) {
24
- const buffer = await fs.readFile(absolutePath);
24
+ const buffer = Buffer.isBuffer(absolutePath) ? absolutePath : await fs.readFile(absolutePath);
25
25
  if (buffer.length > maxSourceBytes) throw new HttpError(413, "The source image is too large.");
26
26
  if (detectImageFormat(buffer) !== expectedFormat) throw new HttpError(415, "The source image changed format while it was read.");
27
27
  const image = new Image(buffer);
@@ -1 +1 @@
1
- {"version":3,"file":"load-variant-source.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/load-variant-source.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { Image } from \"../../image\";\nimport { HttpError } from \"../errors\";\nimport { detectImageFormat } from \"./detect-image-format\";\nimport type { VariantSourceFormat } from \"./image-variant-types\";\n\n/**\n * Bounds and the expected format a source must match\n */\nexport type LoadVariantSourceOptions = {\n maxSourceBytes: number;\n maxSourcePixels: number;\n expectedFormat: VariantSourceFormat;\n};\n\n/**\n * Read a variant source in full and check it before it is decoded.\n *\n * The caller has already sniffed the format from the file head; this re-checks\n * the bytes actually read, the same buffer that is then resized, so the file\n * cannot be swapped between the check and the render.\n *\n * - bytes over `maxSourceBytes` → 413\n * - magic bytes that no longer match `expectedFormat` → 415\n * - a header sharp cannot read (e.g. avif without a decoder) → 415\n * - width × height over `maxSourcePixels` → 413, read from the header only\n *\n * @throws HttpError with status 413 or 415. Any other error (sharp missing,\n * I/O) propagates unchanged.\n */\nexport async function loadVariantSource(\n absolutePath: string,\n { maxSourceBytes, maxSourcePixels, expectedFormat }: LoadVariantSourceOptions,\n): Promise<Image> {\n const buffer = await fs.readFile(absolutePath);\n\n if (buffer.length > maxSourceBytes) {\n throw new HttpError(413, \"The source image is too large.\");\n }\n\n if (detectImageFormat(buffer) !== expectedFormat) {\n throw new HttpError(415, \"The source image changed format while it was read.\");\n }\n\n // Constructed outside the try below: sharp being absent is a server fault\n // (500 with the install hint), not an unsupported image.\n const image = new Image(buffer);\n\n let width: number | undefined;\n let height: number | undefined;\n\n try {\n ({ width, height } = await image.metadata());\n } catch {\n throw new HttpError(415, \"The source image cannot be decoded.\");\n }\n\n if (!width || !height) {\n throw new HttpError(415, \"The source image cannot be decoded.\");\n }\n\n if (width * height > maxSourcePixels) {\n throw new HttpError(413, \"The source image has too many pixels.\");\n }\n\n return image;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,kBACpB,cACA,EAAE,gBAAgB,iBAAiB,kBACnB;CAChB,MAAM,SAAS,MAAM,GAAG,SAAS,YAAY;CAE7C,IAAI,OAAO,SAAS,gBAClB,MAAM,IAAI,UAAU,KAAK,gCAAgC;CAG3D,IAAI,kBAAkB,MAAM,MAAM,gBAChC,MAAM,IAAI,UAAU,KAAK,oDAAoD;CAK/E,MAAM,QAAQ,IAAI,MAAM,MAAM;CAE9B,IAAI;CACJ,IAAI;CAEJ,IAAI;EACF,CAAC,CAAE,OAAO,UAAW,MAAM,MAAM,SAAS;CAC5C,QAAQ;EACN,MAAM,IAAI,UAAU,KAAK,qCAAqC;CAChE;CAEA,IAAI,CAAC,SAAS,CAAC,QACb,MAAM,IAAI,UAAU,KAAK,qCAAqC;CAGhE,IAAI,QAAQ,SAAS,iBACnB,MAAM,IAAI,UAAU,KAAK,uCAAuC;CAGlE,OAAO;AACT"}
1
+ {"version":3,"file":"load-variant-source.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/load-variant-source.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { Image } from \"../../image\";\nimport { HttpError } from \"../errors\";\nimport { detectImageFormat } from \"./detect-image-format\";\nimport type { VariantSourceFormat } from \"./image-variant-types\";\n\n/**\n * Bounds and the expected format a source must match\n */\nexport type LoadVariantSourceOptions = {\n maxSourceBytes: number;\n maxSourcePixels: number;\n expectedFormat: VariantSourceFormat;\n};\n\n/**\n * Read a variant source in full and check it before it is decoded.\n *\n * The caller has already sniffed the format from the file head; this re-checks\n * the bytes actually read, the same buffer that is then resized, so the file\n * cannot be swapped between the check and the render.\n *\n * - bytes over `maxSourceBytes` → 413\n * - magic bytes that no longer match `expectedFormat` → 415\n * - a header sharp cannot read (e.g. avif without a decoder) → 415\n * - width × height over `maxSourcePixels` → 413, read from the header only\n *\n * @throws HttpError with status 413 or 415. Any other error (sharp missing,\n * I/O) propagates unchanged.\n */\nexport async function loadVariantSource(\n absolutePath: string | Buffer,\n { maxSourceBytes, maxSourcePixels, expectedFormat }: LoadVariantSourceOptions,\n): Promise<Image> {\n const buffer = Buffer.isBuffer(absolutePath) ? absolutePath : await fs.readFile(absolutePath);\n\n if (buffer.length > maxSourceBytes) {\n throw new HttpError(413, \"The source image is too large.\");\n }\n\n if (detectImageFormat(buffer) !== expectedFormat) {\n throw new HttpError(415, \"The source image changed format while it was read.\");\n }\n\n // Constructed outside the try below: sharp being absent is a server fault\n // (500 with the install hint), not an unsupported image.\n const image = new Image(buffer);\n\n let width: number | undefined;\n let height: number | undefined;\n\n try {\n ({ width, height } = await image.metadata());\n } catch {\n throw new HttpError(415, \"The source image cannot be decoded.\");\n }\n\n if (!width || !height) {\n throw new HttpError(415, \"The source image cannot be decoded.\");\n }\n\n if (width * height > maxSourcePixels) {\n throw new HttpError(413, \"The source image has too many pixels.\");\n }\n\n return image;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,kBACpB,cACA,EAAE,gBAAgB,iBAAiB,kBACnB;CAChB,MAAM,SAAS,OAAO,SAAS,YAAY,IAAI,eAAe,MAAM,GAAG,SAAS,YAAY;CAE5F,IAAI,OAAO,SAAS,gBAClB,MAAM,IAAI,UAAU,KAAK,gCAAgC;CAG3D,IAAI,kBAAkB,MAAM,MAAM,gBAChC,MAAM,IAAI,UAAU,KAAK,oDAAoD;CAK/E,MAAM,QAAQ,IAAI,MAAM,MAAM;CAE9B,IAAI;CACJ,IAAI;CAEJ,IAAI;EACF,CAAC,CAAE,OAAO,UAAW,MAAM,MAAM,SAAS;CAC5C,QAAQ;EACN,MAAM,IAAI,UAAU,KAAK,qCAAqC;CAChE;CAEA,IAAI,CAAC,SAAS,CAAC,QACb,MAAM,IAAI,UAAU,KAAK,qCAAqC;CAGhE,IAAI,QAAQ,SAAS,iBACnB,MAAM,IAAI,UAAU,KAAK,uCAAuC;CAGlE,OAAO;AACT"}
@@ -6,7 +6,7 @@ import { StorageDriverName } from "../../storage/types.mjs";
6
6
  */
7
7
  type PurgeImageVariantsOptions = {
8
8
  /**
9
- * Local disk the source lives on; the default disk when omitted
9
+ * Disk the source lives on; the default disk when omitted
10
10
  */
11
11
  disk?: StorageDriverName;
12
12
  };
@@ -19,12 +19,17 @@ type PurgeImageVariantsOptions = {
19
19
  * `uploads.images.cacheDirectory` is set, that directory is shared by every
20
20
  * local disk, so this clears all of them.
21
21
  *
22
+ * Only recognized derivative, source-manifest and temporary files in the
23
+ * generated cache layout are removed. The cache root, directories, unrelated
24
+ * files and symlinks stay in place. Use a dedicated folder for derivatives.
25
+ *
22
26
  * Deleting or moving a file through local storage (`storage.delete`,
23
27
  * `deleteMany`, `move`) already purges its derivatives; call this for files
24
28
  * removed some other way.
25
29
  *
30
+ * @throws ImageVariantsConfigError when the cache equals or contains any
31
+ * configured local disk root, including through a symlink
26
32
  * @returns `true` when something was removed
27
- * @throws ImageVariantsConfigError when the disk is not a local disk
28
33
  */
29
34
  declare function purgeImageVariants(relativePath?: string, options?: PurgeImageVariantsOptions): Promise<boolean>;
30
35
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"purge-image-variants.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/purge-image-variants.ts"],"mappings":";;;;;AAUA;KAAY,yBAAA;;;AAIc;EAAxB,IAAA,GAAO,iBAAiB;AAAA;;;;;;;;AAsBhB;;;;;;;;;iBAHY,kBAAA,CACpB,YAAA,WACA,OAAA,GAAS,yBAAA,GACR,OAAO"}
1
+ {"version":3,"file":"purge-image-variants.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/purge-image-variants.ts"],"mappings":";;;;;AAeA;KAAY,yBAAA;;;AAIc;EAAxB,IAAA,GAAO,iBAAiB;AAAA;;;;;;;;AA2BhB;;;;;;;;;;;;;;iBAHY,kBAAA,CACpB,YAAA,WACA,OAAA,GAAS,yBAAA,GACR,OAAO"}
@@ -1,8 +1,9 @@
1
+ import { purgeVariantCacheEntries, variantCacheEntries, variantSourceCacheEntries } from "./image-variant-cache-entries.mjs";
2
+ import { variantSourceDirectory, variantSourceKey } from "./variant-cache-key.mjs";
1
3
  import { purgeImageVariantsOfFile, variantCacheDirectoryFor } from "./image-variant-cache.mjs";
2
- import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
3
4
  import { localDiskRoot } from "./local-disk-root.mjs";
5
+ import { remoteVariantDisk, remoteVariantPath } from "./remote-variant-source.mjs";
4
6
  import path from "node:path";
5
- import fs from "node:fs/promises";
6
7
 
7
8
  //#region ../../packages/core/src/http/uploads/purge-image-variants.ts
8
9
  /**
@@ -14,29 +15,28 @@ import fs from "node:fs/promises";
14
15
  * `uploads.images.cacheDirectory` is set, that directory is shared by every
15
16
  * local disk, so this clears all of them.
16
17
  *
18
+ * Only recognized derivative, source-manifest and temporary files in the
19
+ * generated cache layout are removed. The cache root, directories, unrelated
20
+ * files and symlinks stay in place. Use a dedicated folder for derivatives.
21
+ *
17
22
  * Deleting or moving a file through local storage (`storage.delete`,
18
23
  * `deleteMany`, `move`) already purges its derivatives; call this for files
19
24
  * removed some other way.
20
25
  *
26
+ * @throws ImageVariantsConfigError when the cache equals or contains any
27
+ * configured local disk root, including through a symlink
21
28
  * @returns `true` when something was removed
22
- * @throws ImageVariantsConfigError when the disk is not a local disk
23
29
  */
24
30
  async function purgeImageVariants(relativePath, options = {}) {
25
31
  const diskRoot = localDiskRoot(options.disk);
26
- if (!diskRoot) throw new ImageVariantsConfigError(options.disk === void 0 ? "purgeImageVariants needs a local storage driver with a root." : `purgeImageVariants needs a local disk; "${options.disk}" is not one.`);
27
- if (relativePath === void 0) {
28
- const directory = variantCacheDirectoryFor(diskRoot);
29
- try {
30
- await fs.access(directory);
31
- } catch {
32
- return false;
33
- }
34
- await fs.rm(directory, {
35
- recursive: true,
36
- force: true
37
- });
38
- return true;
32
+ if (!diskRoot) {
33
+ const disk = remoteVariantDisk(options.disk);
34
+ const location = relativePath === void 0 ? void 0 : remoteVariantPath(relativePath);
35
+ if (relativePath !== void 0 && location === void 0) return false;
36
+ const cache = variantCacheDirectoryFor(disk.cacheRoot);
37
+ return purgeVariantCacheEntries(location === void 0 ? variantCacheEntries(cache) : variantSourceCacheEntries(variantSourceDirectory(cache, variantSourceKey(disk.root, disk.driver.applyPrefix(location)))));
39
38
  }
39
+ if (relativePath === void 0) return purgeVariantCacheEntries(variantCacheEntries(variantCacheDirectoryFor(diskRoot)));
40
40
  const location = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
41
41
  return purgeImageVariantsOfFile(diskRoot, path.resolve(diskRoot, location));
42
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"purge-image-variants.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/purge-image-variants.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { StorageDriverName } from \"../../storage/types\";\nimport { purgeImageVariantsOfFile, variantCacheDirectoryFor } from \"./image-variant-cache\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport { localDiskRoot } from \"./local-disk-root\";\n\n/**\n * Options for {@link purgeImageVariants}\n */\nexport type PurgeImageVariantsOptions = {\n /**\n * Local disk the source lives on; the default disk when omitted\n */\n disk?: StorageDriverName;\n};\n\n/**\n * Remove cached image derivatives.\n *\n * - With a path: every derivative of that file, in every variant and format.\n * The file itself is untouched and does not need to exist.\n * - Without a path: the whole derivative cache of the disk. When\n * `uploads.images.cacheDirectory` is set, that directory is shared by every\n * local disk, so this clears all of them.\n *\n * Deleting or moving a file through local storage (`storage.delete`,\n * `deleteMany`, `move`) already purges its derivatives; call this for files\n * removed some other way.\n *\n * @returns `true` when something was removed\n * @throws ImageVariantsConfigError when the disk is not a local disk\n */\nexport async function purgeImageVariants(\n relativePath?: string,\n options: PurgeImageVariantsOptions = {},\n): Promise<boolean> {\n const diskRoot = localDiskRoot(options.disk);\n\n if (!diskRoot) {\n throw new ImageVariantsConfigError(\n options.disk === undefined\n ? \"purgeImageVariants needs a local storage driver with a root.\"\n : `purgeImageVariants needs a local disk; \"${options.disk}\" is not one.`,\n );\n }\n\n if (relativePath === undefined) {\n const directory = variantCacheDirectoryFor(diskRoot);\n\n try {\n await fs.access(directory);\n } catch {\n return false;\n }\n\n await fs.rm(directory, { recursive: true, force: true });\n\n return true;\n }\n\n const location = relativePath.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\");\n\n return purgeImageVariantsOfFile(diskRoot, path.resolve(diskRoot, location));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,eAAsB,mBACpB,cACA,UAAqC,CAAC,GACpB;CAClB,MAAM,WAAW,cAAc,QAAQ,IAAI;CAE3C,IAAI,CAAC,UACH,MAAM,IAAI,yBACR,QAAQ,SAAS,SACb,iEACA,2CAA2C,QAAQ,KAAK,cAC9D;CAGF,IAAI,iBAAiB,QAAW;EAC9B,MAAM,YAAY,yBAAyB,QAAQ;EAEnD,IAAI;GACF,MAAM,GAAG,OAAO,SAAS;EAC3B,QAAQ;GACN,OAAO;EACT;EAEA,MAAM,GAAG,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EAEvD,OAAO;CACT;CAEA,MAAM,WAAW,aAAa,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE;CAEpE,OAAO,yBAAyB,UAAU,KAAK,QAAQ,UAAU,QAAQ,CAAC;AAC5E"}
1
+ {"version":3,"file":"purge-image-variants.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/purge-image-variants.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { StorageDriverName } from \"../../storage/types\";\nimport { purgeImageVariantsOfFile, variantCacheDirectoryFor } from \"./image-variant-cache\";\nimport { remoteVariantDisk, remoteVariantPath } from \"./remote-variant-source\";\nimport { variantSourceDirectory, variantSourceKey } from \"./variant-cache-key\";\nimport { localDiskRoot } from \"./local-disk-root\";\nimport {\n purgeVariantCacheEntries,\n variantCacheEntries,\n variantSourceCacheEntries,\n} from \"./image-variant-cache-entries\";\n\n/**\n * Options for {@link purgeImageVariants}\n */\nexport type PurgeImageVariantsOptions = {\n /**\n * Disk the source lives on; the default disk when omitted\n */\n disk?: StorageDriverName;\n};\n\n/**\n * Remove cached image derivatives.\n *\n * - With a path: every derivative of that file, in every variant and format.\n * The file itself is untouched and does not need to exist.\n * - Without a path: the whole derivative cache of the disk. When\n * `uploads.images.cacheDirectory` is set, that directory is shared by every\n * local disk, so this clears all of them.\n *\n * Only recognized derivative, source-manifest and temporary files in the\n * generated cache layout are removed. The cache root, directories, unrelated\n * files and symlinks stay in place. Use a dedicated folder for derivatives.\n *\n * Deleting or moving a file through local storage (`storage.delete`,\n * `deleteMany`, `move`) already purges its derivatives; call this for files\n * removed some other way.\n *\n * @throws ImageVariantsConfigError when the cache equals or contains any\n * configured local disk root, including through a symlink\n * @returns `true` when something was removed\n */\nexport async function purgeImageVariants(\n relativePath?: string,\n options: PurgeImageVariantsOptions = {},\n): Promise<boolean> {\n const diskRoot = localDiskRoot(options.disk);\n\n if (!diskRoot) {\n const disk = remoteVariantDisk(options.disk);\n const location = relativePath === undefined ? undefined : remoteVariantPath(relativePath);\n if (relativePath !== undefined && location === undefined) return false;\n const cache = variantCacheDirectoryFor(disk.cacheRoot);\n return purgeVariantCacheEntries(\n location === undefined\n ? variantCacheEntries(cache)\n : variantSourceCacheEntries(\n variantSourceDirectory(\n cache,\n variantSourceKey(disk.root, disk.driver.applyPrefix(location)),\n ),\n ),\n );\n }\n\n if (relativePath === undefined) {\n return purgeVariantCacheEntries(variantCacheEntries(variantCacheDirectoryFor(diskRoot)));\n }\n\n const location = relativePath.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\");\n\n return purgeImageVariantsOfFile(diskRoot, path.resolve(diskRoot, location));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,eAAsB,mBACpB,cACA,UAAqC,CAAC,GACpB;CAClB,MAAM,WAAW,cAAc,QAAQ,IAAI;CAE3C,IAAI,CAAC,UAAU;EACb,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,MAAM,WAAW,iBAAiB,SAAY,SAAY,kBAAkB,YAAY;EACxF,IAAI,iBAAiB,UAAa,aAAa,QAAW,OAAO;EACjE,MAAM,QAAQ,yBAAyB,KAAK,SAAS;EACrD,OAAO,yBACL,aAAa,SACT,oBAAoB,KAAK,IACzB,0BACE,uBACE,OACA,iBAAiB,KAAK,MAAM,KAAK,OAAO,YAAY,QAAQ,CAAC,CAC/D,CACF,CACN;CACF;CAEA,IAAI,iBAAiB,QACnB,OAAO,yBAAyB,oBAAoB,yBAAyB,QAAQ,CAAC,CAAC;CAGzF,MAAM,WAAW,aAAa,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE;CAEpE,OAAO,yBAAyB,UAAU,KAAK,QAAQ,UAAU,QAAQ,CAAC;AAC5E"}
@@ -0,0 +1,115 @@
1
+ import { storagePath } from "../../utils/paths.mjs";
2
+ import { HttpError } from "../errors/errors.mjs";
3
+ import { storage } from "../../storage/storage.mjs";
4
+ import "../../storage/index.mjs";
5
+ import { detectImageFormat, isVariantSourceFormat } from "./detect-image-format.mjs";
6
+ import { loadVariantSource } from "./load-variant-source.mjs";
7
+ import { createHash } from "node:crypto";
8
+ import path from "node:path";
9
+
10
+ //#region ../../packages/core/src/http/uploads/remote-variant-source.ts
11
+ const activeSources = /* @__PURE__ */ new Map();
12
+ function remoteVariantDisk(disk) {
13
+ const driver = disk === void 0 ? storage.activeDriver : storage.getDriver(disk);
14
+ const { bucket, endpoint, region } = driver.options;
15
+ return {
16
+ driver,
17
+ root: JSON.stringify([
18
+ driver.name,
19
+ bucket,
20
+ endpoint,
21
+ region
22
+ ]),
23
+ cacheRoot: storagePath()
24
+ };
25
+ }
26
+ /** Object keys are normalized once, without URL decoding, before prefixing. */
27
+ function remoteVariantPath(location) {
28
+ if (!location || location.includes("\0") || location.includes("\\") || path.posix.isAbsolute(location) || path.win32.isAbsolute(location) || /^[A-Za-z]:/.test(location) || location.split("/").includes("..")) return void 0;
29
+ const normalized = path.posix.normalize(location);
30
+ return normalized === "." ? void 0 : normalized;
31
+ }
32
+ async function remoteVariantSource(location, disk, images) {
33
+ const relativePath = remoteVariantPath(location);
34
+ if (!relativePath) return void 0;
35
+ const objectPath = relativePath;
36
+ let metadata;
37
+ try {
38
+ metadata = await disk.driver.metadata(relativePath);
39
+ } catch (error) {
40
+ const detail = error;
41
+ if ([
42
+ "ENOENT",
43
+ "NoSuchKey",
44
+ "NotFound"
45
+ ].includes(detail.code ?? detail.name ?? "") || detail.$metadata?.httpStatusCode === 404) return void 0;
46
+ throw error;
47
+ }
48
+ if (metadata.isDirectory) return void 0;
49
+ if (metadata.size > images.maxSourceBytes) throw new HttpError(413, "The source image is too large.");
50
+ const sourcePath = disk.driver.applyPrefix(relativePath);
51
+ const metadataGeneration = metadata.etag ? createHash("sha256").update(JSON.stringify([
52
+ metadata.etag,
53
+ metadata.size,
54
+ metadata.lastModified?.getTime()
55
+ ])).digest("hex") : void 0;
56
+ const activeKey = JSON.stringify([
57
+ disk.root,
58
+ sourcePath,
59
+ metadataGeneration,
60
+ images.maxSourceBytes,
61
+ images.maxSourcePixels
62
+ ]);
63
+ const existing = activeSources.get(activeKey);
64
+ if (existing) {
65
+ existing.references++;
66
+ return existing.source;
67
+ }
68
+ let loaded;
69
+ async function load() {
70
+ const stream = await disk.driver.getStream(objectPath);
71
+ const chunks = [];
72
+ let size = 0;
73
+ try {
74
+ for await (const chunk of stream) {
75
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
76
+ size += bytes.length;
77
+ if (size > images.maxSourceBytes) throw new HttpError(413, "The source image is too large.");
78
+ chunks.push(bytes);
79
+ }
80
+ } finally {
81
+ stream.destroy();
82
+ }
83
+ const buffer = Buffer.concat(chunks);
84
+ const sourceFormat = detectImageFormat(buffer);
85
+ if (!isVariantSourceFormat(sourceFormat)) throw new HttpError(415, "Variants are only available for jpeg, png, webp and avif images.");
86
+ return {
87
+ image: await loadVariantSource(buffer, {
88
+ maxSourceBytes: images.maxSourceBytes,
89
+ maxSourcePixels: images.maxSourcePixels,
90
+ expectedFormat: sourceFormat
91
+ }),
92
+ sourceFormat,
93
+ generation: metadataGeneration ?? createHash("sha256").update(buffer).digest("hex")
94
+ };
95
+ }
96
+ const source = {
97
+ relativePath,
98
+ sourcePath,
99
+ metadataGeneration,
100
+ load: () => loaded ??= load(),
101
+ release() {
102
+ const active = activeSources.get(activeKey);
103
+ if (active && --active.references === 0) activeSources.delete(activeKey);
104
+ }
105
+ };
106
+ activeSources.set(activeKey, {
107
+ source,
108
+ references: 1
109
+ });
110
+ return source;
111
+ }
112
+
113
+ //#endregion
114
+ export { remoteVariantDisk, remoteVariantPath, remoteVariantSource };
115
+ //# sourceMappingURL=remote-variant-source.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-variant-source.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/remote-variant-source.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport type { StorageDriverContract, StorageDriverName } from \"../../storage/types\";\nimport { storage } from \"../../storage\";\nimport { storagePath } from \"../../utils/paths\";\nimport { HttpError } from \"../errors\";\nimport { detectImageFormat, isVariantSourceFormat } from \"./detect-image-format\";\nimport type { ResolvedImageVariantsConfig } from \"./image-variant-types\";\nimport { loadVariantSource } from \"./load-variant-source\";\nimport type { Image } from \"../../image\";\nimport type { VariantSourceFormat } from \"./image-variant-types\";\n\ntype LoadedRemoteSource = { image: Image; sourceFormat: VariantSourceFormat; generation: string };\nexport type RemoteVariantSource = {\n relativePath: string;\n sourcePath: string;\n metadataGeneration?: string;\n load(): Promise<LoadedRemoteSource>;\n release(): void;\n};\nconst activeSources = new Map<string, { references: number; source: RemoteVariantSource }>();\n\nexport function remoteVariantDisk(disk?: StorageDriverName): {\n driver: StorageDriverContract;\n root: string;\n cacheRoot: string;\n} {\n const driver = disk === undefined ? storage.activeDriver : storage.getDriver(disk);\n const { bucket, endpoint, region } = driver.options;\n return {\n driver,\n root: JSON.stringify([driver.name, bucket, endpoint, region]),\n cacheRoot: storagePath(),\n };\n}\n\n/** Object keys are normalized once, without URL decoding, before prefixing. */\nexport function remoteVariantPath(location: string): string | undefined {\n if (\n !location ||\n location.includes(\"\\0\") ||\n location.includes(\"\\\\\") ||\n path.posix.isAbsolute(location) ||\n path.win32.isAbsolute(location) ||\n /^[A-Za-z]:/.test(location) ||\n location.split(\"/\").includes(\"..\")\n )\n return undefined;\n const normalized = path.posix.normalize(location);\n return normalized === \".\" ? undefined : normalized;\n}\n\nexport async function remoteVariantSource(\n location: string,\n disk: ReturnType<typeof remoteVariantDisk>,\n images: ResolvedImageVariantsConfig,\n): Promise<RemoteVariantSource | undefined> {\n const relativePath = remoteVariantPath(location);\n if (!relativePath) return undefined;\n const objectPath = relativePath;\n let metadata;\n try {\n metadata = await disk.driver.metadata(relativePath);\n } catch (error) {\n const detail = error as {\n code?: string;\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (\n [\"ENOENT\", \"NoSuchKey\", \"NotFound\"].includes(detail.code ?? detail.name ?? \"\") ||\n detail.$metadata?.httpStatusCode === 404\n )\n return undefined;\n throw error;\n }\n if (metadata.isDirectory) return undefined;\n if (metadata.size > images.maxSourceBytes)\n throw new HttpError(413, \"The source image is too large.\");\n // The identity includes the effective prefix, including tenant context.\n const sourcePath = disk.driver.applyPrefix(relativePath);\n const metadataGeneration = metadata.etag\n ? createHash(\"sha256\")\n .update(JSON.stringify([metadata.etag, metadata.size, metadata.lastModified?.getTime()]))\n .digest(\"hex\")\n : undefined;\n const activeKey = JSON.stringify([\n disk.root,\n sourcePath,\n metadataGeneration,\n images.maxSourceBytes,\n images.maxSourcePixels,\n ]);\n const existing = activeSources.get(activeKey);\n if (existing) {\n existing.references++;\n return existing.source;\n }\n let loaded: Promise<LoadedRemoteSource> | undefined;\n async function load() {\n const stream = await disk.driver.getStream(objectPath);\n const chunks: Buffer[] = [];\n let size = 0;\n try {\n for await (const chunk of stream) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += bytes.length;\n if (size > images.maxSourceBytes)\n throw new HttpError(413, \"The source image is too large.\");\n chunks.push(bytes);\n }\n } finally {\n stream.destroy();\n }\n const buffer = Buffer.concat(chunks);\n const sourceFormat = detectImageFormat(buffer);\n if (!isVariantSourceFormat(sourceFormat))\n throw new HttpError(415, \"Variants are only available for jpeg, png, webp and avif images.\");\n const image = await loadVariantSource(buffer, {\n maxSourceBytes: images.maxSourceBytes,\n maxSourcePixels: images.maxSourcePixels,\n expectedFormat: sourceFormat,\n });\n return {\n image,\n sourceFormat,\n generation: metadataGeneration ?? createHash(\"sha256\").update(buffer).digest(\"hex\"),\n };\n }\n const source: RemoteVariantSource = {\n relativePath,\n sourcePath,\n metadataGeneration,\n load: () => (loaded ??= load()),\n release() {\n const active = activeSources.get(activeKey);\n if (active && --active.references === 0) activeSources.delete(activeKey);\n },\n };\n activeSources.set(activeKey, { source, references: 1 });\n return source;\n}\n"],"mappings":";;;;;;;;;;AAoBA,MAAM,gCAAgB,IAAI,IAAiE;AAE3F,SAAgB,kBAAkB,MAIhC;CACA,MAAM,SAAS,SAAS,SAAY,QAAQ,eAAe,QAAQ,UAAU,IAAI;CACjF,MAAM,EAAE,QAAQ,UAAU,WAAW,OAAO;CAC5C,OAAO;EACL;EACA,MAAM,KAAK,UAAU;GAAC,OAAO;GAAM;GAAQ;GAAU;EAAM,CAAC;EAC5D,WAAW,YAAY;CACzB;AACF;;AAGA,SAAgB,kBAAkB,UAAsC;CACtE,IACE,CAAC,YACD,SAAS,SAAS,IAAI,KACtB,SAAS,SAAS,IAAI,KACtB,KAAK,MAAM,WAAW,QAAQ,KAC9B,KAAK,MAAM,WAAW,QAAQ,KAC9B,aAAa,KAAK,QAAQ,KAC1B,SAAS,MAAM,GAAG,CAAC,CAAC,SAAS,IAAI,GAEjC,OAAO;CACT,MAAM,aAAa,KAAK,MAAM,UAAU,QAAQ;CAChD,OAAO,eAAe,MAAM,SAAY;AAC1C;AAEA,eAAsB,oBACpB,UACA,MACA,QAC0C;CAC1C,MAAM,eAAe,kBAAkB,QAAQ;CAC/C,IAAI,CAAC,cAAc,OAAO;CAC1B,MAAM,aAAa;CACnB,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,KAAK,OAAO,SAAS,YAAY;CACpD,SAAS,OAAO;EACd,MAAM,SAAS;EAKf,IACE;GAAC;GAAU;GAAa;EAAU,CAAC,CAAC,SAAS,OAAO,QAAQ,OAAO,QAAQ,EAAE,KAC7E,OAAO,WAAW,mBAAmB,KAErC,OAAO;EACT,MAAM;CACR;CACA,IAAI,SAAS,aAAa,OAAO;CACjC,IAAI,SAAS,OAAO,OAAO,gBACzB,MAAM,IAAI,UAAU,KAAK,gCAAgC;CAE3D,MAAM,aAAa,KAAK,OAAO,YAAY,YAAY;CACvD,MAAM,qBAAqB,SAAS,OAChC,WAAW,QAAQ,CAAC,CACjB,OAAO,KAAK,UAAU;EAAC,SAAS;EAAM,SAAS;EAAM,SAAS,cAAc,QAAQ;CAAC,CAAC,CAAC,CAAC,CACxF,OAAO,KAAK,IACf;CACJ,MAAM,YAAY,KAAK,UAAU;EAC/B,KAAK;EACL;EACA;EACA,OAAO;EACP,OAAO;CACT,CAAC;CACD,MAAM,WAAW,cAAc,IAAI,SAAS;CAC5C,IAAI,UAAU;EACZ,SAAS;EACT,OAAO,SAAS;CAClB;CACA,IAAI;CACJ,eAAe,OAAO;EACpB,MAAM,SAAS,MAAM,KAAK,OAAO,UAAU,UAAU;EACrD,MAAM,SAAmB,CAAC;EAC1B,IAAI,OAAO;EACX,IAAI;GACF,WAAW,MAAM,SAAS,QAAQ;IAChC,MAAM,QAAQ,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK;IAChE,QAAQ,MAAM;IACd,IAAI,OAAO,OAAO,gBAChB,MAAM,IAAI,UAAU,KAAK,gCAAgC;IAC3D,OAAO,KAAK,KAAK;GACnB;EACF,UAAU;GACR,OAAO,QAAQ;EACjB;EACA,MAAM,SAAS,OAAO,OAAO,MAAM;EACnC,MAAM,eAAe,kBAAkB,MAAM;EAC7C,IAAI,CAAC,sBAAsB,YAAY,GACrC,MAAM,IAAI,UAAU,KAAK,kEAAkE;EAM7F,OAAO;GACL,aANkB,kBAAkB,QAAQ;IAC5C,gBAAgB,OAAO;IACvB,iBAAiB,OAAO;IACxB,gBAAgB;GAClB,CAAC;GAGC;GACA,YAAY,sBAAsB,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;EACpF;CACF;CACA,MAAM,SAA8B;EAClC;EACA;EACA;EACA,YAAa,WAAW,KAAK;EAC7B,UAAU;GACR,MAAM,SAAS,cAAc,IAAI,SAAS;GAC1C,IAAI,UAAU,EAAE,OAAO,eAAe,GAAG,cAAc,OAAO,SAAS;EACzE;CACF;CACA,cAAc,IAAI,WAAW;EAAE;EAAQ,YAAY;CAAE,CAAC;CACtD,OAAO;AACT"}