everything-dev 1.42.1 → 1.43.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 (57) hide show
  1. package/dist/cli/prompts.cjs +2 -1
  2. package/dist/cli/prompts.cjs.map +1 -1
  3. package/dist/cli/prompts.mjs +2 -1
  4. package/dist/cli/prompts.mjs.map +1 -1
  5. package/dist/cli/upgrade.cjs +2 -15
  6. package/dist/cli/upgrade.cjs.map +1 -1
  7. package/dist/cli/upgrade.mjs +2 -15
  8. package/dist/cli/upgrade.mjs.map +1 -1
  9. package/dist/config.cjs +1 -12
  10. package/dist/config.cjs.map +1 -1
  11. package/dist/config.d.cts.map +1 -1
  12. package/dist/config.d.mts.map +1 -1
  13. package/dist/config.mjs +1 -12
  14. package/dist/config.mjs.map +1 -1
  15. package/dist/contract.d.cts +0 -60
  16. package/dist/contract.d.cts.map +1 -1
  17. package/dist/contract.d.mts +0 -60
  18. package/dist/contract.d.mts.map +1 -1
  19. package/dist/index.cjs +0 -5
  20. package/dist/index.d.cts +2 -3
  21. package/dist/index.d.mts +2 -3
  22. package/dist/index.mjs +2 -3
  23. package/dist/orchestrator.cjs +17 -4
  24. package/dist/orchestrator.cjs.map +1 -1
  25. package/dist/orchestrator.mjs +17 -4
  26. package/dist/orchestrator.mjs.map +1 -1
  27. package/dist/plugin.cjs +2 -4
  28. package/dist/plugin.cjs.map +1 -1
  29. package/dist/plugin.d.cts +0 -60
  30. package/dist/plugin.d.cts.map +1 -1
  31. package/dist/plugin.d.mts +0 -60
  32. package/dist/plugin.d.mts.map +1 -1
  33. package/dist/plugin.mjs +2 -4
  34. package/dist/plugin.mjs.map +1 -1
  35. package/dist/types.cjs +2 -21
  36. package/dist/types.cjs.map +1 -1
  37. package/dist/types.d.cts +1 -129
  38. package/dist/types.d.cts.map +1 -1
  39. package/dist/types.d.mts +1 -129
  40. package/dist/types.d.mts.map +1 -1
  41. package/dist/types.mjs +3 -20
  42. package/dist/types.mjs.map +1 -1
  43. package/package.json +1 -1
  44. package/skills/api-and-auth/SKILL.md +355 -0
  45. package/skills/api-and-auth/references/generated-types.md +14 -0
  46. package/skills/api-and-auth/references/middleware.md +38 -0
  47. package/skills/cli-reference/SKILL.md +249 -0
  48. package/skills/plugin-development/SKILL.md +470 -0
  49. package/skills/ui-integration/SKILL.md +382 -0
  50. package/dist/sidebar.cjs +0 -116
  51. package/dist/sidebar.cjs.map +0 -1
  52. package/dist/sidebar.d.cts +0 -8
  53. package/dist/sidebar.d.cts.map +0 -1
  54. package/dist/sidebar.d.mts +0 -8
  55. package/dist/sidebar.d.mts.map +0 -1
  56. package/dist/sidebar.mjs +0 -114
  57. package/dist/sidebar.mjs.map +0 -1
