@ecopages/core 0.2.0-beta.7 → 0.2.0-beta.8

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 (102) hide show
  1. package/package.json +26 -2
  2. package/src/adapters/abstract/application-adapter.d.ts +12 -1
  3. package/src/adapters/abstract/application-adapter.js +13 -0
  4. package/src/adapters/abstract/server-adapter.d.ts +3 -0
  5. package/src/adapters/bun/client-bridge.d.ts +1 -0
  6. package/src/adapters/bun/client-bridge.js +3 -0
  7. package/src/adapters/bun/create-app.d.ts +2 -0
  8. package/src/adapters/bun/create-app.js +28 -9
  9. package/src/adapters/bun/server-adapter.d.ts +13 -0
  10. package/src/adapters/bun/server-adapter.js +29 -4
  11. package/src/adapters/create-app.d.ts +13 -0
  12. package/src/adapters/create-app.js +10 -1
  13. package/src/adapters/node/create-app.d.ts +1 -0
  14. package/src/adapters/node/create-app.js +17 -7
  15. package/src/adapters/node/node-client-bridge.d.ts +1 -0
  16. package/src/adapters/node/node-client-bridge.js +3 -0
  17. package/src/adapters/node/server-adapter.d.ts +10 -13
  18. package/src/adapters/node/server-adapter.js +37 -82
  19. package/src/adapters/node/static-content-server.d.ts +1 -0
  20. package/src/adapters/node/static-content-server.js +3 -0
  21. package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
  22. package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
  23. package/src/adapters/shared/runtime-app-bootstrap.d.ts +0 -1
  24. package/src/adapters/shared/runtime-app-bootstrap.js +1 -6
  25. package/src/adapters/shared/server-static-builder.d.ts +4 -6
  26. package/src/adapters/shared/server-static-builder.js +90 -34
  27. package/src/adapters/shared/shared-hmr-manager.d.ts +1 -0
  28. package/src/adapters/shared/shared-hmr-manager.js +15 -2
  29. package/src/build/README.md +44 -4
  30. package/src/build/build-adapter.d.ts +8 -2
  31. package/src/build/build-adapter.js +71 -33
  32. package/src/build/build-input-fingerprint.d.ts +28 -0
  33. package/src/build/build-input-fingerprint.js +31 -0
  34. package/src/build/jsx-ownership-plugins.d.ts +10 -0
  35. package/src/build/jsx-ownership-plugins.js +17 -0
  36. package/src/build/pages-unified-graph-build.d.ts +31 -0
  37. package/src/build/pages-unified-graph-build.js +208 -0
  38. package/src/build/parallel-build-executor.d.ts +21 -0
  39. package/src/build/parallel-build-executor.js +52 -0
  40. package/src/build/rolldown-adapter-helpers.js +5 -0
  41. package/src/build/rolldown-build-adapter.js +2 -0
  42. package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
  43. package/src/build/rolldown-build-invocation-metrics.js +43 -0
  44. package/src/build/rolldown-dev-build-adapter.d.ts +2 -2
  45. package/src/build/rolldown-dev-build-adapter.js +30 -14
  46. package/src/build/runtime-build-executor.d.ts +7 -13
  47. package/src/build/runtime-build-executor.js +31 -7
  48. package/src/build/server-entry-build-cache.d.ts +44 -0
  49. package/src/build/server-entry-build-cache.js +169 -0
  50. package/src/hmr/hmr.test.e2e.js +15 -4
  51. package/src/plugins/integration-plugin.d.ts +19 -8
  52. package/src/plugins/integration-plugin.js +7 -2
  53. package/src/plugins/processor.d.ts +5 -0
  54. package/src/plugins/processor.js +7 -0
  55. package/src/route-renderer/GRAPH.md +4 -4
  56. package/src/route-renderer/README.md +6 -5
  57. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +1 -3
  58. package/src/route-renderer/orchestration/integration-renderer.d.ts +10 -2
  59. package/src/route-renderer/orchestration/integration-renderer.js +32 -12
  60. package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
  61. package/src/route-renderer/orchestration/render-output.utils.js +4 -0
  62. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +2 -2
  63. package/src/route-renderer/orchestration/route-render-orchestrator.js +17 -16
  64. package/src/router/client/navigation-coordinator.js +10 -0
  65. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
  66. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
  67. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
  68. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
  69. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
  70. package/src/services/assets/browser-bundle.service.d.ts +1 -0
  71. package/src/services/assets/browser-bundle.service.js +9 -3
  72. package/src/services/html/html-transformer.service.d.ts +1 -1
  73. package/src/services/html/html-transformer.service.js +4 -4
  74. package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
  75. package/src/services/invalidation/development-invalidation.service.js +29 -0
  76. package/src/services/module-loading/README.md +49 -0
  77. package/src/services/module-loading/app-server-module-transpiler.service.js +4 -17
  78. package/src/services/module-loading/page-module-import.service.d.ts +4 -1
  79. package/src/services/module-loading/page-module-import.service.js +52 -29
  80. package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
  81. package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
  82. package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
  83. package/src/services/module-loading/route-module-build-cache.js +38 -0
  84. package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
  85. package/src/services/module-loading/route-module-build-cache.store.js +203 -0
  86. package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
  87. package/src/services/module-loading/route-module-build-manifest.js +124 -0
  88. package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
  89. package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
  90. package/src/services/module-loading/server-module-transpiler.service.js +1 -1
  91. package/src/static-site-generator/README.md +33 -0
  92. package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
  93. package/src/static-site-generator/static-build-invalidation.js +78 -0
  94. package/src/static-site-generator/static-export-context.d.ts +22 -0
  95. package/src/static-site-generator/static-export-context.js +0 -0
  96. package/src/static-site-generator/static-site-generator.d.ts +17 -8
  97. package/src/static-site-generator/static-site-generator.js +176 -62
  98. package/src/types/internal-types.d.ts +8 -0
  99. package/src/utils/parse-cli-args.d.ts +1 -0
  100. package/src/utils/parse-cli-args.js +3 -0
  101. package/src/watchers/project-watcher.d.ts +8 -0
  102. package/src/watchers/project-watcher.js +32 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.7",
