veryfront 0.1.96 → 0.1.98

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 (53) hide show
  1. package/esm/deno.d.ts +2 -0
  2. package/esm/deno.js +36 -34
  3. package/esm/src/build/production-build/templates.d.ts.map +1 -1
  4. package/esm/src/build/production-build/templates.js +2 -2
  5. package/esm/src/jobs/runtime-env.d.ts +5 -0
  6. package/esm/src/jobs/runtime-env.d.ts.map +1 -0
  7. package/esm/src/jobs/runtime-env.js +101 -0
  8. package/esm/src/platform/compat/esbuild-shared.d.ts +1 -1
  9. package/esm/src/platform/compat/esbuild-shared.js +1 -1
  10. package/esm/src/platform/compat/std/front-matter-yaml.d.ts.map +1 -1
  11. package/esm/src/platform/compat/std/front-matter-yaml.js +10 -1
  12. package/esm/src/proxy/cache/redis-cache.d.ts +3 -0
  13. package/esm/src/proxy/cache/redis-cache.d.ts.map +1 -1
  14. package/esm/src/proxy/cache/redis-cache.js +15 -2
  15. package/esm/src/proxy/cache/types.d.ts +3 -0
  16. package/esm/src/proxy/cache/types.d.ts.map +1 -1
  17. package/esm/src/rendering/page-rendering.d.ts +8 -0
  18. package/esm/src/rendering/page-rendering.d.ts.map +1 -1
  19. package/esm/src/rendering/page-rendering.js +29 -18
  20. package/esm/src/studio/bridge/bridge-bundle.generated.d.ts.map +1 -1
  21. package/esm/src/studio/bridge/bridge-bundle.generated.js +1 -1
  22. package/esm/src/task/runner.d.ts.map +1 -1
  23. package/esm/src/task/runner.js +2 -6
  24. package/esm/src/utils/redis-client.d.ts +4 -2
  25. package/esm/src/utils/redis-client.d.ts.map +1 -1
  26. package/esm/src/utils/redis-client.js +21 -1
  27. package/esm/src/utils/version.d.ts +1 -1
  28. package/esm/src/utils/version.js +1 -1
  29. package/esm/src/workflow/executor/workflow-executor.d.ts.map +1 -1
  30. package/esm/src/workflow/executor/workflow-executor.js +7 -1
  31. package/esm/src/workflow/types.d.ts +1 -0
  32. package/esm/src/workflow/types.d.ts.map +1 -1
  33. package/esm/src/workflow/worker/dynamic-job-entrypoint.d.ts.map +1 -1
  34. package/esm/src/workflow/worker/dynamic-job-entrypoint.js +18 -1
  35. package/esm/src/workflow/worker/job-entrypoint.d.ts.map +1 -1
  36. package/esm/src/workflow/worker/job-entrypoint.js +18 -1
  37. package/package.json +20 -20
  38. package/src/deno.js +36 -34
  39. package/src/src/build/production-build/templates.ts +2 -2
  40. package/src/src/jobs/runtime-env.ts +132 -0
  41. package/src/src/platform/compat/esbuild-shared.ts +1 -1
  42. package/src/src/platform/compat/std/front-matter-yaml.ts +17 -2
  43. package/src/src/proxy/cache/redis-cache.ts +15 -3
  44. package/src/src/proxy/cache/types.ts +3 -0
  45. package/src/src/rendering/page-rendering.ts +64 -39
  46. package/src/src/studio/bridge/bridge-bundle.generated.ts +1 -1
  47. package/src/src/task/runner.ts +2 -8
  48. package/src/src/utils/redis-client.ts +32 -4
  49. package/src/src/utils/version.ts +1 -1
  50. package/src/src/workflow/executor/workflow-executor.ts +7 -1
  51. package/src/src/workflow/types.ts +1 -0
  52. package/src/src/workflow/worker/dynamic-job-entrypoint.ts +19 -1
  53. package/src/src/workflow/worker/job-entrypoint.ts +19 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.96",
3
+ "version": "0.1.98",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -126,37 +126,37 @@
126
126
  }
127
127
  },
