@jay-framework/production-server 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
- import { B as BuildOptions, R as RouteManifest, a as RouteEntry } from './serve-index-Ccw8CNDg.js';
2
- export { A as ActionEntry, b as ArtifactStore, c as BuildMetadata, C as CacheEntry, F as FilesystemArtifactStore, I as InstanceEntry, M as MatchResult, P as PageModule, d as PluginEntry, e as PreImportedPlugin, f as RouteSegment, S as ServerElementModule, g as fetchActionRequest, h as fetchPageRequest, i as fetchStaticFile, j as initializeServices, k as initializeServicesFromModules, l as isActionRequest, m as matchRequest, r as registerActionsFromManifest, n as registerActionsFromModules } from './serve-index-Ccw8CNDg.js';
1
+ import { B as BuildOptions, R as RouteManifest, a as RouteEntry } from './serve-index-cRXZm8vQ.js';
2
+ export { A as ActionEntry, b as ArtifactStore, c as BuildMetadata, C as CacheEntry, F as FilesystemArtifactStore, I as InstanceEntry, M as MatchResult, P as PageModule, d as PluginEntry, e as PreImportedPlugin, f as RouteSegment, S as ServerElementModule, g as fetchActionRequest, h as fetchPageRequest, i as fetchStaticFile, j as initializeServices, k as initializeServicesFromModules, l as isActionRequest, m as matchRequest, r as registerActionsFromManifest, n as registerActionsFromModules } from './serve-index-cRXZm8vQ.js';
3
3
  import '@jay-framework/ssr-runtime';
4
4
  import '@jay-framework/compiler-shared';
5
5
  import '@jay-framework/stack-server-runtime';
6
6
 
7
7
  declare function buildVersion(options: BuildOptions): Promise<RouteManifest>;
8
8
 