@@ -0,0 +1,382 @@
1
+ ---
2
+ name: ui-integration
3
+ description: Route creation, API client usage, auth client, SSR hydration, sidebar system, and the @/app module surface. Use when adding new UI routes, fetching data from the API, implementing auth flows, or customizing sidebar navigation.
4
+ metadata:
5
+ sources: "ui/src/app.ts,ui/src/lib/api.ts,ui/src/lib/auth.ts,ui/src/router.tsx,ui/src/router.server.tsx,ui/src/hydrate.tsx,ui/src/routes/__root.tsx,ui/src/routes/_layout.tsx,ui/src/routes/_layout/_authenticated.tsx"
6
+ ---
7
+
8
+ # UI Integration
9
+
10
+ ## File-based Routing
11
+
12
+ Routes are defined as files in `ui/src/routes/`. TanStack Router auto-generates the route tree.
13
+
14
+ ### Route File Convention
15
+
16
+ ```
17
+ ui/src/routes/
18
+ ├── __root.tsx # Root layout (HTML shell, head, scripts)
19
+ ├── index.tsx # /
20
+ ├── _layout.tsx # Shell layout (sidebar, header, footer)
21
+ ├── _layout/
22
+ │ ├── index.tsx # / (inside shell)
23
+ │ ├── _authenticated.tsx # Auth guard layout (redirects to /login)
24
+ │ └── _authenticated/
25
+ │ ├── index.tsx # / (authenticated)
26
+ │ ├── settings.tsx # /settings
27
+ │ └── your-plugin/
28
+ │ └── index.tsx # /your-plugin
29
+ ├── login.tsx # /login
30
+ └── about.tsx # /about
31
+ ```
32
+
33
+ - Files starting with `_` are **layout** routes (parent components with `<Outlet />`)
34
+ - Files starting with `_` followed by a path segment are **nested layouts**
35
+ - Regular files become path segments (e.g., `settings.tsx` → `/settings`)
36
+ - Directories create nested paths (e.g., `_authenticated/settings.tsx` → `/settings` inside the auth guard)
37
+
38
+ ### Basic Route
39
+
40
+ ```tsx
41
+ import { createFileRoute } from "@tanstack/react-router";
42
+
43
+ export const Route = createFileRoute("/about")({
44
+ component: AboutPage,
45
+ });
46
+
47
+ function AboutPage() {
48
+ return <div>About</div>;
49
+ }
50
+ ```
51
+
52
+ ### Route with Loader
53
+
54
+ ```tsx
55
+ import { createFileRoute } from "@tanstack/react-router";
56
+ import { useApiClient } from "@/app";
57
+
58
+ export const Route = createFileRoute("/projects/$id")({
59
+ loader: async ({ context, params }) => {
60
+ const { apiClient } = context;
61
+ const project = await apiClient.projects.getProject({ id: params.id });
62
+ return { project };
63
+ },
64
+ component: ProjectPage,
65
+ });
66
+
67
+ function ProjectPage() {
68
+ const { project } = Route.useLoaderData();
69
+ return <div>{project.name}</div>;
70
+ }
71
+ ```
72
+
73
+ ### Route with Head/Sidebar Metadata
74
+
75
+ ```tsx
76
+ export const Route = createFileRoute("/_layout/settings")({
77
+ component: SettingsPage,
78
+ head: () => ({
79
+ meta: [{ title: "Settings" }],
80
+ }),
81
+ });
82
+ ```
83
+
84
+ ## The `@/app` Module Surface
85
+
86
+ `ui/src/app.ts` exports everything UI route code needs:
87
+
88
+ ```ts
89
+ // Runtime config helpers
90
+ import { getRuntimeConfig, getAccount, getAppName, getActiveRuntime, getRepository, getCspNonce } from "@/app";
91
+
92
+ // API client
93
+ import { createApiClient, useApiClient, useOrpc, type ApiClient } from "@/app";
94
+
95
+ // Auth client
96
+ import { createAuthClient, useAuthClient, sessionQueryOptions, useRelayHistory, type AuthClient, type SessionData } from "@/app";
97
+
98
+ // Types
99
+ import type { ClientRuntimeConfig, RouterContext, CreateRouterOptions, RenderOptions } from "@/app";
100
+ ```
101
+
102
+ ### Runtime Helpers
103
+
104
+ ```ts
105
+ const config = getRuntimeConfig(); // Full window.__RUNTIME_CONFIG__
106
+ const account = getAccount(config); // NEAR account
107
+ const appName = getAppName(config); // Title or account
108
+ const activeRuntime = getActiveRuntime(config); // { accountId, gatewayId, title }
109
+ const repo = getRepository(config); // Repository URL
110
+ const nonce = getCspNonce(); // CSP nonce for inline scripts
111
+ ```
112
+
113
+ These are SSR-safe — they accept an optional `RuntimeConfigInput` to work with config from loader data.
114
+
115
+ ## API Client
116
+
117
+ ### Creation
118
+
119
+ The client is created once in `hydrate.tsx` and stored in the Router context:
120
+
121
+ ```ts
122
+ const apiClient = createApiClient({
123
+ hostUrl: runtimeConfig.hostUrl,
124
+ rpcBase: runtimeConfig.rpcBase, // "/api/rpc"
125
+ });
126
+ ```
127
+
128
+ It uses `RPCLink` with `credentials: "include"` and a global error interceptor that shows a toast on network failures.
129
+
130
+ ### In Route Components
131
+
132
+ ```ts
133
+ import { useApiClient, useOrpc } from "@/app";
134
+
135
+ function Component() {
136
+ // Direct usage
137
+ const apiClient = useApiClient();
138
+ const { data } = await apiClient.registry.listRegistryApps({ limit: 24 });
139
+
140
+ // With TanStack Query utils (caching, refetch, mutations)
141
+ const orpc = useOrpc();
142
+ const { data, isLoading } = orpc.registry.listRegistryApps.useQuery({ limit: 24 });
143
+ const mutation = orpc.registry.listRegistryApps.useMutation();
144
+
145
+ // In route loaders (from context, no hooks):
146
+ const { apiClient } = context;
147
+ const result = await apiClient.ping();
148
+ }
149
+ ```
150
+
151
+ ### Typed API Calls
152
+
153
+ Every procedure from every plugin is available on `apiClient` with full TypeScript types:
154
+
155
+ ```ts
156
+ apiClient.ping() // → { status, timestamp }
157
+ apiClient.registry.listRegistryApps({ limit: 24 }) // → { apps, meta }
158
+ apiClient.projects.getProject({ id: "proj_123" }) // → Project
159
+ apiClient.authHealth() // → { status, emailConfigured, ... }
160
+ ```
161
+
162
+ The types come from the auto-generated `api-types.gen.ts`, which merges every plugin's contract into a single `ApiContract` type.
163
+
164
+ ### Error Handling
165
+
166
+ Network/fetch errors are automatically caught by the `RPCLink` interceptor and shown as a toast:
167
+
168
+ ```
169
+ "Unable to connect to API" — The API is currently unavailable.
170
+ ```
171
+
172
+ Procedure-level errors (like `UNAUTHORIZED`, `NOT_FOUND`) are thrown as `ORPCError` instances and should be caught inline:
173
+
174
+ ```ts
175
+ try {
176
+ await apiClient.authHealth();
177
+ } catch (error) {
178
+ if (error instanceof ORPCError) {
179
+ // Handle specific error
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## Auth Client
185
+
186
+ ### Creation
187
+
188
+ The auth client is also created once in `hydrate.tsx`:
189
+
190
+ ```ts
191
+ const authClient = createAuthClient(runtimeConfig);
192
+ ```
193
+
194
+ Configured with Better-Auth plugins: SIWN (NEAR), passkey, organization, admin, API key, anonymous, phone.
195
+
196
+ ### In Route Components
197
+
198
+ ```ts
199
+ import { useAuthClient, sessionQueryOptions } from "@/app";
200
+
201
+ function LoginPage() {
202
+ const authClient = useAuthClient();
203
+
204
+ // Sign in with email
205
+ await authClient.signIn.email({ email, password });
206
+
207
+ // Sign in with NEAR (SIWN)
208
+ await authClient.signIn.siwn({ networkId: "mainnet" });
209
+
210
+ // Sign out
211
+ await authClient.signOut();
212
+
213
+ // Organization switching
214
+ await authClient.organization.setActive({ organizationId: "org_123" });
215
+ }
216
+ ```
217
+
218
+ ### Session Query Pattern
219
+
220
+ Use `sessionQueryOptions()` for standardized session fetching with TanStack Query:
221
+
222
+ ```ts
223
+ // In a route loader:
224
+ const session = await queryClient.ensureQueryData(
225
+ sessionQueryOptions(authClient, context.session),
226
+ );
227
+
228
+ // In a component:
229
+ const { data: session } = useQuery(sessionQueryOptions(authClient));
230
+ ```
231
+
232
+ Returns `SessionData` with `user`, `session`, and typed auth context.
233
+
234
+ ## Auth Route Guard
235
+
236
+ The `_authenticated.tsx` layout protects routes that require a session:
237
+
238
+ ```ts
239
+ export const Route = createFileRoute("/_layout/_authenticated")({
240
+ beforeLoad: async ({ context, location }) => {
241
+ const { queryClient, authClient } = context;
242
+ const session = await queryClient.ensureQueryData(
243
+ sessionQueryOptions(authClient, context.session),
244
+ );
245
+ if (!session?.user) {
246
+ throw redirect({
247
+ to: "/login",
248
+ search: { redirect: location.href },
249
+ });
250
+ }
251
+ if (session.user.banned) {
252
+ throw redirect({ to: "/login", hash: "banned" });
253
+ }
254
+ return {
255
+ auth: {
256
+ isAuthenticated: true,
257
+ user: session.user,
258
+ session: session.session,
259
+ activeOrganizationId: session.session?.activeOrganizationId || null,
260
+ isAnonymous: session.user.isAnonymous || false,
261
+ isAdmin: session.user.role === "admin",
262
+ isBanned: session.user.banned || false,
263
+ },
264
+ };
265
+ },
266
+ component: AuthenticatedLayout,
267
+ });
268
+ ```
269
+
270
+ Nest routes under `_layout/_authenticated/` to inherit this guard. Unauthenticated users are redirected to `/login?redirect=<current-path>`.
271
+
272
+ ## Sidebar System
273
+
274
+ Sidebar items are defined inline in `ui/src/routes/_layout.tsx` as a `sidebarItems` array:
275
+
276
+ ```ts
277
+ import { Globe, Home } from "lucide-react";
278
+
279
+ type SidebarRole = "anon" | "member" | "admin";
280
+
281
+ interface SidebarItem {
282
+ icon: React.ComponentType<{ className?: string }>;
283
+ label: string;
284
+ to: string;
285
+ roleRequired: SidebarRole;
286
+ }
287
+ ```
288
+
289
+ ### Role filtering:
290
+
291
+ ```ts
292
+ function filterSidebarByRole(items: SidebarItem[], userRole: SidebarRole): SidebarItem[] {
293
+ return items.filter((item) => {
294
+ if (item.roleRequired === "anon") return true;
295
+ if (item.roleRequired === "member" && userRole !== "anon") return true;
296
+ if (item.roleRequired === "admin" && userRole === "admin") return true;
297
+ return false;
298
+ });
299
+ }
300
+
301
+ const sidebarItems: SidebarItem[] = [
302
+ { icon: Home, label: "home", to: "/home", roleRequired: "anon" },
303
+ ];
304
+ const visibleItems = filterSidebarByRole(sidebarItems, userRole);
305
+ ```
306
+
307
+ Add items manually to the `sidebarItems` array. Available icons: any `lucide-react` icon name.
308
+
309
+ ## SSR Architecture
310
+
311
+ ### Client Bootstrap (`hydrate.tsx`)
312
+
313
+ 1. Reads `window.__RUNTIME_CONFIG__`
314
+ 2. Creates `QueryClient`, `ApiClient`, `AuthClient`
315
+ 3. Creates TanStack Router with browser history
316
+ 4. Hydrates React DOM into the HTML shell
317
+
318
+ ### Server-Side (`router.server.tsx`)
319
+
320
+ 1. Creates request-scoped router with memory history
321
+ 2. Creates per-request `apiClient` and `authClient`
322
+ 3. Renders to stream via TanStack Router SSR's `createRequestHandler`
323
+ 4. Host calls `loadRouterModule()` to dynamically load the SSR bundle
324
+
325
+ ### Route Loading for SSR
326
+
327
+ Loaders run on both server and client. Use `loader` for data needed at render time, `beforeLoad` for auth checks and redirects:
328
+
329
+ ```ts
330
+ export const Route = createFileRoute("/projects")({
331
+ beforeLoad: async ({ context }) => {
332
+ // Runs on server and client — good for auth
333
+ },
334
+ loader: async ({ context }) => {
335
+ // Runs on server and client — good for data fetching
336
+ const { apiClient } = context;
337
+ return await apiClient.projects.listProjects();
338
+ },
339
+ component: ProjectsPage,
340
+ });
341
+ ```
342
+
343
+ The `RouterContext` type:
344
+
345
+ ```ts
346
+ interface RouterContext extends BaseRouterContextWithApi<ApiClient, SessionData> {
347
+ apiClient: ApiClient;
348
+ authClient: AuthClient;
349
+ }
350
+ ```
351
+
352
+ ## Component Patterns
353
+
354
+ ### Semantic Tailwind Classes
355
+
356
+ Use theme-aware classes — never hardcoded colors:
357
+
358
+ ```tsx
359
+ <div className="bg-background text-foreground"> // ✅ correct
360
+ <div className="bg-blue-600 text-white"> // ❌ wrong
361
+ <div className="text-muted-foreground"> // ✅ muted text
362
+ <div className="bg-card border border-border"> // ✅ card surface
363
+ ```
364
+
365
+ ### SSR-Safe Rendering
366
+
367
+ For values that only exist on the client:
368
+
369
+ ```tsx
370
+ import { useClientValue } from "@/hooks";
371
+
372
+ function Component() {
373
+ const appName = useClientValue(() => getAppName(), "app");
374
+ return <h1>{appName}</h1>;
375
+ }
376
+ ```
377
+
378
+ ### Component Location
379
+
380
+ - Shared UI components: `ui/src/components/ui/` — semantic, reusable primitives
381
+ - Feature components: colocated with the route that uses them
382
+ - Exports from `ui/src/components/index.ts` for shared components
package/dist/sidebar.cjs DELETED
@@ -1,116 +0,0 @@
1
- const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
- let node_fs = require("node:fs");
3
- let node_path = require("node:path");
4
-
5
- //#region src/sidebar.ts
6
- const ICON_IMPORTS = {
7
- Home: "lucide-react",
8
- Globe: "lucide-react",
9
- FolderKanban: "lucide-react",
10
- Building2: "lucide-react",
11
- Settings: "lucide-react",
12
- User: "lucide-react",
13
- Users: "lucide-react",
14
- Shield: "lucide-react",
15
- LayoutDashboard: "lucide-react",
16
- CreditCard: "lucide-react",
17
- Bell: "lucide-react",
18
- Key: "lucide-react",
19
- FileText: "lucide-react",
20
- Database: "lucide-react",
21
- Activity: "lucide-react",
22
- BarChart3: "lucide-react",
23
- Zap: "lucide-react",
24
- Terminal: "lucide-react",
25
- Code: "lucide-react",
26
- Package: "lucide-react",
27
- Store: "lucide-react",
28
- ShoppingBag: "lucide-react",
29
- Wallet: "lucide-react",
30
- Coins: "lucide-react",
31
- Plug: "lucide-react",
32
- Link: "lucide-react",
33
- ExternalLink: "lucide-react",
34
- Puzzle: "lucide-react",
35
- Layers: "lucide-react",
36
- Grid3X3: "lucide-react",
37
- AppWindow: "lucide-react"
38
- };
39
- function resolveIconModule(iconName) {
40
- if (ICON_IMPORTS[iconName]) return ICON_IMPORTS[iconName];
41
- return "lucide-react";
42
- }
43
- function collectIconImports(items) {
44
- const moduleMap = /* @__PURE__ */ new Map();
45
- for (const item of items) {
46
- const module = resolveIconModule(item.icon);
47
- if (!moduleMap.has(module)) moduleMap.set(module, /* @__PURE__ */ new Set());
48
- moduleMap.get(module).add(item.icon);
49
- }
50
- return moduleMap;
51
- }
52
- function generatePluginSidebarContent(runtimeConfig) {
53
- const coreItems = [{
54
- icon: "Home",
55
- label: "home",
56
- to: "/home",
57
- roleRequired: "anon"
58
- }];
59
- if (runtimeConfig.auth?.sidebar) for (const item of runtimeConfig.auth.sidebar) coreItems.push({
60
- ...item,
61
- to: item.to ?? "/auth",
62
- roleRequired: item.roleRequired ?? "member"
63
- });
64
- const pluginItems = [];
65
- if (runtimeConfig.plugins) for (const [key, entry] of Object.entries(runtimeConfig.plugins)) {
66
- const sidebar = entry.sidebar;
67
- if (!sidebar) continue;
68
- for (const item of sidebar) pluginItems.push({
69
- ...item,
70
- to: item.to ?? `/${key}`,
71
- roleRequired: item.roleRequired ?? "member"
72
- });
73
- }
74
- const allItems = [...coreItems, ...pluginItems];
75
- const moduleMap = collectIconImports(allItems);
76
- const importLines = [];
77
- for (const [module, icons] of moduleMap) {
78
- const iconList = [...icons].join(", ");
79
- importLines.push(`import { ${iconList} } from "${module}";`);
80
- }
81
- const itemsCode = allItems.map((item) => ` { icon: ${item.icon}, label: "${item.label}", to: "${item.to}" as const, roleRequired: "${item.roleRequired}" as const },`).join("\n");
82
- return `// Auto-generated by bos sync/pluginAdd/pluginRemove. Do not edit.
83
- ${importLines.join("\n")}
84
-
85
- export type SidebarRole = "anon" | "member" | "admin";
86
-
87
- export interface SidebarItem {
88
- icon: React.ComponentType<{ className?: string }>;
89
- label: string;
90
- to: string;
91
- roleRequired: SidebarRole;
92
- }
93
-
94
- export const pluginSidebarItems: SidebarItem[] = [
95
- ${itemsCode}
96
- ];
97
- `;
98
- }
99
- function writePluginSidebarGen(configDir, runtimeConfig) {
100
- const outputPath = (0, node_path.join)(configDir, "ui/src/lib/plugin-sidebar.gen.ts");
101
- const content = generatePluginSidebarContent(runtimeConfig);
102
- const outputDir = (0, node_path.dirname)(outputPath);
103
- if (!(0, node_fs.existsSync)(outputDir)) (0, node_fs.mkdirSync)(outputDir, { recursive: true });
104
- let existingContent = null;
105
- try {
106
- existingContent = (0, node_fs.existsSync)(outputPath) ? (0, node_fs.readFileSync)(outputPath, "utf-8") : null;
107
- } catch {}
108
- if (existingContent === content) return outputPath;
109
- (0, node_fs.writeFileSync)(outputPath, content);
110
- return outputPath;
111
- }
112
-
113
- //#endregion
114
- exports.generatePluginSidebarContent = generatePluginSidebarContent;
115
- exports.writePluginSidebarGen = writePluginSidebarGen;
116
- //# sourceMappingURL=sidebar.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sidebar.cjs","names":[],"sources":["../src/sidebar.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { RuntimeConfig, SidebarItem } from \"./types\";\n\nconst ICON_IMPORTS: Record<string, string> = {\n Home: \"lucide-react\",\n Globe: \"lucide-react\",\n FolderKanban: \"lucide-react\",\n Building2: \"lucide-react\",\n Settings: \"lucide-react\",\n User: \"lucide-react\",\n Users: \"lucide-react\",\n Shield: \"lucide-react\",\n LayoutDashboard: \"lucide-react\",\n CreditCard: \"lucide-react\",\n Bell: \"lucide-react\",\n Key: \"lucide-react\",\n FileText: \"lucide-react\",\n Database: \"lucide-react\",\n Activity: \"lucide-react\",\n BarChart3: \"lucide-react\",\n Zap: \"lucide-react\",\n Terminal: \"lucide-react\",\n Code: \"lucide-react\",\n Package: \"lucide-react\",\n Store: \"lucide-react\",\n ShoppingBag: \"lucide-react\",\n Wallet: \"lucide-react\",\n Coins: \"lucide-react\",\n Plug: \"lucide-react\",\n Link: \"lucide-react\",\n ExternalLink: \"lucide-react\",\n Puzzle: \"lucide-react\",\n Layers: \"lucide-react\",\n Grid3X3: \"lucide-react\",\n AppWindow: \"lucide-react\",\n};\n\nfunction resolveIconModule(iconName: string): string {\n if (ICON_IMPORTS[iconName]) return ICON_IMPORTS[iconName];\n return \"lucide-react\";\n}\n\nfunction collectIconImports(items: SidebarItem[]): Map<string, Set<string>> {\n const moduleMap = new Map<string, Set<string>>();\n for (const item of items) {\n const module = resolveIconModule(item.icon);\n if (!moduleMap.has(module)) moduleMap.set(module, new Set());\n moduleMap.get(module)!.add(item.icon);\n }\n return moduleMap;\n}\n\nexport function generatePluginSidebarContent(runtimeConfig: RuntimeConfig): string {\n const coreItems: SidebarItem[] = [\n { icon: \"Home\", label: \"home\", to: \"/home\", roleRequired: \"anon\" },\n ];\n\n if (runtimeConfig.auth?.sidebar) {\n for (const item of runtimeConfig.auth.sidebar) {\n coreItems.push({\n ...item,\n to: item.to ?? \"/auth\",\n roleRequired: item.roleRequired ?? \"member\",\n });\n }\n }\n\n const pluginItems: SidebarItem[] = [];\n if (runtimeConfig.plugins) {\n for (const [key, entry] of Object.entries(runtimeConfig.plugins)) {\n const sidebar = entry.sidebar;\n if (!sidebar) continue;\n for (const item of sidebar) {\n pluginItems.push({\n ...item,\n to: item.to ?? `/${key}`,\n roleRequired: item.roleRequired ?? \"member\",\n });\n }\n }\n }\n\n const allItems = [...coreItems, ...pluginItems];\n const moduleMap = collectIconImports(allItems);\n\n const importLines: string[] = [];\n for (const [module, icons] of moduleMap) {\n const iconList = [...icons].join(\", \");\n importLines.push(`import { ${iconList} } from \"${module}\";`);\n }\n\n const itemsCode = allItems\n .map(\n (item) =>\n ` { icon: ${item.icon}, label: \"${item.label}\", to: \"${item.to}\" as const, roleRequired: \"${item.roleRequired}\" as const },`,\n )\n .join(\"\\n\");\n\n return `// Auto-generated by bos sync/pluginAdd/pluginRemove. Do not edit.\n${importLines.join(\"\\n\")}\n\nexport type SidebarRole = \"anon\" | \"member\" | \"admin\";\n\nexport interface SidebarItem {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n to: string;\n roleRequired: SidebarRole;\n}\n\nexport const pluginSidebarItems: SidebarItem[] = [\n${itemsCode}\n];\n`;\n}\n\nexport function writePluginSidebarGen(configDir: string, runtimeConfig: RuntimeConfig): string {\n const outputPath = join(configDir, \"ui/src/lib/plugin-sidebar.gen.ts\");\n\n const content = generatePluginSidebarContent(runtimeConfig);\n\n const outputDir = dirname(outputPath);\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n let existingContent: string | null = null;\n try {\n existingContent = existsSync(outputPath)\n ? // eslint-disable-next-line no-restricted-syntax\n readFileSync(outputPath, \"utf-8\")\n : null;\n } catch {\n // file doesn't exist yet\n }\n\n if (existingContent === content) return outputPath;\n\n writeFileSync(outputPath, content);\n return outputPath;\n}\n"],"mappings":";;;;;AAIA,MAAM,eAAuC;CAC3C,MAAM;CACN,OAAO;CACP,cAAc;CACd,WAAW;CACX,UAAU;CACV,MAAM;CACN,OAAO;CACP,QAAQ;CACR,iBAAiB;CACjB,YAAY;CACZ,MAAM;CACN,KAAK;CACL,UAAU;CACV,UAAU;CACV,UAAU;CACV,WAAW;CACX,KAAK;CACL,UAAU;CACV,MAAM;CACN,SAAS;CACT,OAAO;CACP,aAAa;CACb,QAAQ;CACR,OAAO;CACP,MAAM;CACN,MAAM;CACN,cAAc;CACd,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,WAAW;CACZ;AAED,SAAS,kBAAkB,UAA0B;AACnD,KAAI,aAAa,UAAW,QAAO,aAAa;AAChD,QAAO;;AAGT,SAAS,mBAAmB,OAAgD;CAC1E,MAAM,4BAAY,IAAI,KAA0B;AAChD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,kBAAkB,KAAK,KAAK;AAC3C,MAAI,CAAC,UAAU,IAAI,OAAO,CAAE,WAAU,IAAI,wBAAQ,IAAI,KAAK,CAAC;AAC5D,YAAU,IAAI,OAAO,CAAE,IAAI,KAAK,KAAK;;AAEvC,QAAO;;AAGT,SAAgB,6BAA6B,eAAsC;CACjF,MAAM,YAA2B,CAC/B;EAAE,MAAM;EAAQ,OAAO;EAAQ,IAAI;EAAS,cAAc;EAAQ,CACnE;AAED,KAAI,cAAc,MAAM,QACtB,MAAK,MAAM,QAAQ,cAAc,KAAK,QACpC,WAAU,KAAK;EACb,GAAG;EACH,IAAI,KAAK,MAAM;EACf,cAAc,KAAK,gBAAgB;EACpC,CAAC;CAIN,MAAM,cAA6B,EAAE;AACrC,KAAI,cAAc,QAChB,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,cAAc,QAAQ,EAAE;EAChE,MAAM,UAAU,MAAM;AACtB,MAAI,CAAC,QAAS;AACd,OAAK,MAAM,QAAQ,QACjB,aAAY,KAAK;GACf,GAAG;GACH,IAAI,KAAK,MAAM,IAAI;GACnB,cAAc,KAAK,gBAAgB;GACpC,CAAC;;CAKR,MAAM,WAAW,CAAC,GAAG,WAAW,GAAG,YAAY;CAC/C,MAAM,YAAY,mBAAmB,SAAS;CAE9C,MAAM,cAAwB,EAAE;AAChC,MAAK,MAAM,CAAC,QAAQ,UAAU,WAAW;EACvC,MAAM,WAAW,CAAC,GAAG,MAAM,CAAC,KAAK,KAAK;AACtC,cAAY,KAAK,YAAY,SAAS,WAAW,OAAO,IAAI;;CAG9D,MAAM,YAAY,SACf,KACE,SACC,aAAa,KAAK,KAAK,YAAY,KAAK,MAAM,UAAU,KAAK,GAAG,6BAA6B,KAAK,aAAa,eAClH,CACA,KAAK,KAAK;AAEb,QAAO;EACP,YAAY,KAAK,KAAK,CAAC;;;;;;;;;;;;EAYvB,UAAU;;;;AAKZ,SAAgB,sBAAsB,WAAmB,eAAsC;CAC7F,MAAM,iCAAkB,WAAW,mCAAmC;CAEtE,MAAM,UAAU,6BAA6B,cAAc;CAE3D,MAAM,mCAAoB,WAAW;AACrC,KAAI,yBAAY,UAAU,CACxB,wBAAU,WAAW,EAAE,WAAW,MAAM,CAAC;CAG3C,IAAI,kBAAiC;AACrC,KAAI;AACF,4CAA6B,WAAW,6BAEvB,YAAY,QAAQ,GACjC;SACE;AAIR,KAAI,oBAAoB,QAAS,QAAO;AAExC,4BAAc,YAAY,QAAQ;AAClC,QAAO"}
@@ -1,8 +0,0 @@
1
- import { RuntimeConfig } from "./types.cjs";
2
-
3
- //#region src/sidebar.d.ts
4
- declare function generatePluginSidebarContent(runtimeConfig: RuntimeConfig): string;
5
- declare function writePluginSidebarGen(configDir: string, runtimeConfig: RuntimeConfig): string;
6
- //#endregion
7
- export { generatePluginSidebarContent, writePluginSidebarGen };
8
- //# sourceMappingURL=sidebar.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sidebar.d.cts","names":[],"sources":["../src/sidebar.ts"],"mappings":";;;iBAqDgB,4BAAA,CAA6B,aAAA,EAAe,aAAA;AAAA,iBAgE5C,qBAAA,CAAsB,SAAA,UAAmB,aAAA,EAAe,aAAA"}
@@ -1,8 +0,0 @@
1
- import { RuntimeConfig } from "./types.mjs";
2
-
3
- //#region src/sidebar.d.ts
4
- declare function generatePluginSidebarContent(runtimeConfig: RuntimeConfig): string;
5
- declare function writePluginSidebarGen(configDir: string, runtimeConfig: RuntimeConfig): string;
6
- //#endregion
7
- export { generatePluginSidebarContent, writePluginSidebarGen };
8
- //# sourceMappingURL=sidebar.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sidebar.d.mts","names":[],"sources":["../src/sidebar.ts"],"mappings":";;;iBAqDgB,4BAAA,CAA6B,aAAA,EAAe,aAAA;AAAA,iBAgE5C,qBAAA,CAAsB,SAAA,UAAmB,aAAA,EAAe,aAAA"}
package/dist/sidebar.mjs DELETED
@@ -1,114 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
-
4
- //#region src/sidebar.ts
5
- const ICON_IMPORTS = {
6
- Home: "lucide-react",
7
- Globe: "lucide-react",
8
- FolderKanban: "lucide-react",
9
- Building2: "lucide-react",
10
- Settings: "lucide-react",
11
- User: "lucide-react",
12
- Users: "lucide-react",
13
- Shield: "lucide-react",
14
- LayoutDashboard: "lucide-react",
15
- CreditCard: "lucide-react",
16
- Bell: "lucide-react",
17
- Key: "lucide-react",
18
- FileText: "lucide-react",
19
- Database: "lucide-react",
20
- Activity: "lucide-react",
21
- BarChart3: "lucide-react",
22
- Zap: "lucide-react",
23
- Terminal: "lucide-react",
24
- Code: "lucide-react",
25
- Package: "lucide-react",
26
- Store: "lucide-react",
27
- ShoppingBag: "lucide-react",
28
- Wallet: "lucide-react",
29
- Coins: "lucide-react",
30
- Plug: "lucide-react",
31
- Link: "lucide-react",
32
- ExternalLink: "lucide-react",
33
- Puzzle: "lucide-react",
34
- Layers: "lucide-react",
35
- Grid3X3: "lucide-react",
36
- AppWindow: "lucide-react"
37
- };
38
- function resolveIconModule(iconName) {
39
- if (ICON_IMPORTS[iconName]) return ICON_IMPORTS[iconName];
40
- return "lucide-react";
41
- }
42
- function collectIconImports(items) {
43
- const moduleMap = /* @__PURE__ */ new Map();
44
- for (const item of items) {
45
- const module = resolveIconModule(item.icon);
46
- if (!moduleMap.has(module)) moduleMap.set(module, /* @__PURE__ */ new Set());
47
- moduleMap.get(module).add(item.icon);
48
- }
49
- return moduleMap;
50
- }
51
- function generatePluginSidebarContent(runtimeConfig) {
52
- const coreItems = [{
53
- icon: "Home",
54
- label: "home",
55
- to: "/home",
56
- roleRequired: "anon"
57
- }];
58
- if (runtimeConfig.auth?.sidebar) for (const item of runtimeConfig.auth.sidebar) coreItems.push({
59
- ...item,
60
- to: item.to ?? "/auth",
61
- roleRequired: item.roleRequired ?? "member"
62
- });
63
- const pluginItems = [];
64
- if (runtimeConfig.plugins) for (const [key, entry] of Object.entries(runtimeConfig.plugins)) {
65
- const sidebar = entry.sidebar;
66
- if (!sidebar) continue;
67
- for (const item of sidebar) pluginItems.push({
68
- ...item,
69
- to: item.to ?? `/${key}`,
70
- roleRequired: item.roleRequired ?? "member"
71
- });
72
- }
73
- const allItems = [...coreItems, ...pluginItems];
74
- const moduleMap = collectIconImports(allItems);
75
- const importLines = [];
76
- for (const [module, icons] of moduleMap) {
77
- const iconList = [...icons].join(", ");
78
- importLines.push(`import { ${iconList} } from "${module}";`);
79
- }
80
- const itemsCode = allItems.map((item) => ` { icon: ${item.icon}, label: "${item.label}", to: "${item.to}" as const, roleRequired: "${item.roleRequired}" as const },`).join("\n");
81
- return `// Auto-generated by bos sync/pluginAdd/pluginRemove. Do not edit.
82
- ${importLines.join("\n")}
83
-
84
- export type SidebarRole = "anon" | "member" | "admin";
85
-
86
- export interface SidebarItem {
87
- icon: React.ComponentType<{ className?: string }>;
88
- label: string;
89
- to: string;
90
- roleRequired: SidebarRole;
91
- }
92
-
93
- export const pluginSidebarItems: SidebarItem[] = [
94
- ${itemsCode}
95
- ];
96
- `;
97
- }
98
- function writePluginSidebarGen(configDir, runtimeConfig) {
99
- const outputPath = join(configDir, "ui/src/lib/plugin-sidebar.gen.ts");
100
- const content = generatePluginSidebarContent(runtimeConfig);
101
- const outputDir = dirname(outputPath);
102
- if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
103
- let existingContent = null;
104
- try {
105
- existingContent = existsSync(outputPath) ? readFileSync(outputPath, "utf-8") : null;
106
- } catch {}
107
- if (existingContent === content) return outputPath;
108
- writeFileSync(outputPath, content);
109
- return outputPath;
110
- }
111
-
112
- //#endregion
113
- export { generatePluginSidebarContent, writePluginSidebarGen };
114
- //# sourceMappingURL=sidebar.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sidebar.mjs","names":[],"sources":["../src/sidebar.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { RuntimeConfig, SidebarItem } from \"./types\";\n\nconst ICON_IMPORTS: Record<string, string> = {\n Home: \"lucide-react\",\n Globe: \"lucide-react\",\n FolderKanban: \"lucide-react\",\n Building2: \"lucide-react\",\n Settings: \"lucide-react\",\n User: \"lucide-react\",\n Users: \"lucide-react\",\n Shield: \"lucide-react\",\n LayoutDashboard: \"lucide-react\",\n CreditCard: \"lucide-react\",\n Bell: \"lucide-react\",\n Key: \"lucide-react\",\n FileText: \"lucide-react\",\n Database: \"lucide-react\",\n Activity: \"lucide-react\",\n BarChart3: \"lucide-react\",\n Zap: \"lucide-react\",\n Terminal: \"lucide-react\",\n Code: \"lucide-react\",\n Package: \"lucide-react\",\n Store: \"lucide-react\",\n ShoppingBag: \"lucide-react\",\n Wallet: \"lucide-react\",\n Coins: \"lucide-react\",\n Plug: \"lucide-react\",\n Link: \"lucide-react\",\n ExternalLink: \"lucide-react\",\n Puzzle: \"lucide-react\",\n Layers: \"lucide-react\",\n Grid3X3: \"lucide-react\",\n AppWindow: \"lucide-react\",\n};\n\nfunction resolveIconModule(iconName: string): string {\n if (ICON_IMPORTS[iconName]) return ICON_IMPORTS[iconName];\n return \"lucide-react\";\n}\n\nfunction collectIconImports(items: SidebarItem[]): Map<string, Set<string>> {\n const moduleMap = new Map<string, Set<string>>();\n for (const item of items) {\n const module = resolveIconModule(item.icon);\n if (!moduleMap.has(module)) moduleMap.set(module, new Set());\n moduleMap.get(module)!.add(item.icon);\n }\n return moduleMap;\n}\n\nexport function generatePluginSidebarContent(runtimeConfig: RuntimeConfig): string {\n const coreItems: SidebarItem[] = [\n { icon: \"Home\", label: \"home\", to: \"/home\", roleRequired: \"anon\" },\n ];\n\n if (runtimeConfig.auth?.sidebar) {\n for (const item of runtimeConfig.auth.sidebar) {\n coreItems.push({\n ...item,\n to: item.to ?? \"/auth\",\n roleRequired: item.roleRequired ?? \"member\",\n });\n }\n }\n\n const pluginItems: SidebarItem[] = [];\n if (runtimeConfig.plugins) {\n for (const [key, entry] of Object.entries(runtimeConfig.plugins)) {\n const sidebar = entry.sidebar;\n if (!sidebar) continue;\n for (const item of sidebar) {\n pluginItems.push({\n ...item,\n to: item.to ?? `/${key}`,\n roleRequired: item.roleRequired ?? \"member\",\n });\n }\n }\n }\n\n const allItems = [...coreItems, ...pluginItems];\n const moduleMap = collectIconImports(allItems);\n\n const importLines: string[] = [];\n for (const [module, icons] of moduleMap) {\n const iconList = [...icons].join(\", \");\n importLines.push(`import { ${iconList} } from \"${module}\";`);\n }\n\n const itemsCode = allItems\n .map(\n (item) =>\n ` { icon: ${item.icon}, label: \"${item.label}\", to: \"${item.to}\" as const, roleRequired: \"${item.roleRequired}\" as const },`,\n )\n .join(\"\\n\");\n\n return `// Auto-generated by bos sync/pluginAdd/pluginRemove. Do not edit.\n${importLines.join(\"\\n\")}\n\nexport type SidebarRole = \"anon\" | \"member\" | \"admin\";\n\nexport interface SidebarItem {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n to: string;\n roleRequired: SidebarRole;\n}\n\nexport const pluginSidebarItems: SidebarItem[] = [\n${itemsCode}\n];\n`;\n}\n\nexport function writePluginSidebarGen(configDir: string, runtimeConfig: RuntimeConfig): string {\n const outputPath = join(configDir, \"ui/src/lib/plugin-sidebar.gen.ts\");\n\n const content = generatePluginSidebarContent(runtimeConfig);\n\n const outputDir = dirname(outputPath);\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n let existingContent: string | null = null;\n try {\n existingContent = existsSync(outputPath)\n ? // eslint-disable-next-line no-restricted-syntax\n readFileSync(outputPath, \"utf-8\")\n : null;\n } catch {\n // file doesn't exist yet\n }\n\n if (existingContent === content) return outputPath;\n\n writeFileSync(outputPath, content);\n return outputPath;\n}\n"],"mappings":";;;;AAIA,MAAM,eAAuC;CAC3C,MAAM;CACN,OAAO;CACP,cAAc;CACd,WAAW;CACX,UAAU;CACV,MAAM;CACN,OAAO;CACP,QAAQ;CACR,iBAAiB;CACjB,YAAY;CACZ,MAAM;CACN,KAAK;CACL,UAAU;CACV,UAAU;CACV,UAAU;CACV,WAAW;CACX,KAAK;CACL,UAAU;CACV,MAAM;CACN,SAAS;CACT,OAAO;CACP,aAAa;CACb,QAAQ;CACR,OAAO;CACP,MAAM;CACN,MAAM;CACN,cAAc;CACd,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,WAAW;CACZ;AAED,SAAS,kBAAkB,UAA0B;AACnD,KAAI,aAAa,UAAW,QAAO,aAAa;AAChD,QAAO;;AAGT,SAAS,mBAAmB,OAAgD;CAC1E,MAAM,4BAAY,IAAI,KAA0B;AAChD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,kBAAkB,KAAK,KAAK;AAC3C,MAAI,CAAC,UAAU,IAAI,OAAO,CAAE,WAAU,IAAI,wBAAQ,IAAI,KAAK,CAAC;AAC5D,YAAU,IAAI,OAAO,CAAE,IAAI,KAAK,KAAK;;AAEvC,QAAO;;AAGT,SAAgB,6BAA6B,eAAsC;CACjF,MAAM,YAA2B,CAC/B;EAAE,MAAM;EAAQ,OAAO;EAAQ,IAAI;EAAS,cAAc;EAAQ,CACnE;AAED,KAAI,cAAc,MAAM,QACtB,MAAK,MAAM,QAAQ,cAAc,KAAK,QACpC,WAAU,KAAK;EACb,GAAG;EACH,IAAI,KAAK,MAAM;EACf,cAAc,KAAK,gBAAgB;EACpC,CAAC;CAIN,MAAM,cAA6B,EAAE;AACrC,KAAI,cAAc,QAChB,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,cAAc,QAAQ,EAAE;EAChE,MAAM,UAAU,MAAM;AACtB,MAAI,CAAC,QAAS;AACd,OAAK,MAAM,QAAQ,QACjB,aAAY,KAAK;GACf,GAAG;GACH,IAAI,KAAK,MAAM,IAAI;GACnB,cAAc,KAAK,gBAAgB;GACpC,CAAC;;CAKR,MAAM,WAAW,CAAC,GAAG,WAAW,GAAG,YAAY;CAC/C,MAAM,YAAY,mBAAmB,SAAS;CAE9C,MAAM,cAAwB,EAAE;AAChC,MAAK,MAAM,CAAC,QAAQ,UAAU,WAAW;EACvC,MAAM,WAAW,CAAC,GAAG,MAAM,CAAC,KAAK,KAAK;AACtC,cAAY,KAAK,YAAY,SAAS,WAAW,OAAO,IAAI;;CAG9D,MAAM,YAAY,SACf,KACE,SACC,aAAa,KAAK,KAAK,YAAY,KAAK,MAAM,UAAU,KAAK,GAAG,6BAA6B,KAAK,aAAa,eAClH,CACA,KAAK,KAAK;AAEb,QAAO;EACP,YAAY,KAAK,KAAK,CAAC;;;;;;;;;;;;EAYvB,UAAU;;;;AAKZ,SAAgB,sBAAsB,WAAmB,eAAsC;CAC7F,MAAM,aAAa,KAAK,WAAW,mCAAmC;CAEtE,MAAM,UAAU,6BAA6B,cAAc;CAE3D,MAAM,YAAY,QAAQ,WAAW;AACrC,KAAI,CAAC,WAAW,UAAU,CACxB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;CAG3C,IAAI,kBAAiC;AACrC,KAAI;AACF,oBAAkB,WAAW,WAAW,GAEpC,aAAa,YAAY,QAAQ,GACjC;SACE;AAIR,KAAI,oBAAoB,QAAS,QAAO;AAExC,eAAc,YAAY,QAAQ;AAClC,QAAO"}