@lego-box/shell 1.0.6 → 1.0.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/.krasrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "api": "/$api",
3
+ "ui": "/$manage",
4
+ "port": 1234,
5
+ "base": "/",
6
+ "mode": "develop",
7
+ "injectors": {
8
+ "index.html": {
9
+ "active": true,
10
+ "source": "index.html"
11
+ }
12
+ }
13
+ }
package/README.md CHANGED
@@ -1,67 +1,67 @@
1
- # @lego-box/shell
2
-
3
- Piral microfrontend shell providing authentication, PocketBase integration, RBAC, and shared UI components for the Lego Box ecosystem.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @lego-box/shell
9
- # or
10
- pnpm add @lego-box/shell
11
- ```
12
-
13
- ## Usage
14
-
15
- ### Creating a Pilet
16
-
17
- ```bash
18
- pilet new @lego-box/shell --target my-pilet
19
- cd my-pilet
20
- pnpm install
21
- pnpm start
22
- ```
23
-
24
- ### Pilet Development
25
-
26
- ```typescript
27
- import type { PiletApi } from '@lego-box/shell/pilet';
28
-
29
- export function setup(app: PiletApi) {
30
- // Access authentication
31
- const user = app.auth.getUser();
32
-
33
- // Access PocketBase
34
- const records = await app.pocketbase.collection('items').getList();
35
-
36
- // Register protected pages
37
- app.registerProtectedPage('/my-route', MyPage, {
38
- action: 'read',
39
- subject: 'items'
40
- });
41
-
42
- // Register sidebar menu
43
- app.registerSidebarMenu({
44
- id: 'my-menu',
45
- label: 'My Feature',
46
- href: '/my-route',
47
- action: 'read',
48
- subject: 'items'
49
- });
50
- }
51
- ```
52
-
53
- ## Shared Dependencies
54
-
55
- The shell provides these dependencies to all pilets:
56
- - `@lego-box/ui-kit` - Reusable UI components
57
- - `pocketbase` - Backend SDK
58
- - `piral-auth` - Authentication plugin
59
- - `react` & `react-dom` - UI framework
60
-
61
- ## API Reference
62
-
63
- See [full documentation](link-to-docs) for complete API reference.
64
-
65
- ## License
66
-
67
- MIT
1
+ # @lego-box/shell
2
+
3
+ Piral microfrontend shell providing authentication, PocketBase integration, RBAC, and shared UI components for the Lego Box ecosystem.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lego-box/shell
9
+ # or
10
+ pnpm add @lego-box/shell
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Creating a Pilet
16
+
17
+ ```bash
18
+ pilet new @lego-box/shell --target my-pilet
19
+ cd my-pilet
20
+ pnpm install
21
+ pnpm start
22
+ ```
23
+
24
+ ### Pilet Development
25
+
26
+ ```typescript
27
+ import type { PiletApi } from '@lego-box/shell/pilet';
28
+
29
+ export function setup(app: PiletApi) {
30
+ // Access authentication
31
+ const user = app.auth.getUser();
32
+
33
+ // Access PocketBase
34
+ const records = await app.pocketbase.collection('items').getList();
35
+
36
+ // Register protected pages
37
+ app.registerProtectedPage('/my-route', MyPage, {
38
+ action: 'read',
39
+ subject: 'items'
40
+ });
41
+
42
+ // Register sidebar menu
43
+ app.registerSidebarMenu({
44
+ id: 'my-menu',
45
+ label: 'My Feature',
46
+ href: '/my-route',
47
+ action: 'read',
48
+ subject: 'items'
49
+ });
50
+ }
51
+ ```
52
+
53
+ ## Shared Dependencies
54
+
55
+ The shell provides these dependencies to all pilets:
56
+ - `@lego-box/ui-kit` - Reusable UI components
57
+ - `pocketbase` - Backend SDK
58
+ - `piral-auth` - Authentication plugin
59
+ - `react` & `react-dom` - UI framework
60
+
61
+ ## API Reference
62
+
63
+ See [full documentation](link-to-docs) for complete API reference.
64
+
65
+ ## License
66
+
67
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lego-box/shell",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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",
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
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
- * MUST use static references only - e.g. process.env.VITE_FEED_URLS, not process.env[name].
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 = process.env.VITE_FEED_URLS;
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 = process.env.FEED_URL;
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: process.env.VITE_POCKETBASE_URL || 'http://localhost:8090',
96
- adminEmail: process.env.VITE_POCKETBASE_ADMIN_EMAIL || 'superuser@legobox.local',
97
- adminPassword: process.env.VITE_POCKETBASE_ADMIN_PASSWORD || 'SuperUser123!',
98
- appName: process.env.VITE_APP_NAME || 'Lego Box',
99
- appIdentifier: process.env.VITE_APP_IDENTIFIER || 'lego-box-default',
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
+ };