@ecopages/core 0.2.0-alpha.39 → 0.2.0-alpha.40

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 (56) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/bun/create-app.d.ts +8 -1
  3. package/src/adapters/bun/create-app.js +52 -65
  4. package/src/adapters/bun/hmr-manager.d.ts +19 -103
  5. package/src/adapters/bun/hmr-manager.js +26 -280
  6. package/src/adapters/bun/runtime-host.d.ts +52 -0
  7. package/src/adapters/bun/runtime-host.js +56 -0
  8. package/src/adapters/bun/server-adapter.d.ts +89 -28
  9. package/src/adapters/bun/server-adapter.js +113 -61
  10. package/src/adapters/bun/static-preview-host.d.ts +28 -0
  11. package/src/adapters/bun/static-preview-host.js +45 -0
  12. package/src/adapters/node/create-app.d.ts +9 -3
  13. package/src/adapters/node/create-app.js +24 -81
  14. package/src/adapters/node/http-request-bridge.d.ts +57 -0
  15. package/src/adapters/node/http-request-bridge.js +118 -0
  16. package/src/adapters/node/node-hmr-manager.d.ts +22 -91
  17. package/src/adapters/node/node-hmr-manager.js +26 -272
  18. package/src/adapters/node/runtime-host.d.ts +57 -0
  19. package/src/adapters/node/runtime-host.js +92 -0
  20. package/src/adapters/node/server-adapter-dependencies.d.ts +19 -0
  21. package/src/adapters/node/server-adapter-dependencies.js +18 -0
  22. package/src/adapters/node/server-adapter.d.ts +10 -37
  23. package/src/adapters/node/server-adapter.js +55 -125
  24. package/src/adapters/node/static-preview-host.d.ts +55 -0
  25. package/src/adapters/node/static-preview-host.js +68 -0
  26. package/src/adapters/shared/runtime-app-bootstrap.d.ts +26 -0
  27. package/src/adapters/shared/runtime-app-bootstrap.js +46 -0
  28. package/src/adapters/shared/runtime-host.d.ts +12 -0
  29. package/src/adapters/shared/runtime-host.js +0 -0
  30. package/src/adapters/shared/shared-hmr-manager.d.ts +59 -0
  31. package/src/adapters/shared/shared-hmr-manager.js +239 -0
  32. package/src/adapters/shared/static-preview-host.d.ts +10 -0
  33. package/src/adapters/shared/static-preview-host.js +0 -0
  34. package/src/build/build-adapter.js +12 -1
  35. package/src/build/esbuild-build-adapter.d.ts +1 -0
  36. package/src/build/esbuild-build-adapter.js +13 -0
  37. package/src/hmr/strategies/js-hmr-strategy.js +0 -4
  38. package/src/plugins/integration-plugin.d.ts +6 -1
  39. package/src/route-renderer/orchestration/integration-renderer.d.ts +32 -14
  40. package/src/route-renderer/orchestration/integration-renderer.js +80 -14
  41. package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +1 -0
  42. package/src/route-renderer/orchestration/processed-asset-dedupe.js +15 -11
  43. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +22 -8
  44. package/src/route-renderer/orchestration/route-render-orchestrator.js +59 -10
  45. package/src/services/assets/asset-processing-service/page-package.d.ts +4 -1
  46. package/src/services/assets/asset-processing-service/page-package.js +11 -5
  47. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +3 -1
  48. package/src/services/html/html-rewriter-provider.service.d.ts +3 -0
  49. package/src/services/html/html-transformer.service.d.ts +10 -1
  50. package/src/services/html/html-transformer.service.js +80 -9
  51. package/src/services/module-loading/page-module-import.service.js +2 -2
  52. package/src/types/public-types.d.ts +24 -7
  53. package/src/adapters/bun/server-lifecycle.d.ts +0 -63
  54. package/src/adapters/bun/server-lifecycle.js +0 -92
  55. package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
  56. package/src/adapters/shared/runtime-bootstrap.js +0 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-alpha.39",
3
+ "version": "0.2.0-alpha.40",
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-alpha.39",
20
+ "@ecopages/file-system": "0.2.0-alpha.40",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@worker-tools/html-rewriter": "0.1.0-pre.19",
@@ -10,6 +10,8 @@
10
10
  import type { Server } from 'bun';
