bertui 0.1.6 → 0.1.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/LICENSE +20 -20
- package/README.md +1 -1
- package/bin/bertui.js +7 -7
- package/index.js +35 -35
- package/package.json +5 -5
- package/src/build/css-builder.js +83 -83
- package/src/build.js +122 -122
- package/src/cli.js +65 -65
- package/src/client/compiler.js +218 -225
- package/src/config/defaultConfig.js +15 -15
- package/src/config/loadConfig.js +32 -32
- package/src/dev.js +22 -22
- package/src/logger/logger.js +17 -17
- package/src/logger/notes.md +19 -19
- package/src/router/{router.js → Router.js} +128 -128
- package/src/server/dev-server.js +262 -272
- package/src/styles/bertui.css +209 -209
package/src/config/loadConfig.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
// src/config/loadConfig.js
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { defaultConfig } from './defaultConfig.js';
|
|
5
|
-
import logger from '../logger/logger.js';
|
|
6
|
-
|
|
7
|
-
export async function loadConfig(root) {
|
|
8
|
-
const configPath = join(root, 'bertui.config.js');
|
|
9
|
-
|
|
10
|
-
// Check if user created config
|
|
11
|
-
if (existsSync(configPath)) {
|
|
12
|
-
try {
|
|
13
|
-
const userConfig = await import(configPath);
|
|
14
|
-
logger.success('Loaded bertui.config.js');
|
|
15
|
-
|
|
16
|
-
// Merge user config with defaults
|
|
17
|
-
return mergeConfig(defaultConfig, userConfig.default || userConfig);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
logger.error(`Failed to load config. Make sure bertui.config.js is in the root directory: ${error.message}`);
|
|
20
|
-
return defaultConfig;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
logger.info('No config found, using defaults');
|
|
25
|
-
return defaultConfig;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function mergeConfig(defaults, user) {
|
|
29
|
-
return {
|
|
30
|
-
meta: { ...defaults.meta, ...(user.meta || {}) },
|
|
31
|
-
appShell: { ...defaults.appShell, ...(user.appShell || {}) }
|
|
32
|
-
};
|
|
1
|
+
// src/config/loadConfig.js
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { defaultConfig } from './defaultConfig.js';
|
|
5
|
+
import logger from '../logger/logger.js';
|
|
6
|
+
|
|
7
|
+
export async function loadConfig(root) {
|
|
8
|
+
const configPath = join(root, 'bertui.config.js');
|
|
9
|
+
|
|
10
|
+
// Check if user created config
|
|
11
|
+
if (existsSync(configPath)) {
|
|
12
|
+
try {
|
|
13
|
+
const userConfig = await import(configPath);
|
|
14
|
+
logger.success('Loaded bertui.config.js');
|
|
15
|
+
|
|
16
|
+
// Merge user config with defaults
|
|
17
|
+
return mergeConfig(defaultConfig, userConfig.default || userConfig);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error(`Failed to load config. Make sure bertui.config.js is in the root directory: ${error.message}`);
|
|
20
|
+
return defaultConfig;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
logger.info('No config found, using defaults');
|
|
25
|
+
return defaultConfig;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mergeConfig(defaults, user) {
|
|
29
|
+
return {
|
|
30
|
+
meta: { ...defaults.meta, ...(user.meta || {}) },
|
|
31
|
+
appShell: { ...defaults.appShell, ...(user.appShell || {}) }
|
|
32
|
+
};
|
|
33
33
|
}
|
package/src/dev.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
// src/dev.js
|
|
2
|
-
import { compileProject } from './client/compiler.js';
|
|
3
|
-
import { startDevServer } from './server/dev-server.js';
|
|
4
|
-
import logger from './logger/logger.js';
|
|
5
|
-
|
|
6
|
-
export async function startDev(options = {}) {
|
|
7
|
-
const root = options.root || process.cwd();
|
|
8
|
-
const port = options.port || 3000;
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
// Step 1: Compile project
|
|
12
|
-
logger.info('Step 1: Compiling project...');
|
|
13
|
-
await compileProject(root);
|
|
14
|
-
|
|
15
|
-
// Step 2: Start dev server
|
|
16
|
-
logger.info('Step 2: Starting dev server...');
|
|
17
|
-
await startDevServer({ root, port });
|
|
18
|
-
|
|
19
|
-
} catch (error) {
|
|
20
|
-
logger.error(`Dev server failed: ${error.message}`);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
1
|
+
// src/dev.js
|
|
2
|
+
import { compileProject } from './client/compiler.js';
|
|
3
|
+
import { startDevServer } from './server/dev-server.js';
|
|
4
|
+
import logger from './logger/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function startDev(options = {}) {
|
|
7
|
+
const root = options.root || process.cwd();
|
|
8
|
+
const port = options.port || 3000;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Step 1: Compile project
|
|
12
|
+
logger.info('Step 1: Compiling project...');
|
|
13
|
+
await compileProject(root);
|
|
14
|
+
|
|
15
|
+
// Step 2: Start dev server
|
|
16
|
+
logger.info('Step 2: Starting dev server...');
|
|
17
|
+
await startDevServer({ root, port });
|
|
18
|
+
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error(`Dev server failed: ${error.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
23
|
}
|
package/src/logger/logger.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// src/utils/logger.js
|
|
2
|
-
import { createLogger } from 'ernest-logger';
|
|
3
|
-
|
|
4
|
-
// Create logger instance for BertUI
|
|
5
|
-
const logger = createLogger({
|
|
6
|
-
time: true,
|
|
7
|
-
emoji: true,
|
|
8
|
-
level: 'info',
|
|
9
|
-
prefix: '[BertUI]',
|
|
10
|
-
customLevels: {
|
|
11
|
-
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
12
|
-
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
13
|
-
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Export the logger
|
|
1
|
+
// src/utils/logger.js
|
|
2
|
+
import { createLogger } from 'ernest-logger';
|
|
3
|
+
|
|
4
|
+
// Create logger instance for BertUI
|
|
5
|
+
const logger = createLogger({
|
|
6
|
+
time: true,
|
|
7
|
+
emoji: true,
|
|
8
|
+
level: 'info',
|
|
9
|
+
prefix: '[BertUI]',
|
|
10
|
+
customLevels: {
|
|
11
|
+
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
12
|
+
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
13
|
+
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Export the logger
|
|
18
18
|
export default logger;
|
package/src/logger/notes.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
here we will use the ernest logger libraray instead of chalk because i want colourfull logs and not just normal b/w logs the whole module is exported for use in the libraray
|
|
2
|
-
it is a file that is complete and needs no updated later on
|
|
3
|
-
// src/utils/logger.js
|
|
4
|
-
import { createLogger } from 'ernest-logger';
|
|
5
|
-
|
|
6
|
-
// Create logger instance for BertUI
|
|
7
|
-
const logger = createLogger({
|
|
8
|
-
time: true,
|
|
9
|
-
emoji: true,
|
|
10
|
-
level: 'info',
|
|
11
|
-
prefix: '[BertUI]',
|
|
12
|
-
customLevels: {
|
|
13
|
-
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
14
|
-
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
15
|
-
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Export the logger
|
|
1
|
+
here we will use the ernest logger libraray instead of chalk because i want colourfull logs and not just normal b/w logs the whole module is exported for use in the libraray
|
|
2
|
+
it is a file that is complete and needs no updated later on
|
|
3
|
+
// src/utils/logger.js
|
|
4
|
+
import { createLogger } from 'ernest-logger';
|
|
5
|
+
|
|
6
|
+
// Create logger instance for BertUI
|
|
7
|
+
const logger = createLogger({
|
|
8
|
+
time: true,
|
|
9
|
+
emoji: true,
|
|
10
|
+
level: 'info',
|
|
11
|
+
prefix: '[BertUI]',
|
|
12
|
+
customLevels: {
|
|
13
|
+
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
14
|
+
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
15
|
+
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Export the logger
|
|
20
20
|
export default logger;
|
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
// src/router/Router.jsx
|
|
2
|
-
import { useState, useEffect, createContext, useContext } from 'react';
|
|
3
|
-
|
|
4
|
-
// Router context
|
|
5
|
-
const RouterContext = createContext(null);
|
|
6
|
-
|
|
7
|
-
export function useRouter() {
|
|
8
|
-
const context = useContext(RouterContext);
|
|
9
|
-
if (!context) {
|
|
10
|
-
throw new Error('useRouter must be used within a Router component');
|
|
11
|
-
}
|
|
12
|
-
return context;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useParams() {
|
|
16
|
-
const { params } = useRouter();
|
|
17
|
-
return params;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function Router({ routes, children }) {
|
|
21
|
-
const [currentRoute, setCurrentRoute] = useState(null);
|
|
22
|
-
const [params, setParams] = useState({});
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
// Match initial route
|
|
26
|
-
matchAndSetRoute(window.location.pathname);
|
|
27
|
-
|
|
28
|
-
// Handle browser navigation
|
|
29
|
-
const handlePopState = () => {
|
|
30
|
-
matchAndSetRoute(window.location.pathname);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
window.addEventListener('popstate', handlePopState);
|
|
34
|
-
return () => window.removeEventListener('popstate', handlePopState);
|
|
35
|
-
}, []);
|
|
36
|
-
|
|
37
|
-
function matchAndSetRoute(pathname) {
|
|
38
|
-
// Try exact match first (static routes)
|
|
39
|
-
for (const route of routes) {
|
|
40
|
-
if (route.type === 'static' && route.path === pathname) {
|
|
41
|
-
setCurrentRoute(route);
|
|
42
|
-
setParams({});
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Try dynamic routes
|
|
48
|
-
for (const route of routes) {
|
|
49
|
-
if (route.type === 'dynamic') {
|
|
50
|
-
const pattern = route.path.replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
51
|
-
const regex = new RegExp('^' + pattern + '$');
|
|
52
|
-
const match = pathname.match(regex);
|
|
53
|
-
|
|
54
|
-
if (match) {
|
|
55
|
-
// Extract params
|
|
56
|
-
const paramNames = [...route.path.matchAll(/\[([^\]]+)\]/g)].map(m => m[1]);
|
|
57
|
-
const extractedParams = {};
|
|
58
|
-
paramNames.forEach((name, i) => {
|
|
59
|
-
extractedParams[name] = match[i + 1];
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
setCurrentRoute(route);
|
|
63
|
-
setParams(extractedParams);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// No match found - 404
|
|
70
|
-
setCurrentRoute(null);
|
|
71
|
-
setParams({});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function navigate(path) {
|
|
75
|
-
window.history.pushState({}, '', path);
|
|
76
|
-
matchAndSetRoute(path);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const routerValue = {
|
|
80
|
-
currentRoute,
|
|
81
|
-
params,
|
|
82
|
-
navigate,
|
|
83
|
-
pathname: window.location.pathname
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<RouterContext.Provider value={routerValue}>
|
|
88
|
-
{currentRoute ? (
|
|
89
|
-
<currentRoute.component />
|
|
90
|
-
) : (
|
|
91
|
-
children || <NotFound />
|
|
92
|
-
)}
|
|
93
|
-
</RouterContext.Provider>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function Link({ to, children, className, ...props }) {
|
|
98
|
-
const { navigate } = useRouter();
|
|
99
|
-
|
|
100
|
-
function handleClick(e) {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
navigate(to);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<a href={to} onClick={handleClick} className={className} {...props}>
|
|
107
|
-
{children}
|
|
108
|
-
</a>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function NotFound() {
|
|
113
|
-
return (
|
|
114
|
-
<div style={{
|
|
115
|
-
display: 'flex',
|
|
116
|
-
flexDirection: 'column',
|
|
117
|
-
alignItems: 'center',
|
|
118
|
-
justifyContent: 'center',
|
|
119
|
-
minHeight: '100vh',
|
|
120
|
-
fontFamily: 'system-ui, sans-serif'
|
|
121
|
-
}}>
|
|
122
|
-
<h1 style={{ fontSize: '6rem', margin: 0 }}>404</h1>
|
|
123
|
-
<p style={{ fontSize: '1.5rem', color: '#666' }}>Page not found</p>
|
|
124
|
-
<a href="/" style={{ color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }}>
|
|
125
|
-
Go home
|
|
126
|
-
</a>
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
1
|
+
// src/router/Router.jsx
|
|
2
|
+
import { useState, useEffect, createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
// Router context
|
|
5
|
+
const RouterContext = createContext(null);
|
|
6
|
+
|
|
7
|
+
export function useRouter() {
|
|
8
|
+
const context = useContext(RouterContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error('useRouter must be used within a Router component');
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useParams() {
|
|
16
|
+
const { params } = useRouter();
|
|
17
|
+
return params;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Router({ routes, children }) {
|
|
21
|
+
const [currentRoute, setCurrentRoute] = useState(null);
|
|
22
|
+
const [params, setParams] = useState({});
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// Match initial route
|
|
26
|
+
matchAndSetRoute(window.location.pathname);
|
|
27
|
+
|
|
28
|
+
// Handle browser navigation
|
|
29
|
+
const handlePopState = () => {
|
|
30
|
+
matchAndSetRoute(window.location.pathname);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
window.addEventListener('popstate', handlePopState);
|
|
34
|
+
return () => window.removeEventListener('popstate', handlePopState);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
function matchAndSetRoute(pathname) {
|
|
38
|
+
// Try exact match first (static routes)
|
|
39
|
+
for (const route of routes) {
|
|
40
|
+
if (route.type === 'static' && route.path === pathname) {
|
|
41
|
+
setCurrentRoute(route);
|
|
42
|
+
setParams({});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try dynamic routes
|
|
48
|
+
for (const route of routes) {
|
|
49
|
+
if (route.type === 'dynamic') {
|
|
50
|
+
const pattern = route.path.replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
51
|
+
const regex = new RegExp('^' + pattern + '$');
|
|
52
|
+
const match = pathname.match(regex);
|
|
53
|
+
|
|
54
|
+
if (match) {
|
|
55
|
+
// Extract params
|
|
56
|
+
const paramNames = [...route.path.matchAll(/\[([^\]]+)\]/g)].map(m => m[1]);
|
|
57
|
+
const extractedParams = {};
|
|
58
|
+
paramNames.forEach((name, i) => {
|
|
59
|
+
extractedParams[name] = match[i + 1];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
setCurrentRoute(route);
|
|
63
|
+
setParams(extractedParams);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// No match found - 404
|
|
70
|
+
setCurrentRoute(null);
|
|
71
|
+
setParams({});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function navigate(path) {
|
|
75
|
+
window.history.pushState({}, '', path);
|
|
76
|
+
matchAndSetRoute(path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const routerValue = {
|
|
80
|
+
currentRoute,
|
|
81
|
+
params,
|
|
82
|
+
navigate,
|
|
83
|
+
pathname: window.location.pathname
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<RouterContext.Provider value={routerValue}>
|
|
88
|
+
{currentRoute ? (
|
|
89
|
+
<currentRoute.component />
|
|
90
|
+
) : (
|
|
91
|
+
children || <NotFound />
|
|
92
|
+
)}
|
|
93
|
+
</RouterContext.Provider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function Link({ to, children, className, ...props }) {
|
|
98
|
+
const { navigate } = useRouter();
|
|
99
|
+
|
|
100
|
+
function handleClick(e) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
navigate(to);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<a href={to} onClick={handleClick} className={className} {...props}>
|
|
107
|
+
{children}
|
|
108
|
+
</a>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function NotFound() {
|
|
113
|
+
return (
|
|
114
|
+
<div style={{
|
|
115
|
+
display: 'flex',
|
|
116
|
+
flexDirection: 'column',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
minHeight: '100vh',
|
|
120
|
+
fontFamily: 'system-ui, sans-serif'
|
|
121
|
+
}}>
|
|
122
|
+
<h1 style={{ fontSize: '6rem', margin: 0 }}>404</h1>
|
|
123
|
+
<p style={{ fontSize: '1.5rem', color: '#666' }}>Page not found</p>
|
|
124
|
+
<a href="/" style={{ color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }}>
|
|
125
|
+
Go home
|
|
126
|
+
</a>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
129
|
}
|