3
+ "version": "0.2.0-beta.8",
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.7",
20
+ "@ecopages/file-system": "0.2.0-beta.8",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.134.0",
@@ -45,6 +45,10 @@
45
45
  "types": "./src/route-renderer/orchestration/integration-renderer.d.ts",
46
46
  "default": "./src/route-renderer/orchestration/integration-renderer.js"
47
47
  },
48
+ "./route-renderer/route-renderer": {
49
+ "types": "./src/route-renderer/route-renderer.d.ts",
50
+ "default": "./src/route-renderer/route-renderer.js"
51
+ },
48
52
  "./route-renderer/component-render-context": {
49
53
  "import": "./src/route-renderer/orchestration/component-render-context.js",
50
54
  "types": "./src/route-renderer/orchestration/component-render-context.d.ts",
@@ -117,6 +121,14 @@
117
121
  "types": "./src/build/build-adapter.d.ts",
118
122
  "default": "./src/build/build-adapter.js"
119
123
  },
124
+ "./build/runtime-build-executor": {
125
+ "types": "./src/build/runtime-build-executor.d.ts",
126
+ "default": "./src/build/runtime-build-executor.js"
127
+ },
128
+ "./build/server-entry-build-cache": {
129
+ "types": "./src/build/server-entry-build-cache.d.ts",
130
+ "default": "./src/build/server-entry-build-cache.js"
131
+ },
120
132
  "./build/runtime-build-output-normalizer": {
121
133
  "types": "./src/build/runtime-build-output-normalizer.d.ts",
122
134
  "default": "./src/build/runtime-build-output-normalizer.js"
@@ -174,6 +186,10 @@
174
186
  "types": "./src/route-renderer/orchestration/integration-renderer.d.ts",
175
187
  "default": "./src/route-renderer/orchestration/integration-renderer.js"
176
188
  },