11
11
  import type { ApiHandlerContext, RouteGroupBuilder } from '../../types/public-types.js';
12
12
  import { SharedApplicationAdapter } from '../shared/application-adapter.js';
13
+ import type { RuntimeHost } from '../shared/runtime-host.js';
14
+ import type { StaticPreviewHost } from '../shared/static-preview-host.js';
13
15
  import type { EcopagesAppOptions } from '../create-app.js';
14
16
  import { type BunServerAdapterResult } from './server-adapter.js';
15
17
  /**
@@ -29,7 +31,12 @@ export type BunRouteGroupBuilder<WebSocketData = undefined, TContext extends Api
29
31
  export declare class BunEcopagesApp<WebSocketData = undefined> extends SharedApplicationAdapter<EcopagesAppOptions, Server<WebSocketData>, Request> {
30
32
  serverAdapter: BunServerAdapterResult | undefined;
31
33
  private server;
32
- private startStaticPreviewServer;
34
+ private readonly runtimeHost;
35
+ private readonly previewHost;
36
+ constructor(options: EcopagesAppOptions, dependencies: {
37
+ runtimeHost: RuntimeHost<Server<WebSocketData>, Bun.Serve.Options<WebSocketData>>;
38
+ previewHost: StaticPreviewHost;
39
+ });
33
40
  fetch(request: Request): Promise<Response>;
34
41
  /**
35
42
  * Complete the initialization of the server adapter by processing dynamic routes
@@ -1,36 +1,19 @@
1
1
  import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
2
- import { StaticContentServer } from "../../dev/sc-server.js";
3
2
  import { appLogger } from "../../global/app-logger.js";
4
- import { getBunRuntime } from "../../utils/runtime.js";
5
3
  import { SharedApplicationAdapter } from "../shared/application-adapter.js";
4
+ import { resolveRuntimeBinding, resolveStaticRuntimeMode } from "../shared/runtime-app-bootstrap.js";
6
5
  import { createBunServerAdapter } from "./server-adapter.js";
6
+ import { BunStaticPreviewHost } from "./static-preview-host.js";
7
+ import { BunRuntimeHost } from "./runtime-host.js";
7
8
  class BunEcopagesApp extends SharedApplicationAdapter {
8
9
  serverAdapter;
9
10
  server = null;
10
- async startStaticPreviewServer(port, hostname) {
11
- await new Promise((resolve) => setTimeout(resolve, 100));
12
- for (let attempt = 0; attempt < 20; attempt += 1) {
13
- try {
14
- const previewServer = StaticContentServer.createServer({
15
- appConfig: this.appConfig,
16
- options: { port }
17
- });
18
- if (previewServer.server?.port) {
19
- appLogger.info(`Preview running at http://${hostname}:${previewServer.server.port}`);
20
- return;
21
- }
22
- break;
23
- } catch (error) {
24
- const errorMessage = error instanceof Error ? error.message : String(error);
25
- const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
26
- const isPortReleaseRace = errorCode === "EADDRINUSE" || errorMessage.includes("EADDRINUSE");
27
- if (!isPortReleaseRace || attempt === 19) {
28
- throw error;
29
- }
30
- await new Promise((resolve) => setTimeout(resolve, 100));
31
- }
32
- }
33
- appLogger.error("Failed to start preview server");
11
+ runtimeHost;
12
+ previewHost;
13
+ constructor(options, dependencies) {
14
+ super(options);
15
+ this.runtimeHost = dependencies.runtimeHost;
16
+ this.previewHost = dependencies.previewHost;
34
17
  }
35
18
  async fetch(request) {
36
19
  if (!this.serverAdapter) {
@@ -53,34 +36,28 @@ class BunEcopagesApp extends SharedApplicationAdapter {
53
36
  * Initialize the Bun server adapter
54
37
  */
