@ecopages/core 0.2.0-beta.13 → 0.2.0-beta.15

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 (54) hide show
  1. package/README.md +1 -1
  2. package/package.json +18 -2
  3. package/src/adapters/abstract/application-adapter.d.ts +31 -4
  4. package/src/adapters/abstract/application-adapter.js +36 -1
  5. package/src/adapters/abstract/server-adapter.d.ts +3 -2
  6. package/src/adapters/bun/create-app.d.ts +1 -1
  7. package/src/adapters/bun/create-app.js +24 -7
  8. package/src/adapters/bun/server-adapter.d.ts +9 -2
  9. package/src/adapters/bun/server-adapter.js +28 -4
  10. package/src/adapters/create-app.d.ts +1 -12
  11. package/src/adapters/create-app.js +0 -58
  12. package/src/adapters/index.d.ts +1 -1
  13. package/src/adapters/index.js +1 -2
  14. package/src/adapters/node/create-app.d.ts +1 -1
  15. package/src/adapters/node/create-app.js +15 -5
  16. package/src/adapters/node/server-adapter.d.ts +7 -1
  17. package/src/adapters/node/server-adapter.js +25 -3
  18. package/src/adapters/shared/fs-server-response-matcher.d.ts +2 -0
  19. package/src/adapters/shared/fs-server-response-matcher.js +39 -17
  20. package/src/dev/runtime-server-started-message.d.ts +2 -0
  21. package/src/dev/runtime-server-started-message.js +6 -0
  22. package/src/eco/README.md +11 -0
  23. package/src/eco/eco-declared-component.d.ts +14 -0
  24. package/src/eco/eco-declared-component.js +16 -0
  25. package/src/eco/eco.browser.js +5 -6
  26. package/src/eco/eco.js +5 -6
  27. package/src/eco/eco.types.d.ts +2 -2
  28. package/src/eco/page-layout-normalization.d.ts +21 -0
  29. package/src/eco/page-layout-normalization.js +59 -0
  30. package/src/errors/index.d.ts +1 -0
  31. package/src/errors/index.js +7 -1
  32. package/src/errors/undeclared-component-dependency-error.d.ts +11 -0
  33. package/src/errors/undeclared-component-dependency-error.js +17 -0
  34. package/src/route-renderer/orchestration/component-graph.js +12 -4
  35. package/src/route-renderer/orchestration/document-shell-render.service.d.ts +37 -1
  36. package/src/route-renderer/orchestration/document-shell-render.service.js +79 -13
  37. package/src/route-renderer/orchestration/integration-renderer.js +5 -1
  38. package/src/route-renderer/orchestration/layout-shell-props.service.d.ts +20 -0
  39. package/src/route-renderer/orchestration/layout-shell-props.service.js +31 -0
  40. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +1 -0
  41. package/src/route-renderer/orchestration/ownership-validation.service.js +18 -0
  42. package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +3 -1
  43. package/src/route-renderer/orchestration/route-prepared-options.builder.js +3 -1
  44. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +3 -1
  45. package/src/route-renderer/orchestration/route-render-orchestrator.js +3 -3
  46. package/src/route-renderer/orchestration/string-markup-renderer.js +2 -1
  47. package/src/route-renderer/page-loading/component-dependency-collection.js +5 -2
  48. package/src/types/public-types.d.ts +51 -4
  49. package/src/utils/parse-cli-args.d.ts +1 -0
  50. package/src/utils/parse-cli-args.js +3 -0
  51. package/src/utils/server-utils.module.js +1 -0
  52. package/src/watchers/project-watcher-ignore.d.ts +11 -0
  53. package/src/watchers/project-watcher-ignore.js +16 -0
  54. package/src/watchers/project-watcher.js +2 -6