189
+ "./route-renderer/route-renderer.ts": {
190
+ "types": "./src/route-renderer/route-renderer.d.ts",
191
+ "default": "./src/route-renderer/route-renderer.js"
192
+ },
177
193
  "./route-renderer/component-render-context.ts": {
178
194
  "import": "./src/route-renderer/orchestration/component-render-context.js",
179
195
  "types": "./src/route-renderer/orchestration/component-render-context.d.ts",
@@ -246,6 +262,14 @@
246
262
  "types": "./src/build/build-adapter.d.ts",
247
263
  "default": "./src/build/build-adapter.js"
248
264
  },
265
+ "./build/runtime-build-executor.ts": {
266
+ "types": "./src/build/runtime-build-executor.d.ts",
267
+ "default": "./src/build/runtime-build-executor.js"
268
+ },
269
+ "./build/server-entry-build-cache.ts": {
270
+ "types": "./src/build/server-entry-build-cache.d.ts",
271
+ "default": "./src/build/server-entry-build-cache.js"
272
+ },
249
273
  "./build/runtime-build-output-normalizer.ts": {
250
274
  "types": "./src/build/runtime-build-output-normalizer.d.ts",
251
275
  "default": "./src/build/runtime-build-output-normalizer.js"
@@ -50,8 +50,9 @@ export interface ApplicationAdapterOptions {
50
50
  /**
51
51
  * Common interface for application adapters
52
52
  */
53
- export interface ApplicationAdapter<T = any> {
53
+ export interface ApplicationAdapter<T = any> extends AsyncDisposable {
54
54
  start(): Promise<T | void>;
55
+ stop(force?: boolean): Promise<void>;
55
56
  }
56
57
  /**
57
58
  * Handler function type for route handlers
@@ -231,6 +232,16 @@ export declare abstract class AbstractApplicationAdapter<TOptions extends Applic
231
232
  * Start the application server
232
233
  */
233
234
  abstract start(): Promise<TServer | void>;
235
+ /**
236
+ * Stops the application server and releases runtime resources.
237
+ *
238
+ * @remarks
239
+ * Subclasses override this to shut down bound servers, watchers, and other
240
+ * dev-time resources. The default implementation is a no-op so embedded
241
+ * adapters that never call `start()` can still be used with `await using`.
242
+ */
243
+ stop(_force?: boolean): Promise<void>;
244
+ [Symbol.asyncDispose](): Promise<void>;
234
245
  /**
235
246
  * Handles a standard Web request without requiring a bound network server.
236
247
  * This is the primary interoperability surface for embedding Ecopages inside
@@ -185,6 +185,19 @@ class AbstractApplicationAdapter {
185
185
  getErrorHandler() {
186
186
  return this.errorHandler;
187
187
  }
188
+ /**
189
+ * Stops the application server and releases runtime resources.
190
+ *
191
+ * @remarks
192
+ * Subclasses override this to shut down bound servers, watchers, and other
193
+ * dev-time resources. The default implementation is a no-op so embedded
194
+ * adapters that never call `start()` can still be used with `await using`.
195
+ */
196
+ async stop(_force = true) {
197
+ }
198
+ async [Symbol.asyncDispose]() {
199
+ await this.stop(true);
200
+ }
188
201
  }
189
202
  export {
190
203
  AbstractApplicationAdapter
@@ -31,7 +31,9 @@ export interface ServerAdapterResult {
31
31
  }) => any;
32
32
  buildStatic: (options?: {
33
33
  preview?: boolean;
34
+ force?: boolean;
34
35
  }) => Promise<void>;
36
+ dispose(): Promise<void>;
35
37
  }
36
38
  /**
37
39
  * Abstract base class for server adapters across different runtimes
@@ -57,6 +59,7 @@ export declare abstract class AbstractServerAdapter<TOptions extends ServerAdapt
57
59
  */
58
60
  abstract buildStatic(options?: {
59
61
  preview?: boolean;
62
+ force?: boolean;
60
63
  }): Promise<void>;
61
64
  /**
62
65
  * Factory method to create a server adapter with runtime-specific functionality
@@ -30,5 +30,6 @@ export declare class ClientBridge implements IClientBridge {
30
30
  */
31
31
  error(message: string): void;
32
32
  get subscriberCount(): number;
33
+ destroy(): void;
33
34
  }