128
128
  "dependencies": {
129
- "@ai-sdk/anthropic": "3.0.43",
130
- "@ai-sdk/google": "3.0.29",
131
- "@ai-sdk/openai": "3.0.28",
132
- "@babel/generator": "7.26.3",
133
- "@babel/parser": "7.26.3",
134
- "@babel/traverse": "7.26.3",
135
- "@babel/types": "7.26.3",
136
- "@kreuzberg/wasm": "4.4.2",
137
- "@mdx-js/mdx": "3.0.0",
138
- "@opentelemetry/api": "1",
129
+ "@ai-sdk/anthropic": "3.0.63",
130
+ "@ai-sdk/google": "3.0.52",
131
+ "@ai-sdk/openai": "3.0.47",
132
+ "@babel/generator": "7.29.1",
133
+ "@babel/parser": "7.29.2",
134
+ "@babel/traverse": "7.29.0",
135
+ "@babel/types": "7.29.0",
136
+ "@kreuzberg/wasm": "4.5.2",
137
+ "@mdx-js/mdx": "3.1.1",
138
+ "@opentelemetry/api": "1.9.0",
139
139
  "@opentelemetry/context-async-hooks": "1",
140
140
  "@opentelemetry/core": "1",
141
141
  "@opentelemetry/exporter-trace-otlp-http": "0.57",
142
142
  "@opentelemetry/resources": "1",
143
143
  "@opentelemetry/sdk-trace-base": "1",
144
- "@opentelemetry/semantic-conventions": "1",
144
+ "@opentelemetry/semantic-conventions": "1.40.0",
145
145
  "@types/hast": "3.0.3",
146
146
  "@types/mdast": "4.0.3",
147
147
  "@types/unist": "3.0.2",
148
- "ai": "6.0.33",
148
+ "ai": "6.0.134",
149
149
  "class-variance-authority": "0.7.1",
150
150
  "clsx": "2.1.1",
151
151
  "es-module-lexer": "1.5.0",
152
- "esbuild": "0.20.2",
152
+ "esbuild": "0.27.4",
153
153
  "github-slugger": "2.0.0",
154
154
  "gray-matter": "4.0.3",
155
155
  "jose": "5.9.6",
156
156
  "mdast-util-to-string": "4.0.0",
157
157
  "mime-types": "2.1.35",
158
- "react": "19.1.1",
159
- "react-dom": "19.1.1",
158
+ "react": "19.2.4",
159
+ "react-dom": "19.2.4",
160
160
  "redis": "4.6.13",
161
161
  "rehype-highlight": "7.0.2",
162
162
  "rehype-raw": "7.0.0",
@@ -167,12 +167,12 @@
167
167
  "remark-frontmatter": "5.0.0",
168
168
  "remark-gfm": "4.0.1",
169
169
  "remark-parse": "11.0.0",
170
- "remark-rehype": "11.1.1",
170
+ "remark-rehype": "11.1.2",
171
171
  "tailwind-merge": "2.6.0",
172
- "tailwindcss": "4.1.8",
172
+ "tailwindcss": "4.2.2",
173
173
  "unified": "11.0.5",
174
- "unist-util-visit": "5.0.0",
175
- "vfile": "6.0.1",
174
+ "unist-util-visit": "5.1.0",
175
+ "vfile": "6.0.3",
176
176
  "zod": "3.25.76",
177
177
  "@deno/shim-deno": "~0.18.0",
178
178
  "@deno/shim-crypto": "~0.3.1",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.96",
3
+ "version": "0.1.98",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -214,29 +214,29 @@ export default {
214
214
  "csstype": "https://esm.sh/csstype@3.2.3",
215
215
  "@types/react": "https://esm.sh/@types/react@18.3.27?deps=csstype@3.2.3",
216
216
  "@types/react-dom": "https://esm.sh/@types/react-dom@18.3.7?deps=csstype@3.2.3",
217
- "react": "https://esm.sh/react@19.1.1?target=es2022&deps=csstype@3.2.3",
218
- "react-dom": "https://esm.sh/react-dom@19.1.1?external=react&target=es2022&deps=csstype@3.2.3",
219
- "react-dom/server": "https://esm.sh/react-dom@19.1.1/server?external=react&target=es2022&deps=csstype@3.2.3",
220
- "react-dom/client": "https://esm.sh/react-dom@19.1.1/client?external=react&target=es2022&deps=csstype@3.2.3",
221
- "react/jsx-runtime": "https://esm.sh/react@19.1.1/jsx-runtime?external=react&target=es2022&deps=csstype@3.2.3",
222
- "react/jsx-dev-runtime": "https://esm.sh/react@19.1.1/jsx-dev-runtime?external=react&target=es2022&deps=csstype@3.2.3",
223
- "@mdx-js/mdx": "npm:@mdx-js/mdx@3.0.0",
224
- "@mdx-js/react": "npm:@mdx-js/react@3.0.0",
225
- "unist-util-visit": "npm:unist-util-visit@5.0.0",
217
+ "react": "https://esm.sh/react@19.2.4?target=es2022&deps=csstype@3.2.3",
218
+ "react-dom": "https://esm.sh/react-dom@19.2.4?external=react&target=es2022&deps=csstype@3.2.3",
219
+ "react-dom/server": "https://esm.sh/react-dom@19.2.4/server?external=react&target=es2022&deps=csstype@3.2.3",
220
+ "react-dom/client": "https://esm.sh/react-dom@19.2.4/client?external=react&target=es2022&deps=csstype@3.2.3",
221
+ "react/jsx-runtime": "https://esm.sh/react@19.2.4/jsx-runtime?external=react&target=es2022&deps=csstype@3.2.3",
222
+ "react/jsx-dev-runtime": "https://esm.sh/react@19.2.4/jsx-dev-runtime?external=react&target=es2022&deps=csstype@3.2.3",
223
+ "@mdx-js/mdx": "npm:@mdx-js/mdx@3.1.1",
224
+ "@mdx-js/react": "npm:@mdx-js/react@3.1.1",
225
+ "unist-util-visit": "npm:unist-util-visit@5.1.0",
226
226
  "mdast-util-to-string": "npm:mdast-util-to-string@4.0.0",
227
227
  "github-slugger": "npm:github-slugger@2.0.0",
228
228
  "remark-parse": "npm:remark-parse@11.0.0",
229
229
  "remark-gfm": "npm:remark-gfm@4.0.1",
230
230
  "remark-frontmatter": "npm:remark-frontmatter@5.0.0",
231
- "remark-rehype": "npm:remark-rehype@11.1.1",
231
+ "remark-rehype": "npm:remark-rehype@11.1.2",
232
232
  "rehype-highlight": "npm:rehype-highlight@7.0.2",
233
233
  "rehype-starry-night": "npm:rehype-starry-night@2.2.0",
234
234
  "rehype-slug": "npm:rehype-slug@6.0.0",
235
235
  "rehype-raw": "npm:rehype-raw@7.0.0",
236
236
  "rehype-sanitize": "npm:rehype-sanitize@6.0.0",
237
237
  "rehype-stringify": "npm:rehype-stringify@10.0.1",
238
- "esbuild": "npm:esbuild@0.20.2",
239
- "esbuild/mod.js": "npm:esbuild@0.20.2",
238
+ "esbuild": "npm:esbuild@0.27.4",
239
+ "esbuild/mod.js": "npm:esbuild@0.27.4",
240
240
  "es-module-lexer": "npm:es-module-lexer@1.5.0",
241
241
  "gray-matter": "npm:gray-matter@4.0.3",
242
242
  "zod": "npm:zod@3.25.76",
@@ -245,39 +245,39 @@ export default {
245
245
  "hast": "npm:@types/hast@3.0.3",
246
246
  "unist": "npm:@types/unist@3.0.2",
247
247
  "unified": "npm:unified@11.0.5",
248
- "vfile": "npm:vfile@6.0.1",
249
- "ai": "npm:ai@6.0.33",
250
- "ai/react": "npm:@ai-sdk/react@3.0.35",
251
- "@ai-sdk/react": "npm:@ai-sdk/react@3.0.35",
252
- "@ai-sdk/openai": "npm:@ai-sdk/openai@3.0.28",
253
- "@ai-sdk/anthropic": "npm:@ai-sdk/anthropic@3.0.43",
254
- "@ai-sdk/google": "npm:@ai-sdk/google@3.0.29",
248
+ "vfile": "npm:vfile@6.0.3",
249
+ "ai": "npm:ai@6.0.134",
250
+ "ai/react": "npm:@ai-sdk/react@3.0.136",
251
+ "@ai-sdk/react": "npm:@ai-sdk/react@3.0.136",
252
+ "@ai-sdk/openai": "npm:@ai-sdk/openai@3.0.47",
253
+ "@ai-sdk/anthropic": "npm:@ai-sdk/anthropic@3.0.63",
254
+ "@ai-sdk/google": "npm:@ai-sdk/google@3.0.52",
255
255
  "@ai-sdk/mistral": "npm:@ai-sdk/mistral@3.0.14",
256
256
  "@ai-sdk/provider": "npm:@ai-sdk/provider@3.0.2",
257
257
  "@ai-sdk/provider-utils": "npm:@ai-sdk/provider-utils@4.0.5",
258
- "tailwindcss": "https://esm.sh/tailwindcss@4.1.8",
259
- "tailwindcss/plugin": "https://esm.sh/tailwindcss@4.1.8/plugin",
260
- "tailwindcss/defaultTheme": "https://esm.sh/tailwindcss@4.1.8/defaultTheme",
261
- "tailwindcss/colors": "https://esm.sh/tailwindcss@4.1.8/colors",
258
+ "tailwindcss": "https://esm.sh/tailwindcss@4.2.2",
259
+ "tailwindcss/plugin": "https://esm.sh/tailwindcss@4.2.2/plugin",
260
+ "tailwindcss/defaultTheme": "https://esm.sh/tailwindcss@4.2.2/defaultTheme",
261
+ "tailwindcss/colors": "https://esm.sh/tailwindcss@4.2.2/colors",
262
262
  "redis": "npm:redis@4.6.13",
263
263
  "pg": "npm:pg@8.13.1",
264
264
  "jose": "npm:jose@5.9.6",
265
- "@opentelemetry/api": "npm:@opentelemetry/api@1",
265
+ "@opentelemetry/api": "npm:@opentelemetry/api@1.9.0",
266
266
  "@opentelemetry/core": "npm:@opentelemetry/core@1",
267
267
  "@opentelemetry/context-async-hooks": "npm:@opentelemetry/context-async-hooks@1",
268
268
  "@opentelemetry/sdk-trace-base": "npm:@opentelemetry/sdk-trace-base@1",
269
269
  "@opentelemetry/exporter-trace-otlp-http": "npm:@opentelemetry/exporter-trace-otlp-http@0.57",
270
270
  "@opentelemetry/resources": "npm:@opentelemetry/resources@1",
271
- "@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@1",
272
- "@babel/parser": "npm:@babel/parser@7.26.3",
273
- "@babel/traverse": "npm:@babel/traverse@7.26.3",
274
- "@babel/generator": "npm:@babel/generator@7.26.3",
275
- "@babel/types": "npm:@babel/types@7.26.3",
271
+ "@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@1.40.0",
272
+ "@babel/parser": "npm:@babel/parser@7.29.2",
273
+ "@babel/traverse": "npm:@babel/traverse@7.29.0",
274
+ "@babel/generator": "npm:@babel/generator@7.29.1",
275
+ "@babel/types": "npm:@babel/types@7.29.0",
276
276
  "class-variance-authority": "npm:class-variance-authority@0.7.1",
277
277
  "clsx": "npm:clsx@2.1.1",
278
278
  "tailwind-merge": "npm:tailwind-merge@2.6.0",
279
- "@kreuzberg/wasm": "npm:@kreuzberg/wasm@4.4.2",
280
- "#kreuzberg-wasm-glue": "npm:@kreuzberg/wasm@4.4.2/dist/pkg/kreuzberg_wasm.js"
279
+ "@kreuzberg/wasm": "npm:@kreuzberg/wasm@4.5.2",
280
+ "#kreuzberg-wasm-glue": "npm:@kreuzberg/wasm@4.5.2/dist/pkg/kreuzberg_wasm.js"
281
281
  },
282
282
  "compilerOptions": {
283
283
  "jsx": "react-jsx",
@@ -342,6 +342,7 @@ export default {
342
342
  "dupes": "deno run --allow-read scripts/lint/find-duplicate-functions.ts",
343
343
  "lint:platform": "deno run --allow-read scripts/lint/lint-platform-agnostic.ts",
344
344
  "lint:wildcard-exports": "deno run --allow-read scripts/lint/ban-wildcard-exports.ts",
345
+ "lint:deps": "deno run --allow-read scripts/lint/audit-deps.ts",
345
346
  "lint:barrel-jsdoc": "deno run --allow-read scripts/lint/check-barrel-jsdoc.ts",
346
347
  "test:cross-runtime": "deno run --allow-all src/platform/compat/cross-runtime.test.ts",
347
348
  "test:node": "node ./tests/node/run-tests.mjs 'src/**/*.test.ts'",
@@ -364,7 +365,8 @@ export default {
364
365
  "rlm:explore": "deno run -A scripts/rlm-ts/apps/explore.ts",
365
366
  "rlm:audit": "deno run -A scripts/rlm-ts/apps/audit.ts",
366
367
  "start-split": "deno task generate && deno run --allow-all cli/main.ts serve --split",
367
- "start-split:binary": "deno task generate && deno run --allow-all cli/main.ts serve --split --binary"
368
+ "start-split:binary": "deno task generate && deno run --allow-all cli/main.ts serve --split --binary",
369
+ "sbom": "deno run --allow-read --allow-write scripts/build/generate-sbom.ts"
368
370
  },
369
371
  "lint": {
370
372
  "include": [
@@ -417,7 +419,7 @@ export default {
417
419
  "npm:onnxruntime-node@1.21.0"
418
420
  ],
419
421
  "deny": [
420
- "npm:esbuild@0.20.2",
422
+ "npm:esbuild@0.27.4",
421
423
  "npm:protobufjs@7.5.4"
422
424
  ]
423
425
  }
@@ -11,7 +11,7 @@ export const CLIENT_STYLES =
11
11
  ".error-container {\n max-width: 600px;\n margin: 2rem auto;\n padding: 2rem;\n background: #fee;\n border: 1px solid #fcc;\n border-radius: 8px;\n color: #c00;\n}";
12
12
 
13
13
  export const CLIENT_ROUTER_BUNDLE: string | undefined =
14
- 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => {\n __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n return value;\n};\n\n// src/rendering/client/router.ts\nimport { rendererLogger } from "#veryfront/utils";\nimport ReactDOM from "react-dom/client";\nimport {\n extractPageDataFromScript,\n NavigationHandlers,\n PageLoader,\n PageTransition,\n ViewportPrefetch\n} from "#veryfront/routing";\nvar logger = rendererLogger.component("veryfront");\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = globalThis.location.pathname;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n onNavigate: (url) => this.navigate(url, false),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n }\n registerNavigationHandler(handler) {\n logger.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData)\n this.pageLoader.setCache(this.currentPath, pageData);\n }\n async navigate(url, pushState = true) {\n logger.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (pushState)\n globalThis.history.pushState({}, "", url);\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url);\n } else {\n await this.loadPage(url);\n }\n this.options.onNavigate?.(url);\n }\n async loadSpaPage(path) {\n logger.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n await this.spaNavigationHandler?.(spaData);\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n async loadPage(path, updateUI = true) {\n if (this.pageLoader.isCached(path)) {\n logger.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (updateUI)\n this.updatePage(data);\n return;\n }\n logger.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (updateUI)\n this.updatePage(data);\n this.currentPath = path;\n this.options.onComplete?.(path);\n } catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n } finally {\n this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data) {\n if (!this.root)\n return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nif (typeof window !== "undefined" && globalThis.document) {\n const router = new VeryfrontRouter();\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalThis.veryFrontRouter = router;\n}\nexport {\n VeryfrontRouter\n};\n';
14
+ 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/router.ts\nimport { rendererLogger } from "#veryfront/utils";\nimport ReactDOM from "react-dom/client";\nimport {\n extractPageDataFromScript,\n NavigationHandlers,\n PageLoader,\n PageTransition,\n ViewportPrefetch\n} from "#veryfront/routing";\nvar logger = rendererLogger.component("veryfront");\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = globalThis.location.pathname;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n onNavigate: (url) => this.navigate(url, false),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n }\n registerNavigationHandler(handler) {\n logger.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData) this.pageLoader.setCache(this.currentPath, pageData);\n }\n async navigate(url, pushState = true) {\n logger.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (pushState) globalThis.history.pushState({}, "", url);\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url);\n } else {\n await this.loadPage(url);\n }\n this.options.onNavigate?.(url);\n }\n async loadSpaPage(path) {\n logger.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n await this.spaNavigationHandler?.(spaData);\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n async loadPage(path, updateUI = true) {\n if (this.pageLoader.isCached(path)) {\n logger.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (updateUI) this.updatePage(data);\n return;\n }\n logger.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (updateUI) this.updatePage(data);\n this.currentPath = path;\n this.options.onComplete?.(path);\n } catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n } finally {\n this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data) {\n if (!this.root) return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nif (typeof window !== "undefined" && globalThis.document) {\n const router = new VeryfrontRouter();\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalThis.veryFrontRouter = router;\n}\nexport {\n VeryfrontRouter\n};\n';
15
15
 
16
16
  export const CLIENT_PREFETCH_BUNDLE: string | undefined =
17
- 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => {\n __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n return value;\n};\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n this.prefix = prefix;\n this.level = level;\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel)\n return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined")\n return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment)\n return 2 /* WARN */;\n const isDebugEnabled = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting)\n continue;\n if (!isAnchorElement(entry.target))\n continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6)\n this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList")\n continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0)\n return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element))\n this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link))\n continue;\n if (!this.isValidLink(link))\n continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname)\n return false;\n if (link.hasAttribute("download"))\n return false;\n if (link.target === "_blank")\n return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url))\n return false;\n if (url === globalThis.location.href)\n return false;\n if (link.hash && link.pathname === globalThis.location.pathname)\n return false;\n if (link.dataset.noPrefetch)\n return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined")\n return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData)\n return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType))\n return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nimport { PREFETCH_QUEUE_MAX_SIZE_BYTES } from "#veryfront/utils/constants/index.ts";\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null)\n return false;\n if (!("name" in error))\n return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped)\n return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url))\n return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok)\n return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched)\n return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0)\n clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null)\n return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size))\n return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key))\n continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as)\n link.setAttribute("as", hint.as);\n if (hint.crossOrigin)\n link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media)\n link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href)\n continue;\n if (prefetchedUrls.has(href))\n continue;\n if (!this.isValidResourceHintType(link.rel))\n continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src))\n continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href))\n continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">\',\n \'<link rel="dns-prefetch" href="https://esm.sh">\',\n \'<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(`<link rel="modulepreload" href="${asset}">`);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(`<link rel="preload" as="style" href="${asset}">`);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(`<link rel="preload" as="font" href="${asset}" crossorigin>`);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/prefetch.ts\nimport {\n PREFETCH_DEFAULT_DELAY_MS,\n PREFETCH_DEFAULT_TIMEOUT_MS,\n PREFETCH_MAX_SIZE_BYTES\n} from "#veryfront/utils";\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch())\n this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting)\n return null;\n if (setting === true)\n return {};\n if (typeof setting === "object")\n return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options)\n return false;\n if (typeof window === "undefined" || typeof document === "undefined")\n return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub)\n return false;\n if (typeof IntersectionObserver === "undefined")\n return false;\n if (typeof MutationObserver === "undefined")\n return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions))\n initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n';
17
+ 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n this.prefix = prefix;\n this.level = level;\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nimport { PREFETCH_QUEUE_MAX_SIZE_BYTES } from "#veryfront/utils/constants/index.ts";\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">\',\n \'<link rel="dns-prefetch" href="https://esm.sh">\',\n \'<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(`<link rel="modulepreload" href="${asset}">`);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(`<link rel="preload" as="style" href="${asset}">`);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(`<link rel="preload" as="font" href="${asset}" crossorigin>`);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/prefetch.ts\nimport {\n PREFETCH_DEFAULT_DELAY_MS,\n PREFETCH_DEFAULT_TIMEOUT_MS,\n PREFETCH_MAX_SIZE_BYTES\n} from "#veryfront/utils";\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n';
@@ -0,0 +1,132 @@
1
+ import { logger as baseLogger } from "../utils/index.js";
2
+
3
+ const logger = baseLogger.component("job-runtime-env");
4
+
5
+ export const INJECTED_TASK_ENV_JSON = "VERYFRONT_TASK_ENV_JSON";
6
+
7
+ const UNSAFE_INJECTED_ENV_KEYS = new Set(["__proto__", "constructor", "prototype"]);
8
+
9
+ const HIDDEN_TASK_CONTEXT_ENV_KEYS = new Set([
10
+ "VERYFRONT_API_TOKEN",
11
+ "VERYFRONT_API_URL",
12
+ "VERYFRONT_PROJECT_API_URL",
13
+ "VERYFRONT_API_BASE_URL",
14
+ "VERYFRONT_PROJECT_ID",
15
+ "VERYFRONT_PROJECT_SLUG",
16
+ "VERYFRONT_BRANCH_REF",
17
+ "VERYFRONT_API_USER",
18
+ "VERYFRONT_API_PASS",
19
+ "VERYFRONT_JOB_RESULT_PATH",
20
+ INJECTED_TASK_ENV_JSON,
21
+ ]);
22
+
23
+ const DISALLOWED_INJECTED_ENV_KEYS = new Set([
24
+ "VERYFRONT_API_TOKEN",
25
+ "VERYFRONT_API_URL",
26
+ "VERYFRONT_PROJECT_API_URL",
27
+ "VERYFRONT_API_BASE_URL",
28
+ "VERYFRONT_PROJECT_ID",
29
+ "VERYFRONT_PROJECT_SLUG",
30
+ "VERYFRONT_BRANCH_REF",
31
+ "VERYFRONT_API_USER",
32
+ "VERYFRONT_API_PASS",
33
+ "VERYFRONT_JOB_RESULT_PATH",
34
+ INJECTED_TASK_ENV_JSON,
35
+ ]);
36
+
37
+ function isHiddenTaskContextEnvKey(key: string): boolean {
38
+ return key.startsWith("TENANT_") || HIDDEN_TASK_CONTEXT_ENV_KEYS.has(key);
39
+ }
40
+
41
+ function isDisallowedInjectedEnvKey(key: string): boolean {
42
+ return key.startsWith("TENANT_") || DISALLOWED_INJECTED_ENV_KEYS.has(key);
43
+ }
44
+
45
+ function filterExistingProjectEnv(
46
+ env: Record<string, string> | undefined,
47
+ ): Record<string, string> {
48
+ const filtered: Record<string, string> = {};
49
+ for (const [key, value] of Object.entries(env ?? {})) {
50
+ if (
51
+ UNSAFE_INJECTED_ENV_KEYS.has(key) ||
52
+ isDisallowedInjectedEnvKey(key) ||
53
+ typeof value !== "string"
54
+ ) {
55
+ continue;
56
+ }
57
+ filtered[key] = value;
58
+ }
59
+ return filtered;
60
+ }
61
+
62
+ export function readInjectedProjectEnv(
63
+ allEnv: Record<string, string>,
64
+ ): Record<string, string> {
65
+ const raw = allEnv[INJECTED_TASK_ENV_JSON];
66
+ if (!raw) {
67
+ return {};
68
+ }
69
+
70
+ try {
71
+ const parsed = JSON.parse(raw);
72
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
73
+ return {};
74
+ }
75
+
76
+ const injectedEnv = Object.create(null) as Record<string, string>;
77
+ for (const [key, value] of Object.entries(parsed)) {
78
+ if (
79
+ UNSAFE_INJECTED_ENV_KEYS.has(key) ||
80
+ isDisallowedInjectedEnvKey(key) ||
81
+ typeof value !== "string"
82
+ ) {
83
+ continue;
84
+ }
85
+ injectedEnv[key] = value;
86
+ }
87
+
88
+ return injectedEnv;
89
+ } catch {
90
+ logger.warn(`Ignoring invalid ${INJECTED_TASK_ENV_JSON}`);
91
+ return {};
92
+ }
93
+ }
94
+
95
+ export function buildTaskContextEnv(
96
+ allEnv: Record<string, string>,
97
+ envAllowlist?: string[],
98
+ ): Record<string, string> {
99
+ const allowlistedEnvKeys = envAllowlist ? new Set(envAllowlist) : null;
100
+ const env: Record<string, string> = {};
101
+
102
+ for (const [key, value] of Object.entries(allEnv)) {
103
+ if (isHiddenTaskContextEnvKey(key)) {
104
+ continue;
105
+ }
106
+ if (allowlistedEnvKeys && !allowlistedEnvKeys.has(key)) {
107
+ continue;
108
+ }
109
+ env[key] = value;
110
+ }
111
+
112
+ for (const [key, value] of Object.entries(readInjectedProjectEnv(allEnv))) {
113
+ if (allowlistedEnvKeys && !allowlistedEnvKeys.has(key)) {
114
+ continue;
115
+ }
116
+ env[key] = value;
117
+ }
118
+
119
+ return env;
120
+ }
121
+
122
+ export function mergeInjectedWorkflowEnv(
123
+ existingEnv: Record<string, string> | undefined,
124
+ allEnv: Record<string, string>,
125
+ ): Record<string, string> | undefined {
126
+ const mergedEnv = {
127
+ ...filterExistingProjectEnv(existingEnv),
128
+ ...readInjectedProjectEnv(allEnv),
129
+ };
130
+
131
+ return Object.keys(mergedEnv).length > 0 ? mergedEnv : undefined;
132
+ }
@@ -1,5 +1,5 @@
1
1
  import * as dntShim from "../../../_dnt.shims.js";