package/README.md CHANGED
@@ -227,7 +227,7 @@ import { defineApiHandler, defineGroupHandler, eco } from '@ecopages/core';
227
227
  ```
228
228
 
229
229
  > [!NOTE]
230
- > `createApp` is the recommended entrypoint over `EcopagesApp`.
230
+ > Use `createApp()` from `@ecopages/core/create-app` as the application entrypoint.
231
231
 
232
232
  ### Runtime Escape Hatches
233
233
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.13",
3
+ "version": "0.2.0-beta.15",
4
4
  "description": "Core package for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -17,7 +17,7 @@
17
17
  "directory": "packages/core"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/file-system": "0.2.0-beta.13",
20
+ "@ecopages/file-system": "0.2.0-beta.15",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.134.0",
@@ -62,6 +62,10 @@
62
62
  "types": "./src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts",
63
63
  "default": "./src/route-renderer/orchestration/foreign-subtree-execution.service.js"
64
64
  },
65
+ "./route-renderer/orchestration/document-shell-render.service": {
66
+ "types": "./src/route-renderer/orchestration/document-shell-render.service.d.ts",
67
+ "default": "./src/route-renderer/orchestration/document-shell-render.service.js"
68
+ },
65
69
  "./router/navigation-coordinator": {
66
70
  "types": "./src/router/client/navigation-coordinator.d.ts",
67
71
  "default": "./src/router/client/navigation-coordinator.js"
@@ -75,6 +79,10 @@
75
79
  "types": "./src/eco/eco.d.ts",
76
80
  "default": "./src/eco/eco.js"
77
81
  },
82
+ "./eco/page-layout-normalization": {
83
+ "types": "./src/eco/page-layout-normalization.d.ts",
84
+ "default": "./src/eco/page-layout-normalization.js"
85
+ },
78
86
  "./declarations": {
79
87
  "types": "./src/declarations.d.ts"
80
88
  },
@@ -243,6 +251,10 @@
243
251
  "types": "./src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts",
244
252
  "default": "./src/route-renderer/orchestration/foreign-subtree-execution.service.js"
245
253
  },
254
+ "./route-renderer/orchestration/document-shell-render.service.ts": {
255
+ "types": "./src/route-renderer/orchestration/document-shell-render.service.d.ts",
256
+ "default": "./src/route-renderer/orchestration/document-shell-render.service.js"
257
+ },
246
258
  "./router/navigation-coordinator.ts": {
247
259
  "types": "./src/router/client/navigation-coordinator.d.ts",
248
260
  "default": "./src/router/client/navigation-coordinator.js"
@@ -256,6 +268,10 @@
256
268
  "types": "./src/eco/eco.d.ts",
257
269
  "default": "./src/eco/eco.js"
258
270
  },
271
+ "./eco/page-layout-normalization.ts": {
272
+ "types": "./src/eco/page-layout-normalization.d.ts",
273
+ "default": "./src/eco/page-layout-normalization.js"
274
+ },
259
275
  "./declarations.ts": {
260
276
  "types": "./src/declarations.d.ts"
261
277
  },
@@ -10,6 +10,7 @@ import type { SourceModuleLoader } from '../../services/module-loading/module-lo
10
10
  import type { EcoPagesAppConfig } from '../../types/internal-types.js';
11
11
  import type { ApiHandler, ApiHandlerContext, ErrorHandler, Middleware, RouteOptions, StaticRoute, ViewLoader } from '../../types/public-types.js';
12
12
  import type { EcopagesWebSocketHandler } from '../../types/public-types.js';
13
+ import { type EcopagesRuntimeLabel } from '../../dev/runtime-server-started-message.js';
13
14
  import { type ReturnParseCliArgs } from '../../utils/parse-cli-args.js';
14
15
  /**
15
16
  * Runtime bootstrap options layered on top of the app config.
@@ -49,6 +50,18 @@ export interface ApplicationRuntimeOptions {
49
50
  */
50
51
  hostModuleLoader?: SourceModuleLoader;
51
52
  }
53
+ export interface AppStartInfo {
54
+ origin: string;
55
+ }
56
+ export type OnAppStartCallback = (info: AppStartInfo) => void;
57
+ /** @deprecated Use {@link AppStartInfo}. */
58
+ export type ApplicationListeningInfo = AppStartInfo;
59
+ /** @deprecated Use {@link OnAppStartCallback}. */
60
+ export type StartCallback = OnAppStartCallback;
61
+ /** @deprecated Use {@link OnAppStartCallback}. */
62
+ export type ListenCallback = OnAppStartCallback;
63
+ /** @deprecated Use {@link OnAppStartCallback}. */
64
+ export type ApplicationListeningCallback = OnAppStartCallback;
52
65
  /**
53
66
  * Configuration options for application adapters
54
67
  */
@@ -66,7 +79,10 @@ export interface ApplicationAdapterOptions {
66
79
  * Common interface for application adapters
67
80
  */
68
81
  export interface ApplicationAdapter<T = any> extends AsyncDisposable {
69
- start(): Promise<T | void>;
82
+ /** Boot the server. Pass a callback to run when the runtime is ready (optional). */
83
+ start(onAppStart?: OnAppStartCallback): Promise<T | void>;
84
+ /** Invoked by embedded hosts once the app can take traffic. */
85
+ handleListening(origin: string): void;
70
86
  stop(force?: boolean): Promise<void>;
71
87
  }
72
88
  /**
@@ -94,7 +110,9 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
94
110
  * Both Bun and Node adapters read this map to register upgrade routes.
95
111
  */
96
112
  protected websocketHandlers: Map<string, EcopagesWebSocketHandler<any, any>>;
97
- constructor(options: TOptions);
113
+ private onAppStartCallback?;
114
+ protected readonly runtimeLabel: EcopagesRuntimeLabel;
115
+ constructor(options: TOptions, runtimeLabel: EcopagesRuntimeLabel);
98
116
  private clearDistFolder;
99
117
  /**
100
118
  * Register a GET route handler.
@@ -244,9 +262,18 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
244
262
  */
245
263
  protected abstract initializeServerAdapter(): Promise<any>;
246
264
  /**
247
- * Start the application server
265
+ * Boot the server. When `onAppStart` is passed, it runs once the runtime can take traffic.
266
+ * Embedded apps only register the callback — the host (for example Vite) boots the port.
267
+ */
268
+ start(onAppStart?: OnAppStartCallback): Promise<TServer | void>;
269
+ /** Runtime-specific server boot (dev, preview, build). */
270
+ protected abstract bootServer(): Promise<TServer | void>;
271
+ protected logServerStarted(origin: string): void;
272
+ /**
273
+ * Invoked by embedded hosts (for example Vite) once the app can take traffic.
248
274
  */
249
- abstract start(): Promise<TServer | void>;
275
+ handleListening(origin: string): void;
276
+ protected notifyListening(origin: string): void;
250
277
  /**
251
278
  * Stops the application server and releases runtime resources.
252
279
  *
@@ -6,6 +6,9 @@ import {
6
6
  import { getHostModuleLoader } from "../../services/module-loading/host-module-loader-registry.js";
7
7
  import { invariant } from "../../utils/invariant.js";
8
8
  import { fileSystem } from "@ecopages/file-system";
9
+ import {
10
+ formatRuntimeServerStartedMessage
11
+ } from "../../dev/runtime-server-started-message.js";
9
12
  import { parseCliArgs } from "../../utils/parse-cli-args.js";
10
13
  class AbstractApplicationAdapter {
11
14
  appConfig;
@@ -20,7 +23,10 @@ class AbstractApplicationAdapter {
20
23
  * Both Bun and Node adapters read this map to register upgrade routes.
21
24
  */
22
25
  websocketHandlers = /* @__PURE__ */ new Map();
23
- constructor(options) {
26
+ onAppStartCallback;
27
+ runtimeLabel;
28
+ constructor(options, runtimeLabel) {
29
+ this.runtimeLabel = runtimeLabel;
24
30
  this.appConfig = options.appConfig;
25
31
  this.serverOptions = options.serverOptions || {};
26
32
  this.runtimeOptions = options.runtime ?? {};
@@ -191,6 +197,35 @@ class AbstractApplicationAdapter {
191
197
  getErrorHandler() {
192
198
  return this.errorHandler;
193
199
  }
200
+ /**
201
+ * Boot the server. When `onAppStart` is passed, it runs once the runtime can take traffic.
202
+ * Embedded apps only register the callback — the host (for example Vite) boots the port.
203
+ */
204
+ async start(onAppStart) {
205
+ if (onAppStart) {
206
+ this.onAppStartCallback = onAppStart;
207
+ }
208
+ if (this.runtimeOptions.embedded) {
209
+ return;
210
+ }
211
+ return this.bootServer();
212
+ }
213
+ logServerStarted(origin) {
214
+ appLogger.info(formatRuntimeServerStartedMessage(this.runtimeLabel, origin));
215
+ }
216
+ /**
217
+ * Invoked by embedded hosts (for example Vite) once the app can take traffic.
218
+ */
219
+ handleListening(origin) {
220
+ this.notifyListening(origin);
221
+ }
222
+ notifyListening(origin) {
223
+ const normalizedOrigin = origin.replace(/\/$/, "");
224
+ if (!this.onAppStartCallback) {
225
+ this.logServerStarted(normalizedOrigin);
226
+ }
227
+ this.onAppStartCallback?.({ origin: normalizedOrigin });
228
+ }
194
229
  /**
195
230
  * Stops the application server and releases runtime resources.
196
231
  *
@@ -32,7 +32,8 @@ export interface ServerAdapterResult {
32
32
  buildStatic: (options?: {
33
33
  preview?: boolean;
34
34
  force?: boolean;
35
- }) => Promise<void>;
35
+ }) => Promise<string | undefined>;
36
+ servePreviewOnly: () => Promise<string | undefined>;
36
37
  dispose(): Promise<void>;
37
38
  }
38
39
  /**
@@ -60,7 +61,7 @@ export declare abstract class AbstractServerAdapter<TOptions extends ServerAdapt
60
61
  abstract buildStatic(options?: {
61
62
  preview?: boolean;
62
63
  force?: boolean;
63
- }): Promise<void>;
64
+ }): Promise<string | undefined>;
64
65
  /**
65
66
  * Factory method to create a server adapter with runtime-specific functionality
66
67
  */
@@ -53,7 +53,7 @@ export declare class BunEcopagesApp<WebSocketData = undefined> extends SharedApp
53
53
  * @param options Optional settings
54
54
  * @param options.autoCompleteInitialization Whether to automatically complete initialization with dynamic routes after server start (defaults to true)
55
55
  */
56
- start(): Promise<Server<WebSocketData> | void>;
56
+ protected bootServer(): Promise<Server<WebSocketData> | void>;
57
57
  stop(force?: boolean): Promise<void>;
58
58
  }
59
59
  /**
@@ -10,7 +10,7 @@ class BunEcopagesApp extends SharedApplicationAdapter {
10
10
  stopped = false;
11
11
  runtimeHost;
12
12
  constructor(options, dependencies) {
13
- super(options);
13
+ super(options, "Bun");
14
14
  this.runtimeHost = dependencies.runtimeHost;
15
15
  }
16
16
  async fetch(request) {
@@ -66,7 +66,7 @@ class BunEcopagesApp extends SharedApplicationAdapter {
66
66
  * @param options Optional settings
67
67
  * @param options.autoCompleteInitialization Whether to automatically complete initialization with dynamic routes after server start (defaults to true)
68
68
  */
69
- async start() {
69
+ async bootServer() {
70
70
  if (this.stopped) {
71
71
  this.serverAdapter = void 0;
72
72
  this.stopped = false;
@@ -74,15 +74,25 @@ class BunEcopagesApp extends SharedApplicationAdapter {
74
74
  if (!this.serverAdapter) {
75
75
  this.serverAdapter = await this.initializeServerAdapter();
76
76
  }
77
- const { dev, preview, build, force } = this.cliArgs;
77
+ const { dev, preview, build, force, serveOnly } = this.cliArgs;
78
78
  const staticRuntimeMode = resolveStaticRuntimeMode({
79
79
  appConfig: this.appConfig,
80
80
  cliArgs: this.cliArgs
81
81
  });
82
+ if (preview && serveOnly) {
83
+ const previewOrigin2 = await this.serverAdapter.servePreviewOnly();
84
+ if (previewOrigin2) {
85
+ this.notifyListening(previewOrigin2);
86
+ }
87
+ return;
88
+ }
82
89
  if (staticRuntimeMode.canBuildWithoutRuntimeServer) {
83
90
  appLogger.debugTime("Building static pages");
84
- await this.serverAdapter.buildStatic({ preview, force });
91
+ const previewOrigin2 = await this.serverAdapter.buildStatic({ preview, force });
85
92
  appLogger.debugTimeEnd("Building static pages");
93
+ if (preview && previewOrigin2) {
94
+ this.notifyListening(previewOrigin2);
95
+ }
86
96
  if (build) {
87
97
  process.exit(0);
88
98
  }
@@ -103,12 +113,14 @@ class BunEcopagesApp extends SharedApplicationAdapter {
103
113
  if (!this.server) {
104
114
  throw new Error("Server failed to start");
105
115
  }
106
- appLogger.info(
107
- `Server running at ${this.runtimeHost.getOrigin(this.server, runtimeServerOptions)}`
116
+ const runtimeOrigin = this.runtimeHost.getOrigin(
117
+ this.server,
118
+ runtimeServerOptions
108
119
  );
120
+ let previewOrigin;
109
121
  if (build || preview) {
110
122
  appLogger.debugTime("Building static pages");
111
- await this.serverAdapter.buildStatic({ preview, force });
123
+ previewOrigin = await this.serverAdapter.buildStatic({ preview, force });
112
124
  const buildRuntimeServer = this.server;
113
125
  this.server = null;
114
126
  await this.runtimeHost.stop(buildRuntimeServer, { force: true });
@@ -116,6 +128,11 @@ class BunEcopagesApp extends SharedApplicationAdapter {
116
128
  if (build) {
117
129
  process.exit(0);
118
130
  }
131
+ } else {
132
+ this.notifyListening(runtimeOrigin);
133
+ }
134
+ if (preview && previewOrigin) {
135
+ this.notifyListening(previewOrigin);
119
136
  }
120
137
  return this.server ?? void 0;
121
138
  }
@@ -52,7 +52,8 @@ export interface BunServerAdapterResult extends ServerAdapterResult {
52
52
  buildStatic: (options?: {
53
53
  preview?: boolean;
54
54
  force?: boolean;
55
- }) => Promise<void>;
55
+ }) => Promise<string | undefined>;
56
+ servePreviewOnly: () => Promise<string | undefined>;
56
57
  completeInitialization: (server?: BunServerInstance | null) => Promise<void>;
57
58
  handleRequest: (request: Request) => Promise<Response>;
58
59
  attachUserWebSocketUpgrades: (server: NodeHttpServer, options?: {
@@ -216,7 +217,13 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
216
217
  buildStatic(options?: {
217
218
  preview?: boolean;
218
219
  force?: boolean;
219
- }): Promise<void>;
220
+ }): Promise<string | undefined>;
221
+ /**
222
+ * Serves an existing static export without running SSG. Used by e2e preview
223
+ * launchers after a shared prewarm build.
224
+ */
225
+ servePreviewOnly(): Promise<string | undefined>;
226
+ private startPreviewServer;
220
227
  /**
221
228
  * Initializes the server with dynamic routes after server creation.
222
229
  * Must be called before handling any requests.
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
2
3
  import { appLogger } from "../../global/app-logger.js";
3
4
  import { HttpError } from "../../errors/http-error.js";
@@ -433,8 +434,30 @@ class BunServerAdapter extends SharedServerAdapter {
433
434
  }
434
435
  );
435
436
  if (!options?.preview) {
436
- return;
437
+ return void 0;
437
438
  }
439
+ return this.startPreviewServer();
440
+ }
441
+ /**
442
+ * Serves an existing static export without running SSG. Used by e2e preview
443
+ * launchers after a shared prewarm build.
444
+ */
445
+ async servePreviewOnly() {
446
+ if (!this.fullyInitialized) {
447
+ await this.initializeSharedRouteHandling({
448
+ staticRoutes: this.staticRoutes,
449
+ hmrManager: this.hmrManager
450
+ });
451
+ }
452
+ const distPath = path.join(this.appConfig.rootDir, this.appConfig.distDir);
453
+ if (!fileSystem.exists(distPath)) {
454
+ throw new Error(
455
+ `Cannot serve preview without building first: dist directory "${this.appConfig.distDir}" not found in "${this.appConfig.rootDir}".`
456
+ );
457
+ }
458
+ return this.startPreviewServer();
459
+ }
460
+ async startPreviewServer() {
438
461
  const previewHostname = this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME;
439
462
  const previewPort = Number(this.serveOptions.port || DEFAULT_ECOPAGES_PORT);
440
463
  const activePreviewPort = await this.previewHost.start({
@@ -443,10 +466,10 @@ class BunServerAdapter extends SharedServerAdapter {
443
466
  port: previewPort,
444
467
  allowPortFallback: this.allowPortFallback
445
468
  });
446
- if (activePreviewPort) {
447
- appLogger.info(`Preview running at http://${previewHostname}:${activePreviewPort}`);
448
- return;
469
+ if (!activePreviewPort) {
470
+ return void 0;
449
471
  }
