@nsxbet/admin-sdk 0.5.1 → 0.7.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.
Files changed (173) hide show
  1. package/CHECKLIST.md +52 -10
  2. package/README.md +371 -36
  3. package/dist/auth/client/gateway-token.d.ts +19 -0
  4. package/dist/auth/client/gateway-token.js +89 -0
  5. package/dist/auth/client/in-memory.d.ts +5 -1
  6. package/dist/auth/client/in-memory.js +75 -38
  7. package/dist/auth/client/index.d.ts +0 -1
  8. package/dist/auth/client/interface.d.ts +6 -3
  9. package/dist/auth/client/keycloak.d.ts +0 -1
  10. package/dist/auth/client/keycloak.js +6 -3
  11. package/dist/auth/components/UserSelector.d.ts +0 -1
  12. package/dist/auth/components/UserSelector.js +89 -7
  13. package/dist/auth/components/index.d.ts +0 -1
  14. package/dist/auth/index.d.ts +0 -1
  15. package/dist/components/AuthProvider.d.ts +0 -1
  16. package/dist/components/Timestamp.d.ts +7 -0
  17. package/dist/components/Timestamp.js +50 -0
  18. package/dist/hooks/useAuth.d.ts +0 -1
  19. package/dist/hooks/useAuth.js +1 -1
  20. package/dist/hooks/useFetch.d.ts +0 -1
  21. package/dist/hooks/useI18n.d.ts +0 -1
  22. package/dist/hooks/usePlatformAPI.d.ts +0 -1
  23. package/dist/hooks/useTelemetry.d.ts +0 -1
  24. package/dist/hooks/useTimestamp.d.ts +8 -0
  25. package/dist/hooks/useTimestamp.js +122 -0
  26. package/dist/i18n/config.d.ts +20 -2
  27. package/dist/i18n/config.js +48 -0
  28. package/dist/i18n/index.d.ts +2 -3
  29. package/dist/i18n/index.js +1 -1
  30. package/dist/i18n/locales/en-US.json +95 -18
  31. package/dist/i18n/locales/es.json +95 -18
  32. package/dist/i18n/locales/pt-BR.json +95 -18
  33. package/dist/i18n/locales/ro.json +95 -18
  34. package/dist/index.d.ts +11 -7
  35. package/dist/index.js +5 -1
  36. package/dist/registry/AdminShellRegistry.d.ts +1 -2
  37. package/dist/registry/cache/cached-catalog.d.ts +11 -0
  38. package/dist/registry/cache/cached-catalog.js +42 -0
  39. package/dist/registry/cache/catalog-cache.d.ts +10 -0
  40. package/dist/registry/cache/catalog-cache.js +58 -0
  41. package/dist/registry/cache/index.d.ts +5 -0
  42. package/dist/registry/cache/index.js +3 -0
  43. package/dist/registry/cache/types.d.ts +20 -0
  44. package/dist/registry/cache/types.js +3 -0
  45. package/dist/registry/client/http.d.ts +0 -1
  46. package/dist/registry/client/http.js +13 -0
  47. package/dist/registry/client/in-memory.d.ts +0 -1
  48. package/dist/registry/client/in-memory.js +117 -12
  49. package/dist/registry/client/index.d.ts +0 -1
  50. package/dist/registry/client/interface.d.ts +21 -6
  51. package/dist/registry/index.d.ts +5 -2
  52. package/dist/registry/index.js +4 -0
  53. package/dist/registry/types/index.d.ts +2 -3
  54. package/dist/registry/types/manifest.d.ts +20 -24
  55. package/dist/registry/types/manifest.js +17 -18
  56. package/dist/registry/types/module.d.ts +43 -14
  57. package/dist/registry/useRegistryPolling.d.ts +15 -0
  58. package/dist/registry/useRegistryPolling.js +66 -0
  59. package/dist/router/DynamicModule.d.ts +6 -22
  60. package/dist/router/DynamicModule.js +25 -48
  61. package/dist/router/ModuleErrorBoundary.d.ts +39 -0
  62. package/dist/router/ModuleErrorBoundary.js +101 -0
  63. package/dist/router/index.d.ts +1 -1
  64. package/dist/router/url-allowlist.d.ts +22 -0
  65. package/dist/router/url-allowlist.js +65 -0
  66. package/dist/shell/AdminShell.d.ts +0 -1
  67. package/dist/shell/AdminShell.js +178 -43
  68. package/dist/shell/BackofficeShell.d.ts +0 -1
  69. package/dist/shell/BackofficeShell.js +59 -25
  70. package/dist/shell/components/CommandPalette.d.ts +0 -1
  71. package/dist/shell/components/CommandPalette.js +26 -50
  72. package/dist/shell/components/DevtoolsPanel.d.ts +11 -0
  73. package/dist/shell/components/DevtoolsPanel.js +145 -0
  74. package/dist/shell/components/HomePage.d.ts +0 -1
  75. package/dist/shell/components/HomePage.js +9 -4
  76. package/dist/shell/components/LeftNav.d.ts +0 -1
  77. package/dist/shell/components/LeftNav.js +91 -93
  78. package/dist/shell/components/MainContent.d.ts +3 -2
  79. package/dist/shell/components/MainContent.js +8 -23
  80. package/dist/shell/components/ModuleOverview.d.ts +0 -1
  81. package/dist/shell/components/ModuleOverview.js +4 -20
  82. package/dist/shell/components/ProfilePage.d.ts +0 -1
  83. package/dist/shell/components/ProfilePage.js +1 -1
  84. package/dist/shell/components/RegistryPage.d.ts +0 -1
  85. package/dist/shell/components/RegistryPage.js +154 -64
  86. package/dist/shell/components/RegistryStatusBanner.d.ts +6 -0
  87. package/dist/shell/components/RegistryStatusBanner.js +31 -0
  88. package/dist/shell/components/RegistryUnavailable.d.ts +4 -0
  89. package/dist/shell/components/RegistryUnavailable.js +7 -0
  90. package/dist/shell/components/SettingsPage.d.ts +0 -1
  91. package/dist/shell/components/StackedPanel.d.ts +15 -0
  92. package/dist/shell/components/StackedPanel.js +45 -0
  93. package/dist/shell/components/TopBar.d.ts +4 -2
  94. package/dist/shell/components/TopBar.js +9 -3
  95. package/dist/shell/components/UpdateBanner.d.ts +5 -0
  96. package/dist/shell/components/UpdateBanner.js +8 -0
  97. package/dist/shell/components/index.d.ts +4 -1
  98. package/dist/shell/components/index.js +2 -0
  99. package/dist/shell/components/theme-provider.d.ts +0 -1
  100. package/dist/shell/components/theme-provider.js +8 -5
  101. package/dist/shell/hooks/useCspViolations.d.ts +12 -0
  102. package/dist/shell/hooks/useCspViolations.js +34 -0
  103. package/dist/shell/index.d.ts +1 -2
  104. package/dist/shell/polling-config.d.ts +10 -0
  105. package/dist/shell/polling-config.js +26 -0
  106. package/dist/shell/search/fuzzy.d.ts +0 -1
  107. package/dist/shell/search/index.d.ts +0 -1
  108. package/dist/shell/telemetry.d.ts +0 -1
  109. package/dist/shell/types.d.ts +34 -18
  110. package/dist/tailwind/index.d.ts +0 -1
  111. package/dist/types/keycloak.d.ts +0 -1
  112. package/dist/types/platform.d.ts +12 -1
  113. package/dist/vite/AdminShellSharedDeps.d.ts +64 -0
  114. package/dist/vite/AdminShellSharedDeps.js +215 -0
  115. package/dist/vite/config.d.ts +17 -3
  116. package/dist/vite/config.js +34 -8
  117. package/dist/vite/i18n-plugin.d.ts +13 -0
  118. package/dist/vite/i18n-plugin.js +81 -0
  119. package/dist/vite/index.d.ts +3 -2
  120. package/dist/vite/index.js +3 -1
  121. package/dist/vite/plugins.d.ts +0 -1
  122. package/package.json +6 -2
  123. package/dist/auth/client/in-memory.d.ts.map +0 -1
  124. package/dist/auth/client/index.d.ts.map +0 -1
  125. package/dist/auth/client/interface.d.ts.map +0 -1
  126. package/dist/auth/client/keycloak.d.ts.map +0 -1
  127. package/dist/auth/components/UserSelector.d.ts.map +0 -1
  128. package/dist/auth/components/index.d.ts.map +0 -1
  129. package/dist/auth/index.d.ts.map +0 -1
  130. package/dist/components/AuthProvider.d.ts.map +0 -1
  131. package/dist/hooks/useAuth.d.ts.map +0 -1
  132. package/dist/hooks/useFetch.d.ts.map +0 -1
  133. package/dist/hooks/useI18n.d.ts.map +0 -1
  134. package/dist/hooks/usePlatformAPI.d.ts.map +0 -1
  135. package/dist/hooks/useTelemetry.d.ts.map +0 -1
  136. package/dist/i18n/config.d.ts.map +0 -1
  137. package/dist/i18n/index.d.ts.map +0 -1
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/registry/AdminShellRegistry.d.ts.map +0 -1
  140. package/dist/registry/client/http.d.ts.map +0 -1
  141. package/dist/registry/client/in-memory.d.ts.map +0 -1
  142. package/dist/registry/client/index.d.ts.map +0 -1
  143. package/dist/registry/client/interface.d.ts.map +0 -1
  144. package/dist/registry/index.d.ts.map +0 -1
  145. package/dist/registry/types/index.d.ts.map +0 -1
  146. package/dist/registry/types/manifest.d.ts.map +0 -1
  147. package/dist/registry/types/module.d.ts.map +0 -1
  148. package/dist/router/DynamicModule.d.ts.map +0 -1
  149. package/dist/router/index.d.ts.map +0 -1
  150. package/dist/shell/AdminShell.d.ts.map +0 -1
  151. package/dist/shell/BackofficeShell.d.ts.map +0 -1
  152. package/dist/shell/components/CommandPalette.d.ts.map +0 -1
  153. package/dist/shell/components/HomePage.d.ts.map +0 -1
  154. package/dist/shell/components/LeftNav.d.ts.map +0 -1
  155. package/dist/shell/components/MainContent.d.ts.map +0 -1
  156. package/dist/shell/components/ModuleOverview.d.ts.map +0 -1
  157. package/dist/shell/components/ProfilePage.d.ts.map +0 -1
  158. package/dist/shell/components/RegistryPage.d.ts.map +0 -1
  159. package/dist/shell/components/SettingsPage.d.ts.map +0 -1
  160. package/dist/shell/components/TopBar.d.ts.map +0 -1
  161. package/dist/shell/components/index.d.ts.map +0 -1
  162. package/dist/shell/components/theme-provider.d.ts.map +0 -1
  163. package/dist/shell/index.d.ts.map +0 -1
  164. package/dist/shell/search/fuzzy.d.ts.map +0 -1
  165. package/dist/shell/search/index.d.ts.map +0 -1
  166. package/dist/shell/telemetry.d.ts.map +0 -1
  167. package/dist/shell/types.d.ts.map +0 -1
  168. package/dist/tailwind/index.d.ts.map +0 -1
  169. package/dist/types/keycloak.d.ts.map +0 -1
  170. package/dist/types/platform.d.ts.map +0 -1
  171. package/dist/vite/config.d.ts.map +0 -1
  172. package/dist/vite/index.d.ts.map +0 -1
  173. package/dist/vite/plugins.d.ts.map +0 -1
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ const MAX_VIOLATIONS = 50;
3
+ export function useCspViolations(environment) {
4
+ const [violations, setViolations] = useState([]);
5
+ const [unseenCount, setUnseenCount] = useState(0);
6
+ const isProduction = environment === "production";
7
+ const resetUnseenCount = useCallback(() => {
8
+ setUnseenCount(0);
9
+ }, []);
10
+ useEffect(() => {
11
+ if (isProduction)
12
+ return;
13
+ const handler = (event) => {
14
+ const violation = {
15
+ blockedURI: event.blockedURI,
16
+ violatedDirective: event.violatedDirective,
17
+ effectiveDirective: event.effectiveDirective,
18
+ documentURI: event.documentURI,
19
+ timestamp: new Date().toISOString(),
20
+ };
21
+ setViolations((prev) => [violation, ...prev].slice(0, MAX_VIOLATIONS));
22
+ setUnseenCount((prev) => prev + 1);
23
+ };
24
+ document.addEventListener("securitypolicyviolation", handler);
25
+ return () => {
26
+ document.removeEventListener("securitypolicyviolation", handler);
27
+ };
28
+ }, [isProduction]);
29
+ return {
30
+ violations: isProduction ? [] : violations,
31
+ unseenCount: isProduction ? 0 : unseenCount,
32
+ resetUnseenCount,
33
+ };
34
+ }
@@ -2,8 +2,7 @@ export { AdminShell } from "./AdminShell";
2
2
  export type { AdminShellProps } from "./AdminShell";
