@akinon/ui-shell-dev 1.0.0

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.
@@ -0,0 +1,142 @@
1
+ import cors from 'cors';
2
+ import express from 'express';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { createServer as createViteServer } from 'vite';
6
+
7
+ import type { ShellConfig } from '../types';
8
+
9
+ export class ShellDevServer {
10
+ private app: express.Application;
11
+ private config: ShellConfig;
12
+ private viteServer?: import('vite').ViteDevServer;
13
+
14
+ constructor(config: ShellConfig) {
15
+ this.app = express();
16
+ this.config = config;
17
+ this.setupMiddleware();
18
+ }
19
+
20
+ private setupMiddleware() {
21
+ // Enable CORS for plugin communication
22
+ this.app.use(
23
+ cors({
24
+ origin: true,
25
+ credentials: true
26
+ })
27
+ );
28
+
29
+ // Parse JSON bodies
30
+ this.app.use(express.json());
31
+
32
+ // Serve static assets
33
+ this.app.use('/assets', express.static(path.join(__dirname, '../assets')));
34
+ }
35
+
36
+ async start() {
37
+ try {
38
+ // Create Vite server for the shell app
39
+ this.viteServer = await createViteServer({
40
+ server: {
41
+ middlewareMode: true,
42
+ cors: true
43
+ },
44
+ appType: 'spa',
45
+ root: path.resolve(__dirname, '..')
46
+ });
47
+
48
+ // Use Vite middleware
49
+ this.app.use(this.viteServer.ssrFixStacktrace);
50
+ this.app.use(this.viteServer.middlewares);
51
+
52
+ // API endpoints
53
+ this.setupApiRoutes();
54
+
55
+ // Serve the shell app
56
+ this.app.get('*', async (req, res, next) => {
57
+ if (req.path.startsWith('/api')) {
58
+ return next();
59
+ }
60
+
61
+ try {
62
+ let template = fs.readFileSync(
63
+ path.resolve(__dirname, '../index.html'),
64
+ 'utf-8'
65
+ );
66
+
67
+ template = await this.viteServer.transformIndexHtml(
68
+ req.url,
69
+ template
70
+ );
71
+
72
+ // Inject config
73
+ template = template.replace(
74
+ '<!--SHELL_CONFIG-->',
75
+ `<script>window.__SHELL_CONFIG__ = ${JSON.stringify(this.config)};</script>`
76
+ );
77
+
78
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(template);
79
+ } catch (error) {
80
+ this.viteServer.ssrFixStacktrace(error);
81
+ next(error);
82
+ }
83
+ });
84
+
85
+ // Start server
86
+ const server = this.app.listen(this.config.shell.port, () => {
87
+ console.log(
88
+ `🚀 Shell development server running on http://localhost:${this.config.shell.port}`
89
+ );
90
+ console.log(`📱 Plugin expected at: ${this.config.plugin.url}`);
91
+ console.log(`🎨 Theme: ${this.config.shell.theme}`);
92
+ });
93
+
94
+ // Handle graceful shutdown
95
+ process.on('SIGTERM', () => {
96
+ console.log('🛑 Shutting down shell server...');
97
+ server.close(() => {
98
+ if (this.viteServer) {
99
+ this.viteServer.close();
100
+ }
101
+ process.exit(0);
102
+ });
103
+ });
104
+
105
+ return server;
106
+ } catch (error) {
107
+ console.error('❌ Failed to start shell server:', error);
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ private setupApiRoutes() {
113
+ // Health check
114
+ this.app.get('/api/health', (req, res) => {
115
+ res.json({
116
+ status: 'ok',
117
+ config: this.config,
118
+ timestamp: new Date().toISOString()
119
+ });
120
+ });
121
+
122
+ // Configuration endpoint
123
+ this.app.get('/api/config', (req, res) => {
124
+ res.json(this.config);
125
+ });
126
+
127
+ // Plugin proxy endpoint (for development)
128
+ this.app.get('/api/plugin/health', async (req, res) => {
129
+ try {
130
+ const response = await fetch(`${this.config.plugin.url}/api/health`);
131
+ const data = await response.json();
132
+ res.json({ plugin: data, shell: { status: 'ok' } });
133
+ } catch (error) {
134
+ res.status(502).json({
135
+ error: 'Plugin not available',
136
+ plugin_url: this.config.plugin.url,
137
+ message: error.message
138
+ });
139
+ }
140
+ });
141
+ }
142
+ }
@@ -0,0 +1,17 @@
1
+ import { AkinonUiProvider } from '@akinon/ui-system';
2
+ import React from 'react';
3
+
4
+ import { AppLayout } from './components/AppLayout';
5
+ import type { ShellConfig } from './types';
6
+
7
+ interface ShellAppProps {
8
+ config: ShellConfig;
9
+ }
10
+
11
+ export const ShellApp: React.FC<ShellAppProps> = ({ config }) => {
12
+ return (
13
+ <AkinonUiProvider>
14
+ <AppLayout config={config} />
15
+ </AkinonUiProvider>
16
+ );
17
+ };
@@ -0,0 +1,37 @@
1
+ import type { SidebarItem, ThemeConfig } from '../types';
2
+ import { omnitronSidebarItems, omnitronTheme } from './omnitron';
3
+
4
+ export const themes: Record<string, ThemeConfig> = {
5
+ omnitron: omnitronTheme,
6
+ 'seller-center': omnitronTheme, // Use same for now
7
+ minimal: {
8
+ ...omnitronTheme,
9
+ name: 'minimal',
10
+ colors: {
11
+ ...omnitronTheme.colors,
12
+ sidebar: '#ffffff',
13
+ background: '#ffffff'
14
+ }
15
+ }
16
+ };
17
+
18
+ export const defaultSidebarItems: Record<string, SidebarItem[]> = {
19
+ omnitron: omnitronSidebarItems,
20
+ 'seller-center': omnitronSidebarItems,
21
+ minimal: [
22
+ {
23
+ key: 'plugin',
24
+ label: 'Plugin Area',
25
+ icon: 'plugin',
26
+ active: true
27
+ }
28
+ ]
29
+ };
30
+
31
+ export function getTheme(themeName: string): ThemeConfig {
32
+ return themes[themeName] || themes.omnitron;
33
+ }
34
+
35
+ export function getSidebarItems(themeName: string): SidebarItem[] {
36
+ return defaultSidebarItems[themeName] || defaultSidebarItems.omnitron;
37
+ }
@@ -0,0 +1,66 @@
1
+ import type { ThemeConfig } from '../types';
2
+
3
+ export const omnitronTheme: ThemeConfig = {
4
+ name: 'omnitron',
5
+ colors: {
6
+ primary: '#1890ff',
7
+ secondary: '#52c41a',
8
+ background: '#f0f2f5',
9
+ sidebar: '#001529',
10
+ header: '#ffffff'
11
+ },
12
+ sidebar: {
13
+ width: 240,
14
+ collapsedWidth: 80
15
+ },
16
+ header: {
17
+ height: 64
18
+ }
19
+ };
20
+
21
+ export const omnitronSidebarItems = [
22
+ {
23
+ key: '1',
24
+ label: 'Dashboard',
25
+ icon: 'dashboard'
26
+ },
27
+ {
28
+ key: '2',
29
+ label: 'Products',
30
+ icon: 'shopping',
31
+ children: [
32
+ { key: '2-1', label: 'Product List' },
33
+ { key: '2-2', label: 'Categories' },
34
+ { key: '2-3', label: 'Attributes' }
35
+ ]
36
+ },
37
+ {
38
+ key: '3',
39
+ label: 'Orders',
40
+ icon: 'shopping-cart',
41
+ children: [
42
+ { key: '3-1', label: 'Order List' },
43
+ { key: '3-2', label: 'Order Status' }
44
+ ]
45
+ },
46
+ {
47
+ key: '4',
48
+ label: 'Customers',
49
+ icon: 'user'
50
+ },
51
+ {
52
+ key: '5',
53
+ label: 'Reports',
54
+ icon: 'chart',
55
+ children: [
56
+ { key: '5-1', label: 'Sales Reports' },
57
+ { key: '5-2', label: 'Customer Reports' }
58
+ ]
59
+ },
60
+ {
61
+ key: 'plugin',
62
+ label: 'Plugin Area',
63
+ icon: 'plugin',
64
+ active: true
65
+ }
66
+ ];
package/src/types.ts ADDED
@@ -0,0 +1,40 @@
1
+ export interface ShellConfig {
2
+ plugin: {
3
+ url: string;
4
+ path: string;
5
+ };
6
+ shell: {
7
+ port: number;
8
+ theme: 'omnitron' | 'seller-center' | 'minimal';
9
+ title: string;
10
+ };
11
+ sidebar: {
12
+ items: SidebarItem[];
13
+ };
14
+ }
15
+
16
+ export interface SidebarItem {
17
+ key: string;
18
+ label: string;
19
+ icon?: string;
20
+ active?: boolean;
21
+ children?: SidebarItem[];
22
+ }
23
+
24
+ export interface ThemeConfig {
25
+ name: string;
26
+ colors: {
27
+ primary: string;
28
+ secondary: string;
29
+ background: string;
30
+ sidebar: string;
31
+ header: string;
32
+ };
33
+ sidebar: {
34
+ width: number;
35
+ collapsedWidth: number;
36
+ };
37
+ header: {
38
+ height: number;
39
+ };
40
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@akinon/typescript-config/vite.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["src/*"]
7
+ }
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist"]
11
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,25 @@
1
+ import react from '@vitejs/plugin-react-swc';
2
+ import path from 'path';
3
+ import { defineConfig } from 'vite';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src')
10
+ }
11
+ },
12
+ server: {
13
+ port: 4000,
14
+ cors: true
15
+ },
16
+ build: {
17
+ outDir: 'dist',
18
+ rollupOptions: {
19
+ // Exclude server files from build
20
+ external: (id) => {
21
+ return id.includes('/server/') || id.includes('server');
22
+ }
23
+ }
24
+ }
25
+ });