@zenithbuild/cli 0.7.1 → 0.7.2

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 (52) hide show
  1. package/README.md +59 -1
  2. package/dist/adapters/adapter-netlify-static.d.ts +5 -0
  3. package/dist/adapters/adapter-netlify-static.js +39 -0
  4. package/dist/adapters/adapter-netlify.d.ts +5 -0
  5. package/dist/adapters/adapter-netlify.js +129 -0
  6. package/dist/adapters/adapter-node.d.ts +5 -0
  7. package/dist/adapters/adapter-node.js +121 -0
  8. package/dist/adapters/adapter-static.d.ts +5 -0
  9. package/dist/adapters/adapter-static.js +20 -0
  10. package/dist/adapters/adapter-types.d.ts +44 -0
  11. package/dist/adapters/adapter-types.js +65 -0
  12. package/dist/adapters/adapter-vercel-static.d.ts +5 -0
  13. package/dist/adapters/adapter-vercel-static.js +36 -0
  14. package/dist/adapters/adapter-vercel.d.ts +5 -0
  15. package/dist/adapters/adapter-vercel.js +99 -0
  16. package/dist/adapters/resolve-adapter.d.ts +5 -0
  17. package/dist/adapters/resolve-adapter.js +84 -0
  18. package/dist/adapters/route-rules.d.ts +7 -0
  19. package/dist/adapters/route-rules.js +88 -0
  20. package/dist/base-path-html.d.ts +2 -0
  21. package/dist/base-path-html.js +42 -0
  22. package/dist/base-path.d.ts +8 -0
  23. package/dist/base-path.js +74 -0
  24. package/dist/build/compiler-runtime.d.ts +2 -1
  25. package/dist/build/compiler-runtime.js +4 -1
  26. package/dist/build/page-loop.d.ts +2 -2
  27. package/dist/build/page-loop.js +3 -3
  28. package/dist/build-output-manifest.d.ts +28 -0
  29. package/dist/build-output-manifest.js +100 -0
  30. package/dist/build.js +42 -11
  31. package/dist/config.d.ts +10 -46
  32. package/dist/config.js +162 -28
  33. package/dist/dev-build-session.d.ts +1 -0
  34. package/dist/dev-build-session.js +4 -5
  35. package/dist/framework-components/Image.zen +31 -9
  36. package/dist/images/payload.d.ts +2 -1
  37. package/dist/images/payload.js +3 -2
  38. package/dist/images/runtime.js +6 -5
  39. package/dist/images/service.js +2 -2
  40. package/dist/images/shared.d.ts +4 -2
  41. package/dist/images/shared.js +8 -3
  42. package/dist/index.js +36 -15
  43. package/dist/manifest.d.ts +14 -2
  44. package/dist/manifest.js +49 -6
  45. package/dist/preview.js +61 -25
  46. package/dist/server-output.d.ts +26 -0
  47. package/dist/server-output.js +297 -0
  48. package/dist/server-runtime/node-server.d.ts +2 -0
  49. package/dist/server-runtime/node-server.js +354 -0
  50. package/dist/server-runtime/route-render.d.ts +64 -0
  51. package/dist/server-runtime/route-render.js +273 -0
  52. package/package.json +3 -2
@@ -1,3 +1,4 @@
1
+ import { imageEndpointPath, normalizeBasePath, prependBasePath } from '../base-path.js';
1
2
  const DEFAULT_DEVICE_SIZES = [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
2
3
  const DEFAULT_IMAGE_SIZES = [16, 32, 48, 64, 96, 128, 256, 384];
3
4
  const DEFAULT_FORMATS = ['webp', 'avif'];
@@ -253,11 +254,14 @@ export function buildLocalImageKey(publicPath) {
253
254
  }
254
255
  return (hash >>> 0).toString(16).padStart(8, '0');
255
256
  }