3
3
  export { TopBar, LeftNav, MainContent, CommandPalette, ThemeProvider, useTheme, } from "./components";
4
4
  export type { TopBarProps, CommandPaletteProps } from "./components";
5
- export type { Module, ModuleCommand, ModuleStatus, Catalog, ImportMap, SearchableItem, SearchResult, } from "./types";
5
+ export type { Module, ModuleCommand, ModuleStatus, NavigationSection, ModuleNavigation, Catalog, ImportMap, SearchableItem, SearchResult, } from "./types";
6
6
  export { initTelemetry, track, trackError } from "./telemetry";
7
7
  export { fuzzySearch } from "./search/fuzzy";
8
8
  export type { SearchOptions } from "./search/fuzzy";
9
- //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Resolve the registry polling interval in milliseconds.
3
+ *
4
+ * Priority:
5
+ * 1. Explicit `REGISTRY_POLL_INTERVAL` from `window.__ENV__` or `import.meta.env`
6
+ * 2. Environment-based default (local: 60s, staging: 5min, production: 15min)
7
+ *
8
+ * Returns 0 (disabled) when the explicit value is "0".
9
+ */
10
+ export declare function resolvePollingInterval(environment: string): number;
@@ -0,0 +1,26 @@
1
+ const ENVIRONMENT_DEFAULTS = {
2
+ local: 60000,
3
+ development: 60000,
4
+ e2e: 300000,
5
+ staging: 300000,
6
+ production: 900000,
7
+ };
8
+ /**
9
+ * Resolve the registry polling interval in milliseconds.
10
+ *
11
+ * Priority:
12
+ * 1. Explicit `REGISTRY_POLL_INTERVAL` from `window.__ENV__` or `import.meta.env`
13
+ * 2. Environment-based default (local: 60s, staging: 5min, production: 15min)
14
+ *
15
+ * Returns 0 (disabled) when the explicit value is "0".
16
+ */
17
+ export function resolvePollingInterval(environment) {
18
+ const raw = (typeof window !== 'undefined' && window.__ENV__?.REGISTRY_POLL_INTERVAL) ||
19
+ (typeof import.meta !== 'undefined' && import.meta.env?.VITE_REGISTRY_POLL_INTERVAL);
20
+ if (raw !== undefined && raw !== null && raw !== '') {
21
+ const parsed = Number(raw);
22
+ if (!Number.isNaN(parsed))
23
+ return parsed;
24
+ }
25
+ return ENVIRONMENT_DEFAULTS[environment] ?? ENVIRONMENT_DEFAULTS.local;
26
+ }
@@ -15,4 +15,3 @@ export interface SearchOptions {
15
15
  * @returns Sorted array of search results with scores
16
16
  */
17
17
  export declare function fuzzySearch(items: SearchableItem[], query: string, options?: SearchOptions): SearchResult[];
18
- //# sourceMappingURL=fuzzy.d.ts.map
@@ -1,3 +1,2 @@
1
1
  export { fuzzySearch } from "./fuzzy";
2
2
  export type { SearchOptions } from "./fuzzy";
3
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  export declare function initTelemetry(env: string): void;
5
5
  export declare function track(event: string, props?: Record<string, unknown>): void;
6
6
  export declare function trackError(err: unknown, props?: Record<string, unknown>): void;
7
- //# sourceMappingURL=telemetry.d.ts.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Shell Type Definitions
3
3
  */
4
+ import type { LocalizedField } from "../i18n/config.js";
4
5
  /**
5
6
  * Module status in the registry
6
7
  */
@@ -10,12 +11,31 @@ export type ModuleStatus = "active" | "deprecated" | "disabled";
10
11
  */
11
12
  export interface ModuleCommand {
12
13
  id: string;
13
- title: string;
14
- /** i18n key for translated title (optional) */
15
- titleKey?: string;
14
+ /** Human-readable title (localized per locale) */
15
+ title: LocalizedField;
16
16
  keywords?: string[];
17
17
  route: string;
18
18
  icon?: string;
19
+ /** Section ID for grouping in stacked navigation */
20
+ section?: string;
21
+ }
22
+ /**
23
+ * Section within a stacked navigation panel
24
+ */
25
+ export interface NavigationSection {
26
+ /** Unique identifier for the section */
27
+ id: string;
28
+ /** Human-readable label (localized per locale) */
29
+ label: LocalizedField;
30
+ }
31
+ /**
32
+ * Navigation configuration for a module
33
+ */
34
+ export interface ModuleNavigation {
35
+ /** Navigation style: "collapsible" (default inline expand) or "stacked" (dedicated panel) */
36
+ style: "collapsible" | "stacked";
37
+ /** Sections for grouping commands in stacked mode */
38
+ sections?: NavigationSection[];
19
39
  }
20
40
  /**
21
41
  * Module information as defined in the catalog
@@ -24,14 +44,10 @@ export interface ModuleCommand {
24
44
  export interface Module {
25
45
  /** Unique identifier for the module (e.g., "@admin/users") */
26
46
  id: string;
27
- /** Human-readable title */
28
- title: string;
29
- /** i18n key for translated title (optional) */
30
- titleKey?: string;
31
- /** Description of what the module does */
32
- description: string;
33
- /** i18n key for translated description (optional) */
34
- descriptionKey?: string;
47
+ /** Human-readable title (localized per locale) */
48
+ title: LocalizedField;
49
+ /** Description of what the module does (localized per locale) */
50
+ description: LocalizedField;
35
51
  /** Category for navigation grouping */
36
52
  category: string;
37
53
  /** Base route where module is mounted (e.g., "/admin/users") */
@@ -53,9 +69,11 @@ export interface Module {
53
69
  /** Module status */
54
70
  status: ModuleStatus;
55
71
  /** Optional navigation order */
56
- navOrder?: number;
72
+ navigationOrder?: number;
57
73
  /** Optional icon name */
58
74
  icon?: string;
75
+ /** Navigation configuration (stacked or collapsible) */
76
+ navigation?: ModuleNavigation;
59
77
  /** Optional commands */
60
78
  commands?: ModuleCommand[];
61
79
  }
@@ -80,22 +98,21 @@ export interface ImportMap {
80
98
  };
81
99
  }
