@ecopages/core 0.2.0-beta.12 → 0.2.0-beta.14

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 +26 -2
  3. package/src/adapters/abstract/application-adapter.d.ts +27 -3
  4. package/src/adapters/abstract/application-adapter.js +28 -0
  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 +23 -6
  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 +14 -4
  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/server-ready-message.d.ts +6 -0
  21. package/src/dev/server-ready-message.js +8 -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.12",
3
+ "version": "0.2.0-beta.14",
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.12",
20
+ "@ecopages/file-system": "0.2.0-beta.14",
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
  },
@@ -113,6 +121,10 @@
113
121
  "types": "./src/dev/dev-client-ownership.d.ts",
114
122
  "default": "./src/dev/dev-client-ownership.js"
115
123
  },
124
+ "./dev/server-ready-message": {
125
+ "types": "./src/dev/server-ready-message.d.ts",
126
+ "default": "./src/dev/server-ready-message.js"
127
+ },
116
128
  "./services/asset-processing-service": {
117
129
  "types": "./src/services/assets/asset-processing-service/index.d.ts",
118
130
  "default": "./src/services/assets/asset-processing-service/index.js"
@@ -243,6 +255,10 @@
243
255
  "types": "./src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts",
244
256
  "default": "./src/route-renderer/orchestration/foreign-subtree-execution.service.js"
245
257
  },
258
+ "./route-renderer/orchestration/document-shell-render.service.ts": {
259
+ "types": "./src/route-renderer/orchestration/document-shell-render.service.d.ts",
260
+ "default": "./src/route-renderer/orchestration/document-shell-render.service.js"
261
+ },
246
262
  "./router/navigation-coordinator.ts": {
247
263
  "types": "./src/router/client/navigation-coordinator.d.ts",
248
264
  "default": "./src/router/client/navigation-coordinator.js"
@@ -256,6 +272,10 @@
256
272
  "types": "./src/eco/eco.d.ts",
257
273
  "default": "./src/eco/eco.js"
258
274
  },
275
+ "./eco/page-layout-normalization.ts": {
276
+ "types": "./src/eco/page-layout-normalization.d.ts",
277
+ "default": "./src/eco/page-layout-normalization.js"
278
+ },
259
279
  "./declarations.ts": {
260
280
  "types": "./src/declarations.d.ts"
261
281
  },
@@ -294,6 +314,10 @@
294
314
  "types": "./src/dev/dev-client-ownership.d.ts",
295
315
  "default": "./src/dev/dev-client-ownership.js"
296
316
  },
