@jay-framework/stack-server-build 0.24.2 → 0.24.4

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.
package/dist/index.d.ts CHANGED
@@ -58,6 +58,17 @@ interface LoadedPageParts {
58
58
  /** Script tags with jay-script="allow" to include in SSR output */
59
59
  scripts?: JayHtmlScript[];
60
60
  }
61
+ /**
62
+ * Resolve the browser hydration import path for a route (DL#182).
63
+ *
64
+ * SSR loads `route.compPath` (which is `./tools` for devOnly plugin routes, `.` otherwise); the
65
+ * browser must never load those server bundles. Prefer the explicit `clientCompPath` resolved from
66
+ * the plugin's `./client` export. Fall back to the legacy `index.js`→`index.client.js` rewrite only
67
+ * for NPM plugin routes (`componentExport` set) that carry no `clientCompPath` — note this rewrite
68
+ * silently no-ops for a devOnly `compPath` ending in `tools.js`, which is the bug DL#182 fixes.
69
+ * Local (project) components hydrate directly from `compPath`.
70
+ */
71
+ declare function resolveClientImportPath(route: JayRoute): string;
61
72
  interface LoadPagePartsOptions {
62
73
  /**
63
74
  * Path to pre-rendered jay-html file to use instead of the original.
@@ -124,6 +135,8 @@ interface ScriptFragments {
124
135
  * Build the shared script fragments used by both client and hydration scripts.
125
136
  */
126
137
  declare function buildScriptFragments(parts: DevServerPagePart[], clientInitData: Record<string, Record<string, any>>, projectInit: ProjectClientInitInfo | undefined, pluginInits: PluginClientInitInfo[], options: GenerateClientScriptOptions): ScriptFragments;
138
+ /** Dev-only: subscribe to jay:page-reload and reload when route prefix matches. */
139
+ declare function buildPageReloadHmrScript(): string;
127
140
  /**
128
141
  * Generate the automation wrapping code.
129
142
  * @param mode - 'client' appends to DOM; 'hydrate' skips appendChild (DOM already present)
@@ -172,17 +185,23 @@ headTags?: HeadTag[],
172
185
  /** Script tags with jay-script="allow" (DL#149) */
173
186
  scripts?: JayHtmlScript[]): Promise<string>;
174
187
  /**
175
- * Generate a frozen page — pure SSR HTML with no client scripts (DL#127).
188
+ * Generate a frozen page — pure SSR HTML (DL#127).
176
189
  *
177
190
  * Uses the same server element module as generateSSRPageHtml, but:
178
191
  * - No hydration script
179
- * - No Vite client
180
192
  * - No component runtime
181
193
  * - Just rendered HTML + CSS
182
194
  *
195
+ * Script policy:
196
+ * - Production / `format=fragment`: no client scripts
197
+ * - Dev full-page (`injectDevHmr: true`): minimal reload listener only — no hydration,
198
+ * automation, or freeze capture. Caller must run the result through `transformIndexHtml`.
199
+ *
183
200
  * @param format - 'page' for full HTML document, 'fragment' for body-only (shadow DOM)
184
201
  */
185
- declare function generateFrozenPageHtml(vite: ViteDevServer, jayHtmlContent: string, jayHtmlFilename: string, jayHtmlDir: string, viewState: object, buildFolder: string, projectRoot: string, routeDir: string, tsConfigFilePath?: string, sourceDir?: string, format?: 'page' | 'fragment', freezeName?: string): Promise<string>;
202
+ declare function generateFrozenPageHtml(vite: ViteDevServer, jayHtmlContent: string, jayHtmlFilename: string, jayHtmlDir: string, viewState: object, buildFolder: string, projectRoot: string, routeDir: string, tsConfigFilePath?: string, sourceDir?: string, format?: 'page' | 'fragment', freezeName?: string, options?: {
203
+ injectDevHmr?: boolean;
204
+ }): Promise<string>;
186
205
 