82
100
  /**
83
- * Searchable item for command palette
101
+ * Searchable item for command palette.
102
+ * Title and moduleTitle are resolved for the current locale at index time.
84
103
  */
85
104
  export interface SearchableItem {
86
105
  type: "module" | "command";
87
106
  id: string;
107
+ /** Resolved title for current locale */
88
108
  title: string;
89
- /** i18n key for the title */
90
- titleKey?: string;
91
109
  category?: string;
92
110
  keywords?: string[];
93
111
  description?: string;
94
112
  route: string;
95
113
  moduleId?: string;
114
+ /** Resolved module title for current locale */
96
115
  moduleTitle?: string;
97
- /** i18n key for the module title */
98
- moduleTitleKey?: string;
99
116
  /** Icon name (Lucide icon in kebab-case) */
100
117
  icon?: string;
101
118
  }
@@ -107,4 +124,3 @@ export interface SearchResult {
107
124
  score: number;
108
125
  matchedFields: string[];
109
126
  }
110
- //# sourceMappingURL=types.d.ts.map
@@ -17,4 +17,3 @@ export declare function getAdminContentPaths(): string[];
17
17
  * ```
18
18
  */
19
19
  export declare function withAdminSdk(config?: Partial<Config>): Config;
20
- //# sourceMappingURL=index.d.ts.map
@@ -23,4 +23,3 @@ export interface KeycloakTokenParsed {
23
23
  roles?: string[];
24
24
  };
25
25
  }
26
- //# sourceMappingURL=keycloak.d.ts.map
@@ -2,6 +2,8 @@
2
2
  * Platform API type definitions
3
3
  * These types define the global API exposed by the admin shell
4
4
  */
5
+ export type TimezoneMode = "utc" | "local";
6
+ export type TimestampFormat = "datetime" | "date" | "time" | "relative";
5
7
  /**
6
8
  * Breadcrumb item for navigation
7
9
  */
@@ -16,6 +18,7 @@ export interface User {
16
18
  id: string;
17
19
  email: string;
18
20
  displayName: string;
21
+ roles: string[];
19
22
  }
20
23
  /**
21
24
  * Platform API exposed by the admin shell at window.__ADMIN_PLATFORM_API__
@@ -59,6 +62,15 @@ export interface PlatformAPI {
59
62
  /** Track an error */
60
63
  trackError: (err: unknown, props?: Record<string, unknown>) => void;
61
64
  };
65
+ /** Timestamp timezone preference */
66
+ timestamp: {
67
+ /** Current timezone mode */
68
+ mode: TimezoneMode;
69
+ /** Change the timezone mode */
70
+ setMode: (mode: TimezoneMode) => void;
71
+ /** Subscribe to mode changes, returns unsubscribe function */
72
+ onModeChange: (callback: (mode: TimezoneMode) => void) => () => void;
73
+ };
62
74
  /** Authenticated fetch wrapper */
63
75
  fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
64
76
  }
@@ -80,4 +92,3 @@ declare global {
80
92
  }
81
93
  }
82
94
  export {};
83
- //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1,64 @@
1
+ import type { Plugin } from "vite";
2
+ /**
3
+ * Configuration for a shared dependency exposed to dynamically loaded modules.
4
+ */
5
+ export interface SharedDepConfig {
6
+ /** npm package specifier (e.g., "react") */
7
+ specifier: string;
8
+ /** Name of the window global where the shell exposes this dependency */
9
+ globalName: string;
10
+ /** Use `import X from` (true) vs `import * as X from` (false/default) */
11
+ importDefault?: boolean;
12
+ /**
13
+ * Additional specifiers that resolve to the same shim file.
14
+ * Their exports are merged into the shim alongside the main specifier.
15
+ * Useful for subpath exports like "react-dom/client".
16
+ */
17
+ mergedSpecifiers?: {
18
+ specifier: string;
19
+ globalName: string;
20
+ importDefault?: boolean;
21
+ }[];
22
+ }
23
+ /**
24
+ * Single source of truth for shared dependencies between shell and modules.
25
+ *
26
+ * - Module builds externalize these via SHARED_EXTERNALS.
27
+ * - The shell plugin auto-injects a globals script, import map, and shim files.
28
+ * - Adding a dep here is all that's needed — no manual shim files, import map
29
+ * entries, or window assignments.
30
+ */
31
+ export declare const SHARED_DEPS_CONFIG: SharedDepConfig[];
32
+ /**
33
+ * Flat list of all specifiers that should be externalized in module builds.
34
+ * Derived from SHARED_DEPS_CONFIG.
35
+ */
36
+ export declare function getSharedExternals(): string[];
37
+ export type AdminShellSharedDeps = ReturnType<typeof AdminShellSharedDeps.create>;
38
+ /**
39
+ * Vite plugin for the admin shell that auto-manages shared dependency resolution.
40
+ *
41
+ * What it does:
42
+ * 1. **Globals injection** — prepends `window.React = ...` assignments into the
43
+ * shell's entry file via the `transform` hook. This runs through Vite's full
44
+ * pipeline so imports resolve to pre-bundled deps (bypassing the import map).
45
+ * 2. **Import map** — injects `<script type="importmap">` so dynamically loaded
46
+ * modules can resolve bare specifiers like `"react"` to shim files.
47
+ * 3. **Shim files** — auto-generated ES modules that re-export from window globals.
48
+ * (dev: served via middleware, build: emitted as assets)
49
+ *
50
+ * With this plugin, main.tsx needs no awareness of the shared deps mechanism.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // apps/admin-shell/vite.config.ts
55
+ * import { AdminShellSharedDeps } from "@nsxbet/admin-sdk/vite";
56
+ *
57
+ * export default defineConfig({
58
+ * plugins: [react(), AdminShellSharedDeps.create()],
59
+ * });
60
+ * ```
61
+ */
62
+ export declare const AdminShellSharedDeps: {
63
+ create(): Plugin;
64
+ };
@@ -0,0 +1,215 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Single source of truth for shared dependencies between shell and modules.
4
+ *
5
+ * - Module builds externalize these via SHARED_EXTERNALS.
6
+ * - The shell plugin auto-injects a globals script, import map, and shim files.
7
+ * - Adding a dep here is all that's needed — no manual shim files, import map
8
+ * entries, or window assignments.
9
+ */
10
+ export const SHARED_DEPS_CONFIG = [
11
+ { specifier: "react", globalName: "React", importDefault: true },
12
+ {
13
+ specifier: "react-dom",
14
+ globalName: "ReactDOM",
15
+ importDefault: true,
16
+ mergedSpecifiers: [
17
+ { specifier: "react-dom/client", globalName: "ReactDOMClient" },
18
+ ],
19
+ },
20
+ { specifier: "react-router-dom", globalName: "ReactRouterDOM" },
21
+ { specifier: "i18next", globalName: "i18next" },
22
+ { specifier: "react-i18next", globalName: "reactI18next" },
23
+ ];
24
+ /**
25
+ * Flat list of all specifiers that should be externalized in module builds.
26
+ * Derived from SHARED_DEPS_CONFIG.
27
+ */
28
+ export function getSharedExternals() {
29
+ return SHARED_DEPS_CONFIG.flatMap((dep) => [
30
+ dep.specifier,
31
+ ...(dep.mergedSpecifiers?.map((m) => m.specifier) ?? []),
32
+ ]);
33
+ }
34
+ function buildGlobalsScript() {
35
+ const lines = [];
36
+ for (const dep of SHARED_DEPS_CONFIG) {
37
+ const binding = `__${dep.globalName}`;
38
+ lines.push(dep.importDefault
39
+ ? `import ${binding} from "${dep.specifier}";`
40
+ : `import * as ${binding} from "${dep.specifier}";`);
41
+ lines.push(`window.${dep.globalName} = ${binding};`);
42
+ for (const merged of dep.mergedSpecifiers ?? []) {
43
+ const mBinding = `__${merged.globalName}`;
44
+ lines.push(merged.importDefault
45
+ ? `import ${mBinding} from "${merged.specifier}";`
46
+ : `import * as ${mBinding} from "${merged.specifier}";`);
47
+ lines.push(`window.${merged.globalName} = ${mBinding};`);
48
+ }
49
+ }
50
+ return lines.join("\n");
51
+ }
52
+ const VALID_JS_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
53
+ function isValidExportName(name) {
54
+ return VALID_JS_IDENT.test(name) && name !== "default" && name !== "__esModule";
55
+ }
56
+ async function enumerateNamedExports(specifier) {
57
+ const mod = await import(specifier);
58
+ return Object.keys(mod).filter(isValidExportName);
59
+ }
60
+ async function enumerateDefaultExportKeys(specifier) {
61
+ const mod = await import(specifier);
62
+ if (mod.default && typeof mod.default === "object") {
63
+ return Object.keys(mod.default).filter(isValidExportName);
64
+ }
65
+ return [];
66
+ }
67
+ async function generateShimContent(dep) {
68
+ const mainExports = dep.importDefault
69
+ ? await enumerateDefaultExportKeys(dep.specifier)
70
+ : await enumerateNamedExports(dep.specifier);
71
+ const lines = [
72
+ `const _mod = window.${dep.globalName};`,
73
+ `export default _mod.default ?? _mod;`,
74
+ ];
75
+ if (mainExports.length > 0) {
76
+ lines.push(`export const { ${mainExports.join(", ")} } = _mod;`);
77
+ }
78
+ for (const merged of dep.mergedSpecifiers ?? []) {
79
+ const mergedExports = await enumerateNamedExports(merged.specifier);
80
+ const unique = mergedExports.filter((e) => !mainExports.includes(e));
81
+ if (unique.length > 0) {
82
+ lines.push(`const _${merged.globalName} = window.${merged.globalName};`);
83
+ for (const name of unique) {
84
+ lines.push(`export const ${name} = _${merged.globalName}?.${name};`);
85
+ }
86
+ }
87
+ }
88
+ return lines.join("\n");
89
+ }
90
+ async function generateAllShims() {
91
+ const shims = new Map();
92
+ for (const dep of SHARED_DEPS_CONFIG) {
93
+ shims.set(dep.specifier, await generateShimContent(dep));
94
+ }
95
+ return shims;
96
+ }
97
+ function contentHash(content) {
98
+ return createHash("sha256").update(content).digest("hex").slice(0, 8);
99
+ }
100
+ async function generateHashedShims() {
101
+ const raw = await generateAllShims();
102
+ const hashed = new Map();
103
+ for (const [specifier, content] of raw) {
104
+ const hash = contentHash(content);
105
+ hashed.set(specifier, { content, fileName: `shims/${specifier}-${hash}.js` });
106
+ }
107
+ return hashed;
108
+ }
109
+ function buildHashedImportMapEntries(hashed, base) {
110
+ const imports = {};
111
+ for (const dep of SHARED_DEPS_CONFIG) {
112
+ const shimInfo = hashed.get(dep.specifier);
113
+ imports[dep.specifier] = `${base}${shimInfo.fileName}`;
114
+ for (const merged of dep.mergedSpecifiers ?? []) {
115
+ imports[merged.specifier] = `${base}${shimInfo.fileName}`;
116
+ }
117
+ }
118
+ return imports;
119
+ }
120
+ /**
121
+ * Vite plugin for the admin shell that auto-manages shared dependency resolution.
122
+ *
123
+ * What it does:
124
+ * 1. **Globals injection** — prepends `window.React = ...` assignments into the
125
+ * shell's entry file via the `transform` hook. This runs through Vite's full
126
+ * pipeline so imports resolve to pre-bundled deps (bypassing the import map).
127
+ * 2. **Import map** — injects `<script type="importmap">` so dynamically loaded
128
+ * modules can resolve bare specifiers like `"react"` to shim files.
129
+ * 3. **Shim files** — auto-generated ES modules that re-export from window globals.
130
+ * (dev: served via middleware, build: emitted as assets)
131
+ *
132
+ * With this plugin, main.tsx needs no awareness of the shared deps mechanism.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * // apps/admin-shell/vite.config.ts
137
+ * import { AdminShellSharedDeps } from "@nsxbet/admin-sdk/vite";
138
+ *
139
+ * export default defineConfig({
140
+ * plugins: [react(), AdminShellSharedDeps.create()],
141
+ * });
142
+ * ```
143
+ */
144
+ export const AdminShellSharedDeps = {
145
+ create() {
146
+ let hashedShimsPromise = null;
147
+ let resolvedBase = "/";
148
+ let entryPattern;
149
+ function getHashedShims() {
150
+ if (!hashedShimsPromise) {
151
+ hashedShimsPromise = generateHashedShims();
152
+ }
153
+ return hashedShimsPromise;
154
+ }
155
+ return {
156
+ name: "admin-shell-shared-deps",
157
+ configResolved(config) {
158
+ resolvedBase = config.base;
159
+ entryPattern = config.build?.rollupOptions?.input;
160
+ },
161
+ transform(code, id) {
162
+ if (id.includes("node_modules"))
163
+ return;
164
+ const isEntry = entryPattern
165
+ ? id.endsWith(entryPattern)
166
+ : /\/src\/main\.[tj]sx?$/.test(id);
167
+ if (!isEntry)
168
+ return;
169
+ return buildGlobalsScript() + "\n" + code;
170
+ },
171
+ configureServer(server) {
172
+ server.middlewares.use((req, res, next) => {
173
+ const match = req.url?.match(/^\/shims\/(.+?)(?:-[a-f0-9]+)?\.js$/);
174
+ if (!match)
175
+ return next();
176
+ const specifier = match[1];
177
+ getHashedShims()
178
+ .then((shims) => {
179
+ const shimInfo = shims.get(specifier);
180
+ if (shimInfo) {
181
+ res.setHeader("Content-Type", "application/javascript");
182
+ res.end(shimInfo.content);
183
+ }
184
+ else {
185
+ next();
186
+ }
187
+ })
188
+ .catch(next);
189
+ });
190
+ },
191
+ async transformIndexHtml() {
192
+ const hashed = await getHashedShims();
193
+ const imports = buildHashedImportMapEntries(hashed, resolvedBase);
194
+ return [
195
+ {
196
+ tag: "script",
197
+ attrs: { type: "importmap" },
198
+ children: JSON.stringify({ imports }, null, 6),
199
+ injectTo: "head-prepend",
200
+ },
201
+ ];
202
+ },
203
+ async generateBundle() {
204
+ const hashed = await getHashedShims();
205
+ for (const [, shimInfo] of hashed) {
206
+ this.emitFile({
207
+ type: "asset",
208
+ fileName: shimInfo.fileName,
209
+ source: shimInfo.content,
210
+ });
211
+ }
212
+ },
213
+ };
214
+ },
215
+ };
@@ -1,9 +1,15 @@
1
- import type { Plugin } from "vite";
1
+ import { type Plugin } from "vite";
2
+ /**
3
+ * Default admin gateway URL (staging environment).
4
+ * Used by the env injection plugin when no explicit value is configured.
5
+ */
6
+ export declare const ADMIN_GATEWAY_STAGING_URL = "https://admin-bff-stg.nsx.dev";
2
7
  /**
3
8
  * Shared dependencies that are externalized in module builds.
4
9
  * These are provided by the shell via import maps.
10
+ * Derived from SHARED_DEPS_CONFIG — the single source of truth.
5
11
  */
