@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 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, { mode: "development" });
29
+ const config = await loadConfig(cwd);
29
30
  try {
30
31
  await dev(config ?? undefined, { cwd });
31
32
  }
32
33
  catch (err) {
33
- console.error(err);
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, { mode: "production" });
44
+ const config = await loadConfig(cwd);
44
45
  try {
45
46
  await build(config ?? undefined, { cwd });
46
47
  }
47
48
  catch (err) {
48
- console.error(err);
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 EvConfigCtx, type EvPlugin, type ResolvedEvConfig, resolveConfig } from "@evjs/shared";
2
- export { CONFIG_DEFAULTS, type EvConfig, type EvBundlerCtx, type EvConfigCtx, type EvPlugin, type ResolvedEvConfig, resolveConfig, defineConfig, };
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
- // Background: start Node API when server bundle is ready
31
- let apiStarted = false;
89
+ // Track the running API server process for lifecycle management.
90
+ let apiProcess = null;
91
+ let isFirstBuild = true;
32
92
  const handleServerBundleReady = () => {
33
- if (apiStarted)
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
- apiStarted = true;
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
- }).catch(() => {
72
- apiStarted = false;
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
- apiStarted = false;
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
  }
@@ -1,11 +1,8 @@
1
- import type { EvConfig, EvConfigCtx } from "./config.js";
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, ctx?: EvConfigCtx): Promise<EvConfig | undefined>;
8
+ export declare function loadConfig(cwd: string): Promise<EvConfig | undefined>;
@@ -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, ctx) {
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
- let config = mod.default ?? mod;
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.15",
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
@@ -1,2 +0,0 @@
1
- import { CONFIG_DEFAULTS, defineConfig, resolveConfig, } from "@evjs/shared";
2
- export { CONFIG_DEFAULTS, defineConfig, resolveConfig, };