@emberkit/cli 0.6.7 → 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.
- package/dist/brand.js +21 -0
- package/dist/cli.js +5 -4
- package/dist/commands/build.js +54 -53
- package/dist/commands/create.js +4 -3
- package/dist/commands/dev.js +13 -46
- package/dist/commands/preview.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/emberkit-package-versions.js +4 -4
- package/dist/templates/project-templates/starter-kit/with-ui.js +29 -15
- package/dist/utils/load-config.js +107 -0
- package/dist/utils/merge-emberkit-vite.js +35 -0
- package/package.json +2 -1
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
|
@@ -7,6 +7,7 @@ import { serve } from "./commands/serve.js";
|
|
|
7
7
|
import { create, TEMPLATES } from "./commands/create.js";
|
|
8
8
|
import { generate } from "./utils/generator.js";
|
|
9
9
|
import { toKebabCase } from "./templates/index.js";
|
|
10
|
+
import { cliBrand } from "./brand.js";
|
|
10
11
|
export async function runCLI(args) {
|
|
11
12
|
const [command, ...restArgs] = args.slice(2);
|
|
12
13
|
if (!command) {
|
|
@@ -49,7 +50,7 @@ export async function runCLI(args) {
|
|
|
49
50
|
}
|
|
50
51
|
function showHelp() {
|
|
51
52
|
console.log(`
|
|
52
|
-
|
|
53
|
+
${cliBrand.logo()} EmberKit CLI v${getCliPackageVersion()}
|
|
53
54
|
|
|
54
55
|
Usage: emberkit <command> [options]
|
|
55
56
|
|
|
@@ -134,7 +135,7 @@ async function handleCreate(args) {
|
|
|
134
135
|
}
|
|
135
136
|
function showCreateHelp() {
|
|
136
137
|
console.log(`
|
|
137
|
-
|
|
138
|
+
${cliBrand.logo()} EmberKit — Create a new project
|
|
138
139
|
|
|
139
140
|
Usage: emberkit create [name] [options]
|
|
140
141
|
|
|
@@ -206,10 +207,10 @@ async function runGenerate(args) {
|
|
|
206
207
|
`${typeConfig.dir}/${toKebabCase(name)}${typeConfig.ext}`;
|
|
207
208
|
const result = await generate({ name, path: filePath, template: type });
|
|
208
209
|
if (result.success) {
|
|
209
|
-
console.log(
|
|
210
|
+
console.log(`${cliBrand.spark()} Generated ${type}: ${result.path}`);
|
|
210
211
|
}
|
|
211
212
|
else {
|
|
212
|
-
console.error(
|
|
213
|
+
console.error(`${cliBrand.fail()} ${result.error}`);
|
|
213
214
|
process.exit(1);
|
|
214
215
|
}
|
|
215
216
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -2,6 +2,9 @@ import { build as viteBuild } from "vite";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
|
+
import { cliBrand } from "../brand.js";
|
|
6
|
+
import { mergeEmberkitViteConfig } from "../utils/merge-emberkit-vite.js";
|
|
7
|
+
import { loadEmberKitConfig, loadViteConfig } from "../utils/load-config.js";
|
|
5
8
|
const COLORS = {
|
|
6
9
|
reset: "\x1b[0m",
|
|
7
10
|
bright: "\x1b[1m",
|
|
@@ -27,53 +30,12 @@ function log(level, message) {
|
|
|
27
30
|
const emberTag = `${COLORS.ember}[emberkit]${COLORS.reset}`;
|
|
28
31
|
console.log(`${prefix} ${levelLabel} ${emberTag} ${message}`);
|
|
29
32
|
}
|
|
30
|
-
async function loadEmberKitConfig(root) {
|
|
31
|
-
const configPaths = [
|
|
32
|
-
join(root, "emberkit.config.ts"),
|
|
33
|
-
join(root, "emberkit.config.js"),
|
|
34
|
-
join(root, "emberkit.config.mjs"),
|
|
35
|
-
];
|
|
36
|
-
for (const configPath of configPaths) {
|
|
37
|
-
if (existsSync(configPath)) {
|
|
38
|
-
try {
|
|
39
|
-
const configUrl = pathToFileURL(configPath).href;
|
|
40
|
-
const mod = await import(configUrl);
|
|
41
|
-
return mod.default || mod;
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
async function loadViteConfig(root) {
|
|
51
|
-
const viteConfigPaths = [
|
|
52
|
-
join(root, "vite.config.ts"),
|
|
53
|
-
join(root, "vite.config.js"),
|
|
54
|
-
];
|
|
55
|
-
for (const configPath of viteConfigPaths) {
|
|
56
|
-
if (existsSync(configPath)) {
|
|
57
|
-
try {
|
|
58
|
-
const configUrl = pathToFileURL(configPath).href;
|
|
59
|
-
const mod = await import(configUrl);
|
|
60
|
-
const config = mod.default || mod;
|
|
61
|
-
return typeof config === "function"
|
|
62
|
-
? config({ mode: "production", command: "build" })
|
|
63
|
-
: config;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
33
|
export async function build(_args) {
|
|
73
34
|
const root = process.cwd();
|
|
74
|
-
console.log(`\n${COLORS.orange}
|
|
35
|
+
console.log(`\n${cliBrand.logo()} ${COLORS.orange}EmberKit Build${COLORS.reset}\n`);
|
|
75
36
|
const emberkitConfig = await loadEmberKitConfig(root);
|
|
76
|
-
const
|
|
37
|
+
const viteFileConfig = await loadViteConfig(root);
|
|
38
|
+
const viteConfig = mergeEmberkitViteConfig(emberkitConfig, viteFileConfig);
|
|
77
39
|
const mode = emberkitConfig?.mode || "hybrid";
|
|
78
40
|
const outDir = emberkitConfig?.build?.outDir || "dist";
|
|
79
41
|
log("info", `Build mode: ${COLORS.ember}${mode}${COLORS.reset}`);
|
|
@@ -117,7 +79,7 @@ export async function build(_args) {
|
|
|
117
79
|
await prerenderStaticRoutes(root, outDir, true);
|
|
118
80
|
log("success", "Static build complete!");
|
|
119
81
|
}
|
|
120
|
-
console.log(`\n${COLORS.green}
|
|
82
|
+
console.log(`\n${cliBrand.spark()} ${COLORS.green}Build finished successfully!${COLORS.reset}\n`);
|
|
121
83
|
console.log(`${COLORS.gray} Output: ${COLORS.cyan}./${outDir}${COLORS.reset}\n`);
|
|
122
84
|
}
|
|
123
85
|
catch (error) {
|
|
@@ -126,11 +88,16 @@ export async function build(_args) {
|
|
|
126
88
|
}
|
|
127
89
|
}
|
|
128
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] : []);
|
|
129
95
|
const clientConfig = {
|
|
130
96
|
...viteConfig,
|
|
131
97
|
root,
|
|
132
98
|
customLogger,
|
|
133
99
|
logLevel: "silent",
|
|
100
|
+
plugins,
|
|
134
101
|
build: {
|
|
135
102
|
...(viteConfig?.build || {}),
|
|
136
103
|
outDir,
|
|
@@ -142,11 +109,12 @@ async function buildClient(root, outDir, viteConfig, customLogger) {
|
|
|
142
109
|
},
|
|
143
110
|
},
|
|
144
111
|
},
|
|
112
|
+
ssr: undefined,
|
|
145
113
|
};
|
|
146
114
|
await viteBuild(clientConfig);
|
|
147
115
|
}
|
|
148
116
|
function getServerEntryShim() {
|
|
149
|
-
return `import { routes } from 'virtual:emberkit-routes';
|
|
117
|
+
return `import { routes, notFoundRoute, errorRoute } from 'virtual:emberkit-routes';
|
|
150
118
|
import { createElement } from '@emberkit/core';
|
|
151
119
|
import { readFileSync } from 'node:fs';
|
|
152
120
|
import { join, dirname } from 'node:path';
|
|
@@ -165,7 +133,7 @@ const routeToRegex = (routePath) => {
|
|
|
165
133
|
paramNames.push(name);
|
|
166
134
|
return '([^/]+)';
|
|
167
135
|
});
|
|
168
|
-
return { regex: new RegExp('^' + regexStr + '$'), paramNames };
|
|
136
|
+
return { regex: new RegExp('^' + regexStr.replace(/\\/$/, '/?') + '$'), paramNames };
|
|
169
137
|
};
|
|
170
138
|
|
|
171
139
|
const matchRoute = (routeList, pathname) => {
|
|
@@ -274,6 +242,7 @@ export async function render(url) {
|
|
|
274
242
|
|
|
275
243
|
let appHtml = '';
|
|
276
244
|
let headContent = '';
|
|
245
|
+
let status = 200;
|
|
277
246
|
|
|
278
247
|
if (match) {
|
|
279
248
|
try {
|
|
@@ -293,10 +262,42 @@ export async function render(url) {
|
|
|
293
262
|
appHtml = renderToString(element);
|
|
294
263
|
} catch (e) {
|
|
295
264
|
console.error('[SSR] Failed to render route:', pathname, e);
|
|
296
|
-
|
|
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
|
+
}
|
|
297
285
|
}
|
|
298
286
|
} else {
|
|
299
|
-
|
|
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
|
+
}
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
const templatePath = join(__dirname, '..', 'index.html');
|
|
@@ -314,7 +315,7 @@ export async function render(url) {
|
|
|
314
315
|
template = template.replace('</head>', headContent + '</head>');
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
return template;
|
|
318
|
+
return { html: template, status };
|
|
318
319
|
}
|
|
319
320
|
`;
|
|
320
321
|
}
|
|
@@ -355,7 +356,7 @@ async function buildSSR(root, outDir, viteConfig, customLogger) {
|
|
|
355
356
|
},
|
|
356
357
|
},
|
|
357
358
|
ssr: {
|
|
358
|
-
noExternal:
|
|
359
|
+
noExternal: ['virtual:emberkit-routes'],
|
|
359
360
|
},
|
|
360
361
|
};
|
|
361
362
|
await viteBuild(ssrConfig);
|
|
@@ -486,10 +487,10 @@ async function prerenderStaticRoutes(root, outDir, prerenderAll = false) {
|
|
|
486
487
|
mkdirSync(outputDir, { recursive: true });
|
|
487
488
|
}
|
|
488
489
|
writeFileSync(outputPath, html, "utf-8");
|
|
489
|
-
console.log(` ${
|
|
490
|
+
console.log(` ${cliBrand.spark()} ${COLORS.green}${route.path}${COLORS.reset}`);
|
|
490
491
|
}
|
|
491
492
|
catch (e) {
|
|
492
|
-
console.log(` ${COLORS.red}
|
|
493
|
+
console.log(` ${COLORS.red}◆${COLORS.reset} ${COLORS.red}${route.path} - ${e}${COLORS.reset}`);
|
|
493
494
|
}
|
|
494
495
|
}
|
|
495
496
|
}
|
package/dist/commands/create.js
CHANGED
|
@@ -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 =
|
|
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
|
|
66
|
+
const err = `${BRIGHT_RED}◆${RESET}`;
|
|
66
67
|
console.log(`\n ${err} ${BRIGHT_RED + message + RESET}\n`);
|
|
67
68
|
}
|
|
68
69
|
function printInfo(message) {
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { createServer } from "vite";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { pathToFileURL } from "url";
|
|
5
|
-
const EMBERKIT_ASCII = `
|
|
6
|
-
╔═══════════════════════════════════════╗
|
|
7
|
-
║ ║
|
|
8
|
-
║ 🔥 E M B E R K I T 🔥 ║
|
|
9
|
-
║ ║
|
|
10
|
-
║ ░▒▓█ DEV SERVER █▓▒░ ║
|
|
11
|
-
║ ║
|
|
12
|
-
╚═══════════════════════════════════════╝
|
|
13
|
-
`;
|
|
2
|
+
import { mergeEmberkitViteConfig } from "../utils/merge-emberkit-vite.js";
|
|
3
|
+
import { loadEmberKitConfig, loadViteConfig } from "../utils/load-config.js";
|
|
14
4
|
const COLORS = {
|
|
15
5
|
reset: "\x1b[0m",
|
|
16
6
|
bright: "\x1b[1m",
|
|
@@ -23,6 +13,15 @@ const COLORS = {
|
|
|
23
13
|
yellow: "\x1b[38;5;220m",
|
|
24
14
|
red: "\x1b[38;5;196m",
|
|
25
15
|
};
|
|
16
|
+
const EMBERKIT_ASCII = `
|
|
17
|
+
╔═══════════════════════════════════════╗
|
|
18
|
+
║ ║
|
|
19
|
+
║ ${COLORS.orange}◆${COLORS.reset} E M B E R K I T ${COLORS.orange}◆${COLORS.reset} ║
|
|
20
|
+
║ ║
|
|
21
|
+
║ ░▒▓█ DEV SERVER █▓▒░ ║
|
|
22
|
+
║ ║
|
|
23
|
+
╚═══════════════════════════════════════╝
|
|
24
|
+
`;
|
|
26
25
|
function log(level, message, meta) {
|
|
27
26
|
const timestamp = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
28
27
|
const prefix = `${COLORS.gray}[${timestamp}]${COLORS.reset}`;
|
|
@@ -44,46 +43,14 @@ function log(level, message, meta) {
|
|
|
44
43
|
}
|
|
45
44
|
console.log(output);
|
|
46
45
|
}
|
|
47
|
-
async function loadEmberKitConfig(root) {
|
|
48
|
-
const configPath = join(root, "emberkit.config.ts");
|
|
49
|
-
const configPathJs = join(root, "emberkit.config.js");
|
|
50
|
-
const finalPath = existsSync(configPath) ? configPath : existsSync(configPathJs) ? configPathJs : null;
|
|
51
|
-
if (!finalPath) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const configUrl = pathToFileURL(finalPath).href;
|
|
56
|
-
const mod = await import(configUrl);
|
|
57
|
-
return mod.default || mod;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
async function loadViteConfig(root) {
|
|
64
|
-
const viteConfigPath = join(root, "vite.config.ts");
|
|
65
|
-
const viteConfigPathJs = join(root, "vite.config.js");
|
|
66
|
-
const finalPath = existsSync(viteConfigPath) ? viteConfigPath : existsSync(viteConfigPathJs) ? viteConfigPathJs : null;
|
|
67
|
-
if (!finalPath) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const configUrl = pathToFileURL(finalPath).href;
|
|
72
|
-
const mod = await import(configUrl);
|
|
73
|
-
const config = mod.default || mod;
|
|
74
|
-
return typeof config === "function" ? config({ mode: "development", command: "serve" }) : config;
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
46
|
export async function dev(_args) {
|
|
81
47
|
const root = process.cwd();
|
|
82
48
|
console.clear();
|
|
83
49
|
console.log(`${COLORS.orange}${EMBERKIT_ASCII}${COLORS.reset}`);
|
|
84
50
|
log("info", "Initializing development server...");
|
|
85
51
|
const emberkitConfig = await loadEmberKitConfig(root);
|
|
86
|
-
const
|
|
52
|
+
const viteFileConfig = await loadViteConfig(root, "serve");
|
|
53
|
+
const viteConfig = mergeEmberkitViteConfig(emberkitConfig, viteFileConfig);
|
|
87
54
|
if (emberkitConfig) {
|
|
88
55
|
log("debug", "Loaded emberkit.config", { mode: emberkitConfig.mode || "hybrid" });
|
|
89
56
|
}
|
package/dist/commands/preview.js
CHANGED
package/dist/commands/serve.js
CHANGED
|
@@ -17,7 +17,7 @@ const COLORS = {
|
|
|
17
17
|
const EMBERKIT_ASCII = `
|
|
18
18
|
╔═══════════════════════════════════════╗
|
|
19
19
|
║ ║
|
|
20
|
-
║
|
|
20
|
+
║ ${COLORS.orange}◆${COLORS.reset} E M B E R K I T ${COLORS.orange}◆${COLORS.reset} ║
|
|
21
21
|
║ ║
|
|
22
22
|
║ ░▒▓█ PRODUCTION SERVER █▓▒░ ║
|
|
23
23
|
║ ║
|
|
@@ -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.
|
|
5
|
-
ui: "^
|
|
6
|
-
icons: "^
|
|
7
|
-
cli: "^0.6.
|
|
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
|
};
|
|
@@ -119,9 +119,9 @@ const HomePage: RouteComponent = () => {
|
|
|
119
119
|
const [activeTab, setActiveTab] = createSignal('features');
|
|
120
120
|
|
|
121
121
|
const features = [
|
|
122
|
-
{ icon: '
|
|
123
|
-
{ icon: '
|
|
124
|
-
{ icon: '
|
|
122
|
+
{ icon: 'zap' as const, title: 'Lightning Fast', desc: 'Sub-10KB runtime with tree-shakeable architecture' },
|
|
123
|
+
{ icon: 'book' as const, title: 'TypeScript First', desc: 'Full type safety with intelligent autocomplete' },
|
|
124
|
+
{ icon: 'folder' as const, title: 'File-Based Routing', desc: 'Routes automatically created from your file structure' },
|
|
125
125
|
];
|
|
126
126
|
|
|
127
127
|
const components = [
|
|
@@ -139,8 +139,9 @@ const HomePage: RouteComponent = () => {
|
|
|
139
139
|
<section className="relative text-center py-20">
|
|
140
140
|
<div className="pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[400px] w-[400px] rounded-full bg-ember-500/15 blur-[150px] animate-pulse-glow" />
|
|
141
141
|
<div className="relative z-10 space-y-6 animate-fade-in-down">
|
|
142
|
-
<Badge variant="primary" className="inline-
|
|
143
|
-
|
|
142
|
+
<Badge variant="primary" className="inline-flex items-center gap-2">
|
|
143
|
+
<Icon name="emberkit" size={14} className="text-amber-400 shrink-0 drop-shadow-[0_0_6px_rgba(251,191,36,0.45)]" />
|
|
144
|
+
Welcome to {{name}}
|
|
144
145
|
</Badge>
|
|
145
146
|
<Heading level="h1" size="4xl" weight="bold">
|
|
146
147
|
Built with EmberKit <span className="bg-gradient-to-r from-ember-400 via-ember-500 to-amber-500 bg-clip-text text-transparent">UI System</span>
|
|
@@ -172,7 +173,9 @@ const HomePage: RouteComponent = () => {
|
|
|
172
173
|
<Card key={feature.title} padding="lg" className="relative group hover:border-ember-500/50 transition-all hover:-translate-y-1 cursor-pointer">
|
|
173
174
|
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-ember-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
174
175
|
<div className="relative space-y-3">
|
|
175
|
-
<div className="text-
|
|
176
|
+
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-gradient-to-br from-ember-400/20 via-fuchsia-500/10 to-cyan-500/10 text-ember-300 ring-1 ring-ember-400/25 shadow-[0_0_24px_rgba(236,72,153,0.12)]">
|
|
177
|
+
<Icon name={feature.icon} size={28} className="drop-shadow-[0_0_10px_rgba(249,115,22,0.35)]" />
|
|
178
|
+
</div>
|
|
176
179
|
<Heading level="h3" size="md" weight="semibold">
|
|
177
180
|
{feature.title}
|
|
178
181
|
</Heading>
|
|
@@ -230,10 +233,16 @@ const HomePage: RouteComponent = () => {
|
|
|
230
233
|
</div>
|
|
231
234
|
</div>
|
|
232
235
|
<Alert variant="success">
|
|
233
|
-
|
|
236
|
+
<span className="inline-flex items-start gap-2">
|
|
237
|
+
<Icon name="check" size={18} className="text-emerald-400 shrink-0 mt-0.5" />
|
|
238
|
+
<span>This is a success alert message. Use it to confirm important actions.</span>
|
|
239
|
+
</span>
|
|
234
240
|
</Alert>
|
|
235
241
|
<Alert variant="info">
|
|
236
|
-
|
|
242
|
+
<span className="inline-flex items-start gap-2">
|
|
243
|
+
<Icon name="info" size={18} className="text-sky-400 shrink-0 mt-0.5" />
|
|
244
|
+
<span>This is an info alert. Useful for displaying helpful information.</span>
|
|
245
|
+
</span>
|
|
237
246
|
</Alert>
|
|
238
247
|
</div>
|
|
239
248
|
</Card>
|
|
@@ -284,14 +293,14 @@ const HomePage: RouteComponent = () => {
|
|
|
284
293
|
export default HomePage;`,
|
|
285
294
|
"src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
286
295
|
import { Head } from '@emberkit/core';
|
|
287
|
-
import { Heading, Text, Button, Card, Badge, Alert } from '@emberkit/ui';
|
|
296
|
+
import { Heading, Text, Button, Card, Badge, Alert, Icon } from '@emberkit/ui';
|
|
288
297
|
|
|
289
298
|
const AboutPage: RouteComponent = () => {
|
|
290
299
|
const features = [
|
|
291
|
-
{ icon: '
|
|
292
|
-
{ icon: '
|
|
293
|
-
{ icon: '
|
|
294
|
-
{ icon: '
|
|
300
|
+
{ icon: 'type' as const, title: 'TypeScript-first', desc: 'Full type safety with intelligent autocomplete' },
|
|
301
|
+
{ icon: 'grid' as const, title: 'UI Components', desc: 'Pre-built design system components' },
|
|
302
|
+
{ icon: 'zap' as const, title: 'Tailwind CSS', desc: 'Utility-first styling framework' },
|
|
303
|
+
{ icon: 'folder' as const, title: 'File Routing', desc: 'Automatic routes from file structure' },
|
|
295
304
|
];
|
|
296
305
|
|
|
297
306
|
const techStack = ['EmberKit', 'TypeScript', 'Tailwind CSS', 'Vite', 'JSX', 'Design System'];
|
|
@@ -331,7 +340,9 @@ const AboutPage: RouteComponent = () => {
|
|
|
331
340
|
<div className="grid sm:grid-cols-2 gap-4">
|
|
332
341
|
{features.map((f) => (
|
|
333
342
|
<Card key={f.title} padding="lg" className="hover:border-ember-500/50 transition-all hover:-translate-y-0.5">
|
|
334
|
-
<div className="text-
|
|
343
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-ember-400/20 via-fuchsia-500/10 to-cyan-500/10 text-ember-300 ring-1 ring-ember-400/25 mb-3">
|
|
344
|
+
<Icon name={f.icon} size={24} className="drop-shadow-[0_0_8px_rgba(249,115,22,0.35)]" />
|
|
345
|
+
</div>
|
|
335
346
|
<Heading level="h3" size="md" weight="semibold" className="mb-1">
|
|
336
347
|
{f.title}
|
|
337
348
|
</Heading>
|
|
@@ -359,7 +370,10 @@ const AboutPage: RouteComponent = () => {
|
|
|
359
370
|
|
|
360
371
|
{/* Benefits Alert */}
|
|
361
372
|
<Alert variant="success">
|
|
362
|
-
|
|
373
|
+
<span className="inline-flex items-start gap-2">
|
|
374
|
+
<Icon name="emberkit" size={18} className="text-amber-400 shrink-0 mt-0.5 drop-shadow-[0_0_8px_rgba(251,191,36,0.4)]" />
|
|
375
|
+
<span><strong>Pro Tip:</strong> This template uses the EmberKit design system components. Check the component library documentation to learn about all available components and their capabilities.</span>
|
|
376
|
+
</span>
|
|
363
377
|
</Alert>
|
|
364
378
|
|
|
365
379
|
{/* Back Button */}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function pluginsToArray(plugins) {
|
|
2
|
+
if (plugins == null)
|
|
3
|
+
return [];
|
|
4
|
+
return Array.isArray(plugins) ? plugins : [plugins];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Merges `emberkit.config` `vite` block with optional `vite.config.*` (file wins on top-level keys;
|
|
8
|
+
* `plugins` from both are concatenated in order).
|
|
9
|
+
*/
|
|
10
|
+
export function mergeEmberkitViteConfig(emberkitConfig, viteFileConfig) {
|
|
11
|
+
const fromEmber = emberkitConfig?.vite ?? {};
|
|
12
|
+
const fromFile = viteFileConfig ?? {};
|
|
13
|
+
return {
|
|
14
|
+
...fromEmber,
|
|
15
|
+
...fromFile,
|
|
16
|
+
plugins: [...pluginsToArray(fromEmber.plugins), ...pluginsToArray(fromFile.plugins)],
|
|
17
|
+
server: { ...fromEmber.server, ...fromFile.server },
|
|
18
|
+
define: {
|
|
19
|
+
...fromEmber.define,
|
|
20
|
+
...fromFile.define,
|
|
21
|
+
},
|
|
22
|
+
css: { ...fromEmber.css, ...fromFile.css },
|
|
23
|
+
optimizeDeps: { ...fromEmber.optimizeDeps, ...fromFile.optimizeDeps },
|
|
24
|
+
resolve: { ...fromEmber.resolve, ...fromFile.resolve },
|
|
25
|
+
esbuild: { ...fromEmber.esbuild, ...fromFile.esbuild },
|
|
26
|
+
build: {
|
|
27
|
+
...fromEmber.build,
|
|
28
|
+
...fromFile.build,
|
|
29
|
+
rollupOptions: {
|
|
30
|
+
...(fromEmber.build?.rollupOptions ?? {}),
|
|
31
|
+
...(fromFile.build?.rollupOptions ?? {}),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emberkit/cli",
|
|
3
|
-
"version": "0.6.
|
|
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"
|