@emberkit/cli 0.6.5 → 0.6.8

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/brand.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ANSI terminal brand marks (no emoji) for consistent CLI branding.
3
+ */
4
+ export const cliBrand = {
5
+ orange: "\x1b[38;5;208m",
6
+ cyan: "\x1b[38;5;51m",
7
+ red: "\x1b[38;5;196m",
8
+ reset: "\x1b[0m",
9
+ /** EmberKit logo mark in terminal output */
10
+ logo() {
11
+ return `${cliBrand.orange}◆${cliBrand.reset}`;
12
+ },
13
+ /** Success / highlight accent */
14
+ spark() {
15
+ return `${cliBrand.cyan}✦${cliBrand.reset}`;
16
+ },
17
+ /** Error line prefix */
18
+ fail() {
19
+ return `${cliBrand.red}◆${cliBrand.reset}`;
20
+ },
21
+ };
package/dist/cli.js CHANGED
@@ -3,9 +3,11 @@ import { getCliPackageVersion } from "./cli-package-version.js";
3
3
  import { dev } from "./commands/dev.js";
4
4
  import { build } from "./commands/build.js";
5
5
  import { preview } from "./commands/preview.js";
6
+ import { serve } from "./commands/serve.js";
6
7
  import { create, TEMPLATES } from "./commands/create.js";
7
8
  import { generate } from "./utils/generator.js";
8
9
  import { toKebabCase } from "./templates/index.js";