472
+ return `http://${previewHostname}:${activePreviewPort}`;
450
473
  }
451
474
  /**
452
475
  * Initializes the server with dynamic routes after server creation.
@@ -497,6 +520,7 @@ class BunServerAdapter extends SharedServerAdapter {
497
520
  return {
498
521
  getServerOptions: this.getServerOptions.bind(this),
499
522
  buildStatic: this.buildStatic.bind(this),
523
+ servePreviewOnly: this.servePreviewOnly.bind(this),
500
524
  completeInitialization: this.completeInitialization.bind(this),
501
525
  handleRequest: this.handleRequest.bind(this),
502
526
  attachUserWebSocketUpgrades: this.attachUserWebSocketUpgrades.bind(this),
@@ -1,7 +1,7 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
2
  import { AbstractApplicationAdapter } from './abstract/application-adapter.js';
3
3
  import type { ApplicationAdapterOptions } from './abstract/application-adapter.js';
4
- import { SharedApplicationAdapter } from './shared/application-adapter.js';
4
+ export type { OnAppStartCallback, AppStartInfo, StartCallback, ListenCallback, ApplicationListeningCallback, ApplicationListeningInfo, } from './abstract/application-adapter.js';
5
5
  export type EcopagesRuntimeAdapter = 'auto' | 'node' | 'bun';
6
6
  export interface EcopagesAppOptions extends ApplicationAdapterOptions {
7
7
  appConfig: EcoPagesAppConfig;
@@ -20,14 +20,3 @@ export interface EcopagesAppOptions extends ApplicationAdapterOptions {
20
20
  }
21
21
  export type UniversalEcopagesApp = AbstractApplicationAdapter<EcopagesAppOptions, unknown, Request>;
22
22
  export declare function createApp<WebSocketData = undefined>(options: EcopagesAppOptions): Promise<UniversalEcopagesApp>;
23
- export declare class EcopagesApp extends SharedApplicationAdapter<EcopagesAppOptions, unknown, Request> {
24
- private readonly appOptions;
25
- private runtimeApp;
26
- private runtimeAppPromise;
27
- constructor(options: EcopagesAppOptions);
28
- private getRuntimeApp;
29
- protected initializeServerAdapter(): Promise<UniversalEcopagesApp>;
30
- start(): Promise<unknown>;
31
- fetch(request: Request): Promise<Response>;
32
- stop(force?: boolean): Promise<void>;
33
- }
@@ -1,5 +1,4 @@
1
1
  import { AbstractApplicationAdapter } from "./abstract/application-adapter.js";
2
- import { SharedApplicationAdapter } from "./shared/application-adapter.js";
3
2
  import { createApp as createBunApp } from "./bun/create-app.js";
4
3
  import { createNodeApp } from "./node/create-app.js";
5
4
  async function createRuntimeApp(options) {
@@ -13,63 +12,6 @@ async function createRuntimeApp(options) {
13
12
  async function createApp(options) {
14
13
  return createRuntimeApp(options);
15
14
  }
16
- class EcopagesApp extends SharedApplicationAdapter {
17
- appOptions;
18
- runtimeApp = null;
19
- runtimeAppPromise = null;
20
- constructor(options) {
21
- super(options);
22
- this.appOptions = options;
23
- }
24
- async getRuntimeApp() {
25
- if (this.runtimeApp) {
26
- return this.runtimeApp;
27
- }
28
- if (!this.runtimeAppPromise) {
29
- this.runtimeAppPromise = createRuntimeApp({
30
- ...this.appOptions,
31
- clearOutput: false
32
- }).then((app) => {
33
- for (const handler of this.apiHandlers) {
34
- app.add(handler);
35
- }
36
- for (const route of this.staticRoutes) {
37
- app.static(route.path, route.loader);
38
- }
39
- if (this.errorHandler) {
40
- app.onError(this.errorHandler);
41
- }
42
- this.runtimeApp = app;
43
- return app;
44
- });
45
- }
46
- return this.runtimeAppPromise;
47
- }
48
- async initializeServerAdapter() {
49
- return this.getRuntimeApp();
50
- }
51
- async start() {
52
- const runtimeApp = await this.getRuntimeApp();
53
- return runtimeApp.start();
54
- }
55
- async fetch(request) {
56
- const runtimeApp = await this.getRuntimeApp();
57
- const candidate = runtimeApp;
58
- if (!candidate.fetch) {
59
- throw new Error("The selected runtime adapter does not expose fetch()");
60
- }
61
- return candidate.fetch(request);
62
- }
63
- async stop(force = true) {
64
- const runtimeApp = this.runtimeApp ?? (this.runtimeAppPromise ? await this.runtimeAppPromise : null);
65
- if (runtimeApp) {
66
- await runtimeApp.stop(force);
67
- }
68
- this.runtimeApp = null;
69
- this.runtimeAppPromise = null;
70
- }
71
- }
72
15
  export {
73
- EcopagesApp,
74
16
  createApp
75
17
  };
@@ -1,2 +1,2 @@
1
- export { EcopagesApp, createApp } from './create-app.js';
1
+ export { createApp } from './create-app.js';
2
2
  export { defineApiHandler, defineGroupHandler, type GroupHandler } from './shared/define-api-handler.js';
@@ -1,7 +1,6 @@
1
- import { EcopagesApp, createApp } from "./create-app.js";
1
+ import { createApp } from "./create-app.js";
2
2
  import { defineApiHandler, defineGroupHandler } from "./shared/define-api-handler.js";
3
3
  export {
4
- EcopagesApp,
5
4
  createApp,
6
5
  defineApiHandler,
7
6
  defineGroupHandler
@@ -18,7 +18,7 @@ export declare class NodeEcopagesApp extends SharedApplicationAdapter<EcopagesAp
18
18
  protected createServerAdapter(params: Parameters<typeof createNodeServerAdapter>[0]): Promise<NodeServerAdapterResult>;
19
19
  stop(force?: boolean): Promise<void>;
20
20
  protected initializeServerAdapter(): Promise<NodeServerAdapterResult>;
21
- start(): Promise<NodeServerInstance | void>;
21
+ protected bootServer(): Promise<NodeServerInstance | void>;
22
22
  fetch(request: Request): Promise<Response>;
23
23
  attachWebSocketUpgrades(httpServer: import('node:http').Server, options?: {
24
24
  passthroughUnmatched?: boolean;
@@ -12,7 +12,7 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
12
12
  stopped = false;
13
13
  runtimeHost;
14
14
  constructor(options, dependencies) {
15
- super(options);
15
+ super(options, "Node");
16
16
  this.runtimeHost = dependencies.runtimeHost;
17
17
  }
18
18
  createServerAdapter(params) {
@@ -55,7 +55,7 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
55
55
  allowPortFallback: binding.allowPortFallback
56
56
  });
57
57
  }
58
- async start() {
58
+ async bootServer() {
59
59
  if (this.stopped) {
60
60
  this.serverAdapter = void 0;
61
61
  this.stopped = false;
@@ -66,11 +66,21 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
66
66
  if (this.server) {
67
67
  return this.server;
68
68
  }
69
- const { build, preview, force } = this.cliArgs;
69
+ const { build, preview, force, serveOnly } = this.cliArgs;
70
+ if (preview && serveOnly) {
71
+ const previewOrigin = await this.serverAdapter.servePreviewOnly();
72
+ if (previewOrigin) {
73
+ this.notifyListening(previewOrigin);
74
+ }
75
+ return;
76
+ }
70
77
  if (build || preview) {
71
78
  appLogger.debugTime("Building static pages");
72
- await this.serverAdapter.buildStatic({ preview, force });
79
+ const previewOrigin = await this.serverAdapter.buildStatic({ preview, force });
73
80
  appLogger.debugTimeEnd("Building static pages");
81
+ if (preview && previewOrigin) {
82
+ this.notifyListening(previewOrigin);
83
+ }
74
84
  if (build) {
75
85
  process.exit(0);
76
86
  }
@@ -85,7 +95,7 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
85
95
  });
86
96
  this.runtimeOrigin = this.runtimeHost.getOrigin(this.server, serveOptions);
87
97
  await this.serverAdapter.completeInitialization(this.server);
88
- appLogger.info(`Node server running at ${this.runtimeOrigin}`);
98
+ this.notifyListening(this.runtimeOrigin);
89
99
  return this.server;
90
100
  }
91
101
  async fetch(request) {
@@ -126,7 +126,13 @@ export declare class NodeServerAdapter extends SharedServerAdapter<NodeServerAda
126
126
  buildStatic(options?: {
127
127
  preview?: boolean;
128
128
  force?: boolean;
129
- }): Promise<void>;
129
+ }): Promise<string | undefined>;
130
+ /**
131
+ * Serves an existing static export without running SSG. Used by e2e preview
132
+ * launchers after a shared prewarm build.
133
+ */
134
+ servePreviewOnly(): Promise<string | undefined>;
135
+ private startPreviewServer;
130
136
  createAdapter(): Promise<NodeServerAdapterResult>;