34
35
  export {};
@@ -42,6 +42,9 @@ class ClientBridge {
42
42
  get subscriberCount() {
43
43
  return this.subscribers.size;
44
44
  }
45
+ destroy() {
46
+ this.subscribers.clear();
47
+ }
45
48
  }
46
49
  export {
47
50
  ClientBridge
@@ -31,6 +31,7 @@ export type BunRouteGroupBuilder<WebSocketData = undefined, TContext extends Api
31
31
  export declare class BunEcopagesApp<WebSocketData = undefined> extends SharedApplicationAdapter<EcopagesAppOptions, Server<WebSocketData>, Request> {
32
32
  serverAdapter: BunServerAdapterResult | undefined;
33
33
  private server;
34
+ private stopped;
34
35
  private readonly runtimeHost;
35
36
  private readonly previewHost;
36
37
  constructor(options: EcopagesAppOptions, dependencies: {
@@ -56,6 +57,7 @@ export declare class BunEcopagesApp<WebSocketData = undefined> extends SharedApp
56
57
  * @param options.autoCompleteInitialization Whether to automatically complete initialization with dynamic routes after server start (defaults to true)
57
58
  */
58
59
  start(): Promise<Server<WebSocketData> | void>;
60
+ stop(force?: boolean): Promise<void>;
59
61
  }
60
62
  /**
61
63
  * Factory function to create a Bun application
@@ -8,6 +8,7 @@ import { BunRuntimeHost } from "./runtime-host.js";
8
8
  class BunEcopagesApp extends SharedApplicationAdapter {
9
9
  serverAdapter;
10
10
  server = null;
11
+ stopped = false;
11
12
  runtimeHost;
12
13
  previewHost;
13
14
  constructor(options, dependencies) {
@@ -73,17 +74,21 @@ class BunEcopagesApp extends SharedApplicationAdapter {
73
74
  * @param options.autoCompleteInitialization Whether to automatically complete initialization with dynamic routes after server start (defaults to true)
74
75
  */
75
76
  async start() {
77
+ if (this.stopped) {
78
+ this.serverAdapter = void 0;
79
+ this.stopped = false;
80
+ }
76
81
  if (!this.serverAdapter) {
77
82
  this.serverAdapter = await this.initializeServerAdapter();
78
83
  }
79
- const { dev, preview, build } = this.cliArgs;
84
+ const { dev, preview, build, force } = this.cliArgs;
80
85
  const staticRuntimeMode = resolveStaticRuntimeMode({
81
86
  appConfig: this.appConfig,
82
87
  cliArgs: this.cliArgs
83
88
  });
84
89
  if (staticRuntimeMode.canBuildWithoutRuntimeServer) {
85
90
  appLogger.debugTime("Building static pages");
86
- await this.serverAdapter.buildStatic({ preview });
91
+ await this.serverAdapter.buildStatic({ preview, force });
87
92
  appLogger.debugTimeEnd("Building static pages");
88
93
  if (build) {
89
94
  process.exit(0);
@@ -94,10 +99,7 @@ class BunEcopagesApp extends SharedApplicationAdapter {
94
99
  const serverOptions = this.serverAdapter.getServerOptions({ enableHmr });
95
100
  const configuredHostname = String(serverOptions.hostname ?? DEFAULT_ECOPAGES_HOSTNAME);
96
101
  const configuredPort = Number(serverOptions.port ?? DEFAULT_ECOPAGES_PORT);
97
- const runtimeServerOptions = (preview || build) && staticRuntimeMode.requiresFetchRuntime ? {
98
- ...serverOptions,
99
- port: 0
100
- } : serverOptions;
102
+ const runtimeServerOptions = serverOptions;
101
103
  this.server = await this.runtimeHost.start({
102
104
  serveOptions: runtimeServerOptions,
103
105
  handleRequest: async () => new Response(null, { status: 500 }),
@@ -115,8 +117,10 @@ class BunEcopagesApp extends SharedApplicationAdapter {
115
117
  );
116
118
  if (build || preview) {
117
119
  appLogger.debugTime("Building static pages");
118
- await this.serverAdapter.buildStatic({ preview: false });
119
- await this.runtimeHost.stop(this.server, { force: true });
120
+ await this.serverAdapter.buildStatic({ preview: false, force });
121
+ const buildRuntimeServer = this.server;
122
+ this.server = null;
123
+ await this.runtimeHost.stop(buildRuntimeServer, { force: true });
120
124
  if (preview) {
121
125
  const previewPort = await this.previewHost.start({
122
126
  appConfig: this.appConfig,
@@ -132,7 +136,22 @@ class BunEcopagesApp extends SharedApplicationAdapter {
132
136
  process.exit(0);
133
137
  }
134
138
  }
135
- return this.server;
139
+ return this.server ?? void 0;
140
+ }
141
+ async stop(force = true) {
142
+ if (this.stopped) {
143
+ return;
144
+ }
145
+ if (this.server) {
146
+ const activeServer = this.server;
147
+ this.server = null;
148
+ await this.runtimeHost.stop(activeServer, { force });
149
+ }
150
+ await this.previewHost.stop();
151
+ if (this.serverAdapter) {
152
+ await this.serverAdapter.dispose();
153
+ }
154
+ this.stopped = true;
136
155
  }
137
156
  }
138
157
  async function createApp(options) {
@@ -47,12 +47,14 @@ export interface BunServerAdapterResult extends ServerAdapterResult {
47
47
  }) => BunServeOptions;
48
48
  buildStatic: (options?: {
49
49
  preview?: boolean;
50
+ force?: boolean;
50
51
  }) => Promise<void>;
51
52
  completeInitialization: (server?: BunServerInstance | null) => Promise<void>;
52
53
  handleRequest: (request: Request) => Promise<Response>;
53
54
  attachUserWebSocketUpgrades: (server: NodeHttpServer, options?: {
54
55
  passthroughUnmatched?: boolean;
55
56
  }) => void;
57
+ dispose: () => Promise<void>;
56
58
  }
57
59
  /**
58
60
  * Bun transport adapter that wires shared Ecopages request handling onto a live
@@ -76,6 +78,8 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
76
78
  private initializationPromise;
77
79
  private fullyInitialized;
78
80
  serverInstance: BunServerInstance | null;
81
+ private projectWatcher;
82
+ private adapterDisposed;
79
83
  private readonly previewHost;
80
84
  /**
81
85
  * Reference to the application-level WebSocket handlers map.
@@ -219,6 +223,7 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
219
223
  */
220
224
  buildStatic(options?: {
221
225
  preview?: boolean;
226
+ force?: boolean;
222
227
  }): Promise<void>;
223
228
  /**
224
229
  * Initializes the server with dynamic routes after server creation.
@@ -240,6 +245,14 @@ export declare class BunServerAdapter extends SharedServerAdapter<BunServerAdapt
240
245
  * @returns Configured adapter with server methods
241
246
  */
242
247
  createAdapter(): Promise<BunServerAdapterResult>;
248
+ /**
249
+ * Releases dev-time resources owned by the adapter.
250
+ *
251
+ * @remarks
252
+ * Safe to call multiple times. Does not stop the bound Bun server — callers
253
+ * should shut down transport through the runtime host before disposing.
254
+ */
255
+ dispose(): Promise<void>;
243
256
  /**
244
257
  * Handles HTTP requests by passing them securely to the shared core router adapter.
245
258
  *
@@ -21,6 +21,7 @@ import {
21
21
  import { attachNodeHttpWebSocketUpgrades } from "../shared/node-http-websocket-upgrades.js";
22
22
  import { createBunUserWebSocketLifecycle } from "../shared/bun-user-websocket-lifecycle.js";
23
23
  import { resolveServeRuntimeOrigin } from "../shared/runtime-app-bootstrap.js";
24
+ import { copyRuntimePublicDirIfChanged } from "../shared/copy-runtime-public-dir.js";
24
25
  import { ClientBridge } from "./client-bridge.js";
25
26
  import { HmrManager } from "./hmr-manager.js";
26
27
  import { BunStaticPreviewHost } from "./static-preview-host.js";
@@ -32,6 +33,8 @@ class BunServerAdapter extends SharedServerAdapter {
32
33
  hmrManager;
33
34
  initializationPromise = null;
34
35
  fullyInitialized = false;
36
+ projectWatcher = null;
37
+ adapterDisposed = false;
35
38
  previewHost;
36
39
  /**
37
40
  * Reference to the application-level WebSocket handlers map.
@@ -194,7 +197,9 @@ class BunServerAdapter extends SharedServerAdapter {
194
197
  async initialize() {
195
198
  installAppRuntimeBuildExecutor(this.appConfig);
196
199
  this.staticSiteGenerator = new StaticSiteGenerator({ appConfig: this.appConfig });
197
- await this.hmrManager.buildRuntime();
200
+ if (this.options?.watch) {
201
+ await this.hmrManager.buildRuntime();
202
+ }
198
203
  this.prepareRuntimePublicDir();
199
204
  const staticBuilderOptions = {
200
205
  appConfig: this.appConfig,
@@ -212,7 +217,7 @@ class BunServerAdapter extends SharedServerAdapter {
212
217
  prepareRuntimePublicDir() {
213
218
  const srcPublicDir = path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.publicDir);
214
219
  if (fileSystem.exists(srcPublicDir)) {
215
- fileSystem.copyDir(srcPublicDir, path.join(this.appConfig.rootDir, this.appConfig.distDir));
220
+ copyRuntimePublicDirIfChanged(srcPublicDir, path.join(this.appConfig.rootDir, this.appConfig.distDir));
216
221
  }
217
222
  fileSystem.ensureDir(path.join(this.appConfig.absolutePaths.distDir, RESOLVED_ASSETS_DIR));
218
223
  }
@@ -276,6 +281,7 @@ class BunServerAdapter extends SharedServerAdapter {
276
281
  hmrManager: this.hmrManager,
277
282
  bridge: this.bridge
278
283
  });
284
+ this.projectWatcher = watcher;
279
285
  await watcher.createWatcherSubscription();
280
286
  }
281
287
  /**
@@ -500,7 +506,7 @@ class BunServerAdapter extends SharedServerAdapter {
500
506
  hmrManager: this.hmrManager
501
507
  });
502
508
  }
503
- const buildRuntimeOrigin = this.serverInstance ? `http://${this.serverInstance.hostname || DEFAULT_ECOPAGES_HOSTNAME}:${this.serverInstance.port || DEFAULT_ECOPAGES_PORT}` : void 0;
509
+ const buildRuntimeOrigin = resolveServeRuntimeOrigin(this.serveOptions);
504
510
  await this.staticBuilder.build(
505
511
  { ...options, preview: false, baseUrl: buildRuntimeOrigin },
506
512
  {
@@ -575,9 +581,28 @@ class BunServerAdapter extends SharedServerAdapter {
575
581
  buildStatic: this.buildStatic.bind(this),
576
582
  completeInitialization: this.completeInitialization.bind(this),
577
583
  handleRequest: this.handleRequest.bind(this),
578
- attachUserWebSocketUpgrades: this.attachUserWebSocketUpgrades.bind(this)
584
+ attachUserWebSocketUpgrades: this.attachUserWebSocketUpgrades.bind(this),
585
+ dispose: this.dispose.bind(this)
579
586
  };
580
587
  }
588
+ /**
589
+ * Releases dev-time resources owned by the adapter.
590
+ *
591
+ * @remarks
592
+ * Safe to call multiple times. Does not stop the bound Bun server — callers
593
+ * should shut down transport through the runtime host before disposing.
594
+ */
595
+ async dispose() {
596
+ if (this.adapterDisposed) {
597
+ return;
598
+ }
599
+ this.adapterDisposed = true;
600
+ await this.projectWatcher?.close();
601
+ this.projectWatcher = null;
602
+ this.hmrManager?.stop();
603
+ this.bridge?.destroy();
604
+ await this.previewHost.stop();
605
+ }
581
606
  /**
582
607
  * Handles HTTP requests by passing them securely to the shared core router adapter.
583
608
  *
@@ -2,8 +2,20 @@ 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
4
  import { SharedApplicationAdapter } from './shared/application-adapter.js';
5
+ export type EcopagesRuntimeAdapter = 'auto' | 'node' | 'bun';
5
6
  export interface EcopagesAppOptions extends ApplicationAdapterOptions {
6
7
  appConfig: EcoPagesAppConfig;
8
+ /**
9
+ * Selects the runtime adapter used by the universal `createApp()` entrypoint.
10
+ *
11
+ * @default 'auto'
12
+ *
13
+ * @remarks
14
+ * This is separate from `runtime`, which configures embedded host behavior.
15
+ * Use `auto` to select Bun when the current process exposes `globalThis.Bun`
16
+ * and Node otherwise.
17
+ */
18
+ adapter?: EcopagesRuntimeAdapter;
7
19
  serverOptions?: Record<string, any>;
8
20
  }
9
21
  export type UniversalEcopagesApp = AbstractApplicationAdapter<EcopagesAppOptions, unknown, Request>;
@@ -17,4 +29,5 @@ export declare class EcopagesApp extends SharedApplicationAdapter<EcopagesAppOpt
17
29
  protected initializeServerAdapter(): Promise<UniversalEcopagesApp>;
18
30
  start(): Promise<unknown>;
19
31
  fetch(request: Request): Promise<Response>;
32
+ stop(force?: boolean): Promise<void>;
20
33
  }
@@ -3,8 +3,9 @@ import { SharedApplicationAdapter } from "./shared/application-adapter.js";
3
3
  import { createApp as createBunApp } from "./bun/create-app.js";
4
4
  import { createNodeApp } from "./node/create-app.js";
5
5
  async function createRuntimeApp(options) {
6
+ const adapter = options.adapter ?? "auto";
6
7
  const bun = globalThis.Bun;
7
- if (bun) {
8
+ if (adapter === "bun" || adapter === "auto" && bun) {
8
9
  return await createBunApp(options);
9
10
  }
10
11
  return await createNodeApp(options);
@@ -59,6 +60,14 @@ class EcopagesApp extends SharedApplicationAdapter {
59
60
  }
60
61
  return candidate.fetch(request);
61
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
+ }
62
71
  }
63
72
  export {
64
73
  EcopagesApp,
@@ -7,6 +7,7 @@ export declare class NodeEcopagesApp extends SharedApplicationAdapter<EcopagesAp
7
7
  serverAdapter: NodeServerAdapterResult | undefined;
8
8
  private server;
9
9
  private runtimeOrigin;
10
+ private stopped;
10
11
  private readonly runtimeHost;
11
12
  constructor(options: EcopagesAppOptions, dependencies: {
12
13
  runtimeHost: RuntimeHost<NodeServerInstance, {
@@ -8,6 +8,7 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
8
8
  serverAdapter;
9
9
  server = null;
10
10
  runtimeOrigin = "";
11
+ stopped = false;
11
12
  runtimeHost;
12
13
  constructor(options, dependencies) {
13
14
  super(options);
@@ -17,12 +18,18 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
17
18
  return createNodeServerAdapter(params);
18
19
  }
19
20
  async stop(force = true) {
20
- if (!this.server) {
21
+ if (this.stopped) {
21
22
  return;
22
23
  }
23
- const activeServer = this.server;
24
- this.server = null;
25
- await this.runtimeHost.stop(activeServer, { force });
24
+ if (this.server) {
25
+ const activeServer = this.server;
26
+ this.server = null;
27
+ await this.runtimeHost.stop(activeServer, { force });
28
+ }
29
+ if (this.serverAdapter) {
30
+ await this.serverAdapter.dispose();
31
+ }
32
+ this.stopped = true;
26
33
  }
27
34
  async initializeServerAdapter() {
28
35
  const binding = resolveRuntimeBinding({
@@ -42,17 +49,20 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
42
49
  });
43
50
  }
44
51
  async start() {
52
+ if (this.stopped) {
53
+ this.serverAdapter = void 0;
54
+ this.stopped = false;
55
+ }
45
56
  if (!this.serverAdapter) {
46
57
  this.serverAdapter = await this.initializeServerAdapter();
47
58
  }
48
59
  if (this.server) {
49
60
  return this.server;
50
61
  }
51
- const { build, preview } = this.cliArgs;
62
+ const { build, preview, force } = this.cliArgs;
52
63
  if (build || preview) {
53
64
  appLogger.debugTime("Building static pages");
54
- await this.serverAdapter.buildStatic({ preview });
55
- await this.stop(true);
65
+ await this.serverAdapter.buildStatic({ preview, force });
56
66
  appLogger.debugTimeEnd("Building static pages");
57
67
  if (build) {
58
68
  process.exit(0);
@@ -23,4 +23,5 @@ export declare class NodeClientBridge implements IClientBridge {
23
23
  * Call this when the dev server is shutting down.
24
24
  */
25
25
  destroy(): void;
26
+ [Symbol.dispose](): void;
26
27
  }
@@ -60,6 +60,9 @@ class NodeClientBridge {
60
60
  }
61
61
  this.subscribers.clear();
62
62
  }
63
+ [Symbol.dispose]() {
64
+ this.destroy();
65
+ }
63
66
  }
64
67
  export {
65
68
  NodeClientBridge
@@ -33,6 +33,7 @@ export interface NodeServerAdapterResult extends ServerAdapterResult {
33
33
  attachUserWebSocketUpgrades: (server: NodeServerInstance, options?: {
34
34
  passthroughUnmatched?: boolean;
35
35
  }) => void;
36
+ dispose: () => Promise<void>;
36
37
  }
37
38
  /**
38
39
  * Node.js HTTP server adapter for the Ecopages runtime.
@@ -62,6 +63,8 @@ export declare class NodeServerAdapter extends SharedServerAdapter<NodeServerAda
62
63
  private errorHandler?;
63
64
  private bridge;
64
65
  private hmrManager;
66
+ private projectWatcher;
67
+ private adapterDisposed;
65
68
  private readonly previewHost;
66
69
  private readonly requestBridge;
67
70
  private readonly devRuntimeFactory;
@@ -106,23 +109,17 @@ export declare class NodeServerAdapter extends SharedServerAdapter<NodeServerAda
106
109
  getServerOptions(): NodeServeAdapterServerOptions;
107
110
  buildStatic(options?: {
108
111
  preview?: boolean;
112
+ force?: boolean;
109
113
  }): Promise<void>;
110
- private startBuildRuntimeServer;
111
- private getListeningServerOrigin;
114
+ createAdapter(): Promise<NodeServerAdapterResult>;
112
115
  /**
113
- * Gracefully shuts down the ephemeral build runtime server.
114
- *
115
- * `closeAllConnections()` is called *before* `close()` because `server.close()`
116
- * only stops accepting new connections — it waits for existing keep-alive
117
- * connections to finish naturally, which can stall the build indefinitely.
118
- * `closeAllConnections()` force-closes any lingering sockets immediately so
119
- * the `close()` callback fires promptly.
116
+ * Releases dev-time resources owned by the adapter.
120
117
  *
121
- * The `NodeClientBridge` heartbeat is also destroyed here so its `setInterval`
122
- * does not prevent the Node.js process from exiting cleanly after the build.
118
+ * @remarks
119
+ * Safe to call multiple times. Does not stop the bound HTTP server — callers
120
+ * should shut down transport through the runtime host before disposing.
123
121
  */
124
- private stopBuildRuntimeServer;
125
- createAdapter(): Promise<NodeServerAdapterResult>;
122
+ dispose(): Promise<void>;
126
123
  /**
127
124
  * Handles a single incoming Web `Request` and returns a Web `Response`.
128
125
  *