@evjs/cli 0.0.1-rc.13
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/AGENT.md +121 -0
- package/README.md +67 -0
- package/dist/config.js +35 -0
- package/dist/create-webpack-config.js +99 -0
- package/dist/index.js +220 -0
- package/dist/load-config.js +39 -0
- package/package.json +75 -0
- package/templates/basic-csr/index.html +11 -0
- package/templates/basic-csr/package.json +21 -0
- package/templates/basic-csr/src/main.tsx +22 -0
- package/templates/basic-csr/src/pages/__root.tsx +28 -0
- package/templates/basic-csr/src/pages/about.tsx +19 -0
- package/templates/basic-csr/src/pages/home.tsx +19 -0
- package/templates/basic-csr/src/pages/posts/index.tsx +63 -0
- package/templates/basic-csr/tsconfig.json +17 -0
- package/templates/basic-server-fns/index.html +11 -0
- package/templates/basic-server-fns/package.json +21 -0
- package/templates/basic-server-fns/src/api/users.server.ts +31 -0
- package/templates/basic-server-fns/src/main.tsx +12 -0
- package/templates/basic-server-fns/src/routes.tsx +108 -0
- package/templates/basic-server-fns/tsconfig.json +16 -0
- package/templates/complex-routing/index.html +11 -0
- package/templates/complex-routing/package.json +21 -0
- package/templates/complex-routing/src/api/data.server.ts +86 -0
- package/templates/complex-routing/src/main.tsx +29 -0
- package/templates/complex-routing/src/pages/__root.tsx +60 -0
- package/templates/complex-routing/src/pages/catch.tsx +26 -0
- package/templates/complex-routing/src/pages/dashboard.tsx +81 -0
- package/templates/complex-routing/src/pages/home.tsx +35 -0
- package/templates/complex-routing/src/pages/posts/index.tsx +104 -0
- package/templates/complex-routing/src/pages/search.tsx +71 -0
- package/templates/complex-routing/src/pages/user.tsx +32 -0
- package/templates/complex-routing/tsconfig.json +13 -0
- package/templates/configured-server-fns/ev.config.ts +56 -0
- package/templates/configured-server-fns/index.html +11 -0
- package/templates/configured-server-fns/package.json +21 -0
- package/templates/configured-server-fns/src/api/users.server.ts +22 -0
- package/templates/configured-server-fns/src/main.tsx +12 -0
- package/templates/configured-server-fns/src/routes.tsx +111 -0
- package/templates/configured-server-fns/tsconfig.json +16 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @evjs/cli — Agent Guide
|
|
2
|
+
|
|
3
|
+
> AI-agent reference for developing apps with the `@evjs/cli` package.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@evjs/cli` is the CLI and configuration layer. Users install it as a devDependency. It provides:
|
|
8
|
+
|
|
9
|
+
- **`ev init`** — scaffold a new project from templates
|
|
10
|
+
- **`ev dev`** — start dev server (webpack-dev-server + API server)
|
|
11
|
+
- **`ev build`** — production build (client + server bundles)
|
|
12
|
+
- **`defineConfig`** — type-safe config export for `ev.config.ts`
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @evjs/cli@latest init my-app
|
|
18
|
+
cd my-app && npm install
|
|
19
|
+
ev dev # http://localhost:3000
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration (`ev.config.ts`)
|
|
23
|
+
|
|
24
|
+
Optional — everything works zero-config. Create `ev.config.ts` in project root when needed:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { defineConfig } from "@evjs/cli";
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
client: {
|
|
31
|
+
entry: "./src/main.tsx", // default
|
|
32
|
+
html: "./index.html", // default
|
|
33
|
+
dev: {
|
|
34
|
+
port: 3000, // webpack-dev-server port
|
|
35
|
+
open: true, // auto-open browser
|
|
36
|
+
https: false, // enable HTTPS
|
|
37
|
+
historyApiFallback: true, // SPA routing fallback
|
|
38
|
+
},
|
|
39
|
+
transport: {
|
|
40
|
+
baseUrl: "", // API base URL (for separate API host)
|
|
41
|
+
endpoint: "/api/fn", // server function endpoint path
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
server: {
|
|
45
|
+
endpoint: "/api/fn", // must match client transport endpoint
|
|
46
|
+
runner: "@evjs/runtime/server/node",
|
|
47
|
+
middleware: [
|
|
48
|
+
"./src/middleware/auth.ts", // middleware module paths
|
|
49
|
+
"./src/middleware/logging.ts",
|
|
50
|
+
],
|
|
51
|
+
dev: {
|
|
52
|
+
port: 3001, // API server port in dev mode
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Config Defaults
|
|
59
|
+
|
|
60
|
+
| Key | Default |
|
|
61
|
+
|-----|---------|
|
|
62
|
+
| `client.entry` | `"./src/main.tsx"` |
|
|
63
|
+
| `client.html` | `"./index.html"` |
|
|
64
|
+
| `client.dev.port` | `3000` |
|
|
65
|
+
| `server.endpoint` | `"/api/fn"` |
|
|
66
|
+
| `server.dev.port` | `3001` |
|
|
67
|
+
| `server.runner` | `"@evjs/runtime/server/node"` |
|
|
68
|
+
|
|
69
|
+
## Project Structure
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
my-app/
|
|
73
|
+
├── ev.config.ts # optional config
|
|
74
|
+
├── index.html # HTML template
|
|
75
|
+
├── package.json
|
|
76
|
+
├── tsconfig.json
|
|
77
|
+
└── src/
|
|
78
|
+
├── main.tsx # app bootstrap (keep minimal)
|
|
79
|
+
├── routes.tsx # route tree + components
|
|
80
|
+
├── api/ # server functions
|
|
81
|
+
│ ├── users.server.ts
|
|
82
|
+
│ └── posts.server.ts
|
|
83
|
+
└── middleware/ # server middleware (optional)
|
|
84
|
+
└── auth.ts
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## CLI Commands
|
|
88
|
+
|
|
89
|
+
### `ev init [project-name]`
|
|
90
|
+
Interactive scaffolding. Templates:
|
|
91
|
+
- `basic-csr` — client-side rendering only
|
|
92
|
+
- `basic-server-fns` — server functions example
|
|
93
|
+
- `configured-server-fns` — advanced config example
|
|
94
|
+
- `complex-routing` — params, search, layouts, loaders
|
|
95
|
+
|
|
96
|
+
### `ev dev`
|
|
97
|
+
- Starts webpack-dev-server on port 3000
|
|
98
|
+
- Auto-starts API server on port 3001
|
|
99
|
+
- Proxies `/api/fn` requests to API server
|
|
100
|
+
- Hot reloads client; restarts server on changes
|
|
101
|
+
- `NODE_ENV=development`
|
|
102
|
+
|
|
103
|
+
### `ev build`
|
|
104
|
+
- Outputs client bundle to `dist/client/`
|
|
105
|
+
- Outputs server bundle to `dist/server/`
|
|
106
|
+
- Emits `dist/server/manifest.json` with server function registry
|
|
107
|
+
- `NODE_ENV=production`
|
|
108
|
+
|
|
109
|
+
## Common Mistakes
|
|
110
|
+
|
|
111
|
+
1. **Don't create `webpack.config.cjs`** — use `ev.config.ts` instead
|
|
112
|
+
2. **Don't install webpack manually** — it's a dependency of `@evjs/cli`
|
|
113
|
+
3. **Config file must be `ev.config.ts`** — not `evjs.config.ts` or `evjs.config.ts`
|
|
114
|
+
4. **Import `defineConfig` from `@evjs/cli`** — not from `@evjs/runtime`
|
|
115
|
+
|
|
116
|
+
## Dependencies (bundled)
|
|
117
|
+
|
|
118
|
+
Users do NOT need to install these — they're included in `@evjs/cli`:
|
|
119
|
+
- `webpack`, `webpack-dev-server`, `webpack-cli`
|
|
120
|
+
- `html-webpack-plugin`, `swc-loader`, `@swc/core`
|
|
121
|
+
- `@evjs/webpack-plugin`
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @evjs/cli
|
|
2
|
+
|
|
3
|
+
> CLI and configuration for the **@evjs/cli** meta-framework.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @evjs/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Zero-Config
|
|
12
|
+
|
|
13
|
+
No configuration file is needed. `ev dev` and `ev build` work out of the box with sensible defaults:
|
|
14
|
+
|
|
15
|
+
- Entry: `./src/main.tsx`
|
|
16
|
+
- HTML: `./index.html`
|
|
17
|
+
- Client dev server: port 3000
|
|
18
|
+
- API server (dev): port 3001
|
|
19
|
+
- Server functions auto-discovered via `"use server"` directive
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `ev init [name]` | Scaffold a new project from a template |
|
|
26
|
+
| `ev dev` | Start dev server (client HMR + API watch) |
|
|
27
|
+
| `ev build` | Production build (client + server) |
|
|
28
|
+
|
|
29
|
+
### `ev init [name]`
|
|
30
|
+
|
|
31
|
+
Templates: `basic-csr`, `basic-server-fns`, `trpc-server-fns`.
|
|
32
|
+
Option: `-t, --template <template>` to skip interactive selection.
|
|
33
|
+
|
|
34
|
+
### `ev dev`
|
|
35
|
+
|
|
36
|
+
Uses webpack Node API directly (no temp config files):
|
|
37
|
+
1. **WebpackDevServer** (port 3000) — client bundle with HMR.
|
|
38
|
+
2. **Node API Server** (port 3001) — auto-starts when server bundle is emitted, uses `node --watch`.
|
|
39
|
+
|
|
40
|
+
### `ev build`
|
|
41
|
+
|
|
42
|
+
Runs webpack via Node API with `NODE_ENV=production`:
|
|
43
|
+
- `dist/client/` — optimized client assets with content hashes.
|
|
44
|
+
- `dist/server/main.[hash].js` — server bundle (entry discovered via `dist/server/manifest.json`).
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Create `ev.config.ts` in the project root (optional):
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { defineConfig } from "@evjs/cli";
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
client: {
|
|
55
|
+
entry: "./src/main.tsx",
|
|
56
|
+
html: "./index.html",
|
|
57
|
+
dev: { port: 3000 },
|
|
58
|
+
},
|
|
59
|
+
server: {
|
|
60
|
+
endpoint: "/api/fn",
|
|
61
|
+
middleware: [],
|
|
62
|
+
dev: { port: 3001 },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `client.dev` and `server.dev` fields accept extra options that are merged with defaults.
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all defaults across the framework.
|
|
5
|
+
*/
|
|
6
|
+
export const CONFIG_DEFAULTS = {
|
|
7
|
+
entry: "./src/main.tsx",
|
|
8
|
+
html: "./index.html",
|
|
9
|
+
clientPort: 3000,
|
|
10
|
+
serverPort: 3001,
|
|
11
|
+
endpoint: "/api/fn",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Define configuration for the evjs framework.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // ev.config.ts
|
|
19
|
+
* import { defineConfig } from "@evjs/cli";
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig({
|
|
22
|
+
* client: {
|
|
23
|
+
* entry: "./src/main.tsx",
|
|
24
|
+
* dev: { port: 3000 },
|
|
25
|
+
* },
|
|
26
|
+
* server: {
|
|
27
|
+
* endpoint: "/api/fn",
|
|
28
|
+
* dev: { port: 3001 },
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function defineConfig(config) {
|
|
34
|
+
return config;
|
|
35
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CONFIG_DEFAULTS } from "./config.js";
|
|
4
|
+
const esmRequire = createRequire(import.meta.url);
|
|
5
|
+
/**
|
|
6
|
+
* Create a webpack configuration object from EvfConfig.
|
|
7
|
+
*
|
|
8
|
+
* Returns a plain object that can be passed directly to the webpack Node API.
|
|
9
|
+
* No temp files are generated.
|
|
10
|
+
*/
|
|
11
|
+
export function createWebpackConfig(config, cwd) {
|
|
12
|
+
const client = config?.client;
|
|
13
|
+
const server = config?.server;
|
|
14
|
+
const entry = client?.entry ?? CONFIG_DEFAULTS.entry;
|
|
15
|
+
const html = client?.html ?? CONFIG_DEFAULTS.html;
|
|
16
|
+
const clientPort = client?.dev?.port ?? CONFIG_DEFAULTS.clientPort;
|
|
17
|
+
const serverPort = server?.dev?.port ?? CONFIG_DEFAULTS.serverPort;
|
|
18
|
+
const endpoint = server?.endpoint ?? CONFIG_DEFAULTS.endpoint;
|
|
19
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
20
|
+
const HtmlWebpackPlugin = esmRequire("html-webpack-plugin");
|
|
21
|
+
const { EvWebpackPlugin } = esmRequire("@evjs/webpack-plugin");
|
|
22
|
+
const pluginOptions = server?.middleware?.length
|
|
23
|
+
? { server: { middleware: server.middleware } }
|
|
24
|
+
: undefined;
|
|
25
|
+
// Resolve loader paths from evjs's dependency tree so they work
|
|
26
|
+
// even when the user's project doesn't list them as direct deps.
|
|
27
|
+
const resolveLoader = (id) => {
|
|
28
|
+
try {
|
|
29
|
+
return esmRequire.resolve(id);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
// Derive the proxy base path from the configured endpoint.
|
|
36
|
+
// e.g. "/api/fn" → "/api", "/rpc/v1" → "/rpc"
|
|
37
|
+
const proxyBase = `/${endpoint.split("/").filter(Boolean)[0] || "api"}`;
|
|
38
|
+
// Destructure port out of dev overrides to avoid passing it twice.
|
|
39
|
+
const { port: _p, ...devServerOverrides } = client?.dev ?? {};
|
|
40
|
+
return {
|
|
41
|
+
name: "client",
|
|
42
|
+
mode: isProduction ? "production" : "development",
|
|
43
|
+
devtool: isProduction ? "hidden-source-map" : "source-map",
|
|
44
|
+
entry,
|
|
45
|
+
output: {
|
|
46
|
+
path: path.resolve(cwd, "dist/client"),
|
|
47
|
+
filename: isProduction ? "[name].[contenthash:8].js" : "index.js",
|
|
48
|
+
clean: true,
|
|
49
|
+
},
|
|
50
|
+
resolve: {
|
|
51
|
+
extensions: [".tsx", ".ts", ".js"],
|
|
52
|
+
},
|
|
53
|
+
module: {
|
|
54
|
+
rules: [
|
|
55
|
+
{
|
|
56
|
+
test: /\.m?js/,
|
|
57
|
+
resolve: { fullySpecified: false },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
test: /\.tsx?$/,
|
|
61
|
+
exclude: /node_modules/,
|
|
62
|
+
use: [
|
|
63
|
+
{
|
|
64
|
+
loader: resolveLoader("swc-loader"),
|
|
65
|
+
options: {
|
|
66
|
+
jsc: {
|
|
67
|
+
parser: { syntax: "typescript", tsx: true },
|
|
68
|
+
transform: { react: { runtime: "automatic" } },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
loader: resolveLoader("@evjs/webpack-plugin/server-fn-loader"),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
plugins: [
|
|
80
|
+
new HtmlWebpackPlugin({ template: html }),
|
|
81
|
+
new EvWebpackPlugin(pluginOptions),
|
|
82
|
+
],
|
|
83
|
+
optimization: isProduction
|
|
84
|
+
? { splitChunks: { chunks: "all" } }
|
|
85
|
+
: undefined,
|
|
86
|
+
devServer: {
|
|
87
|
+
port: clientPort,
|
|
88
|
+
hot: true,
|
|
89
|
+
devMiddleware: { writeToDisk: true },
|
|
90
|
+
proxy: [
|
|
91
|
+
{
|
|
92
|
+
context: [proxyBase],
|
|
93
|
+
target: `http://localhost:${serverPort}`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
...devServerOverrides,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { execa } from "execa";
|
|
8
|
+
import fs from "fs-extra";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
import { CONFIG_DEFAULTS } from "./config.js";
|
|
11
|
+
const esmRequire = createRequire(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
await configure({
|
|
14
|
+
sinks: { console: getConsoleSink() },
|
|
15
|
+
loggers: [
|
|
16
|
+
{ category: ["logtape", "meta"], lowestLevel: "warning" },
|
|
17
|
+
{ category: ["evjs"], sinks: ["console"], lowestLevel: "info" },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
const logger = getLogger(["evjs", "cli"]);
|
|
21
|
+
const pkg = fs.readJsonSync(path.resolve(__dirname, "../package.json"));
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name("ev")
|
|
25
|
+
.description("CLI for the evjs framework")
|
|
26
|
+
.version(pkg.version);
|
|
27
|
+
program
|
|
28
|
+
.command("init")
|
|
29
|
+
.description("Initialize a new evjs project")
|
|
30
|
+
.argument("[name]", "Project name")
|
|
31
|
+
.option("-t, --template <template>", "Template to use")
|
|
32
|
+
.action(async (name, options) => {
|
|
33
|
+
const response = await prompts([
|
|
34
|
+
{
|
|
35
|
+
type: name ? null : "text",
|
|
36
|
+
name: "projectName",
|
|
37
|
+
message: "Project name:",
|
|
38
|
+
initial: name || "my-evjs-app",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: options.template ? null : "select",
|
|
42
|
+
name: "template",
|
|
43
|
+
message: "Select a template:",
|
|
44
|
+
choices: [
|
|
45
|
+
{ title: "Basic CSR (Client-Side Rendering)", value: "basic-csr" },
|
|
46
|
+
{ title: "Basic Server Functions", value: "basic-server-fns" },
|
|
47
|
+
{
|
|
48
|
+
title: "Configured Server Functions (ev.config.ts + Query)",
|
|
49
|
+
value: "configured-server-fns",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Complex Routing (params, search, layouts, loaders)",
|
|
53
|
+
value: "complex-routing",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
], {
|
|
58
|
+
onCancel: () => {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const projectName = response.projectName || name;
|
|
63
|
+
const template = response.template || options.template;
|
|
64
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
65
|
+
if (fs.existsSync(targetDir)) {
|
|
66
|
+
logger.error `Directory ${projectName} already exists!`;
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const templateDir = path.resolve(__dirname, "../templates", template);
|
|
70
|
+
if (!fs.existsSync(templateDir)) {
|
|
71
|
+
logger.error `Template ${template} not found!`;
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
logger.info `Scaffolding project in ${targetDir}...`;
|
|
75
|
+
await fs.copy(templateDir, targetDir, {
|
|
76
|
+
dereference: true,
|
|
77
|
+
filter: (src) => {
|
|
78
|
+
const basename = path.basename(src);
|
|
79
|
+
return !["node_modules", "dist", ".turbo"].includes(basename);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
// Post-process package.json: sync @evjs/* versions and set project name
|
|
83
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
84
|
+
if (fs.existsSync(pkgPath)) {
|
|
85
|
+
const pkg = await fs.readJson(pkgPath);
|
|
86
|
+
pkg.name = projectName;
|
|
87
|
+
delete pkg.private; // Templates shouldn't be private by default
|
|
88
|
+
const updateDeps = (deps) => {
|
|
89
|
+
if (!deps)
|
|
90
|
+
return;
|
|
91
|
+
for (const [name, val] of Object.entries(deps)) {
|
|
92
|
+
// Sync all @evjs/* packages to current CLI version
|
|
93
|
+
if (name.startsWith("@evjs/") &&
|
|
94
|
+
(val === "*" ||
|
|
95
|
+
(typeof val === "string" && val.includes("workspace")))) {
|
|
96
|
+
deps[name] = `^${pkg.version}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
updateDeps(pkg.dependencies);
|
|
101
|
+
updateDeps(pkg.devDependencies);
|
|
102
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
103
|
+
}
|
|
104
|
+
logger.info `Done! Now run:`;
|
|
105
|
+
logger.info ` cd ${projectName}`;
|
|
106
|
+
logger.info ` npm install`;
|
|
107
|
+
logger.info ` npm run dev`;
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Load config and create webpack configuration object.
|
|
111
|
+
*
|
|
112
|
+
* Uses ev.config.ts when present, otherwise falls back to zero-config defaults.
|
|
113
|
+
* No webpack.config.cjs fallback — the meta-framework owns the build config.
|
|
114
|
+
*/
|
|
115
|
+
async function resolveWebpackConfig(cwd) {
|
|
116
|
+
const { loadConfig } = await import("./load-config.js");
|
|
117
|
+
const evjsConfig = await loadConfig(cwd);
|
|
118
|
+
const { createWebpackConfig } = await import("./create-webpack-config.js");
|
|
119
|
+
logger.info `Using ${evjsConfig ? "ev.config.ts" : "zero-config defaults"}`;
|
|
120
|
+
const webpackConfig = createWebpackConfig(evjsConfig, cwd);
|
|
121
|
+
return { evjsConfig, webpackConfig };
|
|
122
|
+
}
|
|
123
|
+
program
|
|
124
|
+
.command("dev")
|
|
125
|
+
.description("Start development server")
|
|
126
|
+
.action(async () => {
|
|
127
|
+
const cwd = process.cwd();
|
|
128
|
+
process.env.NODE_ENV ??= "development";
|
|
129
|
+
const { evjsConfig, webpackConfig } = await resolveWebpackConfig(cwd);
|
|
130
|
+
const serverPort = evjsConfig?.server?.dev?.port ?? CONFIG_DEFAULTS.serverPort;
|
|
131
|
+
logger.info `Starting development server...`;
|
|
132
|
+
try {
|
|
133
|
+
const webpack = esmRequire("webpack");
|
|
134
|
+
const WebpackDevServer = esmRequire("webpack-dev-server");
|
|
135
|
+
const compiler = webpack(webpackConfig);
|
|
136
|
+
const devServerOptions = webpackConfig.devServer ?? {};
|
|
137
|
+
const server = new WebpackDevServer(devServerOptions, compiler);
|
|
138
|
+
await server.start();
|
|
139
|
+
// Background: wait for server bundle, then start Node API
|
|
140
|
+
void (async () => {
|
|
141
|
+
const manifestPath = path.resolve(cwd, "dist/server/manifest.json");
|
|
142
|
+
const bootstrapPath = path.resolve(cwd, "dist/server/_dev_start.cjs");
|
|
143
|
+
let started = false;
|
|
144
|
+
while (true) {
|
|
145
|
+
if (fs.existsSync(manifestPath)) {
|
|
146
|
+
if (!started) {
|
|
147
|
+
const runnerConfig = evjsConfig?.server?.runner ?? "node";
|
|
148
|
+
const [runner, ...runnerExtraArgs] = runnerConfig.split(/\s+/);
|
|
149
|
+
logger.info `Server bundle detected, starting ${runner} API...`;
|
|
150
|
+
started = true;
|
|
151
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
152
|
+
const serverBundlePath = path.resolve(cwd, "dist/server", manifest.entry);
|
|
153
|
+
fs.writeFileSync(bootstrapPath, [
|
|
154
|
+
`const bundle = require(${JSON.stringify(serverBundlePath)});`,
|
|
155
|
+
`const app = bundle.createApp({ endpoint: ${JSON.stringify(evjsConfig?.server?.endpoint ?? CONFIG_DEFAULTS.endpoint)} });`,
|
|
156
|
+
`const { serve } = require("@evjs/runtime/server/node");`,
|
|
157
|
+
`serve(app, { port: ${serverPort} });`,
|
|
158
|
+
].join("\n"));
|
|
159
|
+
// node gets --watch flags; other runtimes use their own args as-is
|
|
160
|
+
const runnerArgs = runner === "node"
|
|
161
|
+
? [
|
|
162
|
+
"--watch",
|
|
163
|
+
"--watch-preserve-output",
|
|
164
|
+
...runnerExtraArgs,
|
|
165
|
+
bootstrapPath,
|
|
166
|
+
]
|
|
167
|
+
: [...runnerExtraArgs, bootstrapPath];
|
|
168
|
+
try {
|
|
169
|
+
await execa(runner, runnerArgs, {
|
|
170
|
+
stdio: "inherit",
|
|
171
|
+
env: { ...process.env, NODE_ENV: "development" },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (_e) {
|
|
175
|
+
started = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
180
|
+
}
|
|
181
|
+
})().catch((err) => {
|
|
182
|
+
logger.error `Server runner failed: ${err}`;
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.error `Dev server failed to start: ${err}`;
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
program
|
|
192
|
+
.command("build")
|
|
193
|
+
.description("Build project for production")
|
|
194
|
+
.action(async () => {
|
|
195
|
+
const cwd = process.cwd();
|
|
196
|
+
process.env.NODE_ENV ??= "production";
|
|
197
|
+
const { webpackConfig } = await resolveWebpackConfig(cwd);
|
|
198
|
+
logger.info `Building for production...`;
|
|
199
|
+
const webpack = esmRequire("webpack");
|
|
200
|
+
const compiler = webpack(webpackConfig);
|
|
201
|
+
await new Promise((resolve, reject) => {
|
|
202
|
+
compiler.run((err, stats) => {
|
|
203
|
+
if (err) {
|
|
204
|
+
reject(err);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
console.log(stats.toString({
|
|
208
|
+
colors: true,
|
|
209
|
+
modules: false,
|
|
210
|
+
children: true,
|
|
211
|
+
}));
|
|
212
|
+
if (stats.hasErrors()) {
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
compiler.close(() => resolve());
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
logger.info `Build complete!`;
|
|
219
|
+
});
|
|
220
|
+
program.parse();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const CONFIG_FILES = ["ev.config.ts", "ev.config.js", "ev.config.mjs"];
|
|
4
|
+
/**
|
|
5
|
+
* Ensure a TypeScript loader is registered before importing `.ts` config files.
|
|
6
|
+
* Tries `@swc-node/register/esm-register` (ships alongside `@swc/core` which
|
|
7
|
+
* the CLI already bundles), then falls back to Node's built-in `--loader tsx`
|
|
8
|
+
* pathway. If neither is available the raw `import()` is attempted anyway —
|
|
9
|
+
* Node will throw a clear error telling the user to install a loader.
|
|
10
|
+
*/
|
|
11
|
+
async function ensureTsLoader() {
|
|
12
|
+
try {
|
|
13
|
+
// @ts-expect-error — optional dependency, may not be installed
|
|
14
|
+
await import("@swc-node/register/esm-register");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Loader not available — Node may still handle .ts via --loader flag
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load evjs config from the project root.
|
|
22
|
+
*
|
|
23
|
+
* Looks for `ev.config.ts`, `.js`, or `.mjs` in the given directory.
|
|
24
|
+
* Returns undefined if no config file is found.
|
|
25
|
+
*/
|
|
26
|
+
export async function loadConfig(cwd) {
|
|
27
|
+
for (const filename of CONFIG_FILES) {
|
|
28
|
+
const configPath = path.resolve(cwd, filename);
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
// Register TS loader for .ts config files
|
|
31
|
+
if (filename.endsWith(".ts")) {
|
|
32
|
+
await ensureTsLoader();
|
|
33
|
+
}
|
|
34
|
+
const mod = await import(configPath);
|
|
35
|
+
return mod.default ?? mod;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evjs/cli",
|
|
3
|
+
"version": "0.0.1-rc.13",
|
|
4
|
+
"description": "CLI and configuration layer for the evjs framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/config.js",
|
|
7
|
+
"types": "./dist/config.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/config.d.ts",
|
|
11
|
+
"import": "./dist/config.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"ev": "dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"templates",
|
|
23
|
+
"AGENT.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc -w",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"check-types": "tsc --noEmit",
|
|
30
|
+
"prepare": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@evjs/webpack-plugin": "0.0.1-rc.13",
|
|
34
|
+
"@logtape/logtape": "^2.0.4",
|
|
35
|
+
"@swc/core": "^1.2.147",
|
|
36
|
+
"commander": "^12.1.0",
|
|
37
|
+
"execa": "^9.5.2",
|
|
38
|
+
"fs-extra": "^11.3.0",
|
|
39
|
+
"glob": "^13.0.6",
|
|
40
|
+
"html-webpack-plugin": "^5.6.6",
|
|
41
|
+
"picocolors": "^1.1.1",
|
|
42
|
+
"prompts": "^2.4.2",
|
|
43
|
+
"swc-loader": "^0.2.7",
|
|
44
|
+
"webpack": "^5.105.4",
|
|
45
|
+
"webpack-cli": "^6.0.1",
|
|
46
|
+
"webpack-dev-server": "^5.2.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/fs-extra": "^11.0.4",
|
|
50
|
+
"@types/node": "^22.13.5",
|
|
51
|
+
"@types/prompts": "^2.4.9",
|
|
52
|
+
"typescript": "^5.7.3"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/evaijs/evjs#readme",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/evaijs/evjs/issues"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/evaijs/evjs.git"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"evjs",
|
|
64
|
+
"cli",
|
|
65
|
+
"react",
|
|
66
|
+
"server-functions",
|
|
67
|
+
"scaffolding",
|
|
68
|
+
"dev-server",
|
|
69
|
+
"typescript",
|
|
70
|
+
"full-stack",
|
|
71
|
+
"framework"
|
|
72
|
+
],
|
|
73
|
+
"license": "MIT",
|
|
74
|
+
"author": "xusd320"
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "example-basic-csr",
|
|
3
|
+
"version": "0.0.1-rc.8",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "ev dev",
|
|
7
|
+
"build": "ev build",
|
|
8
|
+
"check-types": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@evjs/runtime": "^0.0.1-rc.13",
|
|
12
|
+
"react": "^19.2.4",
|
|
13
|
+
"react-dom": "^19.2.4"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@evjs/cli": "^0.0.1-rc.5",
|
|
17
|
+
"@types/react": "^19.0.8",
|
|
18
|
+
"@types/react-dom": "^19.0.3",
|
|
19
|
+
"typescript": "^5.7.3"
|
|
20
|
+
}
|
|
21
|
+
}
|