@lego-box/shell 1.0.6 → 1.0.7
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/.krasrc +13 -0
- package/dist/emulator/lego-box-shell-1.0.7.tgz +0 -0
- package/package.json +5 -1
- package/postcss.config.js +6 -0
- package/src/config/env.ts +12 -10
- package/src/services/telemetry.ts +48 -1
- package/tailwind.config.js +86 -0
- package/webpack.config.js +89 -0
- package/dist/emulator/lego-box-shell-1.0.6.tgz +0 -0
package/.krasrc
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lego-box/shell",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Piral microfrontend shell for Lego Box - provides authentication, PocketBase integration, RBAC, and shared UI components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"piral",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"dist/emulator",
|
|
27
27
|
"pilet.ts",
|
|
28
28
|
"src",
|
|
29
|
+
"webpack.config.js",
|
|
30
|
+
"tailwind.config.js",
|
|
31
|
+
"postcss.config.js",
|
|
32
|
+
".krasrc",
|
|
29
33
|
"README.md"
|
|
30
34
|
],
|
|
31
35
|
"main": "dist/emulator/index.js",
|
package/src/config/env.ts
CHANGED
|
@@ -67,13 +67,15 @@ function validateEnvConfig(raw: EnvConfig): EnvConfig {
|
|
|
67
67
|
return raw;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/** Safe access to process.env (polyfilled by webpack when shell config is merged) */
|
|
71
|
+
const _env = typeof process !== 'undefined' ? process.env : {};
|
|
72
|
+
|
|
70
73
|
/**
|
|
71
|
-
* Build-time env vars (injected by Webpack DefinePlugin).
|
|
72
|
-
*
|
|
73
|
-
* Dynamic access leaves process in the bundle and throws "process is not defined" in browser.
|
|
74
|
+
* Build-time env vars (injected by Webpack DefinePlugin when shell webpack config is used).
|
|
75
|
+
* Falls back to _env when process is not polyfilled (e.g. Webpack 15 without config merge).
|
|
74
76
|
*/
|
|
75
77
|
function parseFeedUrls(): string[] {
|
|
76
|
-
const viteRaw =
|
|
78
|
+
const viteRaw = _env.VITE_FEED_URLS;
|
|
77
79
|
if (viteRaw && typeof viteRaw === 'string') {
|
|
78
80
|
try {
|
|
79
81
|
const parsed = JSON.parse(viteRaw) as unknown;
|
|
@@ -84,7 +86,7 @@ function parseFeedUrls(): string[] {
|
|
|
84
86
|
/* fall through */
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
const feedUrl =
|
|
89
|
+
const feedUrl = _env.FEED_URL;
|
|
88
90
|
if (feedUrl && typeof feedUrl === 'string' && feedUrl.trim()) {
|
|
89
91
|
return [feedUrl.trim()];
|
|
90
92
|
}
|
|
@@ -92,11 +94,11 @@ function parseFeedUrls(): string[] {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
const rawEnv: EnvConfig = {
|
|
95
|
-
pocketbaseUrl:
|
|
96
|
-
adminEmail:
|
|
97
|
-
adminPassword:
|
|
98
|
-
appName:
|
|
99
|
-
appIdentifier:
|
|
97
|
+
pocketbaseUrl: _env.VITE_POCKETBASE_URL || 'http://localhost:8090',
|
|
98
|
+
adminEmail: _env.VITE_POCKETBASE_ADMIN_EMAIL || 'superuser@legobox.local',
|
|
99
|
+
adminPassword: _env.VITE_POCKETBASE_ADMIN_PASSWORD || 'SuperUser123!',
|
|
100
|
+
appName: _env.VITE_APP_NAME || 'Lego Box',
|
|
101
|
+
appIdentifier: _env.VITE_APP_IDENTIFIER || 'lego-box-default',
|
|
100
102
|
feedUrls: parseFeedUrls(),
|
|
101
103
|
};
|
|
102
104
|
|
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telemetry Service
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Handles tracking of user activities.
|
|
5
5
|
* Integrates with PocketBase for data storage.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import PocketBase from 'pocketbase';
|
|
9
9
|
|
|
10
|
+
/** Generate a unique session ID */
|
|
11
|
+
export function generateSessionId(): string {
|
|
12
|
+
return `session_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Start a user session (track session start in PocketBase if collection exists)
|
|
17
|
+
*/
|
|
18
|
+
export async function startUserSession(
|
|
19
|
+
pocketbase: PocketBase,
|
|
20
|
+
userId: string,
|
|
21
|
+
sessionId: string,
|
|
22
|
+
meta?: { userAgent?: string }
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
try {
|
|
25
|
+
await pocketbase.collection('user_sessions').create({
|
|
26
|
+
user_id: userId,
|
|
27
|
+
session_id: sessionId,
|
|
28
|
+
started_at: new Date().toISOString(),
|
|
29
|
+
user_agent: meta?.userAgent,
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
// Collection may not exist yet - ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* End a user session (update session end time in PocketBase if collection exists)
|
|
38
|
+
*/
|
|
39
|
+
export async function endUserSession(
|
|
40
|
+
pocketbase: PocketBase,
|
|
41
|
+
sessionId: string
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
const records = await pocketbase.collection('user_sessions').getList(1, 1, {
|
|
45
|
+
filter: `session_id="${sessionId}"`,
|
|
46
|
+
});
|
|
47
|
+
if (records.items.length > 0) {
|
|
48
|
+
await pocketbase.collection('user_sessions').update(records.items[0].id, {
|
|
49
|
+
ended_at: new Date().toISOString(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Collection may not exist yet - ignore
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
10
57
|
/**
|
|
11
58
|
* Track a user login event
|
|
12
59
|
*/
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
darkMode: ['class'],
|
|
4
|
+
content: [
|
|
5
|
+
'./src/**/*.{ts,tsx}',
|
|
6
|
+
'../ui-kit/src/**/*.{ts,tsx}',
|
|
7
|
+
'../ui-kit/dist/**/*.js',
|
|
8
|
+
'../ui-kit/dist/**/*.mjs',
|
|
9
|
+
],
|
|
10
|
+
theme: {
|
|
11
|
+
extend: {
|
|
12
|
+
colors: {
|
|
13
|
+
border: 'hsl(var(--border))',
|
|
14
|
+
input: 'hsl(var(--input))',
|
|
15
|
+
ring: 'hsl(var(--ring))',
|
|
16
|
+
background: 'hsl(var(--background))',
|
|
17
|
+
foreground: 'hsl(var(--foreground))',
|
|
18
|
+
primary: {
|
|
19
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
20
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
21
|
+
},
|
|
22
|
+
secondary: {
|
|
23
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
24
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
25
|
+
},
|
|
26
|
+
destructive: {
|
|
27
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
28
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
29
|
+
},
|
|
30
|
+
muted: {
|
|
31
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
32
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
33
|
+
},
|
|
34
|
+
accent: {
|
|
35
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
36
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
37
|
+
},
|
|
38
|
+
card: {
|
|
39
|
+
DEFAULT: 'hsl(var(--card))',
|
|
40
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
41
|
+
},
|
|
42
|
+
success: {
|
|
43
|
+
DEFAULT: 'hsl(var(--success))',
|
|
44
|
+
foreground: 'hsl(var(--success-foreground))',
|
|
45
|
+
},
|
|
46
|
+
warning: {
|
|
47
|
+
DEFAULT: 'hsl(var(--warning))',
|
|
48
|
+
foreground: 'hsl(var(--warning-foreground))',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
borderRadius: {
|
|
52
|
+
lg: 'var(--radius)',
|
|
53
|
+
md: 'calc(var(--radius) - 2px)',
|
|
54
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
55
|
+
},
|
|
56
|
+
fontFamily: {
|
|
57
|
+
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
|
58
|
+
},
|
|
59
|
+
boxShadow: {
|
|
60
|
+
'premium-glow': '0 0 20px rgba(99, 102, 241, 0.15)',
|
|
61
|
+
'premium-glow-lg': '0 0 40px rgba(99, 102, 241, 0.2)',
|
|
62
|
+
elevated: '0 8px 30px rgba(37, 37, 64, 0.6)',
|
|
63
|
+
},
|
|
64
|
+
animation: {
|
|
65
|
+
shimmer: 'shimmer 2.5s linear infinite',
|
|
66
|
+
'fade-in': 'fade-in 0.3s ease-out',
|
|
67
|
+
'slide-up': 'slide-up 0.4s ease-out',
|
|
68
|
+
},
|
|
69
|
+
keyframes: {
|
|
70
|
+
shimmer: {
|
|
71
|
+
'0%': { transform: 'translateX(-100%)' },
|
|
72
|
+
'100%': { transform: 'translateX(100%)' },
|
|
73
|
+
},
|
|
74
|
+
'fade-in': {
|
|
75
|
+
'0%': { opacity: '0' },
|
|
76
|
+
'100%': { opacity: '1' },
|
|
77
|
+
},
|
|
78
|
+
'slide-up': {
|
|
79
|
+
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
|
80
|
+
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
plugins: [require('tailwindcss-animate')],
|
|
86
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Force the shell to use global React (window.React / window.ReactDOM) from index.html script tags
|
|
3
|
+
* so pilets and shell share a single React instance and avoid "dispatcher is null" / Rules of Hooks.
|
|
4
|
+
*/
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const webpack = require('webpack');
|
|
7
|
+
const postcss = require('postcss');
|
|
8
|
+
const tailwindcss = require('tailwindcss');
|
|
9
|
+
const autoprefixer = require('autoprefixer');
|
|
10
|
+
|
|
11
|
+
// Load .env - from monorepo root when in workspace, or from pilet root when used as dependency
|
|
12
|
+
const envPath = path.resolve(process.cwd(), '.env');
|
|
13
|
+
require('dotenv').config({ path: envPath });
|
|
14
|
+
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
|
|
15
|
+
|
|
16
|
+
module.exports = function (config) {
|
|
17
|
+
// Polyfill process for browser (avoids "process is not defined" from deps or dynamic access)
|
|
18
|
+
config.resolve = config.resolve || {};
|
|
19
|
+
config.resolve.fallback = {
|
|
20
|
+
...config.resolve.fallback,
|
|
21
|
+
process: require.resolve('process/browser'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const prev = config.externals;
|
|
25
|
+
const globalExternals = {
|
|
26
|
+
react: 'root React',
|
|
27
|
+
'react-dom': 'root ReactDOM',
|
|
28
|
+
'react-dom/client': 'root ReactDOM',
|
|
29
|
+
};
|
|
30
|
+
if (Array.isArray(prev)) {
|
|
31
|
+
const withoutGlobal = prev.filter(
|
|
32
|
+
(e) => typeof e !== 'object' || (e && typeof e === 'object' && !('react' in e) && !('react-dom' in e))
|
|
33
|
+
);
|
|
34
|
+
config.externals = [...withoutGlobal, globalExternals];
|
|
35
|
+
} else if (prev && typeof prev === 'object' && !Array.isArray(prev)) {
|
|
36
|
+
config.externals = { ...prev, ...globalExternals };
|
|
37
|
+
} else {
|
|
38
|
+
config.externals = globalExternals;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inject environment variables via DefinePlugin (avoids "process is not defined" in browser)
|
|
42
|
+
const definePlugin = new webpack.DefinePlugin({
|
|
43
|
+
'process.env.VITE_POCKETBASE_URL': JSON.stringify(process.env.VITE_POCKETBASE_URL || 'http://localhost:8090'),
|
|
44
|
+
'process.env.VITE_POCKETBASE_ADMIN_EMAIL': JSON.stringify(process.env.VITE_POCKETBASE_ADMIN_EMAIL || 'superuser@legobox.local'),
|
|
45
|
+
'process.env.VITE_POCKETBASE_ADMIN_PASSWORD': JSON.stringify(process.env.VITE_POCKETBASE_ADMIN_PASSWORD || 'SuperUser123!'),
|
|
46
|
+
'process.env.VITE_APP_NAME': JSON.stringify(process.env.VITE_APP_NAME || 'Lego Box'),
|
|
47
|
+
'process.env.VITE_APP_IDENTIFIER': JSON.stringify(process.env.VITE_APP_IDENTIFIER || 'lego-box-default'),
|
|
48
|
+
'process.env.VITE_FEED_URLS': JSON.stringify(process.env.VITE_FEED_URLS || '["http://localhost:9000/$pilet-api"]'),
|
|
49
|
+
'process.env.FEED_URL': JSON.stringify(process.env.FEED_URL || ''),
|
|
50
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
|
51
|
+
});
|
|
52
|
+
if (!config.plugins) config.plugins = [];
|
|
53
|
+
config.plugins.push(definePlugin);
|
|
54
|
+
|
|
55
|
+
// Ensure PostCSS processes CSS files (for Tailwind)
|
|
56
|
+
if (config.module && config.module.rules) {
|
|
57
|
+
config.module.rules.forEach((rule) => {
|
|
58
|
+
if (rule.oneOf) {
|
|
59
|
+
rule.oneOf.forEach((subRule) => {
|
|
60
|
+
if (subRule.use && Array.isArray(subRule.use)) {
|
|
61
|
+
subRule.use.forEach((useRule) => {
|
|
62
|
+
if (useRule.loader && useRule.loader.includes('css-loader')) {
|
|
63
|
+
// Add PostCSS processing before CSS loader
|
|
64
|
+
const postcssLoader = {
|
|
65
|
+
loader: 'postcss-loader',
|
|
66
|
+
options: {
|
|
67
|
+
postcssOptions: {
|
|
68
|
+
plugins: [
|
|
69
|
+
tailwindcss(),
|
|
70
|
+
autoprefixer(),
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
// Insert postcss-loader before css-loader
|
|
76
|
+
const idx = subRule.use.indexOf(useRule);
|
|
77
|
+
if (idx > -1) {
|
|
78
|
+
subRule.use.splice(idx, 0, postcssLoader);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return config;
|
|
89
|
+
};
|
|
Binary file
|