@emberkit/cli 0.6.8 → 0.6.9

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.
@@ -4,6 +4,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
4
  import { pathToFileURL } from "url";
5
5
  import { cliBrand } from "../brand.js";
6
6
  import { mergeEmberkitViteConfig } from "../utils/merge-emberkit-vite.js";
7
+ import { loadEmberKitConfig, loadViteConfig } from "../utils/load-config.js";
7
8
  const COLORS = {
8
9
  reset: "\x1b[0m",
9
10
  bright: "\x1b[1m",
@@ -29,48 +30,6 @@ function log(level, message) {
29
30
  const emberTag = `${COLORS.ember}[emberkit]${COLORS.reset}`;
30
31
  console.log(`${prefix} ${levelLabel} ${emberTag} ${message}`);
31
32
  }
32
- async function loadEmberKitConfig(root) {
33
- const configPaths = [
34
- join(root, "emberkit.config.ts"),
35
- join(root, "emberkit.config.js"),
36
- join(root, "emberkit.config.mjs"),
37
- ];
38
- for (const configPath of configPaths) {
39
- if (existsSync(configPath)) {
40
- try {
41
- const configUrl = pathToFileURL(configPath).href;
42
- const mod = await import(configUrl);
43
- return mod.default || mod;
44
- }
45
- catch {
46
- continue;
47
- }
48
- }
49
- }
50
- return null;
51
- }
52
- async function loadViteConfig(root) {
53
- const viteConfigPaths = [
54
- join(root, "vite.config.ts"),
55
- join(root, "vite.config.js"),
56
- ];
57
- for (const configPath of viteConfigPaths) {
58
- if (existsSync(configPath)) {
59
- try {
60
- const configUrl = pathToFileURL(configPath).href;
61
- const mod = await import(configUrl);
62
- const config = mod.default || mod;
63
- return typeof config === "function"
64
- ? config({ mode: "production", command: "build" })
65
- : config;
66
- }
67
- catch {
68
- continue;
69
- }
70
- }
71
- }
72
- return null;
73
- }
74
33
  export async function build(_args) {
75
34
  const root = process.cwd();
76
35
  console.log(`\n${cliBrand.logo()} ${COLORS.orange}EmberKit Build${COLORS.reset}\n`);
@@ -129,11 +88,16 @@ export async function build(_args) {
129
88
  }
130
89
  }
131
90
  async function buildClient(root, outDir, viteConfig, customLogger) {
91
+ // Always ensure plugins are properly normalized as an array
92
+ const plugins = (viteConfig?.plugins && Array.isArray(viteConfig.plugins))
93
+ ? viteConfig.plugins
94
+ : (viteConfig?.plugins ? [viteConfig.plugins] : []);
132
95
  const clientConfig = {
133
96
  ...viteConfig,
134
97
  root,
135
98
  customLogger,
136
99
  logLevel: "silent",
100
+ plugins,
137
101
  build: {
138
102
  ...(viteConfig?.build || {}),
139
103
  outDir,
@@ -145,11 +109,12 @@ async function buildClient(root, outDir, viteConfig, customLogger) {
145
109
  },
146
110
  },
147
111
  },
112
+ ssr: undefined,
148
113
  };
149
114
  await viteBuild(clientConfig);
150
115
  }
