@evjs/cli 0.0.15 → 0.0.17
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/dist/cli.js +6 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +102 -12
- package/dist/load-config.d.ts +2 -5
- package/dist/load-config.js +2 -31
- package/package.json +2 -4
- package/dist/config.d.ts +0 -2
- package/dist/config.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
5
|
+
import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { build, dev } from "./index.js";
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,18 +19,19 @@ program
|
|
|
19
19
|
.name("ev")
|
|
20
20
|
.description("CLI for the evjs framework")
|
|
21
21
|
.version(pkg.version);
|
|
22
|
+
const logger = getLogger(["evjs", "cli"]);
|
|
22
23
|
program
|
|
23
24
|
.command("dev")
|
|
24
25
|
.description("Start development server")
|
|
25
26
|
.action(async () => {
|
|
26
27
|
const cwd = process.cwd();
|
|
27
28
|
const { loadConfig } = await import("./load-config.js");
|
|
28
|
-
const config = await loadConfig(cwd
|
|
29
|
+
const config = await loadConfig(cwd);
|
|
29
30
|
try {
|
|
30
31
|
await dev(config ?? undefined, { cwd });
|
|
31
32
|
}
|
|
32
33
|
catch (err) {
|
|
33
|
-
|
|
34
|
+
logger.error `Failed to start dev server: ${err}`;
|
|
34
35
|
process.exit(1);
|
|
35
36
|
}
|
|
36
37
|
});
|
|
@@ -40,12 +41,12 @@ program
|
|
|
40
41
|
.action(async () => {
|
|
41
42
|
const cwd = process.cwd();
|
|
42
43
|
const { loadConfig } = await import("./load-config.js");
|
|
43
|
-
const config = await loadConfig(cwd
|
|
44
|
+
const config = await loadConfig(cwd);
|
|
44
45
|
try {
|
|
45
46
|
await build(config ?? undefined, { cwd });
|
|
46
47
|
}
|
|
47
48
|
catch (err) {
|
|
48
|
-
|
|
49
|
+
logger.error `Build failed: ${err}`;
|
|
49
50
|
process.exit(1);
|
|
50
51
|
}
|
|
51
52
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CONFIG_DEFAULTS, defineConfig, type EvBundlerCtx, type EvConfig, type
|
|
2
|
-
export { CONFIG_DEFAULTS, type EvConfig, type
|
|
1
|
+
import { CONFIG_DEFAULTS, defineConfig, type EvBuildResult, type EvBundlerCtx, type EvConfig, type EvPlugin, type EvPluginContext, type EvPluginHooks, type ResolvedEvConfig, resolveConfig } from "@evjs/shared";
|
|
2
|
+
export { CONFIG_DEFAULTS, type EvConfig, type EvBuildResult, type EvBundlerCtx, type EvPlugin, type EvPluginContext, type EvPluginHooks, type ResolvedEvConfig, resolveConfig, defineConfig, };
|
|
3
3
|
export interface DevOptions {
|
|
4
4
|
cwd?: string;
|
|
5
5
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,60 @@ async function getBundlerAdapter(config) {
|
|
|
16
16
|
}
|
|
17
17
|
throw new Error(`Bundler '${bundlerName}' is not supported yet.`);
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Run plugin setup() hooks and collect lifecycle hooks.
|
|
21
|
+
*/
|
|
22
|
+
async function collectPluginHooks(plugins, ctx) {
|
|
23
|
+
const allHooks = [];
|
|
24
|
+
for (const plugin of plugins) {
|
|
25
|
+
if (plugin.setup) {
|
|
26
|
+
const hooks = await plugin.setup(ctx);
|
|
27
|
+
if (hooks) {
|
|
28
|
+
allHooks.push(hooks);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return allHooks;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run all buildStart hooks sequentially.
|
|
36
|
+
*/
|
|
37
|
+
async function runBuildStartHooks(hooks) {
|
|
38
|
+
for (const h of hooks) {
|
|
39
|
+
if (h.buildStart) {
|
|
40
|
+
await h.buildStart();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run all buildEnd hooks sequentially.
|
|
46
|
+
*/
|
|
47
|
+
async function runBuildEndHooks(hooks, result) {
|
|
48
|
+
for (const h of hooks) {
|
|
49
|
+
if (h.buildEnd) {
|
|
50
|
+
await h.buildEnd(result);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read build manifests from disk.
|
|
56
|
+
*/
|
|
57
|
+
function readBuildResult(cwd, serverEnabled, isRebuild) {
|
|
58
|
+
const clientManifestPath = serverEnabled
|
|
59
|
+
? path.resolve(cwd, "dist/client/manifest.json")
|
|
60
|
+
: path.resolve(cwd, "dist/manifest.json");
|
|
61
|
+
if (!fs.existsSync(clientManifestPath))
|
|
62
|
+
return null;
|
|
63
|
+
const clientManifest = JSON.parse(fs.readFileSync(clientManifestPath, "utf-8"));
|
|
64
|
+
let serverManifest;
|
|
65
|
+
if (serverEnabled) {
|
|
66
|
+
const serverManifestPath = path.resolve(cwd, "dist/server/manifest.json");
|
|
67
|
+
if (fs.existsSync(serverManifestPath)) {
|
|
68
|
+
serverManifest = JSON.parse(fs.readFileSync(serverManifestPath, "utf-8"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { clientManifest, serverManifest, isRebuild };
|
|
72
|
+
}
|
|
19
73
|
/**
|
|
20
74
|
* Start the development server programmatically.
|
|
21
75
|
*
|
|
@@ -26,19 +80,42 @@ export async function dev(userConfig, options) {
|
|
|
26
80
|
const config = resolveConfig(userConfig);
|
|
27
81
|
const cwd = options?.cwd ?? process.cwd();
|
|
28
82
|
process.env.NODE_ENV ??= "development";
|
|
83
|
+
// Collect plugin hooks
|
|
84
|
+
const pluginCtx = { mode: "development", config };
|
|
85
|
+
const hooks = await collectPluginHooks(config.plugins, pluginCtx);
|
|
86
|
+
// Run buildStart hooks
|
|
87
|
+
await runBuildStartHooks(hooks);
|
|
29
88
|
const bundler = await getBundlerAdapter(config);
|
|
30
|
-
//
|
|
31
|
-
let
|
|
89
|
+
// Track the running API server process for lifecycle management.
|
|
90
|
+
let apiProcess = null;
|
|
91
|
+
let isFirstBuild = true;
|
|
32
92
|
const handleServerBundleReady = () => {
|
|
33
|
-
if (
|
|
93
|
+
if (!config.serverEnabled)
|
|
34
94
|
return;
|
|
35
95
|
const manifestPath = path.resolve(cwd, "dist/server/manifest.json");
|
|
36
96
|
if (!fs.existsSync(manifestPath))
|
|
37
97
|
return;
|
|
38
98
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
99
|
+
if (manifest.version !== 1) {
|
|
100
|
+
logger.warn `Unexpected server manifest version: ${manifest.version}. Expected 1.`;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
39
103
|
if (!manifest.entry)
|
|
40
104
|
return;
|
|
41
|
-
|
|
105
|
+
// Run buildEnd hooks with manifests
|
|
106
|
+
const buildResult = readBuildResult(cwd, config.serverEnabled, !isFirstBuild);
|
|
107
|
+
if (buildResult) {
|
|
108
|
+
runBuildEndHooks(hooks, buildResult).catch((err) => {
|
|
109
|
+
logger.error `Plugin buildEnd hook failed: ${err}`;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
isFirstBuild = false;
|
|
113
|
+
// Kill previous process before restarting (handles both first start and restarts)
|
|
114
|
+
if (apiProcess) {
|
|
115
|
+
logger.info `Restarting API server...`;
|
|
116
|
+
apiProcess.kill();
|
|
117
|
+
apiProcess = null;
|
|
118
|
+
}
|
|
42
119
|
const serverPort = config?.server?.dev?.port ?? CONFIG_DEFAULTS.serverPort;
|
|
43
120
|
const runtimeConfig = config?.server?.runtime ?? "node";
|
|
44
121
|
const [runtime, ...runtimeExtraArgs] = runtimeConfig.split(/\s+/);
|
|
@@ -65,21 +142,24 @@ export async function dev(userConfig, options) {
|
|
|
65
142
|
]
|
|
66
143
|
: [...runtimeExtraArgs, bootstrapPath];
|
|
67
144
|
// Don't await execa here since it's a long-running watch process
|
|
68
|
-
execa(runtime, runtimeArgs, {
|
|
145
|
+
const child = execa(runtime, runtimeArgs, {
|
|
69
146
|
stdio: "inherit",
|
|
70
147
|
env: { ...process.env, NODE_ENV: "development" },
|
|
71
|
-
})
|
|
72
|
-
|
|
148
|
+
});
|
|
149
|
+
apiProcess = child;
|
|
150
|
+
child.catch(() => {
|
|
151
|
+
// Clear reference so the next compilation can restart
|
|
152
|
+
if (apiProcess === child) {
|
|
153
|
+
apiProcess = null;
|
|
154
|
+
}
|
|
73
155
|
});
|
|
74
156
|
}
|
|
75
157
|
catch (err) {
|
|
76
158
|
logger.error `Server runtime failed: ${err}`;
|
|
77
|
-
|
|
159
|
+
apiProcess = null;
|
|
78
160
|
}
|
|
79
161
|
};
|
|
80
|
-
await bundler.dev(config, cwd, {
|
|
81
|
-
onServerBundleReady: handleServerBundleReady,
|
|
82
|
-
});
|
|
162
|
+
await bundler.dev(config, cwd, { onServerBundleReady: handleServerBundleReady }, hooks);
|
|
83
163
|
}
|
|
84
164
|
/**
|
|
85
165
|
* Run a production build programmatically.
|
|
@@ -91,6 +171,16 @@ export async function build(userConfig, options) {
|
|
|
91
171
|
const config = resolveConfig(userConfig);
|
|
92
172
|
const cwd = options?.cwd ?? process.cwd();
|
|
93
173
|
process.env.NODE_ENV ??= "production";
|
|
174
|
+
// Collect plugin hooks
|
|
175
|
+
const pluginCtx = { mode: "production", config };
|
|
176
|
+
const hooks = await collectPluginHooks(config.plugins, pluginCtx);
|
|
177
|
+
// Run buildStart hooks
|
|
178
|
+
await runBuildStartHooks(hooks);
|
|
94
179
|
const bundler = await getBundlerAdapter(config);
|
|
95
|
-
await bundler.build(config, cwd);
|
|
180
|
+
await bundler.build(config, cwd, hooks);
|
|
181
|
+
// Run buildEnd hooks with manifests
|
|
182
|
+
const buildResult = readBuildResult(cwd, config.serverEnabled, false);
|
|
183
|
+
if (buildResult) {
|
|
184
|
+
await runBuildEndHooks(hooks, buildResult);
|
|
185
|
+
}
|
|
96
186
|
}
|
package/dist/load-config.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { EvConfig
|
|
1
|
+
import type { EvConfig } from "@evjs/shared";
|
|
2
2
|
/**
|
|
3
3
|
* Load evjs config from the project root.
|
|
4
4
|
*
|
|
5
5
|
* Looks for `ev.config.ts`, `.js`, or `.mjs` in the given directory.
|
|
6
6
|
* Returns undefined if no config file is found.
|
|
7
|
-
*
|
|
8
|
-
* After loading, all plugin `config` hooks are executed in order.
|
|
9
|
-
* If a plugin injects new plugins, their hooks are also executed.
|
|
10
7
|
*/
|
|
11
|
-
export declare function loadConfig(cwd: string
|
|
8
|
+
export declare function loadConfig(cwd: string): Promise<EvConfig | undefined>;
|
package/dist/load-config.js
CHANGED
|
@@ -1,47 +1,18 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
const CONFIG_FILES = ["ev.config.ts", "ev.config.js", "ev.config.mjs"];
|
|
4
|
-
/**
|
|
5
|
-
* Historically used @swc-node/register, but it causes ERR_REQUIRE_CYCLE_MODULE inside Node 22.
|
|
6
|
-
* Modern evjs relies on Node's native typescript handling or built-in --loader arguments.
|
|
7
|
-
*/
|
|
8
|
-
async function ensureTsLoader() { }
|
|
9
4
|
/**
|
|
10
5
|
* Load evjs config from the project root.
|
|
11
6
|
*
|
|
12
7
|
* Looks for `ev.config.ts`, `.js`, or `.mjs` in the given directory.
|
|
13
8
|
* Returns undefined if no config file is found.
|
|
14
|
-
*
|
|
15
|
-
* After loading, all plugin `config` hooks are executed in order.
|
|
16
|
-
* If a plugin injects new plugins, their hooks are also executed.
|
|
17
9
|
*/
|
|
18
|
-
export async function loadConfig(cwd
|
|
10
|
+
export async function loadConfig(cwd) {
|
|
19
11
|
for (const filename of CONFIG_FILES) {
|
|
20
12
|
const configPath = path.resolve(cwd, filename);
|
|
21
13
|
if (fs.existsSync(configPath)) {
|
|
22
|
-
// Register TS loader for .ts config files
|
|
23
|
-
if (filename.endsWith(".ts")) {
|
|
24
|
-
await ensureTsLoader();
|
|
25
|
-
}
|
|
26
14
|
const mod = await import(configPath);
|
|
27
|
-
|
|
28
|
-
// Execute plugin config hooks
|
|
29
|
-
const currentCtx = ctx ?? { mode: "development" };
|
|
30
|
-
const executedConfigHooks = new Set();
|
|
31
|
-
let hasNewPlugins = true;
|
|
32
|
-
while (hasNewPlugins) {
|
|
33
|
-
hasNewPlugins = false;
|
|
34
|
-
const allPlugins = config.plugins ?? [];
|
|
35
|
-
for (const plugin of allPlugins) {
|
|
36
|
-
if (plugin.config && !executedConfigHooks.has(plugin.name)) {
|
|
37
|
-
config = plugin.config(config, currentCtx) ?? config;
|
|
38
|
-
executedConfigHooks.add(plugin.name);
|
|
39
|
-
hasNewPlugins = true;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return config;
|
|
15
|
+
return mod.default ?? mod;
|
|
45
16
|
}
|
|
46
17
|
}
|
|
47
18
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evjs/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "CLI and configuration layer for the evjs framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,9 +32,7 @@
|
|
|
32
32
|
"@evjs/shared": "*",
|
|
33
33
|
"@logtape/logtape": "^2.0.4",
|
|
34
34
|
"commander": "^12.1.0",
|
|
35
|
-
"execa": "^9.6.1"
|
|
36
|
-
"glob": "^13.0.6",
|
|
37
|
-
"picocolors": "^1.1.1"
|
|
35
|
+
"execa": "^9.6.1"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
40
38
|
"@types/node": "^22.19.15",
|
package/dist/config.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { CONFIG_DEFAULTS, defineConfig, type EvBundlerCtx, type EvConfig, type EvConfigCtx, type EvPlugin, type ResolvedEvConfig, resolveConfig } from "@evjs/shared";
|
|
2
|
-
export { type EvConfig, type EvConfigCtx, type EvBundlerCtx, type ResolvedEvConfig, type EvPlugin, CONFIG_DEFAULTS, defineConfig, resolveConfig, };
|
package/dist/config.js
DELETED