317
+ "./dev/server-ready-message.ts": {
318
+ "types": "./src/dev/server-ready-message.d.ts",
319
+ "default": "./src/dev/server-ready-message.js"
320
+ },
297
321
  "./services/asset-processing-service.ts": {
298
322
  "types": "./src/services/assets/asset-processing-service/index.d.ts",
299
323
  "default": "./src/services/assets/asset-processing-service/index.js"
@@ -49,6 +49,18 @@ export interface ApplicationRuntimeOptions {
49
49
  */
50
50
  hostModuleLoader?: SourceModuleLoader;
51
51
  }
52
+ export interface AppStartInfo {
53
+ origin: string;
54
+ }
55
+ export type OnAppStartCallback = (info: AppStartInfo) => void;
56
+ /** @deprecated Use {@link AppStartInfo}. */
57
+ export type ApplicationListeningInfo = AppStartInfo;
58
+ /** @deprecated Use {@link OnAppStartCallback}. */
59
+ export type StartCallback = OnAppStartCallback;
60
+ /** @deprecated Use {@link OnAppStartCallback}. */
61
+ export type ListenCallback = OnAppStartCallback;
62
+ /** @deprecated Use {@link OnAppStartCallback}. */
63
+ export type ApplicationListeningCallback = OnAppStartCallback;
52
64
  /**
53
65
  * Configuration options for application adapters
54
66
  */
@@ -66,7 +78,10 @@ export interface ApplicationAdapterOptions {
66
78
  * Common interface for application adapters
67
79
  */
68
80
  export interface ApplicationAdapter<T = any> extends AsyncDisposable {
69
- start(): Promise<T | void>;
81
+ /** Boot the server. Pass a callback to run when the runtime is ready (optional). */
82
+ start(onAppStart?: OnAppStartCallback): Promise<T | void>;
83
+ /** Invoked by embedded hosts once the app can take traffic. */
84
+ handleListening(origin: string): void;
70
85
  stop(force?: boolean): Promise<void>;
71
86
  }
72
87
  /**
@@ -94,6 +109,7 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
94
109
  * Both Bun and Node adapters read this map to register upgrade routes.
95
110
  */
96
111
  protected websocketHandlers: Map<string, EcopagesWebSocketHandler<any, any>>;
112
+ private onAppStartCallback?;
97
113
  constructor(options: TOptions);
98
114
  private clearDistFolder;
99
115
  /**
@@ -244,9 +260,17 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
244
260
  */
245
261
  protected abstract initializeServerAdapter(): Promise<any>;
246
262
  /**
247
- * Start the application server
263
+ * Boot the server. When `onAppStart` is passed, it runs once the runtime can take traffic.
264
+ * Embedded apps only register the callback — the host (for example Vite) boots the port.
265
+ */
266
+ start(onAppStart?: OnAppStartCallback): Promise<TServer | void>;
267
+ /** Runtime-specific server boot (dev, preview, build). */
268
+ protected abstract bootServer(): Promise<TServer | void>;
269
+ /**
270
+ * Invoked by embedded hosts (for example Vite) once the app can take traffic.
248
271
  */
249
- abstract start(): Promise<TServer | void>;
272
+ handleListening(origin: string): void;
273
+ protected notifyListening(origin: string): void;
250
274
  /**
251
275
  * Stops the application server and releases runtime resources.
252
276
  *
@@ -6,6 +6,7 @@ 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 { formatServerReadyMessage } from "../../dev/server-ready-message.js";
9
10
  import { parseCliArgs } from "../../utils/parse-cli-args.js";
10
11
  class AbstractApplicationAdapter {
11
12
  appConfig;
@@ -20,6 +21,7 @@ class AbstractApplicationAdapter {
20
21
  * Both Bun and Node adapters read this map to register upgrade routes.
21
22
  */
22
23
  websocketHandlers = /* @__PURE__ */ new Map();
24
+ onAppStartCallback;
23
25
  constructor(options) {
24
26
  this.appConfig = options.appConfig;
25
27
  this.serverOptions = options.serverOptions || {};
@@ -191,6 +193,32 @@ class AbstractApplicationAdapter {
191
193
  getErrorHandler() {
192
194
  return this.errorHandler;
193
195
  }
196
+ /**
197
+ * Boot the server. When `onAppStart` is passed, it runs once the runtime can take traffic.
198
+ * Embedded apps only register the callback — the host (for example Vite) boots the port.
199
+ */
200
+ async start(onAppStart) {
201
+ if (onAppStart) {
202
+ this.onAppStartCallback = onAppStart;
203
+ }
204
+ if (this.runtimeOptions.embedded) {
205
+ return;
206
+ }
207
+ return this.bootServer();
208
+ }
209
+ /**
210
+ * Invoked by embedded hosts (for example Vite) once the app can take traffic.
211
+ */
212
+ handleListening(origin) {
213
+ this.notifyListening(origin);
214
+ }
215
+ notifyListening(origin) {
216
+ const normalizedOrigin = origin.replace(/\/$/, "");
217
+ if (!this.onAppStartCallback) {
218
+ console.log(formatServerReadyMessage(normalizedOrigin));
219
+ }
220
+ this.onAppStartCallback?.({ origin: normalizedOrigin });
221
+ }
194
222
  /**
195
223
  * Stops the application server and releases runtime resources.
196
224
  *
@@ -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
  /**
@@ -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;
@@ -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.