6
- export declare const SHARED_EXTERNALS: readonly ["react", "react-dom", "react-router-dom", "i18next", "react-i18next"];
12
+ export declare const SHARED_EXTERNALS: readonly string[];
7
13
  export interface AdminModuleOptions {
8
14
  /**
9
15
  * Entry file for the module SPA
@@ -27,6 +33,15 @@ export interface AdminModuleOptions {
27
33
  * - "always": Always apply during `vite build`. Used by defineModuleConfig().
28
34
  */
29
35
  buildMode?: "auto" | "always";
36
+ /**
37
+ * Admin gateway URL for BFF token integration (InMemory auth).
38
+ * - `undefined` (default): auto-inject staging URL (`https://admin-bff-stg.nsx.dev`)
39
+ * - `string`: inject the provided URL
40
+ * - `null`: disable automatic injection entirely
41
+ *
42
+ * Explicit env vars (`.env` files, shell env) always take precedence over this option.
43
+ */
44
+ gatewayUrl?: string | null;
30
45
  }
31
46
  export interface ModuleConfigOptions extends AdminModuleOptions {
32
47
  /**
@@ -110,4 +125,3 @@ export declare function adminModule(options?: AdminModuleOptions): Plugin[];
110
125
  * ```
111
126
  */
112
127
  export declare function defineModuleConfig(options: ModuleConfigOptions): any;
113
- //# sourceMappingURL=config.d.ts.map
@@ -1,15 +1,19 @@
1
+ import { loadEnv } from "vite";
1
2
  import { generateModuleManifestPlugin, serveDistPlugin } from "./plugins.js";
3
+ import { adminModuleI18nPlugin } from "./i18n-plugin.js";
4
+ import { getSharedExternals } from "./AdminShellSharedDeps.js";
5
+ const ENV_KEY = "VITE_ADMIN_GATEWAY_URL";
6
+ /**
7
+ * Default admin gateway URL (staging environment).
8
+ * Used by the env injection plugin when no explicit value is configured.
9
+ */
10
+ export const ADMIN_GATEWAY_STAGING_URL = "https://admin-bff-stg.nsx.dev";
2
11
  /**
3
12
  * Shared dependencies that are externalized in module builds.
4
13
  * These are provided by the shell via import maps.
14
+ * Derived from SHARED_DEPS_CONFIG — the single source of truth.
5
15
  */
6
- export const SHARED_EXTERNALS = [
7
- "react",
8
- "react-dom",
9
- "react-router-dom",
10
- "i18next",
11
- "react-i18next",
12
- ];
16
+ export const SHARED_EXTERNALS = getSharedExternals();
13
17
  /**
14
18
  * Composable Vite plugin array for admin modules.
15
19
  * Returns plugins that handle build-time lib config, dist serving, and manifest generation.
@@ -43,13 +47,33 @@ export const SHARED_EXTERNALS = [
43
47
  * ```
44
48
  */
45
49
  export function adminModule(options = {}) {
46
- const { entry = "./src/spa.tsx", outDir = "dist", additionalExternals = [], buildMode = "auto", } = options;
50
+ const { entry = "./src/spa.tsx", outDir = "dist", additionalExternals = [], buildMode = "auto", gatewayUrl, } = options;
47
51
  const externals = [...SHARED_EXTERNALS, ...additionalExternals];
48
52
  function isModuleBuild() {
49
53
  if (buildMode === "always")
50
54
  return true;
51
55
  return process.env.ADMIN_MODULE === "true";
52
56
  }
57
+ const envPlugin = {
58
+ name: "admin-module-env",
59
+ config(_config, { mode }) {
60
+ if (gatewayUrl === null)
61
+ return;
62
+ const env = loadEnv(mode, process.cwd(), "VITE_");
63
+ const fromEnv = process.env[ENV_KEY] || env[ENV_KEY];
64
+ if (fromEnv) {
65
+ console.log(`[admin-module-env] ${ENV_KEY} → ${fromEnv} (from env)`);
66
+ return;
67
+ }
68
+ const url = gatewayUrl ?? ADMIN_GATEWAY_STAGING_URL;
69
+ console.log(`[admin-module-env] ${ENV_KEY} → ${url} (auto-injected)`);
70
+ return {
71
+ define: {
72
+ [`import.meta.env.${ENV_KEY}`]: JSON.stringify(url),
73
+ },
74
+ };
75
+ },
76
+ };
53
77
  const buildConfigPlugin = {
54
78
  name: "admin-module-build-config",
55
79
  config(_config, { command }) {
@@ -76,6 +100,8 @@ export function adminModule(options = {}) {
76
100
  },
77
101
  };
78
102
  return [
103
+ envPlugin,
104
+ adminModuleI18nPlugin(),
79
105
  serveDistPlugin({ distDir: outDir }),
80
106
  generateModuleManifestPlugin(),
81
107
  buildConfigPlugin,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vite plugin for admin module i18n:
3
+ * - Validates all 4 locale files exist at build start
4
+ * - Auto-injects registerModuleTranslations into spa.tsx and main.tsx entry points
5
+ */
6
+ import type { Plugin } from "vite";
7
+ export interface AdminModuleI18nPluginOptions {
8
+ /** Path to admin.module.json */
9
+ manifestPath?: string;
10
+ /** Root directory (project root) */
11
+ root?: string;
12
+ }
13
+ export declare function adminModuleI18nPlugin(options?: AdminModuleI18nPluginOptions): Plugin;