55
38
  async initializeServerAdapter() {
56
- const { dev } = this.cliArgs;
57
- const { port: cliPort, hostname: cliHostname } = this.cliArgs;
58
- const envPort = process.env.ECOPAGES_PORT ? process.env.ECOPAGES_PORT : void 0;
59
- const envHostname = process.env.ECOPAGES_HOSTNAME;
60
- const preferredPort = cliPort ?? envPort ?? DEFAULT_ECOPAGES_PORT;
61
- const preferredHostname = cliHostname ?? envHostname ?? DEFAULT_ECOPAGES_HOSTNAME;
39
+ const binding = resolveRuntimeBinding({
40
+ cliArgs: this.cliArgs,
41
+ serverOptions: this.serverOptions
42
+ });
62
43
  appLogger.debug("initializeServerAdapter", {
63
- dev,
64
- cliPort,
65
- cliHostname,
66
- envPort,
67
- envHostname,
68
- preferredPort,
69
- preferredHostname,
70
- composedUrl: `http://${preferredHostname}:${preferredPort}`
44
+ dev: this.cliArgs.dev,
45
+ cliPort: this.cliArgs.port,
46
+ cliHostname: this.cliArgs.hostname,
47
+ envPort: process.env.ECOPAGES_PORT,
48
+ envHostname: process.env.ECOPAGES_HOSTNAME,
49
+ preferredPort: binding.preferredPort,
50
+ preferredHostname: binding.preferredHostname,
51
+ composedUrl: binding.runtimeOrigin
71
52
  });
72
53
  return await createBunServerAdapter({
73
- runtimeOrigin: `http://${preferredHostname}:${preferredPort}`,
54
+ runtimeOrigin: binding.runtimeOrigin,
74
55
  appConfig: this.appConfig,
75
56
  apiHandlers: this.apiHandlers,
76
57
  staticRoutes: this.staticRoutes,
77
58
  errorHandler: this.errorHandler,
78
- options: { watch: dev },
79
- serveOptions: {
80
- port: preferredPort,
81
- hostname: preferredHostname,
82
- ...this.serverOptions
83
- }
59
+ options: { watch: binding.watch },
60
+ serveOptions: binding.serveOptions
84
61
  });
85
62
  }
