create-lego-one 2.0.20 → 2.0.21
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/package.json +1 -1
- package/template/.cursor/rules/rules.mdc +3 -3
- package/template/CLAUDE.md +3 -3
- package/template/PROMPT.md +3 -3
- package/template/docs/framework/setup/02-development-workflow.md +10 -2
- package/template/docs/framework/setup/03-environment-setup.md +2 -2
- package/template/docs/framework/setup/04-kernel-architecture.md +1 -1
- package/template/docs/framework/setup/05-plugin-system.md +2 -2
- package/template/docs/framework/setup/README.md +4 -0
- package/template/host/package.json +4 -3
- package/template/host/scripts/generate-plugin-manifest.js +88 -0
- package/template/host/src/kernel/plugins/hooks.ts +165 -0
- package/template/host/src/kernel/plugins/plugin-manifest.ts +15 -0
- package/template/host/src/kernel/providers/PocketBaseProvider.tsx +9 -24
- package/template/host/src/layout/Sidebar.tsx +23 -4
- package/template/host/src/routes/page.tsx +32 -34
- package/template/package.json +2 -3
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
package/package.json
CHANGED
|
@@ -609,9 +609,9 @@ Stay pragmatic. Stay reliable. Keep learning.
|
|
|
609
609
|
|
|
610
610
|
```bash
|
|
611
611
|
# Development
|
|
612
|
-
pnpm run dev
|
|
613
|
-
|
|
614
|
-
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start plugin
|
|
612
|
+
pnpm run dev # Start host + all plugins (default)
|
|
613
|
+
pnpm run dev:host # Start host only (:8080) - plugins won't work without their dev servers
|
|
614
|
+
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start specific plugin (requires host running)
|
|
615
615
|
|
|
616
616
|
# Building
|
|
617
617
|
pnpm run build # Build entire project
|
package/template/CLAUDE.md
CHANGED
|
@@ -604,9 +604,9 @@ Stay pragmatic. Stay reliable. Keep learning.
|
|
|
604
604
|
|
|
605
605
|
```bash
|
|
606
606
|
# Development
|
|
607
|
-
pnpm run dev
|
|
608
|
-
|
|
609
|
-
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start plugin
|
|
607
|
+
pnpm run dev # Start host + all plugins (default)
|
|
608
|
+
pnpm run dev:host # Start host only (:8080) - plugins won't work without their dev servers
|
|
609
|
+
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start specific plugin (requires host running)
|
|
610
610
|
|
|
611
611
|
# Building
|
|
612
612
|
pnpm run build # Build entire project
|
package/template/PROMPT.md
CHANGED
|
@@ -435,9 +435,9 @@ Use the template from `/docs/checkpoints/.template.md`
|
|
|
435
435
|
**Key Commands:**
|
|
436
436
|
```bash
|
|
437
437
|
# Development
|
|
438
|
-
pnpm run dev
|
|
439
|
-
pnpm run dev:host
|
|
440
|
-
pnpm --filter './packages/plugins/@lego/plugin-<name>' dev
|
|
438
|
+
pnpm run dev # Start all services (default)
|
|
439
|
+
pnpm run dev:host # Start host only - plugins won't work without their dev servers
|
|
440
|
+
pnpm --filter './packages/plugins/@lego/plugin-<name>' dev # Start specific plugin (requires host running)
|
|
441
441
|
|
|
442
442
|
# Building
|
|
443
443
|
pnpm run build # Build all
|
|
@@ -13,8 +13,8 @@ This guide covers the complete development workflow for Lego-One, including runn
|
|
|
13
13
|
## Quick Start Commands
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
# Start everything (host + all plugins)
|
|
17
|
-
pnpm run dev
|
|
16
|
+
# Start everything (host + all plugins) - default
|
|
17
|
+
pnpm run dev
|
|
18
18
|
|
|
19
19
|
# Start host only
|
|
20
20
|
pnpm run dev:host
|
|
@@ -39,6 +39,10 @@ pnpm test:e2e
|
|
|
39
39
|
### Running All Services
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
+
# Start host and all plugins (default)
|
|
43
|
+
pnpm run dev
|
|
44
|
+
|
|
45
|
+
# Alternative (same as above)
|
|
42
46
|
pnpm run dev:all
|
|
43
47
|
```
|
|
44
48
|
|
|
@@ -57,12 +61,16 @@ pnpm run dev:host
|
|
|
57
61
|
cd host && pnpm run dev
|
|
58
62
|
```
|
|
59
63
|
|
|
64
|
+
**Note:** When running host only, plugins will appear in navigation but won't work without their dev servers running. Use `pnpm run dev` (default) to run everything together.
|
|
65
|
+
|
|
60
66
|
**Dashboard plugin only:**
|
|
61
67
|
```bash
|
|
62
68
|
cd packages/plugins/@lego/plugin-dashboard
|
|
63
69
|
pnpm run dev
|
|
64
70
|
```
|
|
65
71
|
|
|
72
|
+
**Note:** Plugins are micro-frontends that require the host to load them. Running a plugin dev server alone won't work - you need the host running separately.
|
|
73
|
+
|
|
66
74
|
**Todo plugin only:**
|
|
67
75
|
```bash
|
|
68
76
|
cd packages/plugins/@lego/plugin-todo
|
|
@@ -201,8 +201,8 @@ export default SidebarWidget;
|
|
|
201
201
|
### Dev Mode (Separate Servers)
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
|
-
# Each plugin runs independently
|
|
205
|
-
pnpm run dev
|
|
204
|
+
# Each plugin runs independently (default)
|
|
205
|
+
pnpm run dev
|
|
206
206
|
|
|
207
207
|
# Results in:
|
|
208
208
|
# Host: http://localhost:8080
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "host",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "modern dev",
|
|
8
|
-
"build": "modern build",
|
|
7
|
+
"dev": "node scripts/generate-plugin-manifest.js && modern dev",
|
|
8
|
+
"build": "node scripts/generate-plugin-manifest.js && modern build",
|
|
9
|
+
"generate:plugin-manifest": "node scripts/generate-plugin-manifest.js",
|
|
9
10
|
"start": "modern start",
|
|
10
11
|
"lint": "eslint src --ext .ts,.tsx",
|
|
11
12
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Scan plugins directory and extract dev server ports from modern.config.ts files
|
|
10
|
+
*/
|
|
11
|
+
async function scanPlugins() {
|
|
12
|
+
const pluginsDir = join(__dirname, '../../packages/plugins/@lego');
|
|
13
|
+
const entries = {};
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const pluginDirs = await readdir(pluginsDir, { withFileTypes: true });
|
|
17
|
+
|
|
18
|
+
for (const dirent of pluginDirs) {
|
|
19
|
+
if (!dirent.isDirectory() || !dirent.name.startsWith('plugin-')) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pluginName = `@lego/${dirent.name}`;
|
|
24
|
+
const configPath = join(pluginsDir, dirent.name, 'modern.config.ts');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const configContent = await readFile(configPath, 'utf-8');
|
|
28
|
+
|
|
29
|
+
// Extract port from config (look for port: number)
|
|
30
|
+
const portMatch = configContent.match(/port:\s*(\d+)/);
|
|
31
|
+
if (portMatch) {
|
|
32
|
+
const port = parseInt(portMatch[1], 10);
|
|
33
|
+
entries[pluginName] = {
|
|
34
|
+
port,
|
|
35
|
+
url: `http://localhost:${port}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.warn(`[Plugin Scanner] Could not read config for ${pluginName}:`, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('[Plugin Scanner] Error scanning plugins:', error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return entries;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate TypeScript file with plugin dev entries
|
|
51
|
+
*/
|
|
52
|
+
async function generatePluginManifest() {
|
|
53
|
+
const entries = await scanPlugins();
|
|
54
|
+
|
|
55
|
+
const content = `/**
|
|
56
|
+
* Auto-generated plugin dev server manifest
|
|
57
|
+
* This file is generated by scripts/generate-plugin-manifest.js
|
|
58
|
+
* DO NOT EDIT MANUALLY - Run "pnpm run generate:plugin-manifest" to regenerate
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export const PLUGIN_DEV_ENTRIES: Record<string, string> = ${JSON.stringify(
|
|
62
|
+
Object.fromEntries(
|
|
63
|
+
Object.entries(entries).map(([name, { url }]) => [name, url])
|
|
64
|
+
),
|
|
65
|
+
null,
|
|
66
|
+
2
|
|
67
|
+
)};
|
|
68
|
+
|
|
69
|
+
export const PLUGIN_PORTS: Record<string, number> = ${JSON.stringify(
|
|
70
|
+
Object.fromEntries(
|
|
71
|
+
Object.entries(entries).map(([name, { port }]) => [name, port])
|
|
72
|
+
),
|
|
73
|
+
null,
|
|
74
|
+
2
|
|
75
|
+
)};
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const outputPath = join(__dirname, '../src/kernel/plugins/plugin-manifest.ts');
|
|
79
|
+
await writeFile(outputPath, content, 'utf-8');
|
|
80
|
+
|
|
81
|
+
console.log(`[Plugin Scanner] ✅ Generated manifest with ${Object.keys(entries).length} plugins`);
|
|
82
|
+
if (Object.keys(entries).length > 0) {
|
|
83
|
+
console.log('[Plugin Scanner] Plugins:', Object.keys(entries).join(', '));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Run if executed directly
|
|
88
|
+
generatePluginManifest().catch(console.error);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useMemo, useEffect, useState } from 'react';
|
|
2
|
+
import { saasConfig } from '../../saas.config';
|
|
3
|
+
import { usePluginStore } from './store';
|
|
4
|
+
import { PLUGIN_DEV_ENTRIES } from './plugin-manifest';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin route mapping - maps plugin names to their routes
|
|
8
|
+
* This should match modern.runtime.ts configuration
|
|
9
|
+
*/
|
|
10
|
+
const PLUGIN_ROUTES: Record<string, { path: string; label: string; icon: string }> = {
|
|
11
|
+
'@lego/plugin-dashboard': {
|
|
12
|
+
path: '/dashboard',
|
|
13
|
+
label: 'Dashboard',
|
|
14
|
+
icon: 'LayoutDashboard',
|
|
15
|
+
},
|
|
16
|
+
'@lego/plugin-todo': {
|
|
17
|
+
path: '/todos',
|
|
18
|
+
label: 'Todos',
|
|
19
|
+
icon: 'CheckSquare',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isDev = import.meta.env.MODE === 'development';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if a plugin dev server is running
|
|
27
|
+
*/
|
|
28
|
+
async function checkPluginServerAvailable(entryUrl: string): Promise<boolean> {
|
|
29
|
+
try {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000); // 2 second timeout
|
|
32
|
+
|
|
33
|
+
const response = await fetch(entryUrl, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
cache: 'no-cache',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
// If we get any response (even 404), the server is running
|
|
41
|
+
return response.status < 500;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Network error or timeout means server is not running
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hook to check plugin dev server availability
|
|
50
|
+
*/
|
|
51
|
+
function usePluginAvailability() {
|
|
52
|
+
const [availability, setAvailability] = useState<Record<string, boolean>>({});
|
|
53
|
+
const [isChecking, setIsChecking] = useState(true);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isDev) {
|
|
57
|
+
// In production, all enabled plugins are available (bundled)
|
|
58
|
+
const allAvailable: Record<string, boolean> = {};
|
|
59
|
+
saasConfig.plugins.forEach((plugin) => {
|
|
60
|
+
allAvailable[plugin.name] = plugin.enabled;
|
|
61
|
+
});
|
|
62
|
+
setAvailability(allAvailable);
|
|
63
|
+
setIsChecking(false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// In dev mode, check each plugin's dev server
|
|
68
|
+
const checkAllPlugins = async () => {
|
|
69
|
+
const results: Record<string, boolean> = {};
|
|
70
|
+
|
|
71
|
+
for (const plugin of saasConfig.plugins) {
|
|
72
|
+
if (!plugin.enabled) {
|
|
73
|
+
results[plugin.name] = false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get dev server URL for this plugin
|
|
78
|
+
const devEntry = PLUGIN_DEV_ENTRIES[plugin.name];
|
|
79
|
+
|
|
80
|
+
if (devEntry) {
|
|
81
|
+
const isAvailable = await checkPluginServerAvailable(devEntry);
|
|
82
|
+
results[plugin.name] = isAvailable;
|
|
83
|
+
} else {
|
|
84
|
+
// Plugin not configured for dev mode
|
|
85
|
+
results[plugin.name] = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setAvailability(results);
|
|
90
|
+
setIsChecking(false);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
checkAllPlugins();
|
|
94
|
+
|
|
95
|
+
// Poll every 5 seconds to check if servers come online
|
|
96
|
+
const interval = setInterval(checkAllPlugins, 5000);
|
|
97
|
+
|
|
98
|
+
return () => clearInterval(interval);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
return { availability, isChecking };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hook to get enabled plugins with their route information
|
|
106
|
+
* Only returns plugins that are:
|
|
107
|
+
* 1. Enabled in saas.config.ts
|
|
108
|
+
* 2. Have their dev servers running (in dev mode) OR are bundled (in production)
|
|
109
|
+
*/
|
|
110
|
+
export function useEnabledPlugins() {
|
|
111
|
+
const plugins = usePluginStore((state) => state.plugins);
|
|
112
|
+
const { availability, isChecking } = usePluginAvailability();
|
|
113
|
+
|
|
114
|
+
return useMemo(() => {
|
|
115
|
+
// Don't filter while checking (to avoid flickering)
|
|
116
|
+
if (isChecking && isDev) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return saasConfig.plugins
|
|
121
|
+
.filter((plugin) => {
|
|
122
|
+
// Only show plugins that are enabled in saas.config.ts
|
|
123
|
+
if (!plugin.enabled) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if plugin was explicitly disabled in store (admin override)
|
|
128
|
+
const pluginState = plugins[plugin.name];
|
|
129
|
+
if (pluginState && pluginState.enabled === false) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// In dev mode, only show if dev server is available
|
|
134
|
+
if (isDev) {
|
|
135
|
+
return availability[plugin.name] === true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// In production, show if enabled
|
|
139
|
+
return true;
|
|
140
|
+
})
|
|
141
|
+
.map((plugin) => {
|
|
142
|
+
const route = PLUGIN_ROUTES[plugin.name];
|
|
143
|
+
const pluginState = plugins[plugin.name];
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
name: plugin.name,
|
|
147
|
+
path: route?.path || `/${plugin.name.replace('@lego/plugin-', '')}`,
|
|
148
|
+
label: route?.label || pluginState?.manifest?.displayName || plugin.name,
|
|
149
|
+
icon: route?.icon,
|
|
150
|
+
enabled: plugin.enabled,
|
|
151
|
+
loaded: pluginState?.loaded || false,
|
|
152
|
+
available: availability[plugin.name] ?? false,
|
|
153
|
+
manifest: pluginState?.manifest,
|
|
154
|
+
};
|
|
155
|
+
})
|
|
156
|
+
.filter((plugin) => plugin.path && plugin.label); // Only include plugins with valid routes
|
|
157
|
+
}, [plugins, availability, isChecking]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get plugin route info by name
|
|
162
|
+
*/
|
|
163
|
+
export function getPluginRoute(pluginName: string) {
|
|
164
|
+
return PLUGIN_ROUTES[pluginName];
|
|
165
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated plugin dev server manifest
|
|
3
|
+
* This file is generated by scripts/generate-plugin-manifest.js
|
|
4
|
+
* DO NOT EDIT MANUALLY - Run "pnpm run generate:plugin-manifest" to regenerate
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const PLUGIN_DEV_ENTRIES: Record<string, string> = {
|
|
8
|
+
"@lego/plugin-dashboard": "http://localhost:3001",
|
|
9
|
+
"@lego/plugin-todo": "http://localhost:3002"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const PLUGIN_PORTS: Record<string, number> = {
|
|
13
|
+
"@lego/plugin-dashboard": 3001,
|
|
14
|
+
"@lego/plugin-todo": 3002
|
|
15
|
+
};
|
|
@@ -20,37 +20,22 @@ export function PocketBaseProvider({ children }: { children: React.ReactNode })
|
|
|
20
20
|
|
|
21
21
|
const client = new PocketBase(pbUrl);
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Try to fetch the API root to verify connection
|
|
27
|
-
const response = await fetch(`${pbUrl}/api/health`, {
|
|
28
|
-
method: 'GET',
|
|
29
|
-
signal: AbortSignal.timeout(5000) // 5 second timeout
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!response.ok && response.status !== 404) {
|
|
33
|
-
throw new Error(`PocketBase returned status ${response.status}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('[PocketBaseProvider] ✅ PocketBase connection successful');
|
|
37
|
-
} catch (healthError) {
|
|
38
|
-
// If health check fails, PocketBase is likely not running
|
|
39
|
-
const errorMsg = 'PocketBase is not running or not accessible. Please start PocketBase first.';
|
|
40
|
-
console.error('[PocketBaseProvider] ❌ Connection check failed:', healthError);
|
|
41
|
-
setError(errorMsg);
|
|
42
|
-
setIsChecking(false);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
23
|
+
// Skip health check - let PocketBase SDK handle connection errors naturally
|
|
24
|
+
// The client will throw errors when actually used if PocketBase is not available
|
|
25
|
+
setIsChecking(false);
|
|
45
26
|
|
|
46
27
|
// Load stored token
|
|
47
28
|
const storedToken = localStorage.getItem('pocketbase_auth');
|
|
48
29
|
if (storedToken) {
|
|
49
|
-
|
|
30
|
+
try {
|
|
31
|
+
client.authStore.save(storedToken, null);
|
|
32
|
+
} catch (tokenError) {
|
|
33
|
+
console.warn('[PocketBaseProvider] Failed to restore auth token:', tokenError);
|
|
34
|
+
localStorage.removeItem('pocketbase_auth');
|
|
35
|
+
}
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
setPb(client);
|
|
53
|
-
setIsChecking(false);
|
|
54
39
|
console.log('[PocketBaseProvider] ✅ PocketBase client initialized');
|
|
55
40
|
|
|
56
41
|
// Set up auth cleanup on token invalidation
|
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
import { Link, NavLink } from '@modern-js/runtime/router';
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
3
4
|
import { Slot } from '../kernel/plugins/Slot';
|
|
4
5
|
import { SlotName } from '../kernel/plugins/types';
|
|
6
|
+
import { useEnabledPlugins } from '../kernel/plugins/hooks';
|
|
5
7
|
import {
|
|
6
8
|
Home,
|
|
7
|
-
LayoutDashboard,
|
|
8
9
|
Settings,
|
|
9
10
|
ChevronLeft,
|
|
10
11
|
ChevronRight,
|
|
12
|
+
LayoutDashboard,
|
|
11
13
|
CheckSquare,
|
|
14
|
+
type LucideIcon,
|
|
12
15
|
} from 'lucide-react';
|
|
13
16
|
import { cn } from '../kernel/lib/utils';
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// Icon mapping for plugin icons
|
|
19
|
+
const iconMap: Record<string, LucideIcon> = {
|
|
20
|
+
LayoutDashboard,
|
|
21
|
+
CheckSquare,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Base navigation items (always shown)
|
|
25
|
+
const baseNavItems = [
|
|
16
26
|
{ to: '/', icon: Home, label: 'Home' },
|
|
17
|
-
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
|
|
18
|
-
{ to: '/todos', icon: CheckSquare, label: 'Todos' },
|
|
19
27
|
];
|
|
20
28
|
|
|
21
29
|
export function Sidebar() {
|
|
22
30
|
const { sidebarOpen, toggleSidebar, mobileMenuOpen } = useGlobalKernelState();
|
|
31
|
+
const enabledPlugins = useEnabledPlugins();
|
|
32
|
+
|
|
33
|
+
// Build navigation items: base items + enabled plugins
|
|
34
|
+
const navItems = useMemo(() => {
|
|
35
|
+
const pluginNavItems = enabledPlugins.map((plugin) => ({
|
|
36
|
+
to: plugin.path,
|
|
37
|
+
icon: plugin.icon ? iconMap[plugin.icon] : LayoutDashboard,
|
|
38
|
+
label: plugin.label,
|
|
39
|
+
}));
|
|
40
|
+
return [...baseNavItems, ...pluginNavItems];
|
|
41
|
+
}, [enabledPlugins]);
|
|
23
42
|
|
|
24
43
|
return (
|
|
25
44
|
<>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { Link } from '@modern-js/runtime/router';
|
|
2
|
-
import { ArrowRight, LayoutDashboard, CheckSquare, Shield } from 'lucide-react';
|
|
2
|
+
import { ArrowRight, LayoutDashboard, CheckSquare, Shield, type LucideIcon } from 'lucide-react';
|
|
3
3
|
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
4
|
+
import { useEnabledPlugins } from '../kernel/plugins/hooks';
|
|
5
|
+
|
|
6
|
+
// Icon mapping for plugin icons
|
|
7
|
+
const iconMap: Record<string, LucideIcon> = {
|
|
8
|
+
LayoutDashboard,
|
|
9
|
+
CheckSquare,
|
|
10
|
+
};
|
|
4
11
|
|
|
5
12
|
export default function HomePage() {
|
|
6
13
|
const { isAuthenticated, user } = useGlobalKernelState();
|
|
14
|
+
const enabledPlugins = useEnabledPlugins();
|
|
7
15
|
|
|
8
16
|
return (
|
|
9
17
|
<div className="mx-auto max-w-4xl">
|
|
@@ -36,40 +44,30 @@ export default function HomePage() {
|
|
|
36
44
|
|
|
37
45
|
{/* Features */}
|
|
38
46
|
<div className="grid gap-6 md:grid-cols-3">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<p className="mt-2 text-sm text-muted-foreground">
|
|
62
|
-
Manage your tasks with this example plugin
|
|
63
|
-
</p>
|
|
64
|
-
<Link
|
|
65
|
-
to="/todos"
|
|
66
|
-
className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
|
|
67
|
-
>
|
|
68
|
-
View Todos
|
|
69
|
-
<ArrowRight className="ml-1 h-4 w-4" />
|
|
70
|
-
</Link>
|
|
71
|
-
</div>
|
|
47
|
+
{/* Dynamic plugin cards - only show enabled plugins */}
|
|
48
|
+
{enabledPlugins.map((plugin) => {
|
|
49
|
+
const Icon = plugin.icon ? iconMap[plugin.icon] : LayoutDashboard;
|
|
50
|
+
return (
|
|
51
|
+
<div key={plugin.name} className="rounded-xl border bg-card p-6">
|
|
52
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
53
|
+
<Icon className="h-6 w-6" />
|
|
54
|
+
</div>
|
|
55
|
+
<h3 className="mt-4 text-lg font-semibold">{plugin.label}</h3>
|
|
56
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
57
|
+
{plugin.manifest?.description || `Access the ${plugin.label} plugin`}
|
|
58
|
+
</p>
|
|
59
|
+
<Link
|
|
60
|
+
to={plugin.path}
|
|
61
|
+
className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
|
|
62
|
+
>
|
|
63
|
+
View {plugin.label}
|
|
64
|
+
<ArrowRight className="ml-1 h-4 w-4" />
|
|
65
|
+
</Link>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
72
69
|
|
|
70
|
+
{/* Multi-Tenancy card (always shown) */}
|
|
73
71
|
<div className="rounded-xl border bg-card p-6">
|
|
74
72
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
75
73
|
<Shield className="h-6 w-6" />
|
package/template/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lego-one",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Microkernel SaaS OS with Modern.js + Garfish + PocketBase",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "pnpm --filter host dev",
|
|
8
|
+
"dev": "pnpm --filter host dev & pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
9
9
|
"dev:host": "pnpm --filter host dev",
|
|
10
|
-
"dev:plugins": "pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
11
10
|
"dev:all": "pnpm --filter host dev & pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
12
11
|
"build": "pnpm --filter host build && pnpm -r --filter './packages/plugins/@lego/*' build",
|
|
13
12
|
"lint": "pnpm -r lint",
|