veryfront 0.1.26 → 0.1.28

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 (124) hide show
  1. package/README.md +3 -11
  2. package/esm/cli/app/shell.d.ts.map +1 -1
  3. package/esm/cli/app/shell.js +9 -5
  4. package/esm/cli/commands/demo/demo.js +1 -1
  5. package/esm/cli/commands/init/catalog.d.ts.map +1 -1
  6. package/esm/cli/commands/init/catalog.js +13 -5
  7. package/esm/cli/commands/init/command-help.js +4 -4
  8. package/esm/cli/commands/init/types.d.ts +1 -1
  9. package/esm/cli/commands/init/types.d.ts.map +1 -1
  10. package/esm/cli/commands/serve/command.d.ts.map +1 -1
  11. package/esm/cli/commands/serve/command.js +0 -4
  12. package/esm/cli/commands/start/command.d.ts.map +1 -1
  13. package/esm/cli/commands/start/command.js +16 -9
  14. package/esm/cli/help/tips.js +6 -6
  15. package/esm/cli/mcp/remote-file-tools.js +1 -1
  16. package/esm/cli/mcp/tools/catalog-tools.d.ts +3 -3
  17. package/esm/cli/mcp/tools/catalog-tools.d.ts.map +1 -1
  18. package/esm/cli/mcp/tools/catalog-tools.js +21 -13
  19. package/esm/cli/mcp/tools/project-tools.js +1 -1
  20. package/esm/cli/templates/index.js +11 -11
  21. package/esm/cli/templates/manifest.d.ts +22 -15
  22. package/esm/cli/templates/manifest.js +24 -17
  23. package/esm/cli/templates/types.d.ts +1 -1
  24. package/esm/cli/templates/types.d.ts.map +1 -1
  25. package/esm/cli/utils/index.d.ts.map +1 -1
  26. package/esm/cli/utils/index.js +13 -1
  27. package/esm/deno.js +1 -1
  28. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  29. package/esm/src/html/html-shell-generator.js +2 -0
  30. package/esm/src/html/styles-builder/project-css-cache.d.ts +8 -1
  31. package/esm/src/html/styles-builder/project-css-cache.d.ts.map +1 -1
  32. package/esm/src/html/styles-builder/project-css-cache.js +13 -2
  33. package/esm/src/html/styles-builder/tailwind-compiler.d.ts +2 -0
  34. package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
  35. package/esm/src/html/styles-builder/tailwind-compiler.js +52 -19
  36. package/esm/src/modules/react-loader/css-import-collector.d.ts +29 -0
  37. package/esm/src/modules/react-loader/css-import-collector.d.ts.map +1 -0
  38. package/esm/src/modules/react-loader/css-import-collector.js +41 -0
  39. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  40. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +6 -0
  41. package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.d.ts.map +1 -1
  42. package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.js +5 -0
  43. package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
  44. package/esm/src/platform/adapters/fs/factory.js +5 -1
  45. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +1 -0
  46. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
  47. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +19 -5
  48. package/esm/src/platform/compat/process.d.ts.map +1 -1
  49. package/esm/src/platform/compat/process.js +20 -3
  50. package/esm/src/proxy/main.js +31 -12
  51. package/esm/src/proxy/token-manager.d.ts +2 -0
  52. package/esm/src/proxy/token-manager.d.ts.map +1 -1
  53. package/esm/src/proxy/token-manager.js +47 -8
  54. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +23 -0
  55. package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -0
  56. package/esm/src/rendering/orchestrator/css-candidate-manifest.js +132 -0
  57. package/esm/src/rendering/orchestrator/html.d.ts +11 -1
  58. package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
  59. package/esm/src/rendering/orchestrator/html.js +103 -18
  60. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  61. package/esm/src/rendering/orchestrator/pipeline.js +14 -2
  62. package/esm/src/server/bootstrap.d.ts +2 -0
  63. package/esm/src/server/bootstrap.d.ts.map +1 -1
  64. package/esm/src/server/bootstrap.js +10 -0
  65. package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
  66. package/esm/src/server/handlers/preview/markdown-html-generator.js +11 -5
  67. package/esm/src/server/production-server.js +10 -2
  68. package/esm/src/studio/bridge-template.d.ts +2 -0
  69. package/esm/src/studio/bridge-template.d.ts.map +1 -1
  70. package/esm/src/studio/bridge-template.js +3390 -52
  71. package/esm/src/transforms/css-modules/naming.d.ts +33 -0
  72. package/esm/src/transforms/css-modules/naming.d.ts.map +1 -0
  73. package/esm/src/transforms/css-modules/naming.js +128 -0
  74. package/esm/src/transforms/esm/import-parser.d.ts +1 -0
  75. package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
  76. package/esm/src/transforms/esm/import-parser.js +16 -5
  77. package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
  78. package/esm/src/transforms/pipeline/index.js +3 -1
  79. package/esm/src/transforms/pipeline/stages/index.d.ts +1 -0
  80. package/esm/src/transforms/pipeline/stages/index.d.ts.map +1 -1
  81. package/esm/src/transforms/pipeline/stages/index.js +1 -0
  82. package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts +18 -0
  83. package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts.map +1 -0
  84. package/esm/src/transforms/pipeline/stages/ssr-css-strip.js +168 -0
  85. package/package.json +1 -1
  86. package/src/cli/app/shell.ts +9 -5
  87. package/src/cli/commands/demo/demo.ts +1 -1
  88. package/src/cli/commands/init/catalog.ts +13 -5
  89. package/src/cli/commands/init/command-help.ts +4 -4
  90. package/src/cli/commands/init/types.ts +5 -5
  91. package/src/cli/commands/serve/command.ts +0 -5
  92. package/src/cli/commands/start/command.ts +15 -10
  93. package/src/cli/help/tips.ts +6 -6
  94. package/src/cli/mcp/remote-file-tools.ts +1 -1
  95. package/src/cli/mcp/tools/catalog-tools.ts +21 -13
  96. package/src/cli/mcp/tools/project-tools.ts +1 -1
  97. package/src/cli/templates/index.ts +11 -11
  98. package/src/cli/templates/manifest.js +24 -17
  99. package/src/cli/templates/types.ts +5 -5
  100. package/src/cli/utils/index.ts +12 -1
  101. package/src/deno.js +1 -1
  102. package/src/src/html/html-shell-generator.ts +2 -0
  103. package/src/src/html/styles-builder/project-css-cache.ts +24 -1
  104. package/src/src/html/styles-builder/tailwind-compiler.ts +67 -26
  105. package/src/src/modules/react-loader/css-import-collector.ts +50 -0
  106. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +7 -0
  107. package/src/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.ts +6 -0
  108. package/src/src/platform/adapters/fs/factory.ts +5 -1
  109. package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +21 -5
  110. package/src/src/platform/compat/process.ts +28 -4
  111. package/src/src/proxy/main.ts +32 -12
  112. package/src/src/proxy/token-manager.ts +54 -8
  113. package/src/src/rendering/orchestrator/css-candidate-manifest.ts +176 -0
  114. package/src/src/rendering/orchestrator/html.ts +128 -16
  115. package/src/src/rendering/orchestrator/pipeline.ts +183 -165
  116. package/src/src/server/bootstrap.ts +16 -0
  117. package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -5
  118. package/src/src/server/production-server.ts +12 -2
  119. package/src/src/studio/bridge-template.ts +3392 -52
  120. package/src/src/transforms/css-modules/naming.ts +152 -0
  121. package/src/src/transforms/esm/import-parser.ts +15 -5
  122. package/src/src/transforms/pipeline/index.ts +3 -0
  123. package/src/src/transforms/pipeline/stages/index.ts +1 -0
  124. package/src/src/transforms/pipeline/stages/ssr-css-strip.ts +201 -0
