@ecopages/core 0.2.0-beta.0 → 0.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.0",
3
+ "version": "0.2.0-beta.2",
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.0",
20
+ "@ecopages/file-system": "0.2.0-beta.2",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.134.0",
@@ -152,6 +152,11 @@
152
152
  "default": "./src/utils/hash.js",
153
153
  "types": "./src/utils/hash.d.ts"
154
154
  },
155
+ "./utils/resolve-entry-file": {
156
+ "import": "./src/utils/resolve-entry-file.js",
157
+ "types": "./src/utils/resolve-entry-file.d.ts",
158
+ "default": "./src/utils/resolve-entry-file.js"
159
+ },
155
160
  "./errors": {
156
161
  "types": "./src/errors/index.d.ts",
157
162
  "default": "./src/errors/index.js"
@@ -276,6 +281,11 @@
276
281
  "default": "./src/utils/hash.js",
277
282
  "types": "./src/utils/hash.d.ts"
278
283
  },
284
+ "./utils/resolve-entry-file.ts": {
285
+ "import": "./src/utils/resolve-entry-file.js",
286
+ "types": "./src/utils/resolve-entry-file.d.ts",
287
+ "default": "./src/utils/resolve-entry-file.js"
288
+ },
279
289
  "./errors.ts": {
280
290
  "types": "./src/errors/index.d.ts",
281
291
  "default": "./src/errors/index.js"
@@ -18,6 +18,7 @@ export interface ServerStaticBuilderParams {
18
18
  apiHandlers?: ApiHandler[];
19
19
  logger?: ServerStaticBuilderLogger;
20
20
  previewServerFactory?: ServerStaticPreviewServerFactory;
21
+ entryFile?: string;
21
22
  }
22
23
  /**
23
24
  * Minimal logger dependency used by the static builder.
@@ -52,10 +53,32 @@ export declare class ServerStaticBuilder {
52
53
  private readonly apiHandlers;
53
54
  private readonly logger;
54
55
  private readonly previewServerFactory;
55
- constructor({ appConfig, staticSiteGenerator, serveOptions, apiHandlers, logger, previewServerFactory, }: ServerStaticBuilderParams);
56
+ private readonly entryFile;
57
+ constructor({ appConfig, staticSiteGenerator, serveOptions, apiHandlers, logger, previewServerFactory, entryFile, }: ServerStaticBuilderParams);
56
58
  private warnApiHandlersUnavailableInStaticMode;
57
59
  private prepareExportDirectory;
58
60
  private refreshRuntimeAssets;
61
+ /**
62
+ * Bundles the server entry file for production use.
63
+ *
64
+ * @remarks
65
+ * When the project has API endpoints, the entry file must be bundled
66
+ * into a single JS file so the production server can start without
67
+ * on-the-fly TypeScript transpilation. The bundle is output to
68
+ * `dist/{SERVER_BUNDLE_DIR}/{SERVER_BUNDLE_FILENAME}` and used by
69
+ * `ecopages start` in production.
70
+ *
71
+ * Package imports remain external so native addons and runtime-owned
72
+ * dependencies continue to load through the app's installed
73
+ * `node_modules` tree. Only the app entry graph is bundled.
74
+ *
75
+ * Skips silently when no API endpoints are registered (static-only site).
76
+ * Throws if the build adapter is unavailable, is owned by a host
77
+ * runtime, or bundling fails.
78
+ *
79
+ * @throws If the build adapter is unavailable, is host-owned, or bundling fails.
80
+ */
81
+ private bundleServerEntry;
59
82
  /**
60
83
  * Generates a static build of the site for deployment.
61
84
  * @param options.preview - If true, starts a preview server after build
@@ -3,6 +3,8 @@ import { fileSystem } from "@ecopages/file-system";
3
3
  import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
4
4
  import { StaticContentServer } from "../../dev/sc-server.js";
5
5
  import { appLogger } from "../../global/app-logger.js";
6
+ import { getAppBuildAdapter } from "../../build/build-adapter.js";
7
+ import { resolveEntryFile, SERVER_BUNDLE_DIR, SERVER_BUNDLE_FILENAME } from "../../utils/resolve-entry-file.js";
6
8
  class ServerStaticBuilder {
7
9
  appConfig;
8
10
  staticSiteGenerator;
@@ -10,13 +12,15 @@ class ServerStaticBuilder {
10
12
  apiHandlers;
11
13
  logger;
12
14
  previewServerFactory;
15
+ entryFile;
13
16
  constructor({
14
17
  appConfig,
15
18
  staticSiteGenerator,
16
19
  serveOptions,
17
20
  apiHandlers,
18
21
  logger,
19
- previewServerFactory
22
+ previewServerFactory,
23
+ entryFile
20
24
  }) {
21
25
  this.appConfig = appConfig;
22
26
  this.staticSiteGenerator = staticSiteGenerator;
@@ -24,6 +28,7 @@ class ServerStaticBuilder {
24
28
  this.apiHandlers = apiHandlers ?? [];
25
29
  this.logger = logger ?? appLogger;
26
30
  this.previewServerFactory = previewServerFactory ?? StaticContentServer;
31
+ this.entryFile = resolveEntryFile({ entryFile });
27
32
  }
28
33
  warnApiHandlersUnavailableInStaticMode() {
29
34
  if (this.apiHandlers.length === 0) {
@@ -60,6 +65,62 @@ class ServerStaticBuilder {
60
65
  await integration.setup();
61
66
  }
62
67
  }
68
+ /**
69
+ * Bundles the server entry file for production use.
70
+ *
71
+ * @remarks
72
+ * When the project has API endpoints, the entry file must be bundled
73
+ * into a single JS file so the production server can start without
74
+ * on-the-fly TypeScript transpilation. The bundle is output to
75
+ * `dist/{SERVER_BUNDLE_DIR}/{SERVER_BUNDLE_FILENAME}` and used by
76
+ * `ecopages start` in production.
77
+ *
78
+ * Package imports remain external so native addons and runtime-owned
79
+ * dependencies continue to load through the app's installed
80
+ * `node_modules` tree. Only the app entry graph is bundled.
81
+ *
82
+ * Skips silently when no API endpoints are registered (static-only site).
83
+ * Throws if the build adapter is unavailable, is owned by a host
84
+ * runtime, or bundling fails.
85
+ *
86
+ * @throws If the build adapter is unavailable, is host-owned, or bundling fails.
87
+ */
88
+ async bundleServerEntry() {
89
+ if (this.apiHandlers.length === 0) {
90
+ return;
91
+ }
92
+ const buildAdapter = getAppBuildAdapter(this.appConfig);
93
+ if (buildAdapter.ownership === "vite-host") {
94
+ throw new Error(
95
+ 'Cannot bundle the server entry file: build ownership is "vite-host". The host runtime is expected to produce its own server bundle.'
96
+ );
97
+ }
98
+ const entryPath = path.isAbsolute(this.entryFile) ? this.entryFile : path.join(this.appConfig.rootDir, this.entryFile);
99
+ if (!fileSystem.exists(entryPath)) {
100
+ throw new Error(
101
+ `Cannot bundle server entry: file "${this.entryFile}" not found in "${this.appConfig.rootDir}".`
102
+ );
103
+ }
104
+ const distDir = this.appConfig.absolutePaths?.distDir ?? path.join(this.appConfig.rootDir, this.appConfig.distDir);
105
+ const serverOutdir = path.join(distDir, SERVER_BUNDLE_DIR);
106
+ this.logger.info("Bundling server entry file...");
107
+ const result = await buildAdapter.build({
108
+ entrypoints: [entryPath],
109
+ outdir: serverOutdir,
110
+ naming: SERVER_BUNDLE_FILENAME,
111
+ target: "node",
112
+ format: "esm",
113
+ sourcemap: "hidden",
114
+ externalPackages: true,
115
+ root: this.appConfig.rootDir
116
+ });
117
+ if (!result.success) {
118
+ const errorMessages = result.logs.map((log) => log.message).join("\n");
119
+ throw new Error(`Failed to bundle server entry file:
120
+ ${errorMessages}`);
121
+ }
122
+ this.logger.info("Server entry file bundled successfully");
123
+ }
63
124
  /**
64
125
  * Generates a static build of the site for deployment.
65
126
  * @param options.preview - If true, starts a preview server after build
@@ -73,6 +134,7 @@ class ServerStaticBuilder {
73
134
  this.warnApiHandlersUnavailableInStaticMode();
74
135
  this.prepareExportDirectory();
75
136
  await this.refreshRuntimeAssets();
137
+ await this.bundleServerEntry();
76
138
  await this.staticSiteGenerator.run({
77
139
  router: dependencies.router,
78
140
  baseUrl,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The default entry file name when no other source provides a value.
3
+ */
4
+ export declare const DEFAULT_ENTRY_FILE = "app.ts";
5
+ /**
6
+ * The environment variable used to override the entry file path.
7
+ */
8
+ export declare const ENTRY_FILE_ENV = "ECOPAGES_ENTRY_FILE";
9
+ /**
10
+ * Centralized definition of the entry-file CLI argument.
11
+ *
12
+ * Exported so callers (CLI, tests, docs) reference a single source of truth
13
+ * for the flag name, short alias, and type. Keep this in sync with
14
+ * `resolveEntryFile` below.
15
+ */
16
+ export declare const entryFileOptions: {
17
+ readonly 'entry-file': {
18
+ readonly type: "string";
19
+ readonly short: "e";
20
+ };
21
+ };
22
+ /**
23
+ * The long flag name for the entry file option. Re-exported as a
24
+ * constant so consumers don't hardcode the string.
25
+ */
26
+ export declare const ENTRY_FILE_FLAG = "entry-file";
27
+ /**
28
+ * Resolved sources for the entry file, in precedence order (highest first):
29
+ *
30
+ * 1. `entryFile` argument — explicit caller override (e.g. constructor)
31
+ * 2. `ECOPAGES_ENTRY_FILE` env var — set by the CLI when spawning
32
+ * 3. `--entry-file` / `-e` CLI flag — read from `process.argv` via `parseArgs`
33
+ * 4. `app.ts` — the framework default
34
+ */
35
+ export interface ResolveEntryFileOptions {
36
+ /** Explicit override (e.g. constructor param or test fixture). */
37
+ entryFile?: string;
38
+ /** Environment to read `ECOPAGES_ENTRY_FILE` from. Defaults to `process.env`. */
39
+ env?: Record<string, string | undefined>;
40
+ /** Argv to parse. Defaults to `process.argv`. */
41
+ argv?: string[];
42
+ }
43
+ /**
44
+ * Resolves the entry file path using standard precedence.
45
+ *
46
+ * This is the single source of truth for entry-file resolution across
47
+ * the CLI, build pipeline, and runtime. Both the CLI layer and the
48
+ * runtime builders call into this function instead of reimplementing
49
+ * the precedence chain.
50
+ *
51
+ * @example
52
+ * resolveEntryFile(); // 'app.ts' (or env/argv)
53
+ * resolveEntryFile({ entryFile: 'src/server.ts' }); // 'src/server.ts'
54
+ * resolveEntryFile({ env: { ECOPAGES_ENTRY_FILE: 'x' }}); // 'x'
55
+ * resolveEntryFile({ argv: ['node', '-e', 'flag.ts'] }); // 'flag.ts'
56
+ */
57
+ export declare function resolveEntryFile({ entryFile, env, argv, }?: ResolveEntryFileOptions): string;
58
+ /**
59
+ * Parses argv for the entry-file flag using the shared `parseArgs` config.
60
+ *
61
+ * Exposed for callers that need the raw flag value without the full
62
+ * precedence chain (e.g. CLI help text, validation).
63
+ */
64
+ export declare function readEntryFileFlag(argv?: string[]): string | undefined;
65
+ /**
66
+ * The subdirectory inside `dist/` where the bundled server entry is placed.
67
+ */
68
+ export declare const SERVER_BUNDLE_DIR = ".server";
69
+ /**
70
+ * The filename of the bundled server entry.
71
+ */
72
+ export declare const SERVER_BUNDLE_FILENAME = "app.mjs";
@@ -0,0 +1,41 @@
1
+ import { parseArgs } from "node:util";
2
+ import { getRuntimeArgv } from "./runtime.js";
3
+ const DEFAULT_ENTRY_FILE = "app.ts";
4
+ const ENTRY_FILE_ENV = "ECOPAGES_ENTRY_FILE";
5
+ const entryFileOptions = {
6
+ "entry-file": { type: "string", short: "e" }
7
+ };
8
+ const ENTRY_FILE_FLAG = "entry-file";
9
+ function resolveEntryFile({
10
+ entryFile,
11
+ env = process.env,
12
+ argv = getRuntimeArgv()
13
+ } = {}) {
14
+ if (entryFile && entryFile.length > 0) return entryFile;
15
+ const fromEnv = env[ENTRY_FILE_ENV];
16
+ if (fromEnv && fromEnv.length > 0) return fromEnv;
17
+ const fromFlag = readEntryFileFlag(argv);
18
+ if (fromFlag) return fromFlag;
19
+ return DEFAULT_ENTRY_FILE;
20
+ }
21
+ function readEntryFileFlag(argv = getRuntimeArgv()) {
22
+ const { values } = parseArgs({
23
+ args: argv.slice(1),
24
+ options: { ...entryFileOptions },
25
+ strict: false
26
+ });
27
+ const raw = values[ENTRY_FILE_FLAG];
28
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
29
+ }
30
+ const SERVER_BUNDLE_DIR = ".server";
31
+ const SERVER_BUNDLE_FILENAME = "app.mjs";
32
+ export {
33
+ DEFAULT_ENTRY_FILE,
34
+ ENTRY_FILE_ENV,
35
+ ENTRY_FILE_FLAG,
36
+ SERVER_BUNDLE_DIR,
37
+ SERVER_BUNDLE_FILENAME,
38
+ entryFileOptions,
39
+ readEntryFileFlag,
40
+ resolveEntryFile
41
+ };