veryfront 0.1.38 → 0.1.40

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 (27) hide show
  1. package/esm/deno.js +4 -3
  2. package/esm/src/html/dev-scripts.d.ts.map +1 -1
  3. package/esm/src/html/dev-scripts.js +13 -7
  4. package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
  5. package/esm/src/server/handlers/preview/markdown-html-generator.js +10 -5
  6. package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts +23 -0
  7. package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts.map +1 -0
  8. package/esm/src/server/handlers/studio/bridge-modules.handler.js +109 -0
  9. package/esm/src/server/runtime-handler/index.js +2 -2
  10. package/esm/src/studio/bridge/bridge-bundle.generated.d.ts +9 -0
  11. package/esm/src/studio/bridge/bridge-bundle.generated.d.ts.map +1 -0
  12. package/esm/src/studio/bridge/bridge-bundle.generated.js +8 -0
  13. package/package.json +1 -1
  14. package/src/deno.js +4 -3
  15. package/src/src/html/dev-scripts.ts +14 -7
  16. package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -10
  17. package/src/src/server/handlers/studio/bridge-modules.handler.ts +136 -0
  18. package/src/src/server/runtime-handler/index.ts +2 -2
  19. package/src/src/studio/bridge/bridge-bundle.generated.ts +9 -0
  20. package/esm/src/server/handlers/studio/endpoints.handler.d.ts +0 -12
  21. package/esm/src/server/handlers/studio/endpoints.handler.d.ts.map +0 -1
  22. package/esm/src/server/handlers/studio/endpoints.handler.js +0 -29
  23. package/esm/src/studio/bridge-template.d.ts +0 -11
  24. package/esm/src/studio/bridge-template.d.ts.map +0 -1
  25. package/esm/src/studio/bridge-template.js +0 -4794
  26. package/src/src/server/handlers/studio/endpoints.handler.ts +0 -49
  27. package/src/src/studio/bridge-template.ts +0 -4804
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -283,7 +283,7 @@ export default {
283
283
  "proxy": "deno run --allow-all cli/main.ts serve --mode=proxy",
284
284
  "dev": "deno run --allow-all cli/main.ts dev",
285
285
  "production": "deno run --allow-all cli/main.ts serve --mode=production",
286
- "build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
286
+ "build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno run -A scripts/build/prebundle-bridge.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
287
287
  "build:npm": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/build-npm-dnt.ts",
288
288
  "release": "deno run -A scripts/release.ts",
289
289
  "test": "VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --parallel --allow-all '--ignore=tests/e2e,tests/integration/compiled-binary-e2e.test.ts' --unstable-worker-options --unstable-net",
@@ -352,7 +352,8 @@ export default {
352
352
  ],
353
353
  "exclude": [
354
354
  "dist/",
355
- "coverage/"
355
+ "coverage/",
356
+ "src/studio/bridge/"
356
357
  ],
357
358
  "rules": {
358
359
  "tags": [
@@ -1 +1 @@
1
- {"version":3,"file":"dev-scripts.d.ts","sourceRoot":"","sources":["../../../src/src/html/dev-scripts.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA+BnD;AAMD,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAiBrE"}
1
+ {"version":3,"file":"dev-scripts.d.ts","sourceRoot":"","sources":["../../../src/src/html/dev-scripts.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA+BnD;AAMD,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAwBrE"}
@@ -47,16 +47,22 @@ export function getProdScripts(slug, nonce) {
47
47
  }
48
48
  export function getStudioScripts(options) {
49
49
  const nonceAttr = getNonceAttr(options.nonce);
50
- const params = new URLSearchParams({
50
+ const bridgeConfig = {
51
51
  projectId: options.projectId,
52
52
  pageId: options.pageId,
53
- ...(options.pagePath ? { pagePath: options.pagePath } : {}),
54
- ...(options.wsUrl ? { wsUrl: options.wsUrl } : {}),
55
- ...(options.yjsGuid ? { yjsGuid: options.yjsGuid } : {}),
56
- }).toString();
53
+ pagePath: options.pagePath ?? options.pageId,
54
+ };
55
+ if (options.wsUrl)
56
+ bridgeConfig.wsUrl = options.wsUrl;
57
+ if (options.yjsGuid)
58
+ bridgeConfig.yjsGuid = options.yjsGuid;
57
59
  const sourceHashScript = options.sourceHash
58
- ? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__="${options.sourceHash}";</script>\n `
60
+ ? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__=${JSON.stringify(options.sourceHash).replace(/</g, "\\u003c")};</script>\n `
59
61
  : "";
62
+ // Escape </script> sequences to prevent XSS breakout from inline JSON
63
+ const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
64
+ const configScript = `<script${nonceAttr}>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>`;
60
65
  return `
61
- ${sourceHashScript}<script type="module" src="/_veryfront/studio-bridge.js?${params}"${nonceAttr}></script>`;
66
+ ${sourceHashScript}${configScript}
67
+ <script type="module" src="/_veryfront/studio-bridge.js"${nonceAttr}></script>`;
62
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAMrD,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,gDAAgD;IAChD,GAAG,EAAE,GAAG,CAAC;IACT,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAuED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA2FzE"}
1
+ {"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAKrD,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,gDAAgD;IAChD,GAAG,EAAE,GAAG,CAAC;IACT,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA0ED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA2FzE"}
@@ -1,4 +1,3 @@
1
- import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
2
1
  import { escapeHtml } from "../../../utils/html-escape.js";
3
2
  /**
4
3
  * Detect the preferred color theme from request parameters and client hints.
@@ -49,13 +48,19 @@ function buildStudioScript(url, projectId, filePath, branchId, apiBaseUrl) {
49
48
  }
50
49
  }
51
50
  const yjsGuid = branchId ? `${canonicalProjectId}:${branchId}` : canonicalProjectId;
52
- return `<script>${generateStudioBridgeScript({
51
+ const bridgeConfig = {
53
52
  projectId: canonicalProjectId,
54
53
  pageId: canonicalPageId,
55
54
  pagePath: filePath,
56
- wsUrl,
57
- yjsGuid,
58
- })}</script>`;
55
+ };
56
+ if (wsUrl)
57
+ bridgeConfig.wsUrl = wsUrl;
58
+ if (yjsGuid)
59
+ bridgeConfig.yjsGuid = yjsGuid;
60
+ // Escape </script> sequences to prevent XSS breakout from inline JSON
61
+ const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
62
+ return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
63
+ <script type="module" src="/_veryfront/studio-bridge.js"></script>`;
59
64
  }
60
65
  /**
61
66
  * Generate a complete HTML document for markdown preview rendering.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Studio Bridge Handler
3
+ *
4
+ * Serves the studio bridge script. In compiled binaries, uses a pre-bundled
5
+ * version generated at build time. In dev mode, bundles on-the-fly with esbuild.
6
+ *
7
+ * Route: /_veryfront/studio-bridge.js
8
+ *
9
+ * @module server/handlers/studio/bridge-modules
10
+ */
11
+ import * as dntShim from "../../../../_dnt.shims.js";
12
+ import { BaseHandler } from "../../../security/index.js";
13
+ import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
14
+ export declare class StudioBridgeModulesHandler extends BaseHandler {
15
+ metadata: HandlerMetadata;
16
+ handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
17
+ }
18
+ /**
19
+ * Invalidate the bundle cache.
20
+ * Used by dev file watchers to bust the cache on source changes.
21
+ */
22
+ export declare function invalidateBridgeModuleCache(): void;
23
+ //# sourceMappingURL=bridge-modules.handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-modules.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/studio/bridge-modules.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,aAAa,CAAC;AA4DrB,qBAAa,0BAA2B,SAAQ,WAAW;IACzD,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAwChF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Studio Bridge Handler
3
+ *
4
+ * Serves the studio bridge script. In compiled binaries, uses a pre-bundled
5
+ * version generated at build time. In dev mode, bundles on-the-fly with esbuild.
6
+ *
7
+ * Route: /_veryfront/studio-bridge.js
8
+ *
9
+ * @module server/handlers/studio/bridge-modules
10
+ */
11
+ import * as dntShim from "../../../../_dnt.shims.js";
12
+ import { BaseHandler } from "../../../security/index.js";
13
+ import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
14
+ import { STUDIO_BRIDGE_BUNDLE } from "../../../studio/bridge/bridge-bundle.generated.js";
15
+ /** Cached bundle output. */
16
+ let bundleCache = null;
17
+ /** Resolve the bridge source directory from this module's location. */
18
+ const BRIDGE_DIR = new URL("../../../studio/bridge/", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
19
+ /**
20
+ * Compute a content hash for ETag.
21
+ */
22
+ async function computeEtag(content) {
23
+ const data = new TextEncoder().encode(content);
24
+ const hashBuffer = await dntShim.crypto.subtle.digest("SHA-256", data);
25
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
26
+ return hashArray.slice(0, 16).map((b) => b.toString(16).padStart(2, "0")).join("");
27
+ }
28
+ /**
29
+ * Bundle the bridge coordinator (and all its imports) into a single JS file.
30
+ * Uses pre-bundled output when available (compiled binary), falls back to
31
+ * esbuild JIT bundling in dev mode.
32
+ */
33
+ async function bundleBridge() {
34
+ if (bundleCache)
35
+ return bundleCache;
36
+ // Use pre-bundled output if available (compiled binary / CI builds)
37
+ if (STUDIO_BRIDGE_BUNDLE) {
38
+ const etag = await computeEtag(STUDIO_BRIDGE_BUNDLE);
39
+ bundleCache = { js: STUDIO_BRIDGE_BUNDLE, etag };
40
+ return bundleCache;
41
+ }
42
+ // Dev mode: bundle on-the-fly with esbuild
43
+ const entryPoint = `${BRIDGE_DIR}bridge-coordinator.ts`;
44
+ const source = await dntShim.Deno.readTextFile(entryPoint);
45
+ const { build } = await import("esbuild");
46
+ const { outputFiles } = await build({
47
+ bundle: true,
48
+ write: false,
49
+ format: "esm",
50
+ platform: "browser",
51
+ target: "es2022",
52
+ stdin: {
53
+ contents: source,
54
+ loader: "ts",
55
+ resolveDir: BRIDGE_DIR,
56
+ sourcefile: entryPoint,
57
+ },
58
+ });
59
+ const js = outputFiles?.[0]?.text ?? "";
60
+ const etag = await computeEtag(js);
61
+ bundleCache = { js, etag };
62
+ return bundleCache;
63
+ }
64
+ export class StudioBridgeModulesHandler extends BaseHandler {
65
+ metadata = {
66
+ name: "StudioBridgeModulesHandler",
67
+ priority: PRIORITY_HIGH_DEV,
68
+ patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
69
+ enabled: () => true,
70
+ };
71
+ async handle(req, ctx) {
72
+ if (!this.shouldHandle(req, ctx)) {
73
+ return this.continue();
74
+ }
75
+ const ifNoneMatch = req.headers.get("if-none-match");
76
+ try {
77
+ const { js, etag } = await bundleBridge();
78
+ if (ifNoneMatch === `"${etag}"`) {
79
+ return this.respond(new dntShim.Response(null, {
80
+ status: 304,
81
+ headers: { ETag: `"${etag}"`, "Cache-Control": "no-cache" },
82
+ }));
83
+ }
84
+ return this.respond(new dntShim.Response(js, {
85
+ status: HTTP_OK,
86
+ headers: {
87
+ "Content-Type": "application/javascript; charset=utf-8",
88
+ "Cache-Control": "no-cache",
89
+ ETag: `"${etag}"`,
90
+ },
91
+ }));
92
+ }
93
+ catch (error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ console.error("[StudioBridgeHandler] Bundle error:", message);
96
+ return this.respond(new dntShim.Response(`// Bundle error: ${message}`, {
97
+ status: 500,
98
+ headers: { "Content-Type": "application/javascript; charset=utf-8" },
99
+ }));
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Invalidate the bundle cache.
105
+ * Used by dev file watchers to bust the cache on source changes.
106
+ */
107
+ export function invalidateBridgeModuleCache() {
108
+ bundleCache = null;
109
+ }
@@ -28,7 +28,7 @@ import { DevEndpointsHandler } from "../handlers/dev/endpoints.handler.js";
28
28
  import { DevFileHandler } from "../handlers/dev/files/index.js";
29
29
  import { DebugContextHandler } from "../handlers/dev/debug-context.handler.js";
30
30
  import { StylesCSSHandler } from "../handlers/dev/styles-css.handler.js";
31
- import { StudioEndpointsHandler } from "../handlers/studio/endpoints.handler.js";
31
+ import { StudioBridgeModulesHandler } from "../handlers/studio/bridge-modules.handler.js";
32
32
  import { StaticHandler } from "../handlers/request/static.handler.js";
33
33
  import { SnippetHandler } from "../handlers/request/snippet.handler.js";
34
34
  import { LibModulesHandler } from "../handlers/request/lib-modules.handler.js";
@@ -123,7 +123,7 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
123
123
  new OpenAPIDocsHandler(),
124
124
  new DevDashboardHandler(),
125
125
  new ProjectsHandler(),
126
- new StudioEndpointsHandler(),
126
+ new StudioBridgeModulesHandler(),
127
127
  new CSSHandler(),
128
128
  new DevFileHandler(),
129
129
  new SnippetHandler(),
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Pre-bundled Studio Bridge Script
3
+ *
4
+ * AUTO-GENERATED by scripts/build/prebundle-bridge.ts
5
+ * Do not edit manually — run `deno task build` to regenerate.
6
+ * @module
7
+ */
8
+ export declare const STUDIO_BRIDGE_BUNDLE: string | undefined;
9
+ //# sourceMappingURL=bridge-bundle.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-bundle.generated.d.ts","sourceRoot":"","sources":["../../../../src/src/studio/bridge/bridge-bundle.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB,EAAE,MAAM,GAAG,SAAqB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pre-bundled Studio Bridge Script
3
+ *
4
+ * AUTO-GENERATED by scripts/build/prebundle-bridge.ts
5
+ * Do not edit manually — run `deno task build` to regenerate.
6
+ * @module
7
+ */
8
+ export const STUDIO_BRIDGE_BUNDLE = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -283,7 +283,7 @@ export default {
283
283
  "proxy": "deno run --allow-all cli/main.ts serve --mode=proxy",
284
284
  "dev": "deno run --allow-all cli/main.ts dev",
285
285
  "production": "deno run --allow-all cli/main.ts serve --mode=production",
286
- "build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
286
+ "build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno run -A scripts/build/prebundle-bridge.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
287
287
  "build:npm": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/build-npm-dnt.ts",
288
288
  "release": "deno run -A scripts/release.ts",
289
289
  "test": "VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --parallel --allow-all '--ignore=tests/e2e,tests/integration/compiled-binary-e2e.test.ts' --unstable-worker-options --unstable-net",
@@ -352,7 +352,8 @@ export default {
352
352
  ],
353
353
  "exclude": [
354
354
  "dist/",
355
- "coverage/"
355
+ "coverage/",
356
+ "src/studio/bridge/"
356
357
  ],
357
358
  "rules": {
358
359
  "tags": [
@@ -68,18 +68,25 @@ export interface StudioScriptOptions {
68
68
  export function getStudioScripts(options: StudioScriptOptions): string {
69
69
  const nonceAttr = getNonceAttr(options.nonce);
70
70
 
71
- const params = new URLSearchParams({
71
+ const bridgeConfig: Record<string, unknown> = {
72
72
  projectId: options.projectId,
73
73
  pageId: options.pageId,
74
- ...(options.pagePath ? { pagePath: options.pagePath } : {}),
75
- ...(options.wsUrl ? { wsUrl: options.wsUrl } : {}),
76
- ...(options.yjsGuid ? { yjsGuid: options.yjsGuid } : {}),
77
- }).toString();
74
+ pagePath: options.pagePath ?? options.pageId,
75
+ };
76
+ if (options.wsUrl) bridgeConfig.wsUrl = options.wsUrl;
77
+ if (options.yjsGuid) bridgeConfig.yjsGuid = options.yjsGuid;
78
78
 
79
79
  const sourceHashScript = options.sourceHash
80
- ? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__="${options.sourceHash}";</script>\n `
80
+ ? `<script${nonceAttr}>window.__VERYFRONT_SOURCE_HASH__=${
81
+ JSON.stringify(options.sourceHash).replace(/</g, "\\u003c")
82
+ };</script>\n `
81
83
  : "";
82
84
 
85
+ // Escape </script> sequences to prevent XSS breakout from inline JSON
86
+ const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
87
+ const configScript = `<script${nonceAttr}>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>`;
88
+
83
89
  return `
84
- ${sourceHashScript}<script type="module" src="/_veryfront/studio-bridge.js?${params}"${nonceAttr}></script>`;
90
+ ${sourceHashScript}${configScript}
91
+ <script type="module" src="/_veryfront/studio-bridge.js"${nonceAttr}></script>`;
85
92
  }
@@ -10,7 +10,6 @@
10
10
  import * as dntShim from "../../../../_dnt.shims.js";
11
11
 
12
12
 
13
- import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
14
13
  import { escapeHtml } from "../../../utils/html-escape.js";
15
14
 
16
15
  /** Options for generating markdown preview HTML. */
@@ -93,15 +92,18 @@ function buildStudioScript(
93
92
  }
94
93
  const yjsGuid = branchId ? `${canonicalProjectId}:${branchId}` : canonicalProjectId;
95
94
 
96
- return `<script>${
97
- generateStudioBridgeScript({
98
- projectId: canonicalProjectId,
99
- pageId: canonicalPageId,
100
- pagePath: filePath,
101
- wsUrl,
102
- yjsGuid,
103
- })
104
- }</script>`;
95
+ const bridgeConfig: Record<string, unknown> = {
96
+ projectId: canonicalProjectId,
97
+ pageId: canonicalPageId,
98
+ pagePath: filePath,
99
+ };
100
+ if (wsUrl) bridgeConfig.wsUrl = wsUrl;
101
+ if (yjsGuid) bridgeConfig.yjsGuid = yjsGuid;
102
+
103
+ // Escape </script> sequences to prevent XSS breakout from inline JSON
104
+ const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
105
+ return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
106
+ <script type="module" src="/_veryfront/studio-bridge.js"></script>`;
105
107
  }
106
108
 
107
109
  /**
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Studio Bridge Handler
3
+ *
4
+ * Serves the studio bridge script. In compiled binaries, uses a pre-bundled
5
+ * version generated at build time. In dev mode, bundles on-the-fly with esbuild.
6
+ *
7
+ * Route: /_veryfront/studio-bridge.js
8
+ *
9
+ * @module server/handlers/studio/bridge-modules
10
+ */
11
+ import * as dntShim from "../../../../_dnt.shims.js";
12
+
13
+
14
+ import { BaseHandler } from "../../../security/index.js";
15
+ import type {
16
+ HandlerContext,
17
+ HandlerMetadata,
18
+ HandlerPriority,
19
+ HandlerResult,
20
+ } from "../types.js";
21
+ import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
22
+ import { STUDIO_BRIDGE_BUNDLE } from "../../../studio/bridge/bridge-bundle.generated.js";
23
+
24
+ /** Cached bundle output. */
25
+ let bundleCache: { js: string; etag: string } | null = null;
26
+
27
+ /** Resolve the bridge source directory from this module's location. */
28
+ const BRIDGE_DIR = new URL("../../../studio/bridge/", import.meta.url).pathname;
29
+
30
+ /**
31
+ * Compute a content hash for ETag.
32
+ */
33
+ async function computeEtag(content: string): Promise<string> {
34
+ const data = new TextEncoder().encode(content);
35
+ const hashBuffer = await dntShim.crypto.subtle.digest("SHA-256", data);
36
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
37
+ return hashArray.slice(0, 16).map((b) => b.toString(16).padStart(2, "0")).join("");
38
+ }
39
+
40
+ /**
41
+ * Bundle the bridge coordinator (and all its imports) into a single JS file.
42
+ * Uses pre-bundled output when available (compiled binary), falls back to
43
+ * esbuild JIT bundling in dev mode.
44
+ */
45
+ async function bundleBridge(): Promise<{ js: string; etag: string }> {
46
+ if (bundleCache) return bundleCache;
47
+
48
+ // Use pre-bundled output if available (compiled binary / CI builds)
49
+ if (STUDIO_BRIDGE_BUNDLE) {
50
+ const etag = await computeEtag(STUDIO_BRIDGE_BUNDLE);
51
+ bundleCache = { js: STUDIO_BRIDGE_BUNDLE, etag };
52
+ return bundleCache;
53
+ }
54
+
55
+ // Dev mode: bundle on-the-fly with esbuild
56
+ const entryPoint = `${BRIDGE_DIR}bridge-coordinator.ts`;
57
+ const source = await dntShim.Deno.readTextFile(entryPoint);
58
+
59
+ const { build } = await import("esbuild");
60
+ const { outputFiles } = await build({
61
+ bundle: true,
62
+ write: false,
63
+ format: "esm",
64
+ platform: "browser",
65
+ target: "es2022",
66
+ stdin: {
67
+ contents: source,
68
+ loader: "ts",
69
+ resolveDir: BRIDGE_DIR,
70
+ sourcefile: entryPoint,
71
+ },
72
+ });
73
+
74
+ const js = outputFiles?.[0]?.text ?? "";
75
+ const etag = await computeEtag(js);
76
+ bundleCache = { js, etag };
77
+ return bundleCache;
78
+ }
79
+
80
+ export class StudioBridgeModulesHandler extends BaseHandler {
81
+ metadata: HandlerMetadata = {
82
+ name: "StudioBridgeModulesHandler",
83
+ priority: PRIORITY_HIGH_DEV as HandlerPriority,
84
+ patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
85
+ enabled: () => true,
86
+ };
87
+
88
+ async handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult> {
89
+ if (!this.shouldHandle(req, ctx)) {
90
+ return this.continue();
91
+ }
92
+
93
+ const ifNoneMatch = req.headers.get("if-none-match");
94
+
95
+ try {
96
+ const { js, etag } = await bundleBridge();
97
+
98
+ if (ifNoneMatch === `"${etag}"`) {
99
+ return this.respond(
100
+ new dntShim.Response(null, {
101
+ status: 304,
102
+ headers: { ETag: `"${etag}"`, "Cache-Control": "no-cache" },
103
+ }),
104
+ );
105
+ }
106
+
107
+ return this.respond(
108
+ new dntShim.Response(js, {
109
+ status: HTTP_OK,
110
+ headers: {
111
+ "Content-Type": "application/javascript; charset=utf-8",
112
+ "Cache-Control": "no-cache",
113
+ ETag: `"${etag}"`,
114
+ },
115
+ }),
116
+ );
117
+ } catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ console.error("[StudioBridgeHandler] Bundle error:", message);
120
+ return this.respond(
121
+ new dntShim.Response(`// Bundle error: ${message}`, {
122
+ status: 500,
123
+ headers: { "Content-Type": "application/javascript; charset=utf-8" },
124
+ }),
125
+ );
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Invalidate the bundle cache.
132
+ * Used by dev file watchers to bust the cache on source changes.
133
+ */
134
+ export function invalidateBridgeModuleCache(): void {
135
+ bundleCache = null;
136
+ }
@@ -39,7 +39,7 @@ import { DevEndpointsHandler } from "../handlers/dev/endpoints.handler.js";
39
39
  import { DevFileHandler } from "../handlers/dev/files/index.js";
40
40
  import { DebugContextHandler } from "../handlers/dev/debug-context.handler.js";
41
41
  import { StylesCSSHandler } from "../handlers/dev/styles-css.handler.js";
42
- import { StudioEndpointsHandler } from "../handlers/studio/endpoints.handler.js";
42
+ import { StudioBridgeModulesHandler } from "../handlers/studio/bridge-modules.handler.js";
43
43
  import { StaticHandler } from "../handlers/request/static.handler.js";
44
44
  import { SnippetHandler } from "../handlers/request/snippet.handler.js";
45
45
  import { LibModulesHandler } from "../handlers/request/lib-modules.handler.js";
@@ -206,7 +206,7 @@ export function createVeryfrontHandler(
206
206
  new OpenAPIDocsHandler(),
207
207
  new DevDashboardHandler(),
208
208
  new ProjectsHandler(),
209
- new StudioEndpointsHandler(),
209
+ new StudioBridgeModulesHandler(),
210
210
  new CSSHandler(),
211
211
  new DevFileHandler(),
212
212
  new SnippetHandler(),
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Pre-bundled Studio Bridge Script
3
+ *
4
+ * AUTO-GENERATED by scripts/build/prebundle-bridge.ts
5
+ * Do not edit manually — run `deno task build` to regenerate.
6
+ * @module
7
+ */
8
+
9
+ export const STUDIO_BRIDGE_BUNDLE: string | undefined = undefined;
@@ -1,12 +0,0 @@
1
- /**
2
- * Studio Endpoints Handler
3
- * Handles studio bridge script and other studio-specific endpoints
4
- */
5
- import * as dntShim from "../../../../_dnt.shims.js";
6
- import { BaseHandler } from "../../../security/index.js";
7
- import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
8
- export declare class StudioEndpointsHandler extends BaseHandler {
9
- metadata: HandlerMetadata;
10
- handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
11
- }
12
- //# sourceMappingURL=endpoints.handler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"endpoints.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/studio/endpoints.handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,aAAa,CAAC;AAIrB,qBAAa,sBAAuB,SAAQ,WAAW;IACrD,QAAQ,EAAE,eAAe,CAKvB;IAEF,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAuB1E"}
@@ -1,29 +0,0 @@
1
- import { BaseHandler } from "../../../security/index.js";
2
- import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
3
- import { generateStudioBridgeScript } from "../../../studio/bridge-template.js";
4
- export class StudioEndpointsHandler extends BaseHandler {
5
- metadata = {
6
- name: "StudioEndpointsHandler",
7
- priority: PRIORITY_HIGH_DEV,
8
- patterns: [{ pattern: "/_veryfront/studio-bridge.js", exact: true }],
9
- enabled: () => true, // Always enabled - studio_embed check is in the script
10
- };
11
- handle(req, ctx) {
12
- if (!this.shouldHandle(req, ctx)) {
13
- return Promise.resolve(this.continue());
14
- }
15
- const url = new URL(req.url);
16
- if (url.pathname !== "/_veryfront/studio-bridge.js") {
17
- return Promise.resolve(this.continue());
18
- }
19
- const builder = this.createResponseBuilder(ctx);
20
- const projectId = url.searchParams.get("projectId") ?? "";
21
- const pageId = url.searchParams.get("pageId") ?? "";
22
- const pagePath = url.searchParams.get("pagePath") ?? undefined;
23
- const wsUrl = url.searchParams.get("wsUrl") ?? undefined;
24
- const yjsGuid = url.searchParams.get("yjsGuid") ?? undefined;
25
- const script = generateStudioBridgeScript({ projectId, pageId, pagePath, wsUrl, yjsGuid });
26
- const response = builder.withCache("no-cache").javascript(script, HTTP_OK);
27
- return Promise.resolve(this.respond(response));
28
- }
29
- }
@@ -1,11 +0,0 @@
1
- export interface StudioBridgeOptions {
2
- projectId: string;
3
- pageId: string;
4
- pagePath?: string;
5
- wsUrl?: string;
6
- yjsGuid?: string;
7
- debugSkipInit?: boolean;
8
- debugExposeInternals?: boolean;
9
- }
10
- export declare function generateStudioBridgeScript(options: StudioBridgeOptions): string;
11
- //# sourceMappingURL=bridge-template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAyrJ/E"}