256
- export function buildLocalVariantPath(publicPath, width, quality, format) {
257
+ export function buildLocalVariantAssetPath(publicPath, width, quality, format) {
257
258
  const key = buildLocalImageKey(publicPath);
258
259
  return `/_zenith/image/local/${key}/w${width}-q${quality}.${normalizeImageFormat(format)}`;
259
260
  }
260
- export function buildRemoteVariantPath(remoteUrl, width, quality, format) {
261
+ export function buildLocalVariantPath(publicPath, width, quality, format, basePath = '/') {
262
+ return prependBasePath(basePath, buildLocalVariantAssetPath(publicPath, width, quality, format));
263
+ }
264
+ export function buildRemoteVariantPath(remoteUrl, width, quality, format, basePath = '/') {
261
265
  const query = new URLSearchParams();
262
266
  query.set('url', String(remoteUrl || ''));
263
267
  query.set('w', String(width));
@@ -265,7 +269,7 @@ export function buildRemoteVariantPath(remoteUrl, width, quality, format) {
265
269
  if (format) {
266
270
  query.set('f', normalizeImageFormat(format));
267
271
  }
268
- return `/_zenith/image?${query.toString()}`;
272
+ return `${imageEndpointPath(basePath)}?${query.toString()}`;
269
273
  }
270
274
  export function resolveWidthCandidates(width, sizes, config, manifestEntry) {
271
275
  const base = new Set([
@@ -300,6 +304,7 @@ export function normalizeImageRuntimePayload(payload) {
300
304
  }
301
305
  return {
302
306
  mode: payload.mode === 'endpoint' ? 'endpoint' : 'passthrough',
307
+ basePath: normalizeBasePath(payload.basePath || '/'),
303
308
  config: normalizeImageConfig(payload.config || {}),
304
309
  localImages: isPlainObject(payload.localImages) ? payload.localImages : {}
305
310
  };
package/dist/index.js CHANGED
@@ -4,16 +4,16 @@
4
4
  // ---------------------------------------------------------------------------
5
5
  // Commands:
6
6
  // zenith dev — Development server + HMR
7
- // zenith build — Static site generation to /dist
8
- // zenith preview — Serve /dist statically
7
+ // zenith build — Static site generation to the configured output dir
8
+ // zenith preview — Serve the configured output dir statically
9
9
  //
10
10
  // Minimal arg parsing. No heavy dependencies.
11
11
  // ---------------------------------------------------------------------------
12
- import { resolve, join, dirname } from 'node:path';
13
- import { existsSync, readFileSync } from 'node:fs';
14
- import { fileURLToPath } from 'node:url';
12
+ import { resolve, join, dirname, relative } from 'node:path';
13
+ import { readFileSync } from 'node:fs';
14
+ import { fileURLToPath, pathToFileURL } from 'node:url';
15
15
  import { createZenithLogger } from './ui/logger.js';
16
- import { loadConfig } from './config.js';
16
+ import { loadConfig, resolveConfigOutDir, resolveConfigPagesDir } from './config.js';
17
17
  const COMMANDS = ['dev', 'build', 'preview'];
18
18
  const DEFAULT_VERSION = '0.0.0';
19
19
  const __filename = fileURLToPath(import.meta.url);
@@ -32,14 +32,24 @@ function printUsage(logger) {
32
32
  logger.heading('V0');
33
33
  logger.print('Usage:');
34
34
  logger.print(' zenith dev [port|--port <port>] Start development server');
35
- logger.print(' zenith build Build static site to /dist');
36
- logger.print(' zenith preview [port|--port <port>] Preview /dist statically');
35
+ logger.print(' zenith build Build static site to the configured output dir');
36
+ logger.print(' zenith preview [port|--port <port>] Preview the configured output dir statically');
37
37
  logger.print('');
38
38
  logger.print('Options:');
39
39
  logger.print(' -h, --help Show this help message');
40
40
  logger.print(' -v, --version Print Zenith CLI version');
41
41
  logger.print('');
42
42
  }
43
+ function formatOutputDir(projectRoot, outDir) {
44
+ const relativePath = relative(projectRoot, outDir);
45
+ if (!relativePath || relativePath.length === 0) {
46
+ return '.';
47
+ }
48
+ if (relativePath.startsWith('..')) {
49
+ return outDir;
50
+ }
51
+ return `./${relativePath}`;
52
+ }
43
53
  function resolvePort(args, fallback) {
44
54
  if (!Array.isArray(args) || args.length === 0) {
45
55
  return fallback;
@@ -83,11 +93,9 @@ export async function cli(args, cwd) {
83
93
  process.exit(command ? 1 : 0);
84
94
  }
85
95
  const projectRoot = resolve(cwd || process.cwd());
86
- const rootPagesDir = join(projectRoot, 'pages');
87
- const srcPagesDir = join(projectRoot, 'src', 'pages');
88
- const pagesDir = existsSync(rootPagesDir) ? rootPagesDir : srcPagesDir;
89
- const outDir = join(projectRoot, 'dist');
90
96
  const config = await loadConfig(projectRoot);
97
+ const pagesDir = resolveConfigPagesDir(projectRoot, config);
98
+ const outDir = resolveConfigOutDir(projectRoot, config);
91
99
  if (command === 'build' || command === 'dev') {
92
100
  const { maybeWarnAboutZenithVersionMismatch } = await import('./version-check.js');
93
101
  await maybeWarnAboutZenithVersionMismatch({
@@ -101,7 +109,7 @@ export async function cli(args, cwd) {
101
109
  logger.build('Building…');
102
110
  const result = await build({ pagesDir, outDir, config, logger, showBundlerInfo: false });
103
111
  logger.ok(`Built ${result.pages} page(s), ${result.assets.length} asset(s)`);
104
- logger.summary([{ label: 'Output', value: './dist' }], 'BUILD');
112
+ logger.summary([{ label: 'Output', value: formatOutputDir(projectRoot, outDir) }], 'BUILD');
105
113
  }
106
114
  if (command === 'dev') {
107
115
  const { createDevServer } = await import('./dev-server.js');
@@ -123,11 +131,24 @@ export async function cli(args, cwd) {
123
131
  });
124
132
  }
125
133
  if (command === 'preview') {
126
- const { createPreviewServer } = await import('./preview.js');
127
134
  const port = resolvePort(args.slice(1), 4000);
128
135
  const host = process.env.ZENITH_PREVIEW_HOST || '127.0.0.1';
129
136
  logger.dev('Starting preview server…');
130
- const preview = await createPreviewServer({ distDir: outDir, port, host, logger, config, projectRoot });
137
+ const preview = config.target === 'node'
138
+ ? await import(pathToFileURL(join(outDir, 'index.js')).href).then(async (mod) => {
139
+ if (typeof mod.createNodeServer !== 'function') {
140
+ throw new Error('[Zenith:Preview] Node target output is missing createNodeServer()');
141
+ }
142
+ return mod.createNodeServer({ distDir: outDir, port, host });
143
+ })
144
+ : await import('./preview.js').then((mod) => mod.createPreviewServer({
145
+ distDir: outDir,
146
+ port,
147
+ host,
148
+ logger,
149
+ config,
150
+ projectRoot
151
+ }));
131
152
  logger.ok(`http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${preview.port}`);
132
153
  process.on('SIGINT', () => {
133
154
  preview.close();
@@ -1,14 +1,23 @@
1
1
  /**
2
- * @typedef {{ path: string, file: string }} ManifestEntry
2
+ * @typedef {{
3
+ * path: string,
4
+ * file: string,
5
+ * path_kind: 'static' | 'dynamic',
6
+ * render_mode: 'prerender' | 'server',
7
+ * params: string[]
8
+ * }} ManifestEntry
3
9
  */
4
10
  /**
5
11
  * Scan a pages directory and produce a deterministic RouteManifest.
6
12
  *
7
13
  * @param {string} pagesDir - Absolute path to /pages directory
8
14
  * @param {string} [extension='.zen'] - File extension to scan for
15
+ * @param {{ compilerOpts?: object }} [options]
9
16
  * @returns {Promise<ManifestEntry[]>}
10
17
  */
11
- export function generateManifest(pagesDir: string, extension?: string): Promise<ManifestEntry[]>;
18
+ export function generateManifest(pagesDir: string, extension?: string, options?: {
19
+ compilerOpts?: object;
20
+ }): Promise<ManifestEntry[]>;
12
21
  /**
13
22
  * Generate a JavaScript module string from manifest entries.
14
23
  * Used for writing the manifest file to disk.
@@ -20,4 +29,7 @@ export function serializeManifest(entries: ManifestEntry[]): string;
20
29
  export type ManifestEntry = {
21
30
  path: string;
22
31
  file: string;
32
+ path_kind: "static" | "dynamic";
33
+ render_mode: "prerender" | "server";
34
+ params: string[];
23
35
  };
package/dist/manifest.js CHANGED
@@ -14,20 +14,30 @@
14
14
  // - Deterministic precedence: static > :param > *catchall
15
15
  // - Tie-breaker: lexicographic route path
16
16
  // ---------------------------------------------------------------------------
17
+ import { readFileSync } from 'node:fs';
17
18
  import { readdir, stat } from 'node:fs/promises';
18
19
  import { join, relative, sep, basename, extname, dirname } from 'node:path';
20
+ import { extractServerScript } from './build/server-script.js';
21
+ import { composeServerScriptEnvelope, resolveAdjacentServerModules } from './server-script-composition.js';
19
22
  /**
20
- * @typedef {{ path: string, file: string }} ManifestEntry
23
+ * @typedef {{
24
+ * path: string,
25
+ * file: string,
26
+ * path_kind: 'static' | 'dynamic',
27
+ * render_mode: 'prerender' | 'server',
28
+ * params: string[]
29
+ * }} ManifestEntry
21
30
  */
22
31
  /**
23
32
  * Scan a pages directory and produce a deterministic RouteManifest.
24
33
  *
25
34
  * @param {string} pagesDir - Absolute path to /pages directory
26
35
  * @param {string} [extension='.zen'] - File extension to scan for
36
+ * @param {{ compilerOpts?: object }} [options]
27
37
  * @returns {Promise<ManifestEntry[]>}
28
38
  */
29
- export async function generateManifest(pagesDir, extension = '.zen') {
30
- const entries = await _scanDir(pagesDir, pagesDir, extension);
39
+ export async function generateManifest(pagesDir, extension = '.zen', options = {}) {
40
+ const entries = await _scanDir(pagesDir, pagesDir, extension, options.compilerOpts || {});
31
41
  // Validate: no repeated param names in any single route
32
42
  for (const entry of entries) {
33
43
  _validateParams(entry.path);
@@ -44,7 +54,7 @@ export async function generateManifest(pagesDir, extension = '.zen') {
44
54
  * @param {string} ext - Extension to match
45
55
  * @returns {Promise<ManifestEntry[]>}
46
56
  */
47
- async function _scanDir(dir, root, ext) {
57
+ async function _scanDir(dir, root, ext, compilerOpts) {
48
58
  /** @type {ManifestEntry[]} */
49
59
  const entries = [];
50
60
  let items;
@@ -60,16 +70,49 @@ async function _scanDir(dir, root, ext) {
60
70
  const fullPath = join(dir, item);
61
71
  const info = await stat(fullPath);
62
72
  if (info.isDirectory()) {
63
- const nested = await _scanDir(fullPath, root, ext);
73
+ const nested = await _scanDir(fullPath, root, ext, compilerOpts);
64
74
  entries.push(...nested);
65
75
  }
66
76
  else if (item.endsWith(ext)) {
67
77
  const routePath = _fileToRoute(fullPath, root, ext);
68
- entries.push({ path: routePath, file: relative(root, fullPath) });
78
+ entries.push(buildManifestEntry({
79
+ fullPath,
80
+ root,
81
+ routePath,
82
+ compilerOpts
83
+ }));
69
84
  }
70
85
  }
71
86
  return entries;
72
87
  }
88
+ function buildManifestEntry({ fullPath, root, routePath, compilerOpts }) {
89
+ const rawSource = readFileSync(fullPath, 'utf8');
90
+ const inlineServerScript = extractServerScript(rawSource, fullPath, compilerOpts).serverScript;
91
+ const { guardPath, loadPath } = resolveAdjacentServerModules(fullPath);
92
+ const composed = composeServerScriptEnvelope({
93
+ sourceFile: fullPath,
94
+ inlineServerScript,
95
+ adjacentGuardPath: guardPath,
96
+ adjacentLoadPath: loadPath
97
+ });
98
+ return {
99
+ path: routePath,
100
+ file: relative(root, fullPath),
101
+ path_kind: _isDynamic(routePath) ? 'dynamic' : 'static',
102
+ render_mode: composed.serverScript && composed.serverScript.prerender !== true ? 'server' : 'prerender',
103
+ params: extractRouteParams(routePath)
104
+ };
105
+ }
106
+ function extractRouteParams(routePath) {
107
+ return routePath
108
+ .split('/')
109
+ .filter(Boolean)
110
+ .filter((segment) => segment.startsWith(':') || segment.startsWith('*'))
111
+ .map((segment) => {
112
+ const raw = segment.slice(1);
113
+ return raw.endsWith('?') ? raw.slice(0, -1) : raw;
114
+ });
115
+ }
73
116
  /**
74
117
  * Convert a file path to a route path.
75
118
  *
package/dist/preview.js CHANGED
@@ -13,7 +13,8 @@ import { createServer } from 'node:http';
13
13
  import { access, readFile } from 'node:fs/promises';
14
14
  import { extname, join, normalize, resolve, sep, dirname } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
- import { loadConfig } from './config.js';
16
+ import { appLocalRedirectLocation, imageEndpointPath, normalizeBasePath, routeCheckPath, stripBasePath } from './base-path.js';
17
+ import { isConfigKeyExplicit, loadConfig, validateConfig } from './config.js';
17
18
  import { materializeImageMarkup } from './images/materialize.js';
18
19
  import { createImageRuntimePayload, injectImageRuntimePayload } from './images/payload.js';
19
20
  import { handleImageRequest } from './images/service.js';
@@ -391,14 +392,25 @@ try {
391
392
  */
392
393
  export async function createPreviewServer(options) {
393
394
  const resolvedProjectRoot = options?.projectRoot ? resolve(options.projectRoot) : resolve(options.distDir, '..');
395
+ const loadedConfig = await loadConfig(resolvedProjectRoot);
394
396
  const resolvedConfig = options?.config && typeof options.config === 'object'
395
- ? options.config
396
- : await loadConfig(resolvedProjectRoot);
397
+ ? (() => {
398
+ const overrideConfig = validateConfig(options.config);
399
+ const mergedConfig = { ...loadedConfig };
400
+ for (const key of Object.keys(overrideConfig)) {
401
+ if (isConfigKeyExplicit(overrideConfig, key)) {
402
+ mergedConfig[key] = overrideConfig[key];
403
+ }
404
+ }
405
+ return mergedConfig;
406
+ })()
407
+ : loadedConfig;
397
408
  const { distDir, port = 4000, host = '127.0.0.1', logger: providedLogger = null } = options;
398
409
  const projectRoot = resolvedProjectRoot;
399
410
  const config = resolvedConfig;
400
411
  const logger = providedLogger || createSilentLogger();
401
412
  const verboseLogging = logger.mode?.logLevel === 'verbose';
413
+ const configuredBasePath = normalizeBasePath(config.basePath || '/');
402
414
  let actualPort = port;
403
415
  async function loadImageManifest() {
404
416
  try {
@@ -424,8 +436,10 @@ export async function createPreviewServer(options) {
424
436
  ? `http://${req.headers.host}`
425
437
  : serverOrigin();
426
438
  const url = new URL(req.url, requestBase);
439
+ const { basePath, routes } = await loadRouteManifestState(distDir, configuredBasePath);
440
+ const canonicalPath = stripBasePath(url.pathname, basePath);
427
441
  try {
428
- if (url.pathname === '/__zenith/route-check') {
442
+ if (url.pathname === routeCheckPath(basePath)) {
429
443
  // Security: Require explicitly designated header to prevent public oracle probing
430
444
  if (req.headers['x-zenith-route-check'] !== '1') {
431
445
  res.writeHead(403, { 'Content-Type': 'application/json' });
@@ -445,8 +459,15 @@ export async function createPreviewServer(options) {
445
459
  res.end(JSON.stringify({ error: 'external_route_evaluation_forbidden' }));
446
460
  return;
447
461
  }
448
- const routes = await loadRouteManifest(distDir);
449
- const resolvedCheck = resolveRequestRoute(targetUrl, routes);
462
+ const canonicalTargetPath = stripBasePath(targetUrl.pathname, basePath);
463
+ if (canonicalTargetPath === null) {
464
+ res.writeHead(404, { 'Content-Type': 'application/json' });
465
+ res.end(JSON.stringify({ error: 'route_not_found' }));
466
+ return;
467
+ }
468
+ const canonicalTargetUrl = new URL(targetUrl.toString());
469
+ canonicalTargetUrl.pathname = canonicalTargetPath;
470
+ const resolvedCheck = resolveRequestRoute(canonicalTargetUrl, routes);
450
471
  if (!resolvedCheck.matched || !resolvedCheck.route) {
451
472
  res.writeHead(404, { 'Content-Type': 'application/json' });
452
473
  res.end(JSON.stringify({ error: 'route_not_found' }));
@@ -466,16 +487,17 @@ export async function createPreviewServer(options) {
466
487
  });
467
488
  // Security: Enforce relative or same-origin redirects
468
489
  if (checkResult && checkResult.result && checkResult.result.kind === 'redirect') {
469
- const loc = String(checkResult.result.location || '/');
490
+ const loc = appLocalRedirectLocation(checkResult.result.location || '/', basePath);
491
+ checkResult.result.location = loc;
470
492
  if (loc.includes('://') || loc.startsWith('//')) {
471
493
  try {
472
494
  const parsedLoc = new URL(loc);
473
495
  if (parsedLoc.origin !== targetUrl.origin) {
474
- checkResult.result.location = '/'; // Fallback to root for open redirect attempt
496
+ checkResult.result.location = appLocalRedirectLocation('/', basePath);
475
497
  }
476
498
  }
477
499
  catch {
478
- checkResult.result.location = '/';
500
+ checkResult.result.location = appLocalRedirectLocation('/', basePath);
479
501
  }
480
502
  }
481
503
  }
@@ -493,7 +515,7 @@ export async function createPreviewServer(options) {
493
515
  }));
494
516
  return;
495
517
  }
496
- if (url.pathname === '/_zenith/image') {
518
+ if (url.pathname === imageEndpointPath(basePath)) {
497
519
  await handleImageRequest(req, res, {
498
520
  requestUrl: url,
499
521
  projectRoot,
@@ -501,8 +523,11 @@ export async function createPreviewServer(options) {
501
523
  });
502
524
  return;
503
525
  }
504
- if (extname(url.pathname) && extname(url.pathname) !== '.html') {
505
- const staticPath = resolveWithinDist(distDir, url.pathname);
526
+ if (canonicalPath === null) {
527
+ throw new Error('not found');
528
+ }
529
+ if (extname(canonicalPath) && extname(canonicalPath) !== '.html') {
530
+ const staticPath = resolveWithinDist(distDir, canonicalPath);
506
531
  if (!staticPath || !(await fileExists(staticPath))) {
507
532
  throw new Error('not found');
508
533
  }
@@ -512,8 +537,9 @@ export async function createPreviewServer(options) {
512
537
  res.end(content);
513
538
  return;
514
539
  }
515
- const routes = await loadRouteManifest(distDir);
516
- const resolved = resolveRequestRoute(url, routes);
540
+ const canonicalUrl = new URL(url.toString());
541
+ canonicalUrl.pathname = canonicalPath;
542
+ const resolved = resolveRequestRoute(canonicalUrl, routes);
517
543
  let htmlPath = null;
518
544
  if (resolved.matched && resolved.route) {
519
545
  if (verboseLogging) {
@@ -563,7 +589,7 @@ export async function createPreviewServer(options) {
563
589
  if (result && result.kind === 'redirect') {
564
590
  const status = Number.isInteger(result.status) ? result.status : 302;
565
591
  res.writeHead(status, {
566
- Location: result.location,
592
+ Location: appLocalRedirectLocation(result.location, basePath),
567
593
  'Cache-Control': 'no-store'
568
594
  });
569
595
  res.end('');
@@ -585,15 +611,15 @@ export async function createPreviewServer(options) {
585
611
  html = await materializeImageMarkup({
586
612
  html,
587
613
  pageAssetPath,
588
- payload: createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint'),
614
+ payload: createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint', basePath),
589
615
  ssrData: ssrPayload,
590
- routePathname: resolved.route.path || url.pathname
616
+ routePathname: url.pathname
591
617
  });
592
618
  }
593
619
  if (ssrPayload) {
594
620
  html = injectSsrPayload(html, ssrPayload);
595
621
  }
596
- html = injectImageRuntimePayload(html, createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint'));
622
+ html = injectImageRuntimePayload(html, createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint', basePath));
597
623
  res.writeHead(200, { 'Content-Type': 'text/html' });
598
624
  res.end(html);
599
625
  }
@@ -636,20 +662,30 @@ export async function createPreviewServer(options) {
636
662
  * @returns {Promise<PreviewRoute[]>}
637
663
  */
638
664
  export async function loadRouteManifest(distDir) {
665
+ const state = await loadRouteManifestState(distDir, '/');
666
+ return state.routes;
667
+ }
668
+ async function loadRouteManifestState(distDir, fallbackBasePath = '/') {
639
669
  const manifestPath = join(distDir, 'assets', 'router-manifest.json');
640
670
  try {
641
671
  const source = await readFile(manifestPath, 'utf8');
642
672
  const parsed = JSON.parse(source);
643
673
  const routes = Array.isArray(parsed?.routes) ? parsed.routes : [];
644
- return routes
645
- .filter((entry) => entry &&
646
- typeof entry === 'object' &&
647
- typeof entry.path === 'string' &&
648
- typeof entry.output === 'string')
649
- .sort((a, b) => compareRouteSpecificity(a.path, b.path));
674
+ return {
675
+ basePath: normalizeBasePath(parsed?.base_path || fallbackBasePath || '/'),
676
+ routes: routes
677
+ .filter((entry) => entry &&
678
+ typeof entry === 'object' &&
679
+ typeof entry.path === 'string' &&
680
+ typeof entry.output === 'string')
681
+ .sort((a, b) => compareRouteSpecificity(a.path, b.path))
682
+ };
650
683
  }
651
684
  catch {
652
- return [];
685
+ return {
686
+ basePath: normalizeBasePath(fallbackBasePath || '/'),
687
+ routes: []
688
+ };
653
689
  }
654
690
  }
655
691
  export const matchRoute = matchManifestRoute;
@@ -0,0 +1,26 @@
1
+ export function writeServerOutput({ coreOutputDir, staticDir, projectRoot, config, basePath }: {
2
+ coreOutputDir: any;
3
+ staticDir: any;
4
+ projectRoot: any;
5
+ config: any;
6
+ basePath?: string | undefined;
7
+ }): Promise<{
8
+ serverDir: string;
9
+ routes: {
10
+ name: any;
11
+ path: any;
12
+ output: any;
13
+ base_path: string;
14
+ page_asset: any;
15
+ page_asset_file: string | null;
16
+ route_id: any;
17
+ server_script_path: any;
18
+ guard_module_ref: any;
19
+ load_module_ref: any;
20
+ has_guard: boolean;
21
+ has_load: boolean;
22
+ params: string[];
23
+ image_manifest_file: string | null;
24
+ image_config: any;
25
+ }[];
26
+ }>;