2
- export const ESBUILD_VERSION = "0.20.2";
2
+ export const ESBUILD_VERSION = "0.27.4";
3
3
 
4
4
  export function getEsbuildBinaryName(): string {
5
5
  const archMap: Record<string, string> = {
@@ -14,13 +14,28 @@ export interface Extract<T> {
14
14
  }
15
15
 
16
16
  type GrayMatterResult<T> = { data: T; content: string; matter?: string };
17
- type GrayMatterFn = <T = Record<string, unknown>>(content: string) => GrayMatterResult<T>;
17
+ type GrayMatterEngine = { parse: () => never };
18
+ type GrayMatterOptions = { engines?: Record<string, GrayMatterEngine> };
19
+ type GrayMatterFn = <T = Record<string, unknown>>(
20
+ content: string,
21
+ options?: GrayMatterOptions,
22
+ ) => GrayMatterResult<T>;
18
23
 
19
24
  const grayMatter: GrayMatterFn = (grayMatterImport as { default?: GrayMatterFn }).default ??
20
25
  (grayMatterImport as GrayMatterFn);
21
26
 
27
+ /** Security: override both "js" and "javascript" engine aliases to block eval on untrusted frontmatter */
28
+ const DISABLED_ENGINE: GrayMatterEngine = {
29
+ parse: () => {
30
+ throw new Error("JavaScript frontmatter is disabled for security");
31
+ },
32
+ };
33
+ const SAFE_OPTIONS: GrayMatterOptions = {
34
+ engines: { js: DISABLED_ENGINE, javascript: DISABLED_ENGINE },
35
+ };
36
+
22
37
  export function extract<T = Record<string, unknown>>(text: string): Extract<T> {
23
- const result = grayMatter<T>(text);
38
+ const result = grayMatter<T>(text, SAFE_OPTIONS);
24
39
  return {
25
40
  attrs: result.data,
26
41
  body: result.content,
@@ -16,6 +16,9 @@ export class RedisCache implements TokenCache {
16
16
  private readonly prefix: string;
17
17
  private readonly url: string;
18
18
  private readonly connectTimeout: number;
19
+ private readonly tls: boolean;
20
+ private readonly password?: string;
21
+ private readonly username?: string;
19
22
  private hits = 0;
20
23
  private misses = 0;
21
24
  private connected = false;
@@ -24,6 +27,9 @@ export class RedisCache implements TokenCache {
24
27
  this.url = options.url;
25
28
  this.prefix = options.prefix ?? DEFAULT_PREFIX;
26
29
  this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
30
+ this.tls = options.tls ?? options.url.startsWith("rediss://");
31
+ this.password = options.password;
32
+ this.username = options.username;
27
33
  }
28
34
 
29
35
  private key(k: string): string {
@@ -212,18 +218,24 @@ export class RedisCache implements TokenCache {
212
218
  return withSpan("cache.redis.connect", async () => {
213
219
  if (this.connected && this.client) return;
214
220
 
215
- const client = createClient({
221
+ // deno-lint-ignore no-explicit-any
222
+ const clientOpts: Record<string, any> = {
216
223
  url: this.url,
217
224
  socket: {
218
225
  connectTimeout: this.connectTimeout,
219
- reconnectStrategy: (retries) => {
226
+ tls: this.tls || undefined,
227
+ reconnectStrategy: (retries: number) => {
220
228
  if (retries > MAX_RECONNECT_RETRIES) {
221
229
  return new Error("Max reconnection attempts reached");
222
230
  }
223
231
  return Math.min(retries * RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_MAX_MS);
224
232
  },
225
233
  },
226
- });
234
+ };
235
+ if (this.password) clientOpts.password = this.password;
236
+ if (this.username) clientOpts.username = this.username;
237
+
238
+ const client = createClient(clientOpts);
227
239
 
228
240
  client.on("error", (err) => {
229
241
  logger.error("[RedisCache] Client error", {
@@ -38,6 +38,9 @@ export interface RedisCacheOptions {
38
38
  url: string;
39
39
  prefix?: string;
40
40
  connectTimeout?: number;
41
+ tls?: boolean;
42
+ password?: string;
43
+ username?: string;
41
44
  }
42
45
 
43
46
  export type CacheOptions =