ecopages 0.2.0-alpha.9 → 0.2.0-beta.1
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/README.md +22 -31
- package/bin/cli.js +330 -164
- package/bin/launch-plan.js +124 -134
- package/bin/node-require-preload.js +3 -0
- package/package.json +8 -11
- package/bin/launch-plan.test.ts +0 -316
- package/bin/node-thin-host.js +0 -138
- package/bin/node-thin-host.test.ts +0 -167
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The official CLI for the Ecopages framework.
|
|
4
4
|
|
|
5
|
-
It provides scaffolding and development commands to streamline your workflow. It
|
|
5
|
+
It provides scaffolding and development commands to streamline your workflow. It prefers Bun when available, falls back to Node otherwise, and applies runtime-specific launch behavior for each engine.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -17,15 +17,15 @@ bun dev
|
|
|
17
17
|
|
|
18
18
|
## Commands
|
|
19
19
|
|
|
20
|
-
| Command | Description | Equivalent (Bun) |
|
|
21
|
-
| :--------------------------- | :----------------------------------------- | :------------------------------ |
|
|
22
|
-
| `ecopages init <dir>` | Scaffolds a new project | N/A |
|
|
23
|
-
| `ecopages dev [entry]` | Starts the dev server | `bun run [entry] --dev` |
|
|
24
|
-
| `ecopages dev:watch [entry]` | Dev server + hard restarts on file changes | `bun --watch run [entry] --dev` |
|
|
25
|
-
| `ecopages dev:hot [entry]` | Dev server + HMR (no hard restarts) | `bun --hot run [entry] --dev` |
|
|
26
|
-
| `ecopages build [entry]` | Creates a production build | `bun run [entry] --build` |
|
|
27
|
-
| `ecopages start [entry]` | Starts the production server | `bun run [entry]` |
|
|
28
|
-
| `ecopages preview [entry]` | Previews the production build locally | `bun run [entry] --preview` |
|
|
20
|
+
| Command | Description | Equivalent (Bun) |
|
|
21
|
+
| :--------------------------- | :----------------------------------------- | :------------------------------ |
|
|
22
|
+
| `ecopages init <dir>` | Scaffolds a new project | N/A |
|
|
23
|
+
| `ecopages dev [entry]` | Starts the dev server | `bun run [entry] --dev` |
|
|
24
|
+
| `ecopages dev:watch [entry]` | Dev server + hard restarts on file changes | `bun --watch run [entry] --dev` |
|
|
25
|
+
| `ecopages dev:hot [entry]` | Dev server + HMR (no hard restarts) | `bun --hot run [entry] --dev` |
|
|
26
|
+
| `ecopages build [entry]` | Creates a production build | `bun run [entry] --build` |
|
|
27
|
+
| `ecopages start [entry]` | Starts the production server | `bun run [entry]` |
|
|
28
|
+
| `ecopages preview [entry]` | Previews the production build locally | `bun run [entry] --preview` |
|
|
29
29
|
|
|
30
30
|
> [!NOTE]
|
|
31
31
|
> `[entry]` defaults to `app.ts` if not provided.
|
|
@@ -34,27 +34,23 @@ bun dev
|
|
|
34
34
|
|
|
35
35
|
Server and build commands accept the following options. They automatically map to the equivalent environment variables for the underlying process:
|
|
36
36
|
|
|
37
|
-
| Option | Env Var | Description
|
|
38
|
-
| :------------------------- | :---------------------- |
|
|
39
|
-
| `-p, --port <port>` | `ECOPAGES_PORT` | Server port (default 3000)
|
|
40
|
-
| `-n, --hostname <host>` | `ECOPAGES_HOSTNAME` | Server hostname
|
|
41
|
-
| `-b, --base-url <url>` | `ECOPAGES_BASE_URL` | Base URL string
|
|
42
|
-
| `-d, --debug` | `ECOPAGES_LOGGER_DEBUG` | Enables debug-level logging
|
|
43
|
-
| `-r, --react-fast-refresh` | | Enables React Fast Refresh
|
|
44
|
-
| `--runtime <runtime>` | | Force execution via `bun
|
|
37
|
+
| Option | Env Var | Description |
|
|
38
|
+
| :------------------------- | :---------------------- | :---------------------------------- |
|
|
39
|
+
| `-p, --port <port>` | `ECOPAGES_PORT` | Server port (default 3000) |
|
|
40
|
+
| `-n, --hostname <host>` | `ECOPAGES_HOSTNAME` | Server hostname |
|
|
41
|
+
| `-b, --base-url <url>` | `ECOPAGES_BASE_URL` | Base URL string |
|
|
42
|
+
| `-d, --debug` | `ECOPAGES_LOGGER_DEBUG` | Enables debug-level logging |
|
|
43
|
+
| `-r, --react-fast-refresh` | | Enables React Fast Refresh |
|
|
44
|
+
| `--runtime <runtime>` | | Force execution via `bun` or `node` |
|
|
45
45
|
|
|
46
46
|
### Runtime Detection
|
|
47
47
|
|
|
48
|
-
The CLI
|
|
49
|
-
|
|
50
|
-
Both `node` and `node-experimental` now launch through the thin-host boundary and hand off the same serialized Node runtime manifest. `node-experimental` remains available as an explicit verification alias while the unified Node host path continues to settle.
|
|
48
|
+
The CLI prefers Bun when the package manager already indicates Bun, when the `Bun` global is available, or when you force it with `--runtime bun`. Otherwise it falls back to Node.
|
|
51
49
|
|
|
52
50
|
You can explicitly force the engine using the `--runtime` flag:
|
|
53
51
|
|
|
54
52
|
```bash
|
|
55
|
-
ecopages dev --runtime node
|
|
56
53
|
ecopages build --runtime bun
|
|
57
|
-
ecopages dev --runtime node-experimental
|
|
58
54
|
```
|
|
59
55
|
|
|
60
56
|
### Example Usage
|
|
@@ -69,13 +65,7 @@ ecopages dev -r
|
|
|
69
65
|
|
|
70
66
|
## Ecosystem & Plugins
|
|
71
67
|
|
|
72
|
-
Ecopages relies on a modular architecture. Core logic and framework integrations are published
|
|
73
|
-
|
|
74
|
-
Configure your project to use JSR by adding a `.npmrc` file:
|
|
75
|
-
|
|
76
|
-
```ini
|
|
77
|
-
@jsr:registry=https://npm.jsr.io
|
|
78
|
-
```
|
|
68
|
+
Ecopages relies on a modular architecture. Core logic and framework integrations are published as `@ecopages/*` packages on [npm](https://www.npmjs.com/org/ecopages).
|
|
79
69
|
|
|
80
70
|
### Official Packages
|
|
81
71
|
|
|
@@ -84,6 +74,7 @@ Configure your project to use JSR by adding a `.npmrc` file:
|
|
|
84
74
|
| `@ecopages/browser-router` | Client-side navigation & view transitions. |
|
|
85
75
|
| `@ecopages/codemod` | AST migrations for codebase upgrades. |
|
|
86
76
|
| `@ecopages/core` | The foundational SSG engine. |
|
|
77
|
+
| `@ecopages/ecopages-jsx` | Ecopages-owned JSX routes and hydration. |
|
|
87
78
|
| `@ecopages/file-system` | Runtime-agnostic file system utilities. |
|
|
88
79
|
| `@ecopages/image-processor` | Asset pipeline for responsive images. |
|
|
89
80
|
| `@ecopages/kitajs` | Integration for KitaJS. |
|
|
@@ -93,7 +84,7 @@ Configure your project to use JSR by adding a `.npmrc` file:
|
|
|
93
84
|
| `@ecopages/react` | Integration for React 19 SSR/Islands. |
|
|
94
85
|
| `@ecopages/react-router` | SPA routing for React. |
|
|
95
86
|
|
|
96
|
-
Explore all packages at [
|
|
87
|
+
Explore all packages at [npmjs.com/org/ecopages](https://www.npmjs.com/org/ecopages).
|
|
97
88
|
|
|
98
89
|
## License
|
|
99
90
|
|
package/bin/cli.js
CHANGED
|
@@ -1,169 +1,335 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
2
|
+
import { downloadTemplate } from "giget";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { Logger } from "@ecopages/logger";
|
|
8
|
+
import { createLaunchPlan } from "./launch-plan.js";
|
|
9
|
+
const logger = new Logger("[ecopages:cli]", { debug: process.env.ECOPAGES_LOGGER_DEBUG === "true" });
|
|
10
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
11
|
+
const sharedServerOptionDefinitions = {
|
|
12
|
+
port: {
|
|
13
|
+
type: "string",
|
|
14
|
+
short: "p"
|
|
15
|
+
},
|
|
16
|
+
hostname: {
|
|
17
|
+
type: "string",
|
|
18
|
+
short: "n"
|
|
19
|
+
},
|
|
20
|
+
"base-url": {
|
|
21
|
+
type: "string",
|
|
22
|
+
short: "b"
|
|
23
|
+
},
|
|
24
|
+
debug: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
short: "d"
|
|
27
|
+
},
|
|
28
|
+
"react-fast-refresh": {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
short: "r"
|
|
31
|
+
},
|
|
32
|
+
runtime: {
|
|
33
|
+
type: "string"
|
|
34
|
+
},
|
|
35
|
+
"entry-file": {
|
|
36
|
+
type: "string",
|
|
37
|
+
short: "e"
|
|
38
|
+
},
|
|
39
|
+
help: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
short: "h"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const initOptionDefinitions = {
|
|
45
|
+
template: {
|
|
46
|
+
type: "string"
|
|
47
|
+
},
|
|
48
|
+
repo: {
|
|
49
|
+
type: "string"
|
|
50
|
+
},
|
|
51
|
+
help: {
|
|
52
|
+
type: "boolean",
|
|
53
|
+
short: "h"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function getMainHelpText() {
|
|
57
|
+
return [
|
|
58
|
+
`ecopages ${pkg.version}`,
|
|
59
|
+
"",
|
|
60
|
+
"Usage: ecopages <command> [options]",
|
|
61
|
+
"",
|
|
62
|
+
"Commands:",
|
|
63
|
+
" init <dir> Initialize a new project from a template",
|
|
64
|
+
" dev Start the development server",
|
|
65
|
+
" dev:watch Start the development server with watch mode",
|
|
66
|
+
" dev:hot Start the development server with hot reload",
|
|
67
|
+
" build Build the project for production",
|
|
68
|
+
" start Start the production server",
|
|
69
|
+
" preview Preview the production build",
|
|
70
|
+
"",
|
|
71
|
+
"Global options:",
|
|
72
|
+
" -e, --entry-file <file> Entry file (default: app.ts)",
|
|
73
|
+
" -h, --help Show help",
|
|
74
|
+
" --version Show version"
|
|
75
|
+
].join("\n");
|
|
76
|
+
}
|
|
77
|
+
function getServerCommandHelpText(commandName, description) {
|
|
78
|
+
return [
|
|
79
|
+
`Usage: ecopages ${commandName} [options]`,
|
|
80
|
+
"",
|
|
81
|
+
description,
|
|
82
|
+
"",
|
|
83
|
+
"Options:",
|
|
84
|
+
" -p, --port <port> Override ECOPAGES_PORT",
|
|
85
|
+
" -n, --hostname <hostname> Override ECOPAGES_HOSTNAME",
|
|
86
|
+
" -b, --base-url <baseUrl> Override ECOPAGES_BASE_URL",
|
|
87
|
+
" -d, --debug Enable debug logging",
|
|
88
|
+
" -r, --react-fast-refresh Enable React Fast Refresh for Bun HMR",
|
|
89
|
+
" --runtime <runtime> Force bun or node",
|
|
90
|
+
" -e, --entry-file <file> Entry file (default: app.ts)",
|
|
91
|
+
" -h, --help Show help"
|
|
92
|
+
].join("\n");
|
|
93
|
+
}
|
|
94
|
+
function getBuildCommandHelpText() {
|
|
95
|
+
return [
|
|
96
|
+
"Usage: ecopages build [options]",
|
|
97
|
+
"",
|
|
98
|
+
"Build the project for production.",
|
|
99
|
+
"",
|
|
100
|
+
"Options:",
|
|
101
|
+
" -p, --port <port> Override ECOPAGES_PORT",
|
|
102
|
+
" -n, --hostname <hostname> Override ECOPAGES_HOSTNAME",
|
|
103
|
+
" -b, --base-url <baseUrl> Override ECOPAGES_BASE_URL",
|
|
104
|
+
" -d, --debug Enable debug logging",
|
|
105
|
+
" -r, --react-fast-refresh Enable React Fast Refresh for Bun HMR",
|
|
106
|
+
" --runtime <runtime> Force bun or node",
|
|
107
|
+
" -e, --entry-file <file> Entry file (default: app.ts)",
|
|
108
|
+
" -h, --help Show help"
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
function getInitCommandHelpText() {
|
|
112
|
+
return [
|
|
113
|
+
"Usage: ecopages init <dir> [options]",
|
|
114
|
+
"",
|
|
115
|
+
"Initialize a new project from a template.",
|
|
116
|
+
"",
|
|
117
|
+
"Options:",
|
|
118
|
+
" --template <template> Template name from ecopages/examples/",
|
|
119
|
+
" --repo <repo> GitHub repo in user/repo form",
|
|
120
|
+
" -h, --help Show help"
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
function parseCommandArguments(rawArgs, options) {
|
|
124
|
+
return parseArgs({
|
|
125
|
+
args: rawArgs,
|
|
126
|
+
options,
|
|
127
|
+
allowPositionals: true,
|
|
128
|
+
strict: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function parseServerCommandArgs(rawArgs, commandName, description, mode = "server") {
|
|
132
|
+
const { values, positionals } = parseCommandArguments(rawArgs, sharedServerOptionDefinitions);
|
|
133
|
+
if (values.help) {
|
|
134
|
+
console.log(mode === "build" ? getBuildCommandHelpText() : getServerCommandHelpText(commandName, description));
|
|
135
|
+
return { help: true };
|
|
136
|
+
}
|
|
137
|
+
if (positionals.length > 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Positional entry file arguments are not supported for \`${commandName}\`. Use --entry-file <file> instead.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const entry = values["entry-file"] ?? "app.ts";
|
|
143
|
+
return {
|
|
144
|
+
entry,
|
|
145
|
+
options: {
|
|
146
|
+
port: values.port,
|
|
147
|
+
hostname: values.hostname,
|
|
148
|
+
baseUrl: values["base-url"],
|
|
149
|
+
debug: values.debug,
|
|
150
|
+
reactFastRefresh: values["react-fast-refresh"],
|
|
151
|
+
runtime: values.runtime
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function parseInitCommandArgs(rawArgs) {
|
|
156
|
+
const { values, positionals } = parseCommandArguments(rawArgs, initOptionDefinitions);
|
|
157
|
+
if (values.help) {
|
|
158
|
+
console.log(getInitCommandHelpText());
|
|
159
|
+
return { help: true };
|
|
160
|
+
}
|
|
161
|
+
if (positionals.length !== 1) {
|
|
162
|
+
throw new Error("The `init` command requires exactly one target directory argument.");
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
dir: positionals[0],
|
|
166
|
+
template: values.template ?? "starter-jsx",
|
|
167
|
+
repo: values.repo ?? "ecopages/ecopages"
|
|
168
|
+
};
|
|
51
169
|
}
|
|
52
|
-
|
|
53
|
-
program
|
|
54
|
-
.command('init <dir>')
|
|
55
|
-
.description('Initialize a new project from a template')
|
|
56
|
-
.option('--template <name>', 'Template name from ecopages/examples/', 'starter-jsx')
|
|
57
|
-
.option('--repo <repo>', 'GitHub repo (user/repo)', 'ecopages/ecopages')
|
|
58
|
-
.action(handleInit);
|
|
59
|
-
|
|
60
170
|
function runLaunchPlan(launchPlan) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
171
|
+
if (Object.keys(launchPlan.envOverrides).length > 0) {
|
|
172
|
+
logger.debug(`Environment overrides: ${JSON.stringify(launchPlan.envOverrides)}`);
|
|
173
|
+
}
|
|
174
|
+
logger.debug(`Runtime: ${launchPlan.runtime}`);
|
|
175
|
+
logger.debug(`Running: ${launchPlan.command} ${launchPlan.commandArgs.join(" ")}`);
|
|
176
|
+
const child = spawn(launchPlan.command, launchPlan.commandArgs, {
|
|
177
|
+
stdio: "inherit",
|
|
178
|
+
env: launchPlan.env
|
|
179
|
+
});
|
|
180
|
+
child.on("error", (error) => {
|
|
181
|
+
if (error && error.code === "ENOENT") {
|
|
182
|
+
const hint = launchPlan.runtime === "bun" ? "Install Bun from https://bun.sh to continue." : "Reinstall Node.js or run with --runtime bun if this app requires Bun.";
|
|
183
|
+
logger.error(`Command not found: ${launchPlan.command}. ${hint}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
logger.error(`Failed to run command: ${error.message}`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
});
|
|
189
|
+
child.on("exit", (code) => {
|
|
190
|
+
process.exit(code || 0);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function runEntryCommand(args, options = {}, entryFile = "app.ts", launchMode = "start") {
|
|
194
|
+
let launchPlan;
|
|
195
|
+
const requiresBuiltBundle = launchMode === "start";
|
|
196
|
+
if (!requiresBuiltBundle && !existsSync(entryFile)) {
|
|
197
|
+
logger.error(`Error: Entry file "${entryFile}" not found in the current directory.`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
launchPlan = await createLaunchPlan(args, options, entryFile, launchMode);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
logger.error(message);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
runLaunchPlan(launchPlan);
|
|
208
|
+
}
|
|
209
|
+
async function runInitCommand(rawArgs) {
|
|
210
|
+
const parsed = parseInitCommandArgs(rawArgs);
|
|
211
|
+
if (parsed.help) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const { dir, template, repo } = parsed;
|
|
215
|
+
if (existsSync(dir)) {
|
|
216
|
+
logger.error(`Target directory already exists: ${dir}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
logger.info(`Creating target directory '${dir}'...`);
|
|
220
|
+
try {
|
|
221
|
+
await downloadTemplate(`github:${repo}/examples/${template}`, {
|
|
222
|
+
dir,
|
|
223
|
+
force: true
|
|
224
|
+
});
|
|
225
|
+
const pkgPath = join(dir, "package.json");
|
|
226
|
+
if (existsSync(pkgPath)) {
|
|
227
|
+
const projectPkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
228
|
+
projectPkg.name = dir;
|
|
229
|
+
writeFileSync(pkgPath, JSON.stringify(projectPkg, null, 2) + "\n");
|
|
230
|
+
logger.info(`Renamed project to '${dir}'`);
|
|
231
|
+
}
|
|
232
|
+
logger.info("Project initialized! Run `bun install && bun dev` to start.");
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
235
|
+
logger.error(`Failed to fetch template: ${message}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function runServerCommand(rawArgs, definition) {
|
|
240
|
+
const parsed = parseServerCommandArgs(rawArgs, definition.name, definition.description, definition.mode);
|
|
241
|
+
if (parsed.help) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await runEntryCommand(
|
|
245
|
+
definition.entryArgs,
|
|
246
|
+
{ ...parsed.options, ...definition.optionOverrides, entryFile: parsed.entry },
|
|
247
|
+
parsed.entry,
|
|
248
|
+
definition.launchMode ?? definition.name
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
async function runCli(rawArgs = process.argv.slice(2)) {
|
|
252
|
+
const [commandName, ...commandArgs] = rawArgs;
|
|
253
|
+
if (!commandName || commandName === "--help" || commandName === "-h") {
|
|
254
|
+
console.log(getMainHelpText());
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (commandName === "--version") {
|
|
258
|
+
console.log(pkg.version);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
switch (commandName) {
|
|
263
|
+
case "init":
|
|
264
|
+
await runInitCommand(commandArgs);
|
|
265
|
+
return;
|
|
266
|
+
case "dev":
|
|
267
|
+
await runServerCommand(commandArgs, {
|
|
268
|
+
name: "dev",
|
|
269
|
+
description: "Start the development server.",
|
|
270
|
+
entryArgs: ["--dev"],
|
|
271
|
+
launchMode: "dev",
|
|
272
|
+
optionOverrides: { nodeEnv: "development" }
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
case "dev:watch":
|
|
276
|
+
await runServerCommand(commandArgs, {
|
|
277
|
+
name: "dev:watch",
|
|
278
|
+
description: "Start the development server with watch mode.",
|
|
279
|
+
entryArgs: ["--dev"],
|
|
280
|
+
launchMode: "dev",
|
|
281
|
+
optionOverrides: { watch: true, nodeEnv: "development" }
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
case "dev:hot":
|
|
285
|
+
await runServerCommand(commandArgs, {
|
|
286
|
+
name: "dev:hot",
|
|
287
|
+
description: "Start the development server with hot reload.",
|
|
288
|
+
entryArgs: ["--dev"],
|
|
289
|
+
launchMode: "dev",
|
|
290
|
+
optionOverrides: { hot: true, nodeEnv: "development" }
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
case "build":
|
|
294
|
+
await runServerCommand(commandArgs, {
|
|
295
|
+
name: "build",
|
|
296
|
+
description: "Build the project for production.",
|
|
297
|
+
entryArgs: ["--build"],
|
|
298
|
+
launchMode: "build",
|
|
299
|
+
optionOverrides: { nodeEnv: "production" },
|
|
300
|
+
mode: "build"
|
|
301
|
+
});
|
|
302
|
+
return;
|
|
303
|
+
case "start":
|
|
304
|
+
await runServerCommand(commandArgs, {
|
|
305
|
+
name: "start",
|
|
306
|
+
description: "Start the production server.",
|
|
307
|
+
entryArgs: [],
|
|
308
|
+
launchMode: "start",
|
|
309
|
+
optionOverrides: { nodeEnv: "production" }
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
case "preview":
|
|
313
|
+
await runServerCommand(commandArgs, {
|
|
314
|
+
name: "preview",
|
|
315
|
+
description: "Preview the production build.",
|
|
316
|
+
entryArgs: ["--preview"],
|
|
317
|
+
launchMode: "preview",
|
|
318
|
+
optionOverrides: { nodeEnv: "production" }
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
default:
|
|
322
|
+
throw new Error(`Unknown command \`${commandName}\`.`);
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
326
|
+
logger.error(message);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
90
329
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
* Execute a bun command with the given arguments and options.
|
|
94
|
-
* Automatically detects eco.config.ts and applies preloads.
|
|
95
|
-
* @param {string[]} args - Arguments to pass to the entry file
|
|
96
|
-
* @param {object} options - CLI options (watch, hot, port, hostname, etc.)
|
|
97
|
-
* @param {string} entryFile - Entry file to run
|
|
98
|
-
*/
|
|
99
|
-
async function runBunCommand(args, options = {}, entryFile = 'app.ts') {
|
|
100
|
-
const launchPlan = await createLaunchPlan(args, options, entryFile);
|
|
101
|
-
|
|
102
|
-
if (launchPlanRequiresExistingEntryFile(launchPlan) && !existsSync(entryFile)) {
|
|
103
|
-
logger.error(`Error: Entry file "${entryFile}" not found in the current directory.`);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
runLaunchPlan(launchPlan);
|
|
330
|
+
if (!process.env.VITEST) {
|
|
331
|
+
runCli();
|
|
108
332
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @param {import('commander').Command} cmd - The command to add options to
|
|
113
|
-
* @returns {import('commander').Command} The command with options added
|
|
114
|
-
*/
|
|
115
|
-
const serverOptions = (cmd) =>
|
|
116
|
-
cmd
|
|
117
|
-
.option('-p, --port <port>', 'Override ECOPAGES_PORT')
|
|
118
|
-
.option('-n, --hostname <hostname>', 'Override ECOPAGES_HOSTNAME')
|
|
119
|
-
.option('-b, --base-url <url>', 'Override ECOPAGES_BASE_URL')
|
|
120
|
-
.option('-d, --debug', 'Enable debug logging (ECOPAGES_LOGGER_DEBUG=true)')
|
|
121
|
-
.option('-r, --react-fast-refresh', 'Enable React Fast Refresh for HMR')
|
|
122
|
-
.option('--runtime <runtime>', 'Force a specific runtime (bun, node, or node-experimental)');
|
|
123
|
-
|
|
124
|
-
serverOptions(
|
|
125
|
-
program.command('dev').description('Start the development server').argument('[entry]', 'Entry file', 'app.ts'),
|
|
126
|
-
).action(async (entry, opts) => {
|
|
127
|
-
await runBunCommand(['--dev'], { ...opts, nodeEnv: 'development' }, entry);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
serverOptions(
|
|
131
|
-
program
|
|
132
|
-
.command('dev:watch')
|
|
133
|
-
.description('Start the development server with watch mode (restarts on file changes)')
|
|
134
|
-
.argument('[entry]', 'Entry file', 'app.ts'),
|
|
135
|
-
).action(async (entry, opts) => {
|
|
136
|
-
await runBunCommand(['--dev'], { ...opts, watch: true, nodeEnv: 'development' }, entry);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
serverOptions(
|
|
140
|
-
program
|
|
141
|
-
.command('dev:hot')
|
|
142
|
-
.description('Start the development server with hot reload (HMR without restart)')
|
|
143
|
-
.argument('[entry]', 'Entry file', 'app.ts'),
|
|
144
|
-
).action(async (entry, opts) => {
|
|
145
|
-
await runBunCommand(['--dev'], { ...opts, hot: true, nodeEnv: 'development' }, entry);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
program
|
|
149
|
-
.command('build')
|
|
150
|
-
.description('Build the project for production')
|
|
151
|
-
.argument('[entry]', 'Entry file', 'app.ts')
|
|
152
|
-
.option('--runtime <runtime>', 'Force a specific runtime (bun, node, or node-experimental)')
|
|
153
|
-
.action(async (entry, opts) => {
|
|
154
|
-
await runBunCommand(['--build'], { nodeEnv: 'production', ...opts }, entry);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
serverOptions(
|
|
158
|
-
program.command('start').description('Start the production server').argument('[entry]', 'Entry file', 'app.ts'),
|
|
159
|
-
).action(async (entry, opts) => {
|
|
160
|
-
await runBunCommand([], { ...opts, nodeEnv: 'production' }, entry);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
serverOptions(
|
|
164
|
-
program.command('preview').description('Preview the production build').argument('[entry]', 'Entry file', 'app.ts'),
|
|
165
|
-
).action(async (entry, opts) => {
|
|
166
|
-
await runBunCommand(['--preview'], { ...opts, nodeEnv: 'production' }, entry);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
program.parse();
|
|
333
|
+
export {
|
|
334
|
+
runCli
|
|
335
|
+
};
|