ecopages 0.2.0-alpha.8 → 0.2.0-beta.0

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 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 natively wraps your execution environment (Bun or Node) and automatically detects your `eco.config.ts`.
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) | Equivalent (Node) |
21
- | :--------------------------- | :----------------------------------------- | :------------------------------ | :-------------------------------------------- |
22
- | `ecopages init <dir>` | Scaffolds a new project | N/A | N/A |
23
- | `ecopages dev [entry]` | Starts the dev server | `bun run [entry] --dev` | `node [ecopages thin host] [entry] --dev` |
24
- | `ecopages dev:watch [entry]` | Dev server + hard restarts on file changes | `bun --watch run [entry] --dev` | `node --watch [ecopages thin host] ...` |
25
- | `ecopages dev:hot [entry]` | Dev server + HMR (no hard restarts) | `bun --hot run [entry] --dev` | N/A |
26
- | `ecopages build [entry]` | Creates a production build | `bun run [entry] --build` | `node [ecopages thin host] [entry] --build` |
27
- | `ecopages start [entry]` | Starts the production server | `bun run [entry]` | `node [ecopages thin host] [entry]` |
28
- | `ecopages preview [entry]` | Previews the production build locally | `bun run [entry] --preview` | `node [ecopages thin host] [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`, `node`, or `node-experimental` |
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 automatically detects your runtime environments by inspecting the package manager (`npm_config_user_agent`) or whether the `Bun` global exists. If neither is forcing Bun, it executes your app through the Ecopages-owned Node thin host.
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 separately to [JSR](https://jsr.io/@ecopages).
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 [jsr.io/@ecopages](https://jsr.io/@ecopages).
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,313 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { spawn } from 'node:child_process';
6
- import { join } from 'node:path';
7
- import tiged from 'tiged';
8
- import { Logger } from '@ecopages/logger';
9
- import { createLaunchPlan, launchPlanRequiresExistingEntryFile } from './launch-plan.js';
10
-
11
- const logger = new Logger('[ecopages:cli]');
12
-
13
- const program = new Command();
14
- const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
15
-
16
- program.name('ecopages').description('Ecopages CLI utilities').version(pkg.version);
17
-
18
- async function handleInit(dir, opts) {
19
- const { template, repo } = opts;
20
- const targetDir = dir;
21
-
22
- if (existsSync(targetDir)) {
23
- logger.error(`Target directory already exists: ${targetDir}`);
24
- process.exit(1);
25
- }
26
-
27
- logger.info(`Creating target directory '${targetDir}'...`);
28
-
29
- try {
30
- const emitter = tiged(`${repo}/examples/${template}`, {
31
- disableCache: true,
32
- force: true,
33
- verbose: false,
34
- });
35
-
36
- await emitter.clone(targetDir);
37
-
38
- const pkgPath = join(targetDir, 'package.json');
39
- if (existsSync(pkgPath)) {
40
- const projectPkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
41
- projectPkg.name = dir;
42
- writeFileSync(pkgPath, JSON.stringify(projectPkg, null, 2) + '\n');
43
- logger.info(`Renamed project to '${dir}'`);
44
- }
45
-
46
- logger.info('Project initialized! Run `bun install && bun dev` to start.');
47
- } catch (err) {
48
- logger.error(`Failed to fetch template: ${err.message}`);
49
- process.exit(1);
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
+ help: {
36
+ type: "boolean",
37
+ short: "h"
38
+ }
39
+ };
40
+ const initOptionDefinitions = {
41
+ template: {
42
+ type: "string"
43
+ },
44
+ repo: {
45
+ type: "string"
46
+ },
47
+ help: {
48
+ type: "boolean",
49
+ short: "h"
50
+ }
51
+ };
52
+ function getMainHelpText() {
53
+ return [
54
+ `ecopages ${pkg.version}`,
55
+ "",
56
+ "Usage: ecopages <command> [options]",
57
+ "",
58
+ "Commands:",
59
+ " init <dir> Initialize a new project from a template",
60
+ " dev [entry] Start the development server",
61
+ " dev:watch [entry] Start the development server with watch mode",
62
+ " dev:hot [entry] Start the development server with hot reload",
63
+ " build [entry] Build the project for production",
64
+ " start [entry] Start the production server",
65
+ " preview [entry] Preview the production build",
66
+ "",
67
+ "Global options:",
68
+ " -h, --help Show help",
69
+ " --version Show version"
70
+ ].join("\n");
71
+ }
72
+ function getServerCommandHelpText(commandName, description) {
73
+ return [
74
+ `Usage: ecopages ${commandName} [entry] [options]`,
75
+ "",
76
+ description,
77
+ "",
78
+ "Options:",
79
+ " -p, --port <port> Override ECOPAGES_PORT",
80
+ " -n, --hostname <hostname> Override ECOPAGES_HOSTNAME",
81
+ " -b, --base-url <baseUrl> Override ECOPAGES_BASE_URL",
82
+ " -d, --debug Enable debug logging",
83
+ " -r, --react-fast-refresh Enable React Fast Refresh for Bun HMR",
84
+ " --runtime <runtime> Force bun or node",
85
+ " -h, --help Show help"
86
+ ].join("\n");
87
+ }
88
+ function getBuildCommandHelpText() {
89
+ return [
90
+ "Usage: ecopages build [entry] [options]",
91
+ "",
92
+ "Build the project for production.",
93
+ "",
94
+ "Options:",
95
+ " -p, --port <port> Override ECOPAGES_PORT",
96
+ " -n, --hostname <hostname> Override ECOPAGES_HOSTNAME",
97
+ " -b, --base-url <baseUrl> Override ECOPAGES_BASE_URL",
98
+ " -d, --debug Enable debug logging",
99
+ " -r, --react-fast-refresh Enable React Fast Refresh for Bun HMR",
100
+ " --runtime <runtime> Force bun or node",
101
+ " -h, --help Show help"
102
+ ].join("\n");
103
+ }
104
+ function getInitCommandHelpText() {
105
+ return [
106
+ "Usage: ecopages init <dir> [options]",
107
+ "",
108
+ "Initialize a new project from a template.",
109
+ "",
110
+ "Options:",
111
+ " --template <template> Template name from ecopages/examples/",
112
+ " --repo <repo> GitHub repo in user/repo form",
113
+ " -h, --help Show help"
114
+ ].join("\n");
115
+ }
116
+ function parseCommandArguments(rawArgs, options) {
117
+ return parseArgs({
118
+ args: rawArgs,
119
+ options,
120
+ allowPositionals: true,
121
+ strict: true
122
+ });
123
+ }
124
+ function parseServerCommandArgs(rawArgs, commandName, description, mode = "server") {
125
+ const { values, positionals } = parseCommandArguments(rawArgs, sharedServerOptionDefinitions);
126
+ if (values.help) {
127
+ console.log(mode === "build" ? getBuildCommandHelpText() : getServerCommandHelpText(commandName, description));
128
+ return { help: true };
129
+ }
130
+ if (positionals.length > 1) {
131
+ throw new Error(`Too many positional arguments provided for \`${commandName}\`.`);
132
+ }
133
+ return {
134
+ entry: positionals[0] ?? "app.ts",
135
+ options: {
136
+ port: values.port,
137
+ hostname: values.hostname,
138
+ baseUrl: values["base-url"],
139
+ debug: values.debug,
140
+ reactFastRefresh: values["react-fast-refresh"],
141
+ runtime: values.runtime
142
+ }
143
+ };
144
+ }
145
+ function parseInitCommandArgs(rawArgs) {
146
+ const { values, positionals } = parseCommandArguments(rawArgs, initOptionDefinitions);
147
+ if (values.help) {
148
+ console.log(getInitCommandHelpText());
149
+ return { help: true };
150
+ }
151
+ if (positionals.length !== 1) {
152
+ throw new Error("The `init` command requires exactly one target directory argument.");
153
+ }
154
+ return {
155
+ dir: positionals[0],
156
+ template: values.template ?? "starter-jsx",
157
+ repo: values.repo ?? "ecopages/ecopages"
158
+ };
51
159
  }
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
160
  function runLaunchPlan(launchPlan) {
61
- if (Object.keys(launchPlan.envOverrides).length > 0) {
62
- logger.info(`Environment overrides: ${JSON.stringify(launchPlan.envOverrides)}`);
63
- }
64
-
65
- logger.info(`Runtime: ${launchPlan.runtime}`);
66
- logger.info(`Running: ${launchPlan.command} ${launchPlan.commandArgs.join(' ')}`);
67
-
68
- const child = spawn(launchPlan.command, launchPlan.commandArgs, {
69
- stdio: 'inherit',
70
- env: launchPlan.env,
71
- });
72
-
73
- child.on('error', (error) => {
74
- if (error && error.code === 'ENOENT') {
75
- const hint =
76
- launchPlan.command === 'bun'
77
- ? 'Install Bun from https://bun.sh to continue.'
78
- : 'Install Node.js and ensure the `node` command is available to continue.';
79
- logger.error(`Command not found: ${launchPlan.command}. ${hint}`);
80
- process.exit(1);
81
- }
82
-
83
- logger.error(`Failed to run command: ${error.message}`);
84
- process.exit(1);
85
- });
86
-
87
- child.on('exit', (code) => {
88
- process.exit(code || 0);
89
- });
161
+ if (Object.keys(launchPlan.envOverrides).length > 0) {
162
+ logger.debug(`Environment overrides: ${JSON.stringify(launchPlan.envOverrides)}`);
163
+ }
164
+ logger.debug(`Runtime: ${launchPlan.runtime}`);
165
+ logger.debug(`Running: ${launchPlan.command} ${launchPlan.commandArgs.join(" ")}`);
166
+ const child = spawn(launchPlan.command, launchPlan.commandArgs, {
167
+ stdio: "inherit",
168
+ env: launchPlan.env
169
+ });
170
+ child.on("error", (error) => {
171
+ if (error && error.code === "ENOENT") {
172
+ 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.";
173
+ logger.error(`Command not found: ${launchPlan.command}. ${hint}`);
174
+ process.exit(1);
175
+ }
176
+ logger.error(`Failed to run command: ${error.message}`);
177
+ process.exit(1);
178
+ });
179
+ child.on("exit", (code) => {
180
+ process.exit(code || 0);
181
+ });
182
+ }
183
+ async function runEntryCommand(args, options = {}, entryFile = "app.ts") {
184
+ let launchPlan;
185
+ if (!existsSync(entryFile)) {
186
+ logger.error(`Error: Entry file "${entryFile}" not found in the current directory.`);
187
+ process.exit(1);
188
+ }
189
+ try {
190
+ launchPlan = await createLaunchPlan(args, options, entryFile);
191
+ } catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ logger.error(message);
194
+ process.exit(1);
195
+ }
196
+ runLaunchPlan(launchPlan);
197
+ }
198
+ async function runInitCommand(rawArgs) {
199
+ const parsed = parseInitCommandArgs(rawArgs);
200
+ if (parsed.help) {
201
+ return;
202
+ }
203
+ const { dir, template, repo } = parsed;
204
+ if (existsSync(dir)) {
205
+ logger.error(`Target directory already exists: ${dir}`);
206
+ process.exit(1);
207
+ }
208
+ logger.info(`Creating target directory '${dir}'...`);
209
+ try {
210
+ await downloadTemplate(`github:${repo}/examples/${template}`, {
211
+ dir,
212
+ force: true
213
+ });
214
+ const pkgPath = join(dir, "package.json");
215
+ if (existsSync(pkgPath)) {
216
+ const projectPkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
217
+ projectPkg.name = dir;
218
+ writeFileSync(pkgPath, JSON.stringify(projectPkg, null, 2) + "\n");
219
+ logger.info(`Renamed project to '${dir}'`);
220
+ }
221
+ logger.info("Project initialized! Run `bun install && bun dev` to start.");
222
+ } catch (error) {
223
+ const message = error instanceof Error ? error.message : String(error);
224
+ logger.error(`Failed to fetch template: ${message}`);
225
+ process.exit(1);
226
+ }
227
+ }
228
+ async function runServerCommand(rawArgs, definition) {
229
+ const parsed = parseServerCommandArgs(rawArgs, definition.name, definition.description, definition.mode);
230
+ if (parsed.help) {
231
+ return;
232
+ }
233
+ await runEntryCommand(definition.entryArgs, { ...parsed.options, ...definition.optionOverrides }, parsed.entry);
234
+ }
235
+ async function runCli(rawArgs = process.argv.slice(2)) {
236
+ const [commandName, ...commandArgs] = rawArgs;
237
+ if (!commandName || commandName === "--help" || commandName === "-h") {
238
+ console.log(getMainHelpText());
239
+ return;
240
+ }
241
+ if (commandName === "--version") {
242
+ console.log(pkg.version);
243
+ return;
244
+ }
245
+ try {
246
+ switch (commandName) {
247
+ case "init":
248
+ await runInitCommand(commandArgs);
249
+ return;
250
+ case "dev":
251
+ await runServerCommand(commandArgs, {
252
+ name: "dev",
253
+ description: "Start the development server.",
254
+ entryArgs: ["--dev"],
255
+ optionOverrides: { nodeEnv: "development" }
256
+ });
257
+ return;
258
+ case "dev:watch":
259
+ await runServerCommand(commandArgs, {
260
+ name: "dev:watch",
261
+ description: "Start the development server with watch mode.",
262
+ entryArgs: ["--dev"],
263
+ optionOverrides: { watch: true, nodeEnv: "development" }
264
+ });
265
+ return;
266
+ case "dev:hot":
267
+ await runServerCommand(commandArgs, {
268
+ name: "dev:hot",
269
+ description: "Start the development server with hot reload.",
270
+ entryArgs: ["--dev"],
271
+ optionOverrides: { hot: true, nodeEnv: "development" }
272
+ });
273
+ return;
274
+ case "build":
275
+ await runServerCommand(commandArgs, {
276
+ name: "build",
277
+ description: "Build the project for production.",
278
+ entryArgs: ["--build"],
279
+ optionOverrides: { nodeEnv: "production" },
280
+ mode: "build"
281
+ });
282
+ return;
283
+ case "start":
284
+ await runServerCommand(commandArgs, {
285
+ name: "start",
286
+ description: "Start the production server.",
287
+ entryArgs: [],
288
+ optionOverrides: { nodeEnv: "production" }
289
+ });
290
+ return;
291
+ case "preview":
292
+ await runServerCommand(commandArgs, {
293
+ name: "preview",
294
+ description: "Preview the production build.",
295
+ entryArgs: ["--preview"],
296
+ optionOverrides: { nodeEnv: "production" }
297
+ });
298
+ return;
299
+ default:
300
+ throw new Error(`Unknown command \`${commandName}\`.`);
301
+ }
302
+ } catch (error) {
303
+ const message = error instanceof Error ? error.message : String(error);
304
+ logger.error(message);
305
+ process.exit(1);
306
+ }
90
307
  }
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);
308
+ if (!process.env.VITEST) {
309
+ runCli();
108
310
  }
109
-
110
- /**
111
- * Add shared server options to a command.
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();
311
+ export {
312
+ runCli
313
+ };