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.
- package/dist/cli/prompts.cjs +2 -1
- package/dist/cli/prompts.cjs.map +1 -1
- package/dist/cli/prompts.mjs +2 -1
- package/dist/cli/prompts.mjs.map +1 -1
- package/dist/cli/upgrade.cjs +2 -15
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +2 -15
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/config.cjs +1 -12
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -12
- package/dist/config.mjs.map +1 -1
- package/dist/contract.d.cts +0 -60
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +0 -60
- package/dist/contract.d.mts.map +1 -1
- package/dist/index.cjs +0 -5
- package/dist/index.d.cts +2 -3
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -3
- package/dist/orchestrator.cjs +17 -4
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.mjs +17 -4
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +2 -4
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +0 -60
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +0 -60
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +2 -4
- package/dist/plugin.mjs.map +1 -1
- package/dist/types.cjs +2 -21
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -129
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +1 -129
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +3 -20
- package/dist/types.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/api-and-auth/SKILL.md +355 -0
- package/skills/api-and-auth/references/generated-types.md +14 -0
- package/skills/api-and-auth/references/middleware.md +38 -0
- package/skills/cli-reference/SKILL.md +249 -0
- package/skills/plugin-development/SKILL.md +470 -0
- package/skills/ui-integration/SKILL.md +382 -0
- package/dist/sidebar.cjs +0 -116
- package/dist/sidebar.cjs.map +0 -1
- package/dist/sidebar.d.cts +0 -8
- package/dist/sidebar.d.cts.map +0 -1
- package/dist/sidebar.d.mts +0 -8
- package/dist/sidebar.d.mts.map +0 -1
- package/dist/sidebar.mjs +0 -114
- 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
|
package/dist/sidebar.cjs.map
DELETED
|
@@ -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"}
|
package/dist/sidebar.d.cts
DELETED
|
@@ -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
|
package/dist/sidebar.d.cts.map
DELETED
|
@@ -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"}
|
package/dist/sidebar.d.mts
DELETED
|
@@ -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
|
package/dist/sidebar.d.mts.map
DELETED
|
@@ -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
|
package/dist/sidebar.mjs.map
DELETED
|
@@ -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"}
|