86
63
  /**
@@ -93,11 +70,11 @@ class BunEcopagesApp extends SharedApplicationAdapter {
93
70
  this.serverAdapter = await this.initializeServerAdapter();
94
71
  }
95
72
  const { dev, preview, build } = this.cliArgs;
96
- const requiresFetchRuntime = this.appConfig.integrations.some(
97
- (integration) => integration.staticBuildStep === "fetch"
98
- );
99
- const canBuildWithoutRuntimeServer = (build || preview) && !requiresFetchRuntime;
100
- if (canBuildWithoutRuntimeServer) {
73
+ const staticRuntimeMode = resolveStaticRuntimeMode({
74
+ appConfig: this.appConfig,
75
+ cliArgs: this.cliArgs
76
+ });
77
+ if (staticRuntimeMode.canBuildWithoutRuntimeServer) {
101
78
  appLogger.debugTime("Building static pages");
102
79
  await this.serverAdapter.buildStatic({ preview });
103
80
  appLogger.debugTimeEnd("Building static pages");
@@ -110,31 +87,38 @@ class BunEcopagesApp extends SharedApplicationAdapter {
110
87
  const serverOptions = this.serverAdapter.getServerOptions({ enableHmr });
111
88
  const configuredHostname = String(serverOptions.hostname ?? DEFAULT_ECOPAGES_HOSTNAME);
112
89
  const configuredPort = Number(serverOptions.port ?? DEFAULT_ECOPAGES_PORT);
113
- const runtimeServerOptions = (preview || build) && requiresFetchRuntime ? {
90
+ const runtimeServerOptions = (preview || build) && staticRuntimeMode.requiresFetchRuntime ? {
114
91
  ...serverOptions,
115
92
  port: 0
116
93
  } : serverOptions;
117
- const bun = getBunRuntime();
118
- if (!bun) {
119
- throw new Error("Bun runtime is required for the Bun adapter");
120
- }
121
- const bunServer = bun.serve(runtimeServerOptions);
122
- this.server = bunServer;
94
+ this.server = await this.runtimeHost.start({
95
+ serveOptions: runtimeServerOptions,
96
+ handleRequest: async () => new Response(null, { status: 500 }),
97
+ onError: async () => {
98
+ }
99
+ });
123
100
  await this.serverAdapter.completeInitialization(this.server).catch((error) => {
124
101
  appLogger.error(`Failed to complete initialization: ${error}`);
125
102
  });
126
103
  if (!this.server) {
127
104
  throw new Error("Server failed to start");
128
105
  }
129
- appLogger.info(`Server running at http://${this.server.hostname}:${this.server.port}`);
106
+ appLogger.info(
107
+ `Server running at ${this.runtimeHost.getOrigin(this.server, runtimeServerOptions)}`
108
+ );
130
109
  if (build || preview) {
131
110
  appLogger.debugTime("Building static pages");
132
111
  await this.serverAdapter.buildStatic({ preview: false });
133
- this.server.stop(true);
112
+ await this.runtimeHost.stop(this.server, { force: true });
134
113
  if (preview) {
135
- const previewHostname = configuredHostname;
136
- const previewPort = configuredPort;
137
- await this.startStaticPreviewServer(previewPort, previewHostname);
114
+ const previewPort = await this.previewHost.start({
115
+ appConfig: this.appConfig,
116
+ hostname: configuredHostname,
117
+ port: configuredPort
118
+ });
119
+ if (previewPort) {
120
+ appLogger.info(`Preview running at http://${configuredHostname}:${previewPort}`);
121
+ }
138
122
  }
139
123
  appLogger.debugTimeEnd("Building static pages");
140
124
  if (build) {
@@ -145,7 +129,10 @@ class BunEcopagesApp extends SharedApplicationAdapter {
145
129
  }
146
130
  }
147
131
  async function createApp(options) {
148
- return new BunEcopagesApp(options);
132
+ return new BunEcopagesApp(options, {
133
+ runtimeHost: new BunRuntimeHost(),
134
+ previewHost: new BunStaticPreviewHost()
135
+ });
149
136
  }
150
137
  export {
151
138
  BunEcopagesApp,
@@ -1,17 +1,13 @@
1
1
  import type { WebSocketHandler } from 'bun';
2
- import type { DefaultHmrContext, EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
3
- import type { EcoBuildPlugin } from '../../build/build-types.js';
4
- import { type HmrStrategy } from '../../hmr/hmr-strategy.js';
2
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
5
3
  import type { ClientBridge } from './client-bridge.js';
6
- import type { ClientBridgeEvent } from '../../types/public-types.js';
4
+ import { InMemoryEntrypointDependencyGraph, type EntrypointDependencyGraph } from '../../services/runtime-state/entrypoint-dependency-graph.service.js';
5
+ import { SharedHmrManager } from '../shared/shared-hmr-manager.js';
7
6
  type BunSocketHandler = WebSocketHandler<unknown>;
8
7
  export interface HmrManagerParams {
9
8
  appConfig: EcoPagesAppConfig;
10
9
  bridge: ClientBridge;
11
10
  }
12
- type HandleFileChangeOptions = {
13
- broadcast?: boolean;
14
- };
15
11
  /**
16
12
  * Bun development HMR manager.
17
13
  *
@@ -20,112 +16,32 @@ type HandleFileChangeOptions = {
20
16
  * strict integration-owned registrations, while generic script assets use their
21
17
  * own explicit registration path.
22
18
  */