10
+ import { cliBrand } from "./brand.js";
9
11
  export async function runCLI(args) {
10
12
  const [command, ...restArgs] = args.slice(2);
11
13
  if (!command) {
@@ -22,6 +24,10 @@ export async function runCLI(args) {
22
24
  case "preview":
23
25
  await preview(restArgs);
24
26
  break;
27
+ case "serve":
28
+ case "start":
29
+ await serve(restArgs);
30
+ break;
25
31
  case "create":
26
32
  await handleCreate(restArgs);
27
33
  break;
@@ -44,15 +50,16 @@ export async function runCLI(args) {
44
50
  }
45
51
  function showHelp() {
46
52
  console.log(`
47
- 🔥 EmberKit CLI v${getCliPackageVersion()}
53
+ ${cliBrand.logo()} EmberKit CLI v${getCliPackageVersion()}
48
54
 
49
55
  Usage: emberkit <command> [options]
50
56
 
51
57
  Commands:
52
58
  create [name] Create a new EmberKit project
53
59
  dev Start development server
54
- build Build for production
55
- preview Preview production build
60
+ build Build for production (SSR/hybrid/static/SPA)
61
+ preview Preview production build locally
62
+ serve Start production server (SSR/hybrid)
56
63
  generate <type> <name> Generate a file from a template
57
64
 
58
65
  Options:
@@ -128,7 +135,7 @@ async function handleCreate(args) {
128
135
  }
129
136
  function showCreateHelp() {
130
137
  console.log(`
131
- 🔥 EmberKit — Create a new project
138
+ ${cliBrand.logo()} EmberKit — Create a new project
132
139
 
133
140
  Usage: emberkit create [name] [options]
134
141
 
@@ -200,10 +207,10 @@ async function runGenerate(args) {
200
207
  `${typeConfig.dir}/${toKebabCase(name)}${typeConfig.ext}`;
201
208
  const result = await generate({ name, path: filePath, template: type });
202
209
  if (result.success) {
203
- console.log(`✓ Generated ${type}: ${result.path}`);
210
+ console.log(`${cliBrand.spark()} Generated ${type}: ${result.path}`);
204
211
  }
205
212
  else {
206
- console.error(`✗ ${result.error}`);
213
+ console.error(`${cliBrand.fail()} ${result.error}`);
207
214
  process.exit(1);
208
215
  }
209
216
  }
@@ -1,24 +1,498 @@
1
- import { spawn } from "child_process";
2
- import { platform } from "os";
3
- export async function build(args) {
4
- console.log("🔨 Building for production...\n");
5
- const isWindows = platform() === "win32";
6
- const vite = spawn("vite", ["build", ...args], {
7
- stdio: "inherit",
8
- shell: isWindows,
1
+ import { build as viteBuild } from "vite";
2
+ import { join } from "path";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
+ import { pathToFileURL } from "url";
5
+ import { cliBrand } from "../brand.js";
6
+ import { mergeEmberkitViteConfig } from "../utils/merge-emberkit-vite.js";
7
+ const COLORS = {
8
+ reset: "\x1b[0m",
9
+ bright: "\x1b[1m",
10
+ dim: "\x1b[2m",
11
+ orange: "\x1b[38;5;208m",
12
+ ember: "\x1b[38;5;202m",
13
+ gray: "\x1b[38;5;240m",
14
+ green: "\x1b[38;5;40m",
15
+ cyan: "\x1b[38;5;51m",
16
+ yellow: "\x1b[38;5;220m",
17
+ red: "\x1b[38;5;196m",
18
+ };
19
+ function log(level, message) {
20
+ const timestamp = new Date().toLocaleTimeString("en-US", { hour12: false });
21
+ const prefix = `${COLORS.gray}[${timestamp}]${COLORS.reset}`;
22
+ const levelColors = {
23
+ info: COLORS.cyan,
24
+ warn: COLORS.yellow,
25
+ error: COLORS.red,
26
+ success: COLORS.green,
27
+ };
28
+ const levelLabel = `${levelColors[level]}${level.toUpperCase().padEnd(7)}${COLORS.reset}`;
29
+ const emberTag = `${COLORS.ember}[emberkit]${COLORS.reset}`;
30
+ console.log(`${prefix} ${levelLabel} ${emberTag} ${message}`);
31
+ }
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
+ export async function build(_args) {
75
+ const root = process.cwd();
76
+ console.log(`\n${cliBrand.logo()} ${COLORS.orange}EmberKit Build${COLORS.reset}\n`);
77
+ const emberkitConfig = await loadEmberKitConfig(root);
78
+ const viteFileConfig = await loadViteConfig(root);
79
+ const viteConfig = mergeEmberkitViteConfig(emberkitConfig, viteFileConfig);
80
+ const mode = emberkitConfig?.mode || "hybrid";
81
+ const outDir = emberkitConfig?.build?.outDir || "dist";
82
+ log("info", `Build mode: ${COLORS.ember}${mode}${COLORS.reset}`);
83
+ log("info", `Output directory: ${COLORS.cyan}${outDir}${COLORS.reset}`);
84
+ const customLogger = {
85
+ info: () => { },
86
+ warn: (msg) => log("warn", msg),
87
+ error: (msg) => log("error", msg),
88
+ warnOnce: () => { },
89
+ clearScreen: () => { },
90
+ hasWarned: false,
91
+ hasErrorLogged: () => false,
92
+ };
93
+ try {
94
+ if (mode === "spa") {
95
+ log("info", "Building SPA (client-only)...");
96
+ await buildClient(root, outDir, viteConfig, customLogger);
97
+ log("success", "SPA build complete!");
98
+ }
99
+ else if (mode === "ssr" || mode === "hybrid") {
100
+ log("info", "Building client bundle...");
101
+ await buildClient(root, outDir, viteConfig, customLogger);
102
+ log("info", "Building SSR bundle...");
103
+ await buildSSR(root, outDir, viteConfig, customLogger);
104
+ log("info", "Generating SSR manifest...");
105
+ await generateManifest(root, outDir, mode);
106
+ if (mode === "hybrid") {
107
+ log("info", "Pre-rendering static routes...");
108
+ await prerenderStaticRoutes(root, outDir);
109
+ }
110
+ log("success", `${mode.toUpperCase()} build complete!`);
111
+ }
112
+ else if (mode === "static") {
113
+ log("info", "Building static site...");
114
+ await buildClient(root, outDir, viteConfig, customLogger);
115
+ log("info", "Building SSR bundle for pre-rendering...");
116
+ await buildSSR(root, outDir, viteConfig, customLogger);
117
+ log("info", "Generating manifest...");
118
+ await generateManifest(root, outDir, mode);
119
+ log("info", "Pre-rendering all routes...");
120
+ await prerenderStaticRoutes(root, outDir, true);
121
+ log("success", "Static build complete!");
122
+ }
123
+ console.log(`\n${cliBrand.spark()} ${COLORS.green}Build finished successfully!${COLORS.reset}\n`);
124
+ console.log(`${COLORS.gray} Output: ${COLORS.cyan}./${outDir}${COLORS.reset}\n`);
125
+ }
126
+ catch (error) {
127
+ log("error", `Build failed: ${error instanceof Error ? error.message : String(error)}`);
128
+ process.exit(1);
129
+ }
130
+ }
131
+ async function buildClient(root, outDir, viteConfig, customLogger) {
132
+ const clientConfig = {
133
+ ...viteConfig,
134
+ root,
135
+ customLogger,
136
+ logLevel: "silent",
137
+ build: {
138
+ ...(viteConfig?.build || {}),
139
+ outDir,
140
+ emptyOutDir: true,
141
+ rollupOptions: {
142
+ ...(viteConfig?.build?.rollupOptions || {}),
143
+ output: {
144
+ manualChunks: undefined,
145
+ },
146
+ },
147
+ },
148
+ };
149
+ await viteBuild(clientConfig);
150
+ }
151
+ function getServerEntryShim() {
152
+ return `import { routes } from 'virtual:emberkit-routes';
153
+ import { createElement } from '@emberkit/core';
154
+ import { readFileSync } from 'node:fs';
155
+ import { join, dirname } from 'node:path';
156
+ import { fileURLToPath } from 'node:url';
157
+
158
+ const __dirname = dirname(fileURLToPath(import.meta.url));
159
+
160
+ const routeToRegex = (routePath) => {
161
+ const paramNames = [];
162
+ const regexStr = routePath
163
+ .replace(/:([^/]+)\\*/g, (_, name) => {
164
+ paramNames.push(name);
165
+ return '(.*)';
166
+ })
167
+ .replace(/:([^/]+)/g, (_, name) => {
168
+ paramNames.push(name);
169
+ return '([^/]+)';
9
170
  });
10
- return new Promise((resolve, reject) => {
11
- vite.on("exit", (code) => {
12
- if (code === 0) {
13
- console.log("\n✨ Build complete!");
14
- resolve();
171
+ return { regex: new RegExp('^' + regexStr.replace(/\\/$/, '/?') + '$'), paramNames };
172
+ };
173
+
174
+ const matchRoute = (routeList, pathname) => {
175
+ const normalizedPath = pathname.replace(/\\/+$/, '') || '/';
176
+ const sortedRoutes = [...routeList].sort((a, b) => {
177
+ const aScore = a.path.includes(':') ? 0 : 1;
178
+ const bScore = b.path.includes(':') ? 0 : 1;
179
+ return bScore - aScore;
180
+ });
181
+ for (const route of sortedRoutes) {
182
+ const pattern = routeToRegex(route.path);
183
+ const match = normalizedPath.match(pattern.regex);
184
+ if (match) {
185
+ const params = {};
186
+ pattern.paramNames.forEach((name, i) => { params[name] = match[i + 1]; });
187
+ return { route, params };
188
+ }
189
+ }
190
+ return null;
191
+ };
192
+
193
+ const escapeHtml = (str) => {
194
+ if (typeof str !== 'string') return str;
195
+ return str
196
+ .replace(/&/g, '&amp;')
197
+ .replace(/</g, '&lt;')
198
+ .replace(/>/g, '&gt;')
199
+ .replace(/"/g, '&quot;')
200
+ .replace(/'/g, '&#039;');
201
+ };
202
+
203
+ const renderToString = (element) => {
204
+ if (!element && element !== 0) return '';
205
+ if (typeof element === 'string') return escapeHtml(element);
206
+ if (typeof element === 'number') return String(element);
207
+ if (Array.isArray(element)) return element.map(renderToString).join('');
208
+
209
+ if (typeof element !== 'object' || !element.type) return '';
210
+
211
+ let { type, props } = element;
212
+ props = props || {};
213
+
214
+ let depth = 0;
215
+ while (typeof type === 'function' && depth < 50) {
216
+ depth++;
217
+ try {
218
+ const result = type(props);
219
+ if (result && typeof result === 'object' && result.type) {
220
+ type = result.type;
221
+ props = result.props || {};
222
+ } else if (typeof result === 'string' || typeof result === 'number') {
223
+ return typeof result === 'string' ? escapeHtml(result) : String(result);
224
+ } else if (Array.isArray(result)) {
225
+ return result.map(renderToString).join('');
226
+ } else {
227
+ return '';
228
+ }
229
+ } catch (e) {
230
+ console.error('[SSR render error]', e);
231
+ return '';
232
+ }
233
+ }
234
+
235
+ if (type === 'Fragment' || type === 'React.Fragment') {
236
+ const children = Array.isArray(props.children) ? props.children : [props.children];
237
+ return children.filter(Boolean).map(renderToString).join('');
238
+ }
239
+
240
+ const SELF_CLOSING = new Set(['area','base','br','col','embed','hr','img','input','link','meta','source','track','wbr']);
241
+
242
+ const children = Array.isArray(props.children) ? props.children : (props.children ? [props.children] : []);
243
+ let childHtml = children.filter(c => c != null).map(renderToString).join('');
244
+
245
+ if (props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html) {
246
+ childHtml = props.dangerouslySetInnerHTML.__html;
247
+ }
248
+
249
+ const attrs = Object.entries(props)
250
+ .filter(([k, v]) => k !== 'children' && k !== 'key' && k !== 'dangerouslySetInnerHTML' && v != null && typeof v !== 'function')
251
+ .map(([k, v]) => {
252
+ if (k === 'className') k = 'class';
253
+ if (v === true) return ' ' + k;
254
+ if (v === false) return '';
255
+ if (k === 'style' && typeof v === 'object') {
256
+ const styleStr = Object.entries(v)
257
+ .filter(([, sv]) => sv != null)
258
+ .map(([sp, sv]) => sp.replace(/([A-Z])/g, '-$1').toLowerCase() + ': ' + sv)
259
+ .join('; ');
260
+ return ' ' + k + '="' + escapeHtml(styleStr) + '"';
261
+ }
262
+ return ' ' + k + '="' + escapeHtml(String(v)) + '"';
263
+ })
264
+ .join('');
265
+
266
+ if (SELF_CLOSING.has(type)) {
267
+ return '<' + type + attrs + '/>';
268
+ }
269
+
270
+ return '<' + type + attrs + '>' + childHtml + '</' + type + '>';
271
+ };
272
+
273
+ export async function render(url) {
274
+ const pathname = url.split('?')[0];
275
+
276
+ const match = matchRoute(routes, pathname);
277
+
278
+ let appHtml = '';
279
+ let headContent = '';
280
+
281
+ if (match) {
282
+ try {
283
+ const mod = await match.route.component();
284
+ const Component = mod.default || mod;
285
+
286
+ if (mod.metadata) {
287
+ if (mod.metadata.title) {
288
+ headContent += '<title>' + escapeHtml(mod.metadata.title) + '</title>\\n';
289
+ }
290
+ if (mod.metadata.description) {
291
+ headContent += '<meta name="description" content="' + escapeHtml(mod.metadata.description) + '">\\n';
292
+ }
293
+ }
294
+
295
+ const element = createElement(Component, { params: match.params });
296
+ appHtml = renderToString(element);
297
+ } catch (e) {
298
+ console.error('[SSR] Failed to render route:', pathname, e);
299
+ appHtml = '<div style="color: red; padding: 20px;">SSR Error: ' + escapeHtml(String(e)) + '</div>';
300
+ }
301
+ } else {
302
+ appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
303
+ }
304
+
305
+ const templatePath = join(__dirname, '..', 'index.html');
306
+ let template = readFileSync(templatePath, 'utf-8');
307
+
308
+ if (template.includes('<body id="app">')) {
309
+ template = template.replace('<body id="app">', '<body id="app">' + appHtml);
310
+ } else if (template.includes('<div id="app">')) {
311
+ template = template.replace('<div id="app"></div>', '<div id="app">' + appHtml + '</div>');
312
+ } else if (template.includes('<div id="app"/>')) {
313
+ template = template.replace('<div id="app"/>', '<div id="app">' + appHtml + '</div>');
314
+ }
315
+
316
+ if (headContent && template.includes('</head>')) {
317
+ template = template.replace('</head>', headContent + '</head>');
318
+ }
319
+
320
+ return template;
321
+ }
322
+ `;
323
+ }
324
+ async function resolveSSREntry(root) {
325
+ const userEntryTs = join(root, "src", "entry-server.ts");
326
+ const userEntryTsx = join(root, "src", "entry-server.tsx");
327
+ if (existsSync(userEntryTs)) {
328
+ return userEntryTs;
329
+ }
330
+ if (existsSync(userEntryTsx)) {
331
+ return userEntryTsx;
332
+ }
333
+ const cacheDir = join(root, "node_modules", ".cache", "emberkit");
334
+ mkdirSync(cacheDir, { recursive: true });
335
+ const shimPath = join(cacheDir, "server-entry.js");
336
+ writeFileSync(shimPath, getServerEntryShim(), "utf-8");
337
+ return shimPath;
338
+ }
339
+ async function buildSSR(root, outDir, viteConfig, customLogger) {
340
+ const ssrEntry = await resolveSSREntry(root);
341
+ const ssrConfig = {
342
+ ...viteConfig,
343
+ root,
344
+ customLogger,
345
+ logLevel: "silent",
346
+ build: {
347
+ ...(viteConfig?.build || {}),
348
+ outDir: join(outDir, "server"),
349
+ emptyOutDir: true,
350
+ ssr: true,
351
+ rollupOptions: {
352
+ ...(viteConfig?.build?.rollupOptions || {}),
353
+ input: ssrEntry,
354
+ output: {
355
+ format: "esm",
356
+ entryFileNames: "entry-server.js",
357
+ },
358
+ },
359
+ },
360
+ ssr: {
361
+ noExternal: true,
362
+ },
363
+ };
364
+ await viteBuild(ssrConfig);
365
+ }
366
+ async function generateManifest(root, outDir, mode) {
367
+ const routesDir = join(root, "src", "routes");
368
+ const routes = [];
369
+ if (existsSync(routesDir)) {
370
+ const files = await scanRouteFiles(routesDir);
371
+ for (const file of files) {
372
+ const relativePath = file.replace(routesDir, "").replace(/\\/g, "/");
373
+ if (relativePath.includes("_layout") || relativePath.includes("_error") || relativePath.includes("_loading")) {
374
+ continue;
375
+ }
376
+ if (relativePath.startsWith("/_api/") || relativePath.includes("/_api/")) {
377
+ continue;
378
+ }
379
+ let routePath = relativePath
380
+ .replace(/\.(tsx|ts|jsx|js|md|mdx)$/, "")
381
+ .replace(/(^|\/)index$/, "$1")
382
+ .replace(/\[\.\.\.(\w+)\]/g, ":$1*")
383
+ .replace(/\[([^\]]+)\]/g, ":$1");
384
+ if (routePath === "" || routePath === "/") {
385
+ routePath = "/";
15
386
  }
16
387
  else {
17
- reject(new Error(`Build failed with code ${code}`));
388
+ routePath = routePath.startsWith("/") ? routePath : "/" + routePath;
18
389
  }
19
- });
20
- vite.on("error", (error) => {
21
- reject(error);
22
- });
390
+ const isStatic = !routePath.includes(":");
391
+ routes.push({
392
+ path: routePath,
393
+ file: relativePath,
394
+ isStatic,
395
+ });
396
+ }
397
+ }
398
+ routes.sort((a, b) => {
399
+ const aScore = a.path.includes(":") ? 0 : 1;
400
+ const bScore = b.path.includes(":") ? 0 : 1;
401
+ return bScore - aScore;
23
402
  });
403
+ const indexHtmlPath = join(root, outDir, "index.html");
404
+ let template = "";
405
+ if (existsSync(indexHtmlPath)) {
406
+ template = readFileSync(indexHtmlPath, "utf-8");
407
+ }
408
+ const manifest = {
409
+ mode,
410
+ routes,
411
+ clientEntry: "/src/index.tsx",
412
+ template,
413
+ buildTime: new Date().toISOString(),
414
+ };
415
+ const manifestPath = join(root, outDir, "ssr-manifest.json");
416
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
417
+ }
418
+ async function scanRouteFiles(dir) {
419
+ const { readdir, stat } = await import("fs/promises");
420
+ const files = [];
421
+ const extensions = new Set(["tsx", "ts", "jsx", "js", "md", "mdx"]);
422
+ async function walk(currentDir) {
423
+ let entries;
424
+ try {
425
+ entries = await readdir(currentDir);
426
+ }
427
+ catch {
428
+ return;
429
+ }
430
+ for (const entry of entries) {
431
+ const fullPath = join(currentDir, entry);
432
+ const fileStat = await stat(fullPath);
433
+ if (fileStat.isDirectory()) {
434
+ await walk(fullPath);
435
+ }
436
+ else {
437
+ const ext = entry.split(".").pop() ?? "";
438
+ if (extensions.has(ext)) {
439
+ files.push(fullPath);
440
+ }
441
+ }
442
+ }
443
+ }
444
+ await walk(dir);
445
+ return files;
446
+ }
447
+ async function prerenderStaticRoutes(root, outDir, prerenderAll = false) {
448
+ const manifestPath = join(root, outDir, "ssr-manifest.json");
449
+ if (!existsSync(manifestPath)) {
450
+ log("warn", "No SSR manifest found, skipping pre-rendering");
451
+ return;
452
+ }
453
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
454
+ const serverEntryPath = join(root, outDir, "server", "entry-server.js");
455
+ if (!existsSync(serverEntryPath)) {
456
+ log("warn", "Server entry not found, skipping pre-rendering");
457
+ return;
458
+ }
459
+ let serverModule;
460
+ try {
461
+ const serverUrl = pathToFileURL(serverEntryPath).href;
462
+ serverModule = await import(serverUrl);
463
+ }
464
+ catch (e) {
465
+ log("warn", `Failed to load server module: ${e}`);
466
+ return;
467
+ }
468
+ const routesToPrerender = manifest.routes.filter((route) => prerenderAll || route.isStatic);
469
+ for (const route of routesToPrerender) {
470
+ if (route.path.includes(":")) {
471
+ continue;
472
+ }
473
+ try {
474
+ let html;
475
+ if (serverModule.render) {
476
+ html = await serverModule.render(route.path);
477
+ }
478
+ else if (serverModule.default && typeof serverModule.default === "function") {
479
+ html = "<!DOCTYPE html><html><head></head><body></body></html>";
480
+ }
481
+ else {
482
+ continue;
483
+ }
484
+ const outputPath = route.path === "/"
485
+ ? join(root, outDir, "index.html")
486
+ : join(root, outDir, route.path, "index.html");
487
+ const outputDir = outputPath.replace(/\/index\.html$/, "");
488
+ if (!existsSync(outputDir)) {
489
+ mkdirSync(outputDir, { recursive: true });
490
+ }
491
+ writeFileSync(outputPath, html, "utf-8");
492
+ console.log(` ${cliBrand.spark()} ${COLORS.green}${route.path}${COLORS.reset}`);
493
+ }
494
+ catch (e) {
495
+ console.log(` ${COLORS.red}◆${COLORS.reset} ${COLORS.red}${route.path} - ${e}${COLORS.reset}`);
496
+ }
497
+ }
24
498
  }
@@ -10,6 +10,7 @@ import { blogTemplate } from "../templates/project-templates/blog/blog.js";
10
10
  import { saasTemplate } from "../templates/project-templates/saas/saas.js";
11
11
  import { dashboardTemplate } from "../templates/project-templates/dashboard/dashboard.js";
12
12
  import { apiTemplate } from "../templates/project-templates/api/api.js";
13
+ import { cliBrand } from "../brand.js";
13
14
  const RESET = "\x1b[0m";
14
15
  const BOLD = "\x1b[1m";
15
16
  const DIM = "\x1b[2m";
@@ -45,7 +46,7 @@ function printTemplateList() {
45
46
  function printHeader() {
46
47
  const header = `
47
48
  ${BRIGHT_BLACK}╭─────────────────────────────────────────────────────╮${RESET}
48
- ${BRIGHT_BLACK}│${RESET} ${ORANGE_BG}${BRIGHT_BLACK} EmberKit ${RESET} ${BRIGHT_BLACK}│${RESET}
49
+ ${BRIGHT_BLACK}│${RESET} ${cliBrand.logo()} ${ORANGE_BG}${BRIGHT_BLACK} EmberKit ${RESET} ${BRIGHT_BLACK}│${RESET}
49
50
  ${BRIGHT_BLACK}│${RESET} ${DIM}A minimalist TypeScript-first JSX framework${RESET} ${BRIGHT_BLACK}│${RESET}
50
51
  ${BRIGHT_BLACK}╰─────────────────────────────────────────────────────╯${RESET}
51
52
  `;
@@ -58,11 +59,11 @@ function printStep(step, total, message) {
58
59
  console.log(` ${numStr} ${BRIGHT_WHITE + message + RESET} ${bar}`);
59
60
  }
60
61
  function printSuccess(message) {
61
- const check = BRIGHT_GREEN + "✓" + RESET;
62
+ const check = cliBrand.spark();
62
63
  console.log(`\n ${check} ${BRIGHT_GREEN + message + RESET}\n`);
63
64
  }
64
65
  function printError(message) {
65
- const err = BRIGHT_RED + "✗" + RESET;
66
+ const err = `${BRIGHT_RED}◆${RESET}`;
66
67
  console.log(`\n ${err} ${BRIGHT_RED + message + RESET}\n`);
67
68
  }
68
69
  function printInfo(message) {