@@ -0,0 +1,132 @@
1
+ import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
2
+ import { getRouteModulePaths } from "../../modules/manifest/route-module-manifest.js";
3
+ import { rendererLogger } from "../../utils/index.js";
4
+ const logger = rendererLogger.component("css-candidate-manifest");
5
+ const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
6
+ const DEV_MANIFEST_TTL_MS = 2000;
7
+ const manifestCache = new Map();
8
+ const routeCandidateCache = new Map();
9
+ function normalizePath(path) {
10
+ return path.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
11
+ }
12
+ function toRelativeProjectPath(path, projectDir) {
13
+ const normalized = normalizePath(path);
14
+ const normalizedProjectDir = normalizePath(projectDir).replace(/\/+$/, "");
15
+ if (normalized.startsWith(normalizedProjectDir)) {
16
+ return normalized.slice(normalizedProjectDir.length).replace(/^\/+/, "");
17
+ }
18
+ return normalized.replace(/^\/+/, "");
19
+ }
20
+ function buildManifestCacheKey(projectScope, projectVersion) {
21
+ return `${projectScope}:${projectVersion}`;
22
+ }
23
+ function shouldRebuildManifest(existing, developmentMode) {
24
+ if (!existing)
25
+ return true;
26
+ if (!developmentMode)
27
+ return false;
28
+ return (Date.now() - existing.builtAt) > DEV_MANIFEST_TTL_MS;
29
+ }
30
+ function buildSourceCandidatePaths(modulePath) {
31
+ const normalized = normalizePath(modulePath).replace(/^\/+/, "").replace(/^_vf_modules\//, "");
32
+ if (!normalized.endsWith(".js"))
33
+ return [normalized];
34
+ const withoutJs = normalized.slice(0, -3);
35
+ return [
36
+ `${withoutJs}.tsx`,
37
+ `${withoutJs}.ts`,
38
+ `${withoutJs}.jsx`,
39
+ `${withoutJs}.mdx`,
40
+ `${withoutJs}.js`,
41
+ ];
42
+ }
43
+ function buildCandidateManifest(files, projectDir) {
44
+ const fileCandidates = new Map();
45
+ const allCandidates = new Set();
46
+ for (const file of files) {
47
+ if (!file.content)
48
+ continue;
49
+ if (!SOURCE_EXTENSIONS.some((ext) => file.path.endsWith(ext)))
50
+ continue;
51
+ const candidates = new Set(extractCandidates(file.content));
52
+ const relativePath = toRelativeProjectPath(file.path, projectDir);
53
+ const absolutePath = normalizePath(file.path);
54
+ fileCandidates.set(relativePath, candidates);
55
+ fileCandidates.set(absolutePath, candidates);
56
+ for (const cls of candidates)
57
+ allCandidates.add(cls);
58
+ }
59
+ return { fileCandidates, allCandidates, builtAt: Date.now() };
60
+ }
61
+ function addCandidatesForPath(target, manifest, path, projectDir) {
62
+ const absolutePath = normalizePath(path);
63
+ const relativePath = toRelativeProjectPath(path, projectDir);
64
+ const candidates = manifest.fileCandidates.get(absolutePath) ??
65
+ manifest.fileCandidates.get(relativePath);
66
+ if (!candidates)
67
+ return;
68
+ for (const cls of candidates)
69
+ target.add(cls);
70
+ }
71
+ /**
72
+ * Resolve route-scoped Tailwind candidates from a precomputed per-project manifest.
73
+ */
74
+ export function getRouteCandidates(options) {
75
+ const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
76
+ const existingManifest = manifestCache.get(manifestKey);
77
+ const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
78
+ ? buildCandidateManifest(options.files, options.projectDir)
79
+ : existingManifest;
80
+ if (manifest !== existingManifest) {
81
+ manifestCache.set(manifestKey, manifest);
82
+ // Clear route subsets when project-level file manifest is rebuilt.
83
+ for (const key of routeCandidateCache.keys()) {
84
+ if (key.startsWith(`${manifestKey}:`))
85
+ routeCandidateCache.delete(key);
86
+ }
87
+ }
88
+ const routeCacheKey = `${manifestKey}:${options.routeKey}`;
89
+ const cachedRoute = routeCandidateCache.get(routeCacheKey);
90
+ if (cachedRoute)
91
+ return new Set(cachedRoute);
92
+ const routeCandidates = new Set();
93
+ for (const path of options.routeFilePaths) {
94
+ addCandidatesForPath(routeCandidates, manifest, path, options.projectDir);
95
+ }
96
+ for (const modulePath of getRouteModulePaths(options.projectScope, options.routeKey)) {
97
+ for (const sourcePath of buildSourceCandidatePaths(modulePath)) {
98
+ addCandidatesForPath(routeCandidates, manifest, sourcePath, options.projectDir);
99
+ }
100
+ }
101
+ // Fallback to full-project candidates for correctness if route manifest is incomplete.
102
+ if (routeCandidates.size === 0) {
103
+ for (const cls of manifest.allCandidates)
104
+ routeCandidates.add(cls);
105
+ }
106
+ routeCandidateCache.set(routeCacheKey, routeCandidates);
107
+ logger.debug("Resolved route candidates", {
108
+ projectScope: options.projectScope,
109
+ projectVersion: options.projectVersion,
110
+ route: options.routeKey,
111
+ count: routeCandidates.size,
112
+ });
113
+ return new Set(routeCandidates);
114
+ }
115
+ /**
116
+ * Invalidate cached candidate manifests for one project scope (or all scopes).
117
+ */
118
+ export function invalidateProjectCandidateManifests(projectScope) {
119
+ if (!projectScope) {
120
+ manifestCache.clear();
121
+ routeCandidateCache.clear();
122
+ return;
123
+ }
124
+ for (const key of manifestCache.keys()) {
125
+ if (key.startsWith(`${projectScope}:`))
126
+ manifestCache.delete(key);
127
+ }
128
+ for (const key of routeCandidateCache.keys()) {
129
+ if (key.startsWith(`${projectScope}:`))
130
+ routeCandidateCache.delete(key);
131
+ }
132
+ }
@@ -20,6 +20,8 @@ export interface HTMLGenerationContext {
20
20
  ssrHash: string;
21
21
  options?: RenderOptions;
22
22
  collectedHead?: CollectedHead;
23
+ /** Absolute paths to CSS files imported by components (collected during module loading) */
24
+ cssImports?: string[];
23
25
  }
24
26
  export declare class HTMLGenerator {
25
27
  private config;
@@ -35,6 +37,14 @@ export declare class HTMLGenerator {
35
37
  private resolveAppPath;
36
38
  private loadProjectFile;
37
39
  private buildHTMLOptions;
38
- private extractProjectClasses;
40
+ /**
41
+ * Load CSS files imported by components and merge with the global stylesheet.
42
+ * Deduplicates against the configured Tailwind stylesheet path to avoid
43
+ * double-loading globals.css when it's both auto-discovered and explicitly imported.
44
+ */
45
+ private mergeImportedCSS;
46
+ private getProjectContentVersion;
47
+ private buildRouteManifestKey;
48
+ private extractProjectClassesForRoute;
39
49
  }
40
50
  //# sourceMappingURL=html.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/html.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAS7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,SAAS,EAET,UAAU,EACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAShD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,aAAa,EAAE,UAAU,EAAE,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAIjC,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IAWjE,kBAAkB,CACtB,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,GAC3C,OAAO,CAAC,cAAc,CAAC;YAmCZ,sBAAsB;YAsBtB,wBAAwB;YAkBxB,gBAAgB;YAehB,kBAAkB;IAsDhC,OAAO,CAAC,iBAAiB;IA4DzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;YAQR,eAAe;YAYf,gBAAgB;YAmEhB,qBAAqB;CAsCpC"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/html.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,SAAS,EAET,UAAU,EACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAchD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,aAAa,EAAE,UAAU,EAAE,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAIjC,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IAWjE,kBAAkB,CACtB,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,GAC3C,OAAO,CAAC,cAAc,CAAC;YAmCZ,sBAAsB;YAsBtB,wBAAwB;YAkBxB,gBAAgB;YAehB,kBAAkB;IAsDhC,OAAO,CAAC,iBAAiB;IA4DzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;YAQR,eAAe;YAYf,gBAAgB;IAuE9B;;;;OAIG;YACW,gBAAgB;IA0D9B,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,qBAAqB;YAOf,6BAA6B;CAwD5C"}
@@ -1,13 +1,14 @@
1
1
  import { join } from "../../platform/compat/path/index.js";
2
2
  import { getExtensionName } from "../../utils/path-utils.js";
3
3
  import { extractHTMLMetadata, generateHTMLShellParts, injectHTMLContent, isFullHTMLDocument, } from "../../html/index.js";
4
- import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
5
4
  import { DEFAULT_DASHBOARD_PORT, rendererLogger } from "../../utils/index.js";
6
5
  import { injectElementSelectors } from "../../studio/element-selector-injector.js";
7
6
  import { computeSourceHash } from "../../studio/hash-utils.js";
8
7
  import { extractRelativePath } from "../../utils/route-path-utils.js";
9
8
  import { resolveAppComponentPath } from "../layouts/utils/app-resolver.js";
10
9
  import { StreamTimeoutError, streamToString } from "../utils/stream-utils.js";
10
+ import { normalizeCssModuleKey, rewriteCssModuleContent, } from "../../transforms/css-modules/naming.js";
11
+ import { getRouteCandidates } from "./css-candidate-manifest.js";
11
12
  const logger = rendererLogger.component("html-generator");
12
13
  export class HTMLGenerator {
13
14
  config;
@@ -204,11 +205,14 @@ export class HTMLGenerator {
204
205
  }
205
206
  async buildHTMLOptions(context, mergedFrontmatter) {
206
207
  const stylesheetPath = this.config.config?.tailwind?.stylesheet || "globals.css";
207
- const [appComponentPath, globalCSS, projectClasses] = await Promise.all([
208
+ const [appComponentPath, globalCSS] = await Promise.all([
208
209
  this.resolveAppPath().then((p) => p ?? undefined),
209
210
  this.loadProjectFile(stylesheetPath),
210
- this.extractProjectClasses(),
211
211
  ]);
212
+ const projectClasses = await this.extractProjectClassesForRoute(context, appComponentPath);
213
+ // Load CSS imported by components and merge with globalCSS.
214
+ // Deduplicate against the configured stylesheet to avoid double-loading.
215
+ const combinedCSS = await this.mergeImportedCSS(globalCSS, context.cssImports, stylesheetPath);
212
216
  logger.debug("App component resolution", {
213
217
  appComponentPath,
214
218
  projectDir: this.config.projectDir,
@@ -234,7 +238,7 @@ export class HTMLGenerator {
234
238
  pagePath,
235
239
  pageType,
236
240
  nonce: context.options?.nonce,
237
- globalCSS,
241
+ globalCSS: combinedCSS,
238
242
  frontmatter: mergedFrontmatter,
239
243
  studioEmbed: context.options?.studioEmbed,
240
244
  projectId: context.options?.projectId,
@@ -250,8 +254,74 @@ export class HTMLGenerator {
250
254
  noHmr: context.options?.noHmr,
251
255
  };
252
256
  }
253
- async extractProjectClasses() {
254
- const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
257
+ /**
258
+ * Load CSS files imported by components and merge with the global stylesheet.
259
+ * Deduplicates against the configured Tailwind stylesheet path to avoid
260
+ * double-loading globals.css when it's both auto-discovered and explicitly imported.
261
+ */
262
+ async mergeImportedCSS(globalCSS, cssImports, stylesheetPath) {
263
+ if (!cssImports || cssImports.length === 0)
264
+ return globalCSS;
265
+ const normalizedStylesheetPath = stylesheetPath.replace(/^\/+/, "");
266
+ const configuredStylesheetAbsolute = normalizeCssModuleKey(join(this.config.projectDir, normalizedStylesheetPath));
267
+ const uniqueImports = new Map();
268
+ for (const cssPath of cssImports) {
269
+ const normalized = normalizeCssModuleKey(cssPath);
270
+ if (!uniqueImports.has(normalized)) {
271
+ uniqueImports.set(normalized, cssPath);
272
+ }
273
+ }
274
+ const sortedImports = [...uniqueImports.entries()].sort((a, b) => a[0].localeCompare(b[0]));
275
+ const regularCssSegments = [];
276
+ const moduleCssSegments = [];
277
+ for (const [normalizedCssPath, cssPath] of sortedImports) {
278
+ // Deduplicate only exact path matches to avoid skipping unrelated files
279
+ // like /styles/globals.css when the configured stylesheet is /globals.css.
280
+ if (normalizedCssPath === configuredStylesheetAbsolute) {
281
+ continue;
282
+ }
283
+ try {
284
+ const content = await this.config.adapter.fs.readFile(cssPath);
285
+ if (!content)
286
+ continue;
287
+ if (normalizedCssPath.endsWith(".module.css")) {
288
+ moduleCssSegments.push(rewriteCssModuleContent(content, normalizedCssPath));
289
+ }
290
+ else {
291
+ regularCssSegments.push(content);
292
+ }
293
+ }
294
+ catch {
295
+ logger.debug("Could not load imported CSS file", { cssPath });
296
+ }
297
+ }
298
+ if (regularCssSegments.length === 0 && moduleCssSegments.length === 0)
299
+ return globalCSS;
300
+ const combined = [globalCSS, ...regularCssSegments, ...moduleCssSegments]
301
+ .filter(Boolean)
302
+ .join("\n");
303
+ logger.debug("Merged imported CSS with global stylesheet", {
304
+ importedCount: regularCssSegments.length + moduleCssSegments.length,
305
+ regularCount: regularCssSegments.length,
306
+ moduleCount: moduleCssSegments.length,
307
+ totalLength: combined.length,
308
+ });
309
+ return combined;
310
+ }
311
+ getProjectContentVersion() {
312
+ const wrappedFs = this.config.adapter.fs;
313
+ if (typeof wrappedFs.getUnderlyingAdapter !== "function")
314
+ return undefined;
315
+ const fsAdapter = wrappedFs.getUnderlyingAdapter();
316
+ return fsAdapter.getProjectData?.()?.updated_at;
317
+ }
318
+ buildRouteManifestKey(pagePath) {
319
+ const relativePagePath = extractRelativePath(pagePath, this.config.projectDir);
320
+ return relativePagePath
321
+ .replace(/\.(tsx|ts|jsx|mdx|md|js)$/, "")
322
+ .replace(/^pages\//, "");
323
+ }
324
+ async extractProjectClassesForRoute(context, appComponentPath) {
255
325
  const classes = new Set();
256
326
  const wrappedFs = this.config.adapter.fs;
257
327
  if (typeof wrappedFs.getUnderlyingAdapter !== "function")
@@ -260,19 +330,34 @@ export class HTMLGenerator {
260
330
  if (typeof fsAdapter.getAllSourceFiles !== "function")
261
331
  return classes;
262
332
  const files = await fsAdapter.getAllSourceFiles();
263
- let filesProcessed = 0;
264
- for (const file of files) {
265
- if (!file.content)
266
- continue;
267
- if (!SOURCE_EXTENSIONS.some((ext) => file.path.endsWith(ext)))
268
- continue;
269
- filesProcessed++;
270
- for (const cls of extractCandidates(file.content)) {
271
- classes.add(cls);
272
- }
273
- }
333
+ const projectScope = context.options?.projectSlug || context.options?.projectId ||
334
+ this.config.projectDir;
335
+ const projectVersion = this.getProjectContentVersion() ??
336
+ (this.config.mode === "development" ? "dev" : "unknown");
337
+ const routeKey = this.buildRouteManifestKey(context.pageInfo.entity.path);
338
+ const routeLayoutPaths = context.nestedLayouts
339
+ .map((layout) => layout.componentPath || layout.path)
340
+ .filter((path) => Boolean(path));
341
+ const routeFilePaths = [
342
+ context.pageInfo.entity.path,
343
+ ...routeLayoutPaths,
344
+ ...(appComponentPath ? [appComponentPath] : []),
345
+ ];
346
+ const routeCandidates = getRouteCandidates({
347
+ projectScope,
348
+ projectVersion,
349
+ projectDir: this.config.projectDir,
350
+ routeKey,
351
+ routeFilePaths,
352
+ files,
353
+ developmentMode: this.config.mode === "development",
354
+ });
355
+ for (const cls of routeCandidates)
356
+ classes.add(cls);
274
357
  logger.debug("extractProjectClasses", {
275
- filesProcessed,
358
+ filesProcessed: files.length,
359
+ routeKey,
360
+ routeFileCount: routeFilePaths.length,
276
361
  totalClasses: classes.size,
277
362
  });
278
363
  return classes;
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAoChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB;;;;;;;;;OASG;YACW,qBAAqB;IAsDnC;;;OAGG;YACW,mBAAmB;IAkH3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA8N9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4KvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAwChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB;;;;;;;;;OASG;YACW,qBAAqB;IAsDnC;;;OAGG;YACW,mBAAmB;IAkH3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA4O9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4KvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
@@ -31,6 +31,7 @@ import { LAYOUT_EXTENSIONS } from "../layouts/types.js";
31
31
  import { withTimeout, withTimeoutThrow } from "../utils/stream-utils.js";
32
32
  import { generateTailwind4CSS } from "../../html/styles-builder/index.js";
33
33
  import { createEsmCache, createModuleCache, loadModule } from "./module-loader/index.js";
34
+ import { getCSSImports, runWithCSSCollector, } from "../../modules/react-loader/css-import-collector.js";
34
35
  // Extracted modules
35
36
  import { EMPTY_LAYOUT_RESULT, isDotPath } from "./path-helpers.js";
36
37
  import { cachePageCss, CSS_SSR_TIMEOUT_MS, getCachedPageCss, getPageCssCacheKey, } from "./css-cache.js";
@@ -215,7 +216,7 @@ export class RenderPipeline {
215
216
  if (this.config.mode === "development") {
216
217
  clearSSRModuleCacheForProject(projectId);
217
218
  }
218
- return withSpan("render.page", async () => {
219
+ return withSpan("render.page", () => runWithCSSCollector(async () => {
219
220
  const pageResolveStart = performance.now();
220
221
  const pageInfo = await withSpan("render.resolve_page", () => this.config.pageResolver.resolvePage(slug), { "render.slug": slug });
221
222
  timing.pageResolve = Math.round(performance.now() - pageResolveStart);
@@ -283,6 +284,9 @@ export class RenderPipeline {
283
284
  const layoutApplyStart = performance.now();
284
285
  const wrappedElement = await withSpan("render.apply_layouts", () => this.config.layoutOrchestrator.applyLayoutsAndWrappers(pageElement, pageInfo, layoutResult.layoutBundle, layoutResult.nestedLayouts, layoutDataMap, options?.url, mergedFrontmatter, headings, options?.projectSlug), { "render.slug": slug, "render.layout_count": layoutResult.nestedLayouts.length });
285
286
  timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
287
+ // Snapshot CSS imports collected during module loading (before SSR rendering).
288
+ // These are passed to the HTML generator to be included in the output.
289
+ const collectedCSSImports = getCSSImports();
286
290
  const ssrStart = performance.now();
287
291
  const ssrResult = await withSpan("render.ssr", () => withTimeoutThrow(this.config.ssrOrchestrator.performSSRRendering(wrappedElement, {
288
292
  pageInfo,
@@ -291,8 +295,16 @@ export class RenderPipeline {
291
295
  nestedLayouts: layoutResult.nestedLayouts,
292
296
  collectedMetadata: pageBundleResult.collectedMetadata,
293
297
  slug,
298
+ cssImports: collectedCSSImports,
294
299
  }, mergedOptions), SSR_RENDER_TIMEOUT_MS, `SSR rendering for ${slug}`), { "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" });
295
300
  timing.ssr = Math.round(performance.now() - ssrStart);
301
+ if (collectedCSSImports.length > 0) {
302
+ renderPipelineLog.debug("CSS imports collected for HTML generation", {
303
+ slug,
304
+ count: collectedCSSImports.length,
305
+ paths: collectedCSSImports.map((p) => p.split("/").pop()),
306
+ });
307
+ }
296
308
  const pageModule = pageBundleResult.clientModuleCode && pageBundleResult.pageModuleType
297
309
  ? {
298
310
  slug,
@@ -321,7 +333,7 @@ export class RenderPipeline {
321
333
  timing.total = Math.round(performance.now() - pipelineStartTime);
322
334
  renderPipelineLog.debug("Complete", { slug, timing });
323
335
  return result;
324
- }, {
336
+ }).then(({ result }) => result), {
325
337
  "render.slug": slug,
326
338
  "render.project_id": options?.projectId || this.config.projectDir,
327
339
  "render.mode": this.config.mode,
@@ -9,6 +9,8 @@ export interface BootstrapResult {
9
9
  usingFSAdapter: boolean;
10
10
  /** FSAdapter type (if used) */
11
11
  fsAdapterType?: string;
12
+ /** Dispose FSAdapter resources (WebSocket connections, caches) */
13
+ dispose?: () => void;
12
14
  }
13
15
  export declare function bootstrap(projectDir: string, adapter: RuntimeAdapter): Promise<BootstrapResult>;
14
16
  export declare function bootstrapDev(projectDir: string, adapter: RuntimeAdapter): Promise<BootstrapResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/src/server/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA2B1D,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,cAAc,CAAC;IAExB,2BAA2B;IAC3B,MAAM,EAAE,eAAe,CAAC;IAExB,wCAAwC;IACxC,cAAc,EAAE,OAAO,CAAC;IAExB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkDD,wBAAsB,SAAS,CAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAyF1B;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAyB1B"}
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/src/server/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA4B1D,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,cAAc,CAAC;IAExB,2BAA2B;IAC3B,MAAM,EAAE,eAAe,CAAC;IAExB,wCAAwC;IACxC,cAAc,EAAE,OAAO,CAAC;IAExB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAkDD,wBAAsB,SAAS,CAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAqG1B;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAyB1B"}
@@ -2,6 +2,7 @@ import { clearConfigCache, getConfig } from "../config/index.js";
2
2
  import { getEnvironmentConfig, refreshEnvironmentConfig, } from "../config/environment-config.js";
3
3
  import { getErrorMessage } from "../errors/veryfront-error.js";
4
4
  import { enhanceAdapterWithFS } from "../platform/adapters/fs/integration.js";
5
+ import { isExtendedFSAdapter } from "../platform/adapters/fs/wrapper.js";
5
6
  import { getEnv } from "../platform/compat/process.js";
6
7
  import { initializeEsbuild } from "../platform/compat/esbuild.js";
7
8
  import { logger } from "../utils/index.js";
@@ -120,11 +121,20 @@ export async function bootstrap(projectDir, adapter) {
120
121
  runtime: adapter.id,
121
122
  fsAdapter: fsType,
122
123
  });
124
+ let dispose;
125
+ if (isExtendedFSAdapter(enhancedAdapter.fs)) {
126
+ const underlying = enhancedAdapter.fs.getUnderlyingAdapter();
127
+ if ("dispose" in underlying &&
128
+ typeof underlying.dispose === "function") {
129
+ dispose = () => underlying.dispose();
130
+ }
131
+ }
123
132
  return {
124
133
  adapter: enhancedAdapter,
125
134
  config,
126
135
  usingFSAdapter: true,
127
136
  fsAdapterType: fsType,
137
+ dispose,
128
138
  };
129
139
  }
130
140
  export async function bootstrapDev(projectDir, adapter) {
@@ -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;CAClB;AAyCD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA0FzE"}
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;CAClB;AAgDD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA0FzE"}
@@ -21,16 +21,22 @@ function detectTheme(req, url) {
21
21
  return null;
22
22
  }
23
23
  /**
24
- * Generate the studio bridge `<script>` tag when embedded in Studio.
25
- * Returns an empty string when not in studio embed mode.
24
+ * Generate the studio bridge `<script>` tag.
25
+ * Injected when embedded in Studio (`studio_embed=true`) or for standalone
26
+ * markdown/MDX pages so the edit button and editor features are available.
26
27
  */
27
28
  function buildStudioScript(url, projectId, filePath) {
28
29
  const studioEmbed = url.searchParams.get("studio_embed") === "true";
29
- if (!studioEmbed)
30
+ const isMarkdown = /\.mdx?$/i.test(filePath);
31
+ if (!studioEmbed && !isMarkdown)
30
32
  return "";
33
+ const queryProjectId = url.searchParams.get("vf_project_id")?.trim() || "";
34
+ const queryFileId = url.searchParams.get("vf_file_id")?.trim() || "";
35
+ const canonicalProjectId = queryProjectId || projectId;
36
+ const canonicalPageId = queryFileId || filePath;
31
37
  return `<script>${generateStudioBridgeScript({
32
- projectId,
33
- pageId: filePath,
38
+ projectId: canonicalProjectId,
39
+ pageId: canonicalPageId,
34
40
  pagePath: filePath,
35
41
  })}</script>`;
36
42
  }
@@ -170,12 +170,14 @@ if (globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main) {
170
170
  // BIND_ADDRESS: 0.0.0.0 = all interfaces, 127.0.0.1 = localhost only
171
171
  // Note: Don't use HOSTNAME - K8s sets it to pod name which resolves to pod IP
172
172
  const bindAddress = adapter.env.get("BIND_ADDRESS") ?? "0.0.0.0";
173
+ const bootstrap = await bootstrapProd(projectDir, adapter);
173
174
  const server = await startProductionServer({
174
175
  projectDir,
175
176
  port,
176
177
  bindAddress,
177
178
  debug: isDebugEnabled(adapter.env),
178
179
  adapter, // Pass adapter to avoid re-detection
180
+ bootstrapResult: bootstrap,
179
181
  signal: shutdownController.signal,
180
182
  });
181
183
  // Wait for server to be fully ready before accepting traffic
@@ -209,6 +211,7 @@ if (globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main) {
209
211
  // Phase 3: Stop accepting new connections and clean up
210
212
  stopMemoryMonitoring();
211
213
  requestTracker.shutdown();
214
+ bootstrap.dispose?.();
212
215
  shutdownController.abort();
213
216
  await server.stop();
214
217
  await shutdownOTLP();
@@ -218,8 +221,13 @@ if (globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main) {
218
221
  logger.warn("Error while shutting down production server:", error);
219
222
  }
220
223
  };
221
- onSignal("SIGINT", () => void shutdown("SIGINT"));
222
- onSignal("SIGTERM", () => void shutdown("SIGTERM"));
224
+ const handleSignal = (signal) => {
225
+ void shutdown(signal).catch((error) => {
226
+ logger.warn("Unhandled error while shutting down production server", { signal, error });
227
+ });
228
+ };
229
+ onSignal("SIGINT", () => handleSignal("SIGINT"));
230
+ onSignal("SIGTERM", () => handleSignal("SIGTERM"));
223
231
  }
224
232
  catch (e) {
225
233
  logger.error("Failed to start production server:", e);
@@ -2,6 +2,8 @@ export interface StudioBridgeOptions {
2
2
  projectId: string;
3
3
  pageId: string;
4
4
  pagePath?: string;
5
+ debugSkipInit?: boolean;
6
+ debugExposeInternals?: boolean;
5
7
  }
6
8
  export declare function generateStudioBridgeScript(options: StudioBridgeOptions): string;
7
9
  //# sourceMappingURL=bridge-template.d.ts.map
@@ -1 +1 @@
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;CACnB;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA+uB/E"}
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,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAy/H/E"}