151
116
  function getServerEntryShim() {
152
- return `import { routes } from 'virtual:emberkit-routes';
117
+ return `import { routes, notFoundRoute, errorRoute } from 'virtual:emberkit-routes';
153
118
  import { createElement } from '@emberkit/core';
154
119
  import { readFileSync } from 'node:fs';
155
120
  import { join, dirname } from 'node:path';
@@ -277,6 +242,7 @@ export async function render(url) {
277
242
 
278
243
  let appHtml = '';
279
244
  let headContent = '';
245
+ let status = 200;
280
246
 
281
247
  if (match) {
282
248
  try {
@@ -296,10 +262,42 @@ export async function render(url) {
296
262
  appHtml = renderToString(element);
297
263
  } catch (e) {
298
264
  console.error('[SSR] Failed to render route:', pathname, e);
299
- appHtml = '<div style="color: red; padding: 20px;">SSR Error: ' + escapeHtml(String(e)) + '</div>';
265
+ if (errorRoute) {
266
+ try {
267
+ status = 500;
268
+ const mod = await errorRoute();
269
+ const Component = mod.default || mod;
270
+ const errorInfo = {
271
+ status: 500,
272
+ message: e instanceof Error ? e.message : 'Internal Server Error',
273
+ error: e,
274
+ };
275
+ const element = createElement(Component, { error: errorInfo });
276
+ appHtml = renderToString(element);
277
+ } catch (fallbackError) {
278
+ console.error('[SSR] Failed to render 500 page:', fallbackError);
279
+ appHtml = '<div style="color: red; padding: 20px;">Internal Server Error</div>';
280
+ }
281
+ } else {
282
+ appHtml = '<div style="color: red; padding: 20px;">SSR Error: ' + escapeHtml(String(e)) + '</div>';
283
+ status = 500;
284
+ }
300
285
  }
301
286
  } else {
302
- appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
287
+ status = 404;
288
+ if (notFoundRoute) {
289
+ try {
290
+ const mod = await notFoundRoute();
291
+ const Component = mod.default || mod;
292
+ const element = createElement(Component, {});
293
+ appHtml = renderToString(element);
294
+ } catch (e) {
295
+ console.error('[SSR] Failed to render 404 page:', e);
296
+ appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
297
+ }
298
+ } else {
299
+ appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
300
+ }
303
301
  }
304
302
 
305
303
  const templatePath = join(__dirname, '..', 'index.html');
@@ -317,7 +315,7 @@ export async function render(url) {
317
315
  template = template.replace('</head>', headContent + '</head>');
318
316
  }
319
317
 
320
- return template;
318
+ return { html: template, status };
321
319
  }
322
320
  `;
323
321
  }
@@ -358,7 +356,7 @@ async function buildSSR(root, outDir, viteConfig, customLogger) {
358
356
  },
359
357
  },
360
358
  ssr: {
361
- noExternal: true,
359
+ noExternal: ['virtual:emberkit-routes'],
362
360
  },
363
361
  };
364
362
  await viteBuild(ssrConfig);
@@ -1,8 +1,6 @@
1
1
  import { createServer } from "vite";
2
- import { join } from "path";
3
- import { existsSync } from "fs";
4
- import { pathToFileURL } from "url";
5
2
  import { mergeEmberkitViteConfig } from "../utils/merge-emberkit-vite.js";
3
+ import { loadEmberKitConfig, loadViteConfig } from "../utils/load-config.js";
6
4
  const COLORS = {
7
5
  reset: "\x1b[0m",
8
6
  bright: "\x1b[1m",
@@ -45,46 +43,13 @@ function log(level, message, meta) {
45
43
  }
46
44
  console.log(output);
47
45
  }
48
- async function loadEmberKitConfig(root) {
49
- const configPath = join(root, "emberkit.config.ts");
50
- const configPathJs = join(root, "emberkit.config.js");
51
- const finalPath = existsSync(configPath) ? configPath : existsSync(configPathJs) ? configPathJs : null;
52
- if (!finalPath) {
53
- return null;
54
- }
55
- try {
56
- const configUrl = pathToFileURL(finalPath).href;
57
- const mod = await import(configUrl);
58
- return mod.default || mod;
59
- }
60
- catch {
61
- return null;
62
- }
63
- }
64
- async function loadViteConfig(root) {
65
- const viteConfigPath = join(root, "vite.config.ts");
66
- const viteConfigPathJs = join(root, "vite.config.js");
67
- const finalPath = existsSync(viteConfigPath) ? viteConfigPath : existsSync(viteConfigPathJs) ? viteConfigPathJs : null;
68
- if (!finalPath) {
69
- return null;
70
- }
71
- try {
72
- const configUrl = pathToFileURL(finalPath).href;
73
- const mod = await import(configUrl);
74
- const config = mod.default || mod;
75
- return typeof config === "function" ? config({ mode: "development", command: "serve" }) : config;
76
- }
77
- catch {
78
- return null;
79
- }
80
- }
81
46
  export async function dev(_args) {
82
47
  const root = process.cwd();
83
48
  console.clear();
84
49
  console.log(`${COLORS.orange}${EMBERKIT_ASCII}${COLORS.reset}`);
85
50
  log("info", "Initializing development server...");
86
51
  const emberkitConfig = await loadEmberKitConfig(root);
87
- const viteFileConfig = await loadViteConfig(root);
52
+ const viteFileConfig = await loadViteConfig(root, "serve");
88
53
  const viteConfig = mergeEmberkitViteConfig(emberkitConfig, viteFileConfig);
89
54
  if (emberkitConfig) {
90
55
  log("debug", "Loaded emberkit.config", { mode: emberkitConfig.mode || "hybrid" });
@@ -1,10 +1,10 @@
1
1
  // Semver ranges for @emberkit/* packages written into generated projects.
2
2
  // When releasing libraries, bump these to match packages/*/package.json "version".
3
3
  export const EMBERKIT_PACKAGE_VERSIONS = {
4
- core: "^0.3.8",
5
- ui: "^1.0.1",
6
- icons: "^1.0.8",
7
- cli: "^0.6.8",
4
+ core: "^0.4.0",
5
+ ui: "^2.0.0",
6
+ icons: "^2.0.0",
7
+ cli: "^0.6.9",
8
8
  edge: "^0.2.4",
9
9
  tsconfig: "^0.2.1",
10
10
  };
@@ -0,0 +1,107 @@
1
+ import { existsSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+ import { pathToFileURL } from "url";
4
+ /**
5
+ * Loads a TypeScript or JavaScript config file in any Node environment.
6
+ *
7
+ * Direct `import()` of `.ts` files only works on runtimes with TypeScript
8
+ * support (e.g. tsx, ts-node, Node ≥ 22 with --experimental-strip-types).
9
+ * Cloudflare Pages CI runs plain Node 18/20, so `.ts` imports silently fail.
10
+ *
11
+ * We use esbuild (a transitive dep of Vite, always present) to bundle the
12
+ * config to a temporary `.mjs` file and import that instead.
13
+ */
14
+ async function transpileAndImport(filePath, root) {
15
+ // Use a cache dir that survives across the two viteBuild calls
16
+ const cacheDir = join(root, "node_modules", ".cache", "emberkit");
17
+ mkdirSync(cacheDir, { recursive: true });
18
+ const outFile = join(cacheDir, `config-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
19
+ try {
20
+ // esbuild is always available as a transitive dependency of Vite
21
+ const { build: esbuild } = await import("esbuild");
22
+ await esbuild({
23
+ entryPoints: [filePath],
24
+ bundle: true,
25
+ format: "esm",
26
+ platform: "node",
27
+ outfile: outFile,
28
+ // Preserve all package imports so they resolve from node_modules at runtime
29
+ packages: "external",
30
+ logLevel: "silent",
31
+ });
32
+ const mod = await import(pathToFileURL(outFile).href);
33
+ return (mod.default ?? mod);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Loads the `emberkit.config.ts` (or `.js` / `.mjs`) for a project root.
41
+ * Returns `null` when no config file is found or loading fails.
42
+ */
43
+ export async function loadEmberKitConfig(root) {
44
+ const candidates = [
45
+ join(root, "emberkit.config.ts"),
46
+ join(root, "emberkit.config.js"),
47
+ join(root, "emberkit.config.mjs"),
48
+ ];
49
+ for (const filePath of candidates) {
50
+ if (!existsSync(filePath))
51
+ continue;
52
+ const ext = filePath.split(".").pop();
53
+ // Plain JS/MJS files can be imported directly
54
+ if (ext === "js" || ext === "mjs") {
55
+ try {
56
+ const mod = await import(pathToFileURL(filePath).href);
57
+ return (mod.default ?? mod);
58
+ }
59
+ catch {
60
+ continue;
61
+ }
62
+ }
63
+ // TypeScript files need transpilation
64
+ const result = await transpileAndImport(filePath, root);
65
+ if (result !== null)
66
+ return result;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Loads the `vite.config.ts` (or `.js`) for a project root.
72
+ * Returns `null` when no config file is found or loading fails.
73
+ */
74
+ export async function loadViteConfig(root, command = "build") {
75
+ const candidates = [
76
+ join(root, "vite.config.ts"),
77
+ join(root, "vite.config.js"),
78
+ ];
79
+ for (const filePath of candidates) {
80
+ if (!existsSync(filePath))
81
+ continue;
82
+ const ext = filePath.split(".").pop();
83
+ let raw = null;
84
+ if (ext === "js") {
85
+ try {
86
+ const mod = await import(pathToFileURL(filePath).href);
87
+ raw = mod.default ?? mod;
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ }
93
+ else {
94
+ raw = await transpileAndImport(filePath, root);
95
+ }
96
+ if (raw === null)
97
+ continue;
98
+ const resolved = typeof raw === "function"
99
+ ? raw({
100
+ mode: command === "serve" ? "development" : "production",
101
+ command,
102
+ })
103
+ : raw;
104
+ return resolved;
105
+ }
106
+ return null;
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emberkit/cli",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "CLI tool for EmberKit projects",
@@ -51,6 +51,7 @@
51
51
  "devDependencies": {
52
52
  "@eslint/js": "^10.0.1",
53
53
  "@types/inquirer": "^9.0.3",
54
+ "esbuild": "^0.28.0",
54
55
  "eslint": "^10.0.0",
55
56
  "eslint-plugin-perfectionist": "^5.9.0",
56
57
  "typescript-eslint": "^8.59.3"