@evjs/cli 0.0.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 +99 -0
- package/dist/config.js +35 -0
- package/dist/create-webpack-config.js +99 -0
- package/dist/index.js +218 -0
- package/dist/load-config.js +39 -0
- package/package.json +74 -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/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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/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.
|
|
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
|
+
## Common Mistakes
|
|
88
|
+
|
|
89
|
+
1. **Don't create `webpack.config.cjs`** — use `ev.config.ts` instead
|
|
90
|
+
2. **Don't install webpack manually** — it's a dependency of `@evjs/cli`
|
|
91
|
+
3. **Config file must be `ev.config.ts`** — not `evjs.config.ts`
|
|
92
|
+
4. **Import `defineConfig` from `@evjs/cli`** — not from `@evjs/runtime`
|
|
93
|
+
|
|
94
|
+
## Bundled Dependencies
|
|
95
|
+
|
|
96
|
+
Users do NOT need to install these — they're included in `@evjs/cli`:
|
|
97
|
+
- `webpack`, `webpack-dev-server`
|
|
98
|
+
- `html-webpack-plugin`, `swc-loader`, `@swc/core`
|
|
99
|
+
- `@evjs/webpack-plugin`, `@evjs/build-tools`
|
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,218 @@
|
|
|
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: start Node API when server bundle is ready
|
|
140
|
+
let apiStarted = false;
|
|
141
|
+
compiler.hooks.done.tap("EvDevServer", async () => {
|
|
142
|
+
if (apiStarted)
|
|
143
|
+
return;
|
|
144
|
+
const manifestPath = path.resolve(cwd, "dist/manifest.json");
|
|
145
|
+
const bootstrapPath = path.resolve(cwd, "dist/server/_dev_start.cjs");
|
|
146
|
+
if (fs.existsSync(manifestPath)) {
|
|
147
|
+
apiStarted = true;
|
|
148
|
+
const backendConfig = evjsConfig?.server?.backend ?? "node";
|
|
149
|
+
const [backend, ...backendExtraArgs] = backendConfig.split(/\s+/);
|
|
150
|
+
logger.info `Server bundle detected, starting ${backend} API...`;
|
|
151
|
+
try {
|
|
152
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
153
|
+
const serverBundlePath = path.resolve(cwd, "dist/server", manifest.server.entry);
|
|
154
|
+
fs.writeFileSync(bootstrapPath, [
|
|
155
|
+
`const bundle = require(${JSON.stringify(serverBundlePath)});`,
|
|
156
|
+
`const app = bundle.createApp({ endpoint: ${JSON.stringify(evjsConfig?.server?.endpoint ?? CONFIG_DEFAULTS.endpoint)} });`,
|
|
157
|
+
`const { serve } = require("@evjs/runtime/server/node");`,
|
|
158
|
+
`serve(app, { port: ${serverPort} });`,
|
|
159
|
+
].join("\n"));
|
|
160
|
+
// node gets --watch flags; other runtimes use their own args as-is
|
|
161
|
+
const backendArgs = backend === "node"
|
|
162
|
+
? [
|
|
163
|
+
"--watch",
|
|
164
|
+
"--watch-preserve-output",
|
|
165
|
+
...backendExtraArgs,
|
|
166
|
+
bootstrapPath,
|
|
167
|
+
]
|
|
168
|
+
: [...backendExtraArgs, bootstrapPath];
|
|
169
|
+
// Don't await execa here since it's a long-running watch process
|
|
170
|
+
execa(backend, backendArgs, {
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
env: { ...process.env, NODE_ENV: "development" },
|
|
173
|
+
}).catch(() => {
|
|
174
|
+
apiStarted = false;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
logger.error `Server backend failed: ${err}`;
|
|
179
|
+
apiStarted = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
logger.error `Dev server failed to start: ${err}`;
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
program
|
|
190
|
+
.command("build")
|
|
191
|
+
.description("Build project for production")
|
|
192
|
+
.action(async () => {
|
|
193
|
+
const cwd = process.cwd();
|
|
194
|
+
process.env.NODE_ENV ??= "production";
|
|
195
|
+
const { webpackConfig } = await resolveWebpackConfig(cwd);
|
|
196
|
+
logger.info `Building for production...`;
|
|
197
|
+
const webpack = esmRequire("webpack");
|
|
198
|
+
const compiler = webpack(webpackConfig);
|
|
199
|
+
await new Promise((resolve, reject) => {
|
|
200
|
+
compiler.run((err, stats) => {
|
|
201
|
+
if (err) {
|
|
202
|
+
reject(err);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log(stats.toString({
|
|
206
|
+
colors: true,
|
|
207
|
+
modules: false,
|
|
208
|
+
children: true,
|
|
209
|
+
}));
|
|
210
|
+
if (stats.hasErrors()) {
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
compiler.close(() => resolve());
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
logger.info `Build complete!`;
|
|
217
|
+
});
|
|
218
|
+
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,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evjs/cli",
|
|
3
|
+
"version": "0.0.0",
|
|
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
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"dev": "tsc -w",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"check-types": "tsc --noEmit",
|
|
29
|
+
"prepare": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@evjs/webpack-plugin": "*",
|
|
33
|
+
"@logtape/logtape": "^2.0.4",
|
|
34
|
+
"@swc/core": "^1.2.147",
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"execa": "^9.5.2",
|
|
37
|
+
"fs-extra": "^11.3.0",
|
|
38
|
+
"glob": "^13.0.6",
|
|
39
|
+
"html-webpack-plugin": "^5.6.6",
|
|
40
|
+
"picocolors": "^1.1.1",
|
|
41
|
+
"prompts": "^2.4.2",
|
|
42
|
+
"swc-loader": "^0.2.7",
|
|
43
|
+
"webpack": "^5.105.4",
|
|
44
|
+
"webpack-cli": "^6.0.1",
|
|
45
|
+
"webpack-dev-server": "^5.2.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/fs-extra": "^11.0.4",
|
|
49
|
+
"@types/node": "^22.13.5",
|
|
50
|
+
"@types/prompts": "^2.4.9",
|
|
51
|
+
"typescript": "^5.7.3"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/evaijs/evjs#readme",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/evaijs/evjs/issues"
|
|
56
|
+
},
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/evaijs/evjs.git"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"evjs",
|
|
63
|
+
"cli",
|
|
64
|
+
"react",
|
|
65
|
+
"server-functions",
|
|
66
|
+
"scaffolding",
|
|
67
|
+
"dev-server",
|
|
68
|
+
"typescript",
|
|
69
|
+
"full-stack",
|
|
70
|
+
"framework"
|
|
71
|
+
],
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"author": "xusd320"
|
|
74
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "example-basic-csr",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "ev dev",
|
|
7
|
+
"build": "ev build",
|
|
8
|
+
"check-types": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@evjs/runtime": "*",
|
|
12
|
+
"react": "^19.2.4",
|
|
13
|
+
"react-dom": "^19.2.4"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@evjs/cli": "*",
|
|
17
|
+
"@types/react": "^19.0.8",
|
|
18
|
+
"@types/react-dom": "^19.0.3",
|
|
19
|
+
"typescript": "^5.7.3"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createApp } from "@evjs/runtime/client";
|
|
2
|
+
import { rootRoute } from "./pages/__root";
|
|
3
|
+
import { aboutRoute } from "./pages/about";
|
|
4
|
+
import { homeRoute } from "./pages/home";
|
|
5
|
+
import { postDetailRoute, postsIndexRoute, postsRoute } from "./pages/posts";
|
|
6
|
+
|
|
7
|
+
const routeTree = rootRoute.addChildren([
|
|
8
|
+
homeRoute,
|
|
9
|
+
aboutRoute,
|
|
10
|
+
postsRoute.addChildren([postsIndexRoute, postDetailRoute]),
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const app = createApp({ routeTree });
|
|
14
|
+
|
|
15
|
+
// Register router type for full IDE type-safety on useParams, useSearch, Link, etc.
|
|
16
|
+
declare module "@tanstack/react-router" {
|
|
17
|
+
interface Register {
|
|
18
|
+
router: typeof app.router;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
app.render("#app");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createRootRoute, Link, Outlet } from "@evjs/runtime/client";
|
|
2
|
+
|
|
3
|
+
function Root() {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{ fontFamily: "system-ui, sans-serif", padding: "1rem" }}>
|
|
6
|
+
<nav
|
|
7
|
+
style={{
|
|
8
|
+
display: "flex",
|
|
9
|
+
gap: "1rem",
|
|
10
|
+
borderBottom: "1px solid #e5e7eb",
|
|
11
|
+
paddingBottom: "0.5rem",
|
|
12
|
+
marginBottom: "1rem",
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
<Link to="/" style={{ fontWeight: "bold" }}>
|
|
16
|
+
Home
|
|
17
|
+
</Link>
|
|
18
|
+
<Link to="/about">About</Link>
|
|
19
|
+
<Link to="/posts">Posts</Link>
|
|
20
|
+
</nav>
|
|
21
|
+
<Outlet />
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const rootRoute = createRootRoute({
|
|
27
|
+
component: Root,
|
|
28
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createRoute } from "@evjs/runtime/client";
|
|
2
|
+
import { rootRoute } from "./__root";
|
|
3
|
+
|
|
4
|
+
function About() {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<h1>About</h1>
|
|
8
|
+
<p>
|
|
9
|
+
Code-based routing with TanStack Router via <code>createApp</code>.
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const aboutRoute = createRoute({
|
|
16
|
+
getParentRoute: () => rootRoute,
|
|
17
|
+
path: "/about",
|
|
18
|
+
component: About,
|
|
19
|
+
});
|