131
137
  /**
132
138
  * Releases dev-time resources owned by the adapter.
@@ -1,4 +1,6 @@
1
1
  import {} from "node:http";
2
+ import path from "node:path";
3
+ import { fileSystem } from "@ecopages/file-system";
2
4
  import { setupAppRuntimePlugins } from "../../build/build-adapter.js";
3
5
  import { installAppRuntimeBuildExecutor } from "../../build/runtime-build-executor.js";
4
6
  import { appLogger } from "../../global/app-logger.js";
@@ -155,8 +157,27 @@ class NodeServerAdapter extends SharedServerAdapter {
155
157
  }
156
158
  );
157
159
  if (!options?.preview) {
158
- return;
160
+ return void 0;
161
+ }
162
+ return this.startPreviewServer();
163
+ }
164
+ /**
165
+ * Serves an existing static export without running SSG. Used by e2e preview
166
+ * launchers after a shared prewarm build.
167
+ */
168
+ async servePreviewOnly() {
169
+ if (!this.initialized) {
170
+ await this.initialize();
171
+ }
172
+ const distPath = path.join(this.appConfig.rootDir, this.appConfig.distDir);
173
+ if (!fileSystem.exists(distPath)) {
174
+ throw new Error(
175
+ `Cannot serve preview without building first: dist directory "${this.appConfig.distDir}" not found in "${this.appConfig.rootDir}".`
176
+ );
159
177
  }
178
+ return this.startPreviewServer();
179
+ }
180
+ async startPreviewServer() {
160
181
  const activePreviewPort = await this.previewHost.start({
161
182
  appConfig: this.appConfig,
162
183
  hostname: String(this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME),
@@ -164,16 +185,17 @@ class NodeServerAdapter extends SharedServerAdapter {
164
185
  allowPortFallback: this.allowPortFallback
165
186
  });
166
187
  if (!activePreviewPort) {
167
- return;
188
+ return void 0;
168
189
  }
169
190
  const previewHostname = this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME;
170
- appLogger.info(`Preview running at http://${previewHostname}:${activePreviewPort}`);
191
+ return `http://${previewHostname}:${activePreviewPort}`;
171
192
  }
172
193
  async createAdapter() {
173
194
  await this.initialize();
174
195
  return {
175
196
  getServerOptions: this.getServerOptions.bind(this),
176
197
  buildStatic: this.buildStatic.bind(this),
198
+ servePreviewOnly: this.servePreviewOnly.bind(this),
177
199
  completeInitialization: this.completeInitialization.bind(this),
178
200
  handleRequest: this.handleRequest.bind(this),
179
201
  attachUserWebSocketUpgrades: this.attachUserWebSocketUpgrades.bind(this),
@@ -55,6 +55,8 @@ export declare class FileSystemResponseMatcher {
55
55
  * when the page template cannot be resolved.
56
56
  */
57
57
  private renderCustomNotFoundResponse;
58
+ private renderCustomNotFoundResponseOrServerError;
59
+ private createInternalServerErrorResponse;
58
60
  private createExecutionPlan;
59
61
  /**
60
62
  * Loads the matched page module for request-time inspection.