9
+ declare function generateSitemap(manifest: RouteManifest, baseUrl: string, outputPath: string): Promise<number>;
10
+
9
11
  interface MainServerOptions {
10
12
  buildRoot: string;
11
13
  version: string;
@@ -24,6 +26,8 @@ interface RendererServerOptions {
24
26
  pagesRoot: string;
25
27
  tsConfigFilePath?: string;
26
28
  minify?: boolean;
29
+ /** Site base URL for sitemap regeneration on rebuild (e.g. "https://example.com"). */
30
+ siteBaseUrl?: string;
27
31
  }
28
32
  declare function startRendererServer(options: RendererServerOptions): Promise<void>;
29
33
 
@@ -47,6 +51,8 @@ interface RebuildOptions {
47
51
  target: RebuildTarget;
48
52
  tsConfigFilePath?: string;
49
53
  minify?: boolean;
54
+ /** Site base URL for sitemap regeneration (e.g. "https://example.com"). */
55
+ siteBaseUrl?: string;
50
56
  }
51
57
  interface RebuildResult {
52
58
  affected: number;
@@ -69,7 +75,8 @@ declare function rebuildContract(options: {
69
75
  params?: Record<string, string>;
70
76
  tsConfigFilePath?: string;
71
77
  minify?: boolean;
78
+ siteBaseUrl?: string;
72
79
  }): Promise<RebuildResult>;
73
80
  declare function cleanupOrphanedFiles(buildRoot: string, version: string): Promise<number>;
74
81
 
75
- export { BuildOptions, type RebuildOptions, type RebuildResult, type RebuildTarget, type RendererServerOptions, RouteEntry, RouteManifest, buildVersion, cleanupOrphanedFiles, rebuild, rebuildContract, resolveContractToRoutes, startMainServer, startRendererServer };
82
+ export { BuildOptions, type RebuildOptions, type RebuildResult, type RebuildTarget, type RendererServerOptions, RouteEntry, RouteManifest, buildVersion, cleanupOrphanedFiles, generateSitemap, rebuild, rebuildContract, resolveContractToRoutes, startMainServer, startRendererServer };
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  import { createRequire } from "node:module";
8
8
  import { DevSlowlyChangingPhase, slowRenderInstances, scanPlugins, runLoadParams, parseCookies } from "@jay-framework/stack-server-runtime";
9
- import { l as loadProductionPageParts, g as buildPagePartsConfig, F as FilesystemArtifactStore, i as initializeServices, r as registerActionsFromManifest, d as isActionRequest, f as fetchActionRequest, b as fetchStaticFile, m as matchRequest, a as fetchPageRequest } from "./init-services-BVEuyIVG.js";
10
- import { c, e } from "./init-services-BVEuyIVG.js";
9
+ import { l as loadProductionPageParts, g as buildPagePartsConfig, F as FilesystemArtifactStore, i as initializeServices, r as registerActionsFromManifest, d as isActionRequest, f as fetchActionRequest, b as fetchStaticFile, m as matchRequest, a as fetchPageRequest } from "./init-services-Dy2SiHzw.js";
10
+ import { c, e } from "./init-services-Dy2SiHzw.js";
11
11
  import crypto, { createHash } from "node:crypto";
12
12
  import fs$1 from "node:fs";
13
13
  import { jayRuntime } from "@jay-framework/vite-plugin";
@@ -18,6 +18,27 @@ import { transform } from "esbuild";
18
18
  import http from "node:http";
19
19
  import { Readable } from "node:stream";
20
20
  import { isJayWebhook } from "@jay-framework/fullstack-component";
21
+ function isCompilableTypeScriptFile(fileName) {
22
+ return fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") && fileName !== "page.ts";
23
+ }
24
+ async function collectTypeScriptEntries(rootDir, entryPrefix, pages) {
25
+ async function walk(currentDir, relativePath) {
26
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
27
+ for (const entry of entries) {
28
+ const fullPath = path.join(currentDir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ const nextRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
31
+ await walk(fullPath, nextRelative);
32
+ continue;
33
+ }
34
+ if (!isCompilableTypeScriptFile(entry.name)) continue;
35
+ const stem = entry.name.replace(/\.ts$/, "");
36
+ const entryName = relativePath ? `${entryPrefix}/${relativePath}/${stem}` : `${entryPrefix}/${stem}`;
37
+ pages[entryName] = fullPath;
38
+ }
39
+ }
40
+ await walk(rootDir, "");
41
+ }
21
42
  async function discoverServerEntries(projectRoot, pagesRoot) {
22
43
  const logger = getLogger();
23
44
  const routes = await scanRoutes(pagesRoot, {
@@ -47,17 +68,16 @@ async function discoverServerEntries(projectRoot, pagesRoot) {
47
68
  for (const subDir of ["plugins", "components"]) {
48
69
  const scanDir = path.join(projectRoot, "src", subDir);
49
70
  try {
50
- const dirs = await fs.readdir(scanDir, { withFileTypes: true });
51
- for (const dir of dirs) {
52
- if (!dir.isDirectory()) continue;
53
- const dirPath = path.join(scanDir, dir.name);
54
- const files = await fs.readdir(dirPath);
55
- for (const file of files) {
56
- if (file.endsWith(".ts") && !file.endsWith(".d.ts") && file !== "page.ts") {
57
- const entryName = `${subDir}/${dir.name}/${file.replace(/\.ts$/, "")}`;
58
- pages[entryName] = path.join(dirPath, file);
59
- }
71
+ const entries2 = await fs.readdir(scanDir, { withFileTypes: true });
72
+ for (const entry of entries2) {
73
+ const entryPath = path.join(scanDir, entry.name);
74
+ if (entry.isDirectory()) {
75
+ await collectTypeScriptEntries(entryPath, `${subDir}/${entry.name}`, pages);
76
+ continue;
60
77
  }
78
+ if (!isCompilableTypeScriptFile(entry.name)) continue;
79
+ const stem = entry.name.replace(/\.ts$/, "");
80
+ pages[`${subDir}/${stem}`] = entryPath;
61
81
  }
62
82
  } catch {
63
83
  }
@@ -425,6 +445,47 @@ async function writeRouteManifest(manifest, buildDir) {
425
445
  `[Build] Route manifest written: ${manifest.routes.length} routes, ${manifest.routes.reduce((n, r) => n + r.instances.length, 0)} instances`
426
446
  );
427
447
  }
448
+ async function generateSitemap(manifest, baseUrl, outputPath) {
449
+ const base = baseUrl.replace(/\/$/, "");
450
+ const urls = [];
451
+ for (const route of manifest.routes) {
452
+ if (route.devOnly) continue;
453
+ if (route.noIndex) continue;
454
+ if (route.instances.length === 0) {
455
+ const hasDynamic = route.segments.some((s) => s.type !== "static");
456
+ if (!hasDynamic) {
457
+ const urlPath = route.pattern === "/" ? "/" : route.pattern;
458
+ urls.push(`${base}${urlPath}`);
459
+ }
460
+ continue;
461
+ }
462
+ for (const instance of route.instances) {
463
+ const urlPath = buildUrlFromManifest(route.pattern, instance.params);
464
+ urls.push(`${base}${urlPath}`);
465
+ }
466
+ }
467
+ const tmpPath = outputPath + ".tmp";
468
+ const handle = await fs.open(tmpPath, "w");
469
+ try {
470
+ await handle.write('<?xml version="1.0" encoding="UTF-8"?>\n');
471
+ await handle.write('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n');
472
+ for (const url of urls) {
473
+ await handle.write(` <url><loc>${escapeXml(url)}</loc></url>
474
+ `);
475
+ }
476
+ await handle.write("</urlset>\n");
477
+ } finally {
478
+ await handle.close();
479
+ }
480
+ await fs.rename(tmpPath, outputPath);
481
+ return urls.length;
482
+ }
483
+ function buildUrlFromManifest(pattern, params) {
484
+ return pattern.replace(/\[\[(\w+)\]\]/g, (_, name) => params[name] || "").replace(/\[\.\.\.(\w+)\]/g, (_, name) => params[name] || "").replace(/\[(\w+)\]/g, (_, name) => params[name] || "").replace(/\/\/+/g, "/").replace(/\/$/, "") || "/";
485
+ }
486
+ function escapeXml(str) {
487
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
488
+ }
428
489
  createRequire(import.meta.url);
429
490
  async function scanPluginRoutes(projectRoot, projectRoutes) {
430
491
  const logger = getLogger();
@@ -973,7 +1034,19 @@ async function buildVersion(options) {
973
1034
  );
974
1035
  for (const pluginInit of pluginsWithInit) {
975
1036
  try {
976
- const pluginModule = await import(pluginInit.packageName);
1037
+ let modulePath;
1038
+ if (pluginInit.isLocal) {
1039
+ const pluginDirName = path.basename(pluginInit.pluginPath);
1040
+ modulePath = path.join(
1041
+ serverOutputDir,
1042
+ "plugins",
1043
+ pluginDirName,
1044
+ `${pluginInit.initModule}.js`
1045
+ );
1046
+ } else {
1047
+ modulePath = pluginInit.packageName;
1048
+ }
1049
+ const pluginModule = await import(modulePath);
977
1050
  const init = pluginModule.init || pluginModule[pluginInit.initExport || "init"];
978
1051
  if (init?._serverInit) {
979
1052
  logger.info(`[Build] Running plugin init: ${pluginInit.name}`);
@@ -1173,6 +1246,12 @@ async function buildVersion(options) {
1173
1246
  }
1174
1247
  if (seResult.headMeta) {
1175
1248
  entry.headMeta = seResult.headMeta;
1249
+ const robotsMeta = seResult.headMeta.meta?.find(
1250
+ (m) => m.name === "robots" && m.content?.some((p) => p.kind === "static" && p.value.includes("noindex"))
1251
+ );
1252
+ if (robotsMeta) {
1253
+ entry.noIndex = true;
1254
+ }
1176
1255
  }
1177
1256
  logger.important(`[Build] Route server element: ${routeDir}`);
1178
1257
  } catch (err) {
@@ -1308,6 +1387,11 @@ async function buildVersion(options) {
1308
1387
  logger.info("[Build] Copied public/ contents to frontend/");
1309
1388
  } catch {
1310
1389
  }
1390
+ if (options.siteBaseUrl) {
1391
+ const sitemapPath = path.join(frontendDir, "sitemap.xml");
1392
+ const urlCount = await generateSitemap(manifest, options.siteBaseUrl, sitemapPath);
1393
+ logger.important(`[Build] Sitemap generated: ${urlCount} URLs`);
1394
+ }
1311
1395
  const sourceHash = await computeBuildHash(buildDir);
1312
1396
  const metadata = {
1313
1397
  version: options.version,
@@ -1657,6 +1741,11 @@ async function rebuild(options) {
1657
1741
  );
1658
1742
  }
1659
1743
  logger.important(`[Rebuild] Manifest and metadata updated`);
1744
+ if (options.siteBaseUrl) {
1745
+ const sitemapPath = path.join(frontendDir, "sitemap.xml");
1746
+ const urlCount = await generateSitemap(manifest, options.siteBaseUrl, sitemapPath);
1747
+ logger.info(`[Rebuild] Sitemap regenerated: ${urlCount} URLs`);
1748
+ }
1660
1749
  if (orphanedFiles.length > 0) {
1661
1750
  await appendCleanupManifest(buildDir, orphanedFiles);
1662
1751
  logger.info(`[Rebuild] ${orphanedFiles.length} orphaned file(s) queued for cleanup`);
@@ -1805,7 +1894,8 @@ async function startRendererServer(options) {
1805
1894
  contractName,
1806
1895
  params,
1807
1896
  tsConfigFilePath: options.tsConfigFilePath,
1808
- minify: options.minify
1897
+ minify: options.minify,
1898
+ siteBaseUrl: options.siteBaseUrl
1809
1899
  });
1810
1900
  };
1811
1901
  };
@@ -1864,7 +1954,8 @@ async function startRendererServer(options) {
1864
1954
  version: options.version,
1865
1955
  target,
1866
1956
  tsConfigFilePath: options.tsConfigFilePath,
1867
- minify: options.minify
1957
+ minify: options.minify,
1958
+ siteBaseUrl: options.siteBaseUrl
1868
1959
  });
1869
1960
  res.writeHead(200, { "Content-Type": "application/json" });
1870
1961
  res.end(JSON.stringify(result));
@@ -1920,6 +2011,7 @@ export {
1920
2011
  fetchActionRequest,
1921
2012
  fetchPageRequest,
1922
2013
  fetchStaticFile,
2014
+ generateSitemap,
1923
2015
  initializeServices,
1924
2016
  c as initializeServicesFromModules,
1925
2017
  isActionRequest,
@@ -710,7 +710,9 @@ const MIME_TYPES = {
710
710
  ".woff2": "font/woff2",
711
711
  ".woff": "font/woff",
712
712
  ".ttf": "font/ttf",
713
- ".webp": "image/webp"
713
+ ".webp": "image/webp",
714
+ ".xml": "application/xml",
715
+ ".txt": "text/plain"
714
716
  };
715
717
  async function fetchStaticFile(pathname, frontendDir) {
716
718
  const normalizedBase = path.resolve(frontendDir);
@@ -723,7 +725,7 @@ async function fetchStaticFile(pathname, frontendDir) {
723
725
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
724
726
  const isHashed = /[-][a-zA-Z0-9_-]{6,}\./.test(path.basename(candidate));
725
727
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
726
- return new Response(content, {
728
+ return new Response(new Uint8Array(content), {
727
729
  headers: {
728
730
  "Content-Type": contentType,
729
731
  "Content-Length": String(content.length),
@@ -33,6 +33,8 @@ interface RouteEntry {
33
33
  cssImports?: string[];
34
34
  /** Head metadata from jay-html <head> (title, meta tags). */
35
35
  headMeta?: JayHtmlHeadMeta;
36
+ /** True when page has static <meta name="robots" content="noindex"> */
37
+ noIndex?: boolean;
36
38
  instances: InstanceEntry[];
37
39
  isPlugin?: boolean;
38
40
  pluginName?: string;
@@ -75,6 +77,8 @@ interface BuildOptions {
75
77
  concurrency: number;
76
78
  tsConfigFilePath: string;
77
79
  minify?: boolean;
80
+ /** Site base URL for sitemap generation (e.g. "https://example.com"). */
81
+ siteBaseUrl?: string;
78
82
  }
79
83
  interface ServerElementModule {
80
84
  renderToStream: (vs: object, ctx: _jay_framework_ssr_runtime.ServerRenderContext) => void;
@@ -1,4 +1,4 @@
1
- export { b as ArtifactStore, C as CacheEntry, F as FilesystemArtifactStore, I as InstanceEntry, M as MatchResult, e as PreImportedPlugin, a as RouteEntry, R as RouteManifest, S as ServerElementModule, g as fetchActionRequest, h as fetchPageRequest, i as fetchStaticFile, j as initializeServices, k as initializeServicesFromModules, l as isActionRequest, m as matchRequest, r as registerActionsFromManifest, n as registerActionsFromModules } from './serve-index-Ccw8CNDg.js';
1
+ export { b as ArtifactStore, C as CacheEntry, F as FilesystemArtifactStore, I as InstanceEntry, M as MatchResult, e as PreImportedPlugin, a as RouteEntry, R as RouteManifest, S as ServerElementModule, g as fetchActionRequest, h as fetchPageRequest, i as fetchStaticFile, j as initializeServices, k as initializeServicesFromModules, l as isActionRequest, m as matchRequest, r as registerActionsFromManifest, n as registerActionsFromModules } from './serve-index-cRXZm8vQ.js';
2
2
  import '@jay-framework/ssr-runtime';
3
3
  import '@jay-framework/compiler-shared';
4
4
  import '@jay-framework/stack-server-runtime';
@@ -1,4 +1,4 @@
1
- import { F, f, a, b, i, c, d, m, r, e } from "./init-services-BVEuyIVG.js";
1
+ import { F, f, a, b, i, c, d, m, r, e } from "./init-services-Dy2SiHzw.js";
2
2
  export {
3
3
  F as FilesystemArtifactStore,
4
4
  f as fetchActionRequest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/production-server",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,20 +30,20 @@
30
30
  "test:watch": "vitest"
31
31
  },
32
32
  "dependencies": {
33
- "@jay-framework/compiler-jay-html": "^0.23.1",
34
- "@jay-framework/compiler-jay-stack": "^0.23.1",
35
- "@jay-framework/compiler-shared": "^0.23.1",
36
- "@jay-framework/fullstack-component": "^0.23.1",
37
- "@jay-framework/logger": "^0.23.1",
38
- "@jay-framework/ssr-runtime": "^0.23.1",
39
- "@jay-framework/stack-route-scanner": "^0.23.1",
40
- "@jay-framework/stack-server-runtime": "^0.23.1",
41
- "@jay-framework/view-state-merge": "^0.23.1",
42
- "@jay-framework/vite-plugin": "^0.23.1",
33
+ "@jay-framework/compiler-jay-html": "^0.24.0",
34
+ "@jay-framework/compiler-jay-stack": "^0.24.0",
35
+ "@jay-framework/compiler-shared": "^0.24.0",
36
+ "@jay-framework/fullstack-component": "^0.24.0",
37
+ "@jay-framework/logger": "^0.24.0",
38
+ "@jay-framework/ssr-runtime": "^0.24.0",
39
+ "@jay-framework/stack-route-scanner": "^0.24.0",
40
+ "@jay-framework/stack-server-runtime": "^0.24.0",
41
+ "@jay-framework/view-state-merge": "^0.24.0",
42
+ "@jay-framework/vite-plugin": "^0.24.0",
43
43
  "vite": "^5.0.11"
44
44
  },
45
45
  "devDependencies": {
46
- "@jay-framework/dev-environment": "^0.23.1",
46
+ "@jay-framework/dev-environment": "^0.24.0",
47
47
  "@types/node": "^22.15.21",
48
48
  "rimraf": "^5.0.5",
49
49
  "tsup": "^8.0.1",