187
206
  /**
188
207
  * Action metadata loaded from .jay-action files.
@@ -661,4 +680,4 @@ declare class SlowRenderCache {
661
680
  private scanAndDeleteCacheFiles;
662
681
  }
663
682
 
664
- export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionIndexEntry, type ActionMetadata, type ActionSchema, type CommandFlag, type CommandIndexEntry, type CommandMetadata, type ContextIndexEntry, type DevServerPagePart, type DiscoveredCommand, type GenerateClientScriptOptions, type HeadlessInstanceComponent, type LoadPagePartsOptions, type LoadedPageParts, type MaterializeContractsOptions, type MaterializeResult, type PluginActionDiscoveryOptions, type PluginContractEntry, type PluginWithAgentKit, type PluginWithSetup, type PluginsIndex, type PluginsIndexEntry, type ProjectClientInitInfo, type RouteIndexEntry, type ScriptFragments, type ServiceIndexEntry, SetupNeedsAnswerError, SlowRenderCache, type SlowRenderCacheEntry, type ViteSSRLoader, buildAutomationWrap, buildScriptFragments, clearServerElementCache, commandSchemaToFlags, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginCommands, discoverPluginsWithAgentKit, discoverPluginsWithSetup, executePluginAgentKit, executePluginCommand, executePluginSetup, generateClientScript, generateFrozenPageHtml, generatePromiseReconstruction, generateSSRPageHtml, headMetaToHeadTags, invalidateServerElementCache, listContracts, loadActionMetadata, loadPageParts, materializeContracts, parseActionMetadata, parseInputFromFlags, resolveActionMetadataPath, resolveViewStatePromises };
683
+ export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionIndexEntry, type ActionMetadata, type ActionSchema, type CommandFlag, type CommandIndexEntry, type CommandMetadata, type ContextIndexEntry, type DevServerPagePart, type DiscoveredCommand, type GenerateClientScriptOptions, type HeadlessInstanceComponent, type LoadPagePartsOptions, type LoadedPageParts, type MaterializeContractsOptions, type MaterializeResult, type PluginActionDiscoveryOptions, type PluginContractEntry, type PluginWithAgentKit, type PluginWithSetup, type PluginsIndex, type PluginsIndexEntry, type ProjectClientInitInfo, type RouteIndexEntry, type ScriptFragments, type ServiceIndexEntry, SetupNeedsAnswerError, SlowRenderCache, type SlowRenderCacheEntry, type ViteSSRLoader, buildAutomationWrap, buildPageReloadHmrScript, buildScriptFragments, clearServerElementCache, commandSchemaToFlags, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginCommands, discoverPluginsWithAgentKit, discoverPluginsWithSetup, executePluginAgentKit, executePluginCommand, executePluginSetup, generateClientScript, generateFrozenPageHtml, generatePromiseReconstruction, generateSSRPageHtml, headMetaToHeadTags, invalidateServerElementCache, listContracts, loadActionMetadata, loadPageParts, materializeContracts, parseActionMetadata, parseInputFromFlags, resolveActionMetadataPath, resolveClientImportPath, resolveViewStatePromises };
package/dist/index.js CHANGED
@@ -18,6 +18,11 @@ import * as fs from "node:fs";
18
18
  import { createRequire as createRequire$1 } from "node:module";
19
19
  import { isJayAction, isJayStreamAction, isJayCliCommand } from "@jay-framework/fullstack-component";
20
20
  const require$3 = createRequire(import.meta.url);
21
+ function resolveClientImportPath(route) {
22
+ if (route.clientCompPath) return route.clientCompPath;
23
+ if (route.componentExport) return route.compPath.replace(/index\.js$/, "index.client.js");
24
+ return route.compPath;
25
+ }
21
26
  async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
22
27
  const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
23
28
  const parts = [];
@@ -30,7 +35,7 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
30
35
  `${route.compPath} does not export "${exportName}". The file exists but the expected export is missing — remove the file or add the export.`
31
36
  );
32
37
  } else {
33
- const clientImportPath = route.componentExport ? route.compPath.replace(/index\.js$/, "index.client.js") : route.compPath;
38
+ const clientImportPath = resolveClientImportPath(route);
34
39
  parts.push({
35
40
  compDefinition: pageComponent,
36
41
  clientImport: `import {${exportName}} from '${clientImportPath}'`,
@@ -315,7 +320,7 @@ function normalizeActionEntry(entry) {
315
320
  if (typeof entry === "string") {
316
321
  return { name: entry };
317
322
  }
318
- return { name: entry.name, action: entry.action };
323
+ return { name: entry.name, action: entry.action, devOnly: entry.devOnly };
319
324
  }
320
325
  function loadPluginManifest(pluginDir) {
321
326
  const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
@@ -397,6 +402,18 @@ ${clientParts.map((part) => " " + part.clientPart).join(",\n")}
397
402
  slowViewStateDecl
398
403
  };
399
404
  }
405
+ function buildPageReloadHmrScript() {
406
+ return `
407
+ if (import.meta.hot) {
408
+ import.meta.hot.on('jay:page-reload', (data) => {
409
+ const prefix = data.routePrefix;
410
+ const pathname = window.location.pathname;
411
+ if (pathname === prefix || pathname.startsWith(prefix + '/')) {
412
+ window.location.reload();
413
+ }
414
+ });
415
+ }`;
416
+ }
400
417
  function buildFreezeScript(routePattern) {
401
418
  const routePatternLiteral = routePattern ? `'${routePattern}'` : "undefined";
402
419
  return `
@@ -472,15 +489,7 @@ function buildFreezeScript(routePattern) {
472
489
  }
473
490
 
474
491
  // Targeted page reload: only reload if this page matches the changed route prefix
475
- if (import.meta.hot) {
476
- import.meta.hot.on('jay:page-reload', (data) => {
477
- const prefix = data.routePrefix;
478
- const pathname = window.location.pathname;
479
- if (pathname === prefix || pathname.startsWith(prefix + '/')) {
480
- window.location.reload();
481
- }
482
- });
483
- }`;
492
+ ${buildPageReloadHmrScript()}`;
484
493
  }
485
494
  function buildAutomationWrap(options, mode) {
486
495
  const { enableAutomation = true, slowViewState } = options;
@@ -719,7 +728,7 @@ ${titleTag}${headExtras ? headExtras + "\n" : ""} </head>
719
728
  </body>
720
729
  </html>`;
721
730
  }
722
- async function generateFrozenPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir, format = "page", freezeName) {
731
+ async function generateFrozenPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir, format = "page", freezeName, options) {
723
732
  const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
724
733
  let cached = serverModuleCache.get(jayHtmlPath);
725
734
  if (!cached) {
@@ -765,6 +774,9 @@ ${ssrHtml}`;
765
774
  const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
766
775
  const headExtras = [cssLink, frozenHeadTagsHtml].filter((_) => _).join("\n");
767
776
  const label = freezeName ? ` — ${freezeName}` : "";
777
+ const devHmrScript = options?.injectDevHmr === true ? `
778
+ <script type="module">${buildPageReloadHmrScript()}
779
+ <\/script>` : "";
768
780
  return `<!doctype html>
769
781
  <html lang="en">
770
782
  <head>
@@ -788,7 +800,7 @@ ${headExtras ? headExtras + "\n" : ""} <style>
788
800
  </style>
789
801
  </head>
790
802
  <body>
791
- <div id="target">${ssrHtml}</div>
803
+ <div id="target">${ssrHtml}</div>${devHmrScript}
792
804
  </body>
793
805
  </html>`;
794
806
  }
@@ -1169,9 +1181,18 @@ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, re
1169
1181
  } else {
1170
1182
  pluginModule = await import(packageName);
1171
1183
  }
1184
+ let toolsModule;
1185
+ const loadToolsModule = async () => {
1186
+ if (!toolsModule) {
1187
+ const toolsEntry = `${packageName}/tools`;
1188
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsEntry) : await import(toolsEntry);
1189
+ }
1190
+ return toolsModule;
1191
+ };
1172
1192
  for (const entry of pluginConfig.actions) {
1173
- const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
1174
- const actionExport = pluginModule[actionName];
1193
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
1194
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
1195
+ const actionExport = sourceModule[actionName];
1175
1196
  if (actionExport && isJayAction(actionExport)) {
1176
1197
  registry.register(actionExport);
1177
1198
  const registeredName = actionExport.actionName;
@@ -1243,6 +1264,13 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
1243
1264
  return [];
1244
1265
  }
1245
1266
  }
1267
+ const resolveLocalToolsModulePath = () => {
1268
+ for (const candidate of ["tools.ts", "tools.js"]) {
1269
+ const candidatePath = path.join(pluginPath, candidate);
1270
+ if (fs.existsSync(candidatePath)) return candidatePath;
1271
+ }
1272
+ return null;
1273
+ };
1246
1274
  try {
1247
1275
  let pluginModule;
1248
1276
  if (viteServer) {
@@ -1250,9 +1278,23 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
1250
1278
  } else {
1251
1279
  pluginModule = await import(modulePath);
1252
1280
  }
1281
+ let toolsModule;
1282
+ const loadToolsModule = async () => {
1283
+ if (toolsModule) return toolsModule;
1284
+ const toolsPath = resolveLocalToolsModulePath();
1285
+ if (!toolsPath) {
1286
+ getLogger().warn(
1287
+ `[Actions] Plugin "${pluginName}" declares a devOnly action but has no tools.ts/tools.js module`
1288
+ );
1289
+ return void 0;
1290
+ }
1291
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsPath) : await import(toolsPath);
1292
+ return toolsModule;
1293
+ };
1253
1294
  for (const entry of pluginConfig.actions) {
1254
- const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
1255
- const actionExport = pluginModule[actionName];
1295
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
1296
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
1297
+ const actionExport = sourceModule?.[actionName];
1256
1298
  if (actionExport && isJayAction(actionExport)) {
1257
1299
  registry.register(actionExport);
1258
1300
  const registeredName = actionExport.actionName;
@@ -1878,10 +1920,11 @@ async function loadCommandHandler(command, viteServer) {
1878
1920
  module = await import(modulePath);
1879
1921
  }
1880
1922
  } else {
1923
+ const toolsEntry = `${command.packageName}/tools`;
1881
1924
  if (viteServer) {
1882
- module = await viteServer.ssrLoadModule(command.packageName);
1925
+ module = await viteServer.ssrLoadModule(toolsEntry);
1883
1926
  } else {
1884
- module = await import(command.packageName);
1927
+ module = await import(toolsEntry);
1885
1928
  }
1886
1929
  }
1887
1930
  for (const [, exported] of Object.entries(module)) {
@@ -2015,14 +2058,15 @@ async function loadHandler(plugin, handlerName, viteServer) {
2015
2058
  `Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
2016
2059
  );
2017
2060
  } else {
2061
+ const toolsEntry = `${plugin.packageName}/tools`;
2018
2062
  if (viteServer) {
2019
- module = await viteServer.ssrLoadModule(plugin.packageName);
2063
+ module = await viteServer.ssrLoadModule(toolsEntry);
2020
2064
  } else {
2021
- module = await import(plugin.packageName);
2065
+ module = await import(toolsEntry);
2022
2066
  }
2023
2067
  if (typeof module[handlerName] !== "function") {
2024
2068
  throw new Error(
2025
- `Handler "${handlerName}" not found as export in "${plugin.packageName}". Available exports: ${Object.keys(module).join(", ")}`
2069
+ `Handler "${handlerName}" not found as export in "${toolsEntry}". Available exports: ${Object.keys(module).join(", ")}`
2026
2070
  );
2027
2071
  }
2028
2072
  return module[handlerName];
@@ -2264,6 +2308,7 @@ export {
2264
2308
  SetupNeedsAnswerError,
2265
2309
  SlowRenderCache,
2266
2310
  buildAutomationWrap,
2311
+ buildPageReloadHmrScript,
2267
2312
  buildScriptFragments,
2268
2313
  clearServerElementCache,
2269
2314
  commandSchemaToFlags,
@@ -2289,5 +2334,6 @@ export {
2289
2334
  parseActionMetadata,
2290
2335
  parseInputFromFlags,
2291
2336
  resolveActionMetadataPath,
2337
+ resolveClientImportPath,
2292
2338
  resolveViewStatePromises
2293
2339
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-server-build",
3
- "version": "0.24.2",
3
+ "version": "0.24.4",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.mts",
@@ -24,21 +24,21 @@
24
24
  "test:watch": "vitest"
25
25
  },
26
26
  "dependencies": {
27
- "@jay-framework/compiler-jay-html": "^0.24.2",
28
- "@jay-framework/compiler-shared": "^0.24.2",
29
- "@jay-framework/component": "^0.24.2",
30
- "@jay-framework/fullstack-component": "^0.24.2",
31
- "@jay-framework/logger": "^0.24.2",
32
- "@jay-framework/runtime": "^0.24.2",
33
- "@jay-framework/ssr-runtime": "^0.24.2",
34
- "@jay-framework/stack-route-scanner": "^0.24.2",
35
- "@jay-framework/stack-server-runtime": "^0.24.2",
36
- "@jay-framework/view-state-merge": "^0.24.2",
27
+ "@jay-framework/compiler-jay-html": "^0.24.4",
28
+ "@jay-framework/compiler-shared": "^0.24.4",
29
+ "@jay-framework/component": "^0.24.4",
30
+ "@jay-framework/fullstack-component": "^0.24.4",
31
+ "@jay-framework/logger": "^0.24.4",
32
+ "@jay-framework/runtime": "^0.24.4",
33
+ "@jay-framework/ssr-runtime": "^0.24.4",
34
+ "@jay-framework/stack-route-scanner": "^0.24.4",
35
+ "@jay-framework/stack-server-runtime": "^0.24.4",
36
+ "@jay-framework/view-state-merge": "^0.24.4",
37
37
  "yaml": "^2.3.4"
38
38
  },
39
39
  "devDependencies": {
40
- "@jay-framework/dev-environment": "^0.24.2",
41
- "@jay-framework/rollup-plugin": "^0.24.2",
40
+ "@jay-framework/dev-environment": "^0.24.4",
41
+ "@jay-framework/rollup-plugin": "^0.24.4",
42
42
  "@types/node": "^22.15.21",
43
43
  "rimraf": "^5.0.5",
44
44
  "tsup": "^8.0.1",