23
- export declare class HmrManager implements IHmrManager {
24
- private static readonly entrypointRegistrationTimeoutMs;
25
- readonly appConfig: EcoPagesAppConfig;
26
- private readonly bridge;
27
- /** Keep track of watchers */
28
- private watchers;
29
- /** entrypoint -> output path */
30
- private watchedFiles;
31
- private entrypointRegistrations;
32
- private distDir;
33
- private plugins;
34
- private enabled;
35
- private strategies;
36
- private readonly entrypointRegistrar;
37
- private readonly browserBundleService;
38
- private readonly entrypointDependencyGraph;
39
- private readonly serverModuleTranspiler;
19
+ export declare class HmrManager extends SharedHmrManager {
40
20
  private wsHandler;
41
- constructor({ appConfig, bridge }: HmrManagerParams);
42
- /**
43
- * Ensures the HMR output directory exists.
44
- *
45
- * This must not remove the directory because multiple app processes
46
- * can share the same dist path during e2e runs.
47
- */
48
- private cleanDistDir;
49
21
  /**
50
- * Returns whether the generic JS strategy may rebuild an entrypoint.
22
+ * Creates the Bun HMR manager around the shared HMR orchestration pipeline.
51
23
  *
52
24
  * @remarks
53
- * Integration-owned page entrypoints are excluded so a shared dependency
54
- * invalidation cannot replace framework-owned browser output with a generic JS
55
- * rebuild.
56
- */
57
- private shouldJsStrategyProcessEntrypoint;
58
- /**
59
- * Initializes core HMR strategies.
60
- * Strategies are evaluated in priority order (highest first).
61
- */
62
- private initializeStrategies;
63
- /**
64
- * Registers a custom HMR strategy.
65
- * Used by integrations to provide framework-specific HMR handling.
66
- * @param strategy - The HMR strategy to register
67
- */
68
- registerStrategy(strategy: HmrStrategy): void;
69
- setPlugins(plugins: EcoBuildPlugin[]): void;
70
- setEnabled(enabled: boolean): void;
71
- isEnabled(): boolean;
72
- getWebSocketHandler(): BunSocketHandler;
73
- /**
74
- * Builds the client-side HMR runtime script.
25
+ * Bun delegates route watching, rebuild dispatch, and runtime bundle
26
+ * generation to `SharedHmrManager`. The Bun subclass only supplies the
27
+ * transport-specific dependency graph policy and websocket hook surface.
75
28
  */
76
- buildRuntime(): Promise<void>;
77
- getRuntimePath(): string;
78
- broadcast(event: ClientBridgeEvent): void;
79
- handleFileChange(filePath: string, options?: HandleFileChangeOptions): Promise<void>;
80
- getOutputUrl(entrypointPath: string): string | undefined;
81
- getWatchedFiles(): Map<string, string>;
82
- getDistDir(): string;
83
- getPlugins(): EcoBuildPlugin[];
84
- getDefaultContext(): DefaultHmrContext;
85
- private clearFailedEntrypointRegistration;
86
- /**
87
- * Registers one integration-owned page entrypoint.
88
- *
89
- * @remarks
90
- * Concurrent callers share one in-flight registration. The registration is
91
- * cleared from the dedupe map when it settles so later callers cannot inherit a
92
- * stale promise.
93
- */
94
- registerEntrypoint(entrypointPath: string): Promise<string>;
95
- /**
96
- * Registers one generic script entrypoint.
97
- *
98
- * @remarks
99
- * This explicit path keeps the page-entrypoint contract strict while still
100
- * allowing generic script assets to use the fallback build path.
101
- */
102
- registerScriptEntrypoint(entrypointPath: string): Promise<string>;
103
- /**
104
- * Performs strict integration-owned registration for one normalized path.
105
- *
106
- * @remarks
107
- * The manager reserves the output URL, removes any stale emitted file, runs
108
- * strategy processing without broadcasting, and then verifies that the owning
109
- * integration emitted the expected file.
110
- */
111
- private emitStrictEntrypoint;
29
+ constructor({ appConfig, bridge }: HmrManagerParams);
112
30
  /**
113
- * Performs registration for a generic script asset.
114
- *
115
- * @remarks
116
- * This path performs a targeted browser bundle for the requested script
117
- * entrypoint only. The resulting dependency graph is retained so later file
118
- * changes can invalidate just the affected script entrypoints.
31
+ * Reuses the shared in-memory dependency graph when possible and otherwise
32
+ * creates the Bun-compatible default graph implementation.
119
33
  */
120
- private emitScriptEntrypoint;
34
+ protected createEntrypointDependencyGraph(existingEntrypointDependencyGraph: EntrypointDependencyGraph): InMemoryEntrypointDependencyGraph;
121
35
  /**
122
- * Stops active watchers and releases retained registration state.
36
+ * Returns the Bun websocket hooks that attach and detach live HMR subscribers.
123
37
  *
124
38
  * @remarks
125
- * Emitted `_hmr` files remain on disk because parallel app processes may share
126
- * the same dist directory. The in-memory indexes are cleared so stale
127
- * entrypoints cannot leak through a reused manager object.
39
+ * `SharedHmrManager` stores the bridge behind the transport-agnostic
40
+ * `IClientBridge` contract because most HMR coordination only needs broadcast
41
+ * behavior. Bun connection lifecycle wiring is the point where that abstraction
42
+ * intentionally narrows back to the concrete Bun bridge so websocket instances
43
+ * can be tracked directly.
128
44
  */
129
- stop(): void;
45
+ getWebSocketHandler(): BunSocketHandler;
130
46
  }
131
47
  export {};