@murumets-ee/create 0.1.12 → 0.1.14

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.
@@ -1,14 +1,1123 @@
1
- import{a as e,b as c,c as u,d as l,e as y}from"./chunk-CUB6XFPE.js";import{join as v}from"path";import{join as s}from"path";async function w(t,n){await e(s(t,"lib/auth.ts"),`import { getToolkitApp } from './app'
1
+ import{i as e,n as t,o as n,r,t as i}from"./utils-Bmw75lEa.mjs";import{join as a}from"node:path";import{mkdir as o}from"node:fs/promises";async function s(e,t){await n(a(e,`lib/admin-config.ts`),`import { Media } from '@murumets-ee/media'
2
+ import { Article, Category } from '@/entities'
3
+
4
+ /** All entities available in the admin (drives sidebar nav + CRUD) */
5
+ export const allEntities = [Article, Media, Category]
6
+
7
+ /** Taxonomy entities keyed by name */
8
+ export const taxonomyVocabularies = { category: Category }
9
+
10
+ /** Entities exposed via generic CRUD API handler */
11
+ export const crudEntities = [Article, Media]
12
+
13
+ /** Plugin resources for the permission catalog */
14
+ export const pluginResources = [
15
+ { resource: 'storage', actions: ['view', 'create', 'update', 'delete'] },
16
+ { resource: 'settings', actions: ['view', 'update'] },
17
+ { resource: 'audit-logs', actions: ['view'] },
18
+ { resource: 'permissions', actions: ['view', 'create', 'update', 'delete'] },
19
+ ]
20
+ `),await n(a(e,`lib/content-locale.ts`),`import { cookies } from 'next/headers'
21
+ import { hasLocale } from 'next-intl'
22
+ import { routing } from '@/i18n/routing'
23
+
24
+ /**
25
+ * Get the content editing locale from the cookie, falling back to the interface locale.
26
+ */
27
+ export async function getContentLocale(interfaceLocale: string): Promise<string> {
28
+ const jar = await cookies()
29
+ const raw = jar.get('content-locale')?.value
30
+ if (raw && hasLocale(routing.locales, raw)) return raw
31
+ return interfaceLocale
32
+ }
33
+ `),await n(a(e,`lib/with-admin-context.ts`),`import { getContentConfig } from '@murumets-ee/content/plugin'
34
+ import type { RequestContext } from '@murumets-ee/core'
35
+ import { runWithContextAsync } from '@murumets-ee/core'
36
+ import { getToolkitApp } from './app'
37
+ import { getContentLocale } from './content-locale'
38
+
39
+ export async function withAdminContext<T>(
40
+ interfaceLocale: string,
41
+ fn: (ctx: { locale: string; defaultLocale: string }) => Promise<T>,
42
+ ): Promise<T> {
43
+ const app = await getToolkitApp()
44
+ const contentLocale = await getContentLocale(interfaceLocale)
45
+ const { defaultLocale } = getContentConfig()
46
+
47
+ const context: RequestContext = {
48
+ locale: contentLocale,
49
+ defaultLocale,
50
+ app,
51
+ }
52
+
53
+ return runWithContextAsync(context, () => fn({ locale: contentLocale, defaultLocale }))
54
+ }
55
+ `),await n(a(e,`lib/load-roles.ts`),`import { buildInitialRoleDefinitions } from '@murumets-ee/auth'
56
+ import type { ToolkitApp } from '@murumets-ee/core'
57
+ import { createSettingsClient } from '@murumets-ee/settings'
58
+ import { permissionSettings } from '@/settings/permissions'
59
+
60
+ export async function loadRoles(
61
+ app: ToolkitApp,
62
+ ): Promise<Record<string, Record<string, string[]>>> {
63
+ const client = createSettingsClient(permissionSettings, { app })
64
+ const saved = await client.get('roles')
65
+
66
+ if (saved) return saved
67
+
68
+ // First run — seed built-in roles with zero permissions
69
+ const initial = buildInitialRoleDefinitions()
70
+ await client.set('roles', initial)
71
+ return initial
72
+ }
73
+ `),await n(a(e,`settings/permissions.ts`),`import { defineSettings, setting } from '@murumets-ee/settings'
74
+
75
+ export const permissionSettings = defineSettings({
76
+ namespace: 'permissions',
77
+ scope: 'global',
78
+ label: 'Permissions',
79
+ schema: {
80
+ roles: setting.json<Record<string, Record<string, string[]>>>(),
81
+ },
82
+ })
83
+ `),await n(a(e,`settings/site.ts`),`import { defineSettings, setting } from '@murumets-ee/settings'
84
+
85
+ export const siteSettings = defineSettings({
86
+ namespace: 'site',
87
+ scope: 'global',
88
+ label: 'Site Settings',
89
+ schema: {
90
+ siteName: setting.text({ default: 'My Site', label: 'Site Name', translatable: true }),
91
+ siteDescription: setting.text({ label: 'Site Description', translatable: true }),
92
+ maintenanceMode: setting.boolean({ default: false, label: 'Maintenance Mode' }),
93
+ postsPerPage: setting.number({
94
+ default: 10,
95
+ min: 1,
96
+ max: 100,
97
+ integer: true,
98
+ label: 'Posts Per Page',
99
+ }),
100
+ },
101
+ })
102
+ `),await n(a(e,`app/admin-layout.tsx`),`'use client'
103
+
104
+ import type { LinkComponent, SidebarNavGroup } from '@murumets-ee/admin-ui'
105
+ import { AdminShell } from '@murumets-ee/admin-ui'
106
+ import type { AdminNavGroup } from '@murumets-ee/admin-ui/server'
107
+ import {
108
+ Activity,
109
+ FileText,
110
+ FolderTree,
111
+ Home,
112
+ Image,
113
+ ImageDown,
114
+ Lock,
115
+ PenTool,
116
+ ShieldCheck,
117
+ Tags,
118
+ Users,
119
+ type LucideIcon,
120
+ } from 'lucide-react'
121
+ import type { ReactNode } from 'react'
122
+ import { Link, usePathname } from '@/i18n/navigation'
123
+
124
+ /** Map from icon name strings (from entity admin config) to Lucide components */
125
+ const ICON_MAP: Record<string, LucideIcon> = {
126
+ 'file-text': FileText,
127
+ 'folder-tree': FolderTree,
128
+ tags: Tags,
129
+ }
130
+
131
+ interface AdminLayoutProps {
132
+ children: ReactNode
133
+ defaultOpen?: boolean
134
+ headerActions?: ReactNode
135
+ sidebarFooter?: ReactNode
136
+ entityNavGroups?: AdminNavGroup[]
137
+ }
138
+
139
+ export function AdminLayout({
140
+ children,
141
+ defaultOpen,
142
+ headerActions,
143
+ sidebarFooter,
144
+ entityNavGroups,
145
+ }: AdminLayoutProps) {
146
+ const pathname = usePathname()
147
+
148
+ const dynamicGroups: SidebarNavGroup[] = (entityNavGroups ?? []).map((group) => ({
149
+ label: group.label,
150
+ items: group.items.map((item) => ({
151
+ label: item.label,
152
+ href: item.href,
153
+ icon: item.iconName ? ICON_MAP[item.iconName] : undefined,
154
+ })),
155
+ }))
156
+
157
+ const staticBefore: SidebarNavGroup = {
158
+ items: [
159
+ { label: 'Home', href: '/', icon: Home },
160
+ { label: 'Admin', href: '/admin', icon: PenTool },
161
+ ],
162
+ }
163
+
164
+ const systemGroup: SidebarNavGroup = {
165
+ label: 'System',
166
+ items: [
167
+ { label: 'Users', href: '/admin/users', icon: Users },
168
+ { label: 'Roles', href: '/admin/roles', icon: ShieldCheck },
169
+ { label: 'Permissions', href: '/admin/permissions', icon: Lock },
170
+ { label: 'Media', href: '/admin/media', icon: Image },
171
+ { label: 'Image Styles', href: '/admin/media/image-styles', icon: ImageDown },
172
+ { label: 'Activity', href: '/admin/activity', icon: Activity },
173
+ ],
174
+ }
175
+
176
+ const navGroups: SidebarNavGroup[] = [
177
+ staticBefore,
178
+ ...dynamicGroups,
179
+ systemGroup,
180
+ ]
181
+
182
+ return (
183
+ <AdminShell
184
+ defaultOpen={defaultOpen}
185
+ sidebar={{
186
+ navGroups,
187
+ pathname,
188
+ Link: Link as unknown as LinkComponent,
189
+ logo: (
190
+ <div className="flex h-8 items-center gap-2 px-2">
191
+ <span className="font-semibold text-sm text-zinc-700 dark:text-zinc-300 truncate">
192
+ Admin
193
+ </span>
194
+ </div>
195
+ ),
196
+ footer: sidebarFooter,
197
+ }}
198
+ header={{
199
+ actions: headerActions,
200
+ }}
201
+ >
202
+ {children}
203
+ </AdminShell>
204
+ )
205
+ }
206
+ `),await n(a(e,`app/[locale]/(shell)/layout.tsx`),`import { buildAdminNav } from '@murumets-ee/admin-ui/server'
207
+ import { cookies, headers } from 'next/headers'
208
+ import { redirect } from 'next/navigation'
209
+ import { Suspense } from 'react'
210
+ import { allEntities } from '@/lib/admin-config'
211
+ import { auth } from '@/lib/auth'
212
+ import { AdminLayout } from '../../admin-layout'
213
+ import { ThemeToggle } from '../../theme-toggle'
214
+
215
+ async function DynamicShell({
216
+ children,
217
+ interfaceLocale,
218
+ }: {
219
+ children: React.ReactNode
220
+ interfaceLocale: string
221
+ }) {
222
+ const h = await headers()
223
+ const session = await auth.api.getSession({ headers: h })
224
+ if (!session?.user) {
225
+ redirect(\`/\${interfaceLocale}/auth/sign-in\`)
226
+ }
227
+
228
+ const jar = await cookies()
229
+ const sidebarOpen = jar.get('sidebar:state')?.value !== 'false'
230
+ const entityNavGroups = buildAdminNav(allEntities, { basePath: '/admin' })
231
+
232
+ return (
233
+ <AdminLayout
234
+ defaultOpen={sidebarOpen}
235
+ entityNavGroups={entityNavGroups}
236
+ sidebarFooter={
237
+ <div className="flex justify-center gap-2">
238
+ <ThemeToggle />
239
+ </div>
240
+ }
241
+ >
242
+ {children}
243
+ </AdminLayout>
244
+ )
245
+ }
246
+
247
+ export default async function ShellLayout({
248
+ children,
249
+ params,
250
+ }: {
251
+ children: React.ReactNode
252
+ params: Promise<{ locale: string }>
253
+ }) {
254
+ const { locale } = await params
255
+
256
+ return (
257
+ <Suspense>
258
+ <DynamicShell interfaceLocale={locale}>{children}</DynamicShell>
259
+ </Suspense>
260
+ )
261
+ }
262
+ `),await n(a(e,`app/[locale]/(shell)/admin/layout.tsx`),`import { headers } from 'next/headers'
263
+ import { redirect } from 'next/navigation'
264
+ import { auth } from '@/lib/auth'
265
+
266
+ const ALLOWED_ROLES = new Set(['admin', 'editor'])
267
+
268
+ export default async function AdminGuardLayout({
269
+ children,
270
+ params,
271
+ }: {
272
+ children: React.ReactNode
273
+ params: Promise<{ locale: string }>
274
+ }) {
275
+ const { locale } = await params
276
+ const h = await headers()
277
+ const session = await auth.api.getSession({ headers: h })
278
+
279
+ if (!session?.user) {
280
+ redirect(\`/\${locale}/auth/sign-in\`)
281
+ }
282
+
283
+ const role = (session.user as Record<string, unknown>).role as string | undefined
284
+ if (!role || !ALLOWED_ROLES.has(role)) {
285
+ redirect(\`/\${locale}\`)
286
+ }
287
+
288
+ return <>{children}</>
289
+ }
290
+ `),await n(a(e,`app/[locale]/(shell)/admin/page.tsx`),`import { setRequestLocale } from 'next-intl/server'
291
+
292
+ export default async function AdminDashboard({ params }: { params: Promise<{ locale: string }> }) {
293
+ const { locale } = await params
294
+ setRequestLocale(locale)
295
+
296
+ return (
297
+ <div className="p-6">
298
+ <h1 className="text-2xl font-bold mb-4">Dashboard</h1>
299
+ <p className="text-muted-foreground">
300
+ Welcome to the admin panel. Use the sidebar to manage your content.
301
+ </p>
302
+ </div>
303
+ )
304
+ }
305
+ `),await n(a(e,`app/[locale]/(shell)/admin/[entity]/page.tsx`),`import { EntityList } from '@murumets-ee/admin-ui/entity-list'
306
+ import {
307
+ entityNameToSlug,
308
+ fetchEntityList,
309
+ getEntityLabel,
310
+ resolveEntityFromSlug,
311
+ toEntityMeta,
312
+ } from '@murumets-ee/admin-ui/server'
313
+ import { getContentConfig } from '@murumets-ee/content/plugin'
314
+ import { notFound } from 'next/navigation'
315
+ import { setRequestLocale } from 'next-intl/server'
316
+ import { allEntities, taxonomyVocabularies } from '@/lib/admin-config'
317
+ import { withAdminContext } from '@/lib/with-admin-context'
318
+
319
+ interface EntityListPageProps {
320
+ params: Promise<{ locale: string; entity: string }>
321
+ }
322
+
323
+ export default async function EntityListPage({ params }: EntityListPageProps) {
324
+ const { locale, entity: entitySlug } = await params
325
+ setRequestLocale(locale)
326
+
327
+ const entity = resolveEntityFromSlug(entitySlug, allEntities)
328
+ if (!entity) notFound()
329
+
330
+ const admin = entity.admin
331
+ const meta = toEntityMeta(entity)
332
+ const urlSlug = entityNameToSlug(entity.name)
333
+ const hasTranslatable = Object.values(entity.allFields).some((f) => f.translatable)
334
+ const isPublishable = entity.behaviors?.some((b) => b.name === 'publishable')
335
+ const isTaxonomy = entity.name in taxonomyVocabularies
336
+
337
+ return withAdminContext(locale, async ({ locale: contentLocale, defaultLocale }) => {
338
+ let locales: Array<{ code: string; label: string }> | undefined
339
+ if (hasTranslatable) {
340
+ try {
341
+ const config = getContentConfig()
342
+ locales = config.locales
343
+ } catch {
344
+ // content plugin not available
345
+ }
346
+ }
347
+
348
+ const initialData = await fetchEntityList(entity, {
349
+ sortField: admin?.defaultSort ?? 'createdAt',
350
+ sortDirection: admin?.defaultSortDirection ?? 'desc',
351
+ limit: admin?.pageSize ?? 20,
352
+ locale: contentLocale,
353
+ includeTranslationStatus: hasTranslatable,
354
+ })
355
+
356
+ return (
357
+ <div className="p-6">
358
+ <div className="mb-6">
359
+ <h1 className="text-2xl font-bold">{getEntityLabel(entity)}</h1>
360
+ {admin?.description && (
361
+ <p className="text-sm text-muted-foreground">{admin.description}</p>
362
+ )}
363
+ </div>
364
+ <EntityList
365
+ entity={meta}
366
+ allowDelete
367
+ allowStatusToggle={isPublishable}
368
+ {...(isTaxonomy ? { apiBasePath: '/api/admin/taxonomy', entityPath: entity.name } : {})}
369
+ defaultSort={admin?.defaultSort ?? 'createdAt'}
370
+ defaultSortDirection={admin?.defaultSortDirection ?? 'desc'}
371
+ hiddenColumns={admin?.hiddenColumns}
372
+ pageSize={admin?.pageSize}
373
+ locale={contentLocale}
374
+ locales={locales}
375
+ defaultLocale={defaultLocale}
376
+ showTranslationStatus={hasTranslatable && !!locales}
377
+ searchPlaceholder={\`Search \${getEntityLabel(entity).toLowerCase()}...\`}
378
+ editHref={\`/\${locale}/admin/\${urlSlug}/:id\`}
379
+ createHref={admin?.disableCreate ? undefined : \`/\${locale}/admin/\${urlSlug}/new\`}
380
+ initialData={initialData}
381
+ />
382
+ </div>
383
+ )
384
+ })
385
+ }
386
+ `),await n(a(e,`app/[locale]/(shell)/admin/[entity]/[id]/page.tsx`),`import {
387
+ entityNameToSlug,
388
+ fetchReferenceOptions,
389
+ getBlocksFieldName,
390
+ getEntityLabelSingular,
391
+ getReferenceFields,
392
+ inferRootFields,
393
+ resolveEntityFromSlug,
394
+ toEntityMeta,
395
+ } from '@murumets-ee/admin-ui/server'
396
+ import { buildPermissionChecker } from '@murumets-ee/auth'
397
+ import { ContentClient } from '@murumets-ee/content/client'
398
+ import { LockService } from '@murumets-ee/content/lock'
399
+ import { createAdminClient } from '@murumets-ee/core/clients'
400
+ import { getApp } from '@murumets-ee/core'
401
+ import { prepareBlockEditor } from '@murumets-ee/editor/server'
402
+ import { headers } from 'next/headers'
403
+ import { notFound } from 'next/navigation'
404
+ import { setRequestLocale } from 'next-intl/server'
405
+ import { GenericBlockEditor } from '@murumets-ee/admin-ui/content-editor'
406
+ import { GenericEntityForm } from '@murumets-ee/admin-ui/entity-form'
407
+ import { allEntities } from '@/lib/admin-config'
408
+ import { auth } from '@/lib/auth'
409
+ import { loadRoles } from '@/lib/load-roles'
410
+ import { withAdminContext } from '@/lib/with-admin-context'
411
+
412
+ interface EditEntityPageProps {
413
+ params: Promise<{ locale: string; entity: string; id: string }>
414
+ }
415
+
416
+ export default async function EditEntityPage({ params }: EditEntityPageProps) {
417
+ const { locale, entity: entitySlug, id } = await params
418
+ setRequestLocale(locale)
419
+
420
+ const entity = resolveEntityFromSlug(entitySlug, allEntities)
421
+ if (!entity) notFound()
422
+
423
+ const blocksField = getBlocksFieldName(entity)
424
+ const urlSlug = entityNameToSlug(entity.name)
425
+
426
+ return withAdminContext(locale, async ({ locale: contentLocale, defaultLocale }) => {
427
+ const client = createAdminClient(entity)
428
+ const isDefaultLocale = contentLocale === defaultLocale
429
+ const isNonDefaultLocale = !isDefaultLocale
430
+
431
+ const isVersionable = entity.behaviors?.some((b) => b.name === 'versionable') ?? false
432
+ const isPublishable = entity.behaviors?.some((b) => b.name === 'publishable') ?? false
433
+
434
+ let draft: { data: Record<string, unknown>; createdBy: string; createdByName: string | null; updatedAt: Date | string } | undefined
435
+ let canPublish = false
436
+ let lockResult: { acquired: boolean; lock?: { lockedBy: string; lockedByName: string | null; lockedAt: Date | string; expiresAt: Date | string } | null } | undefined
437
+ const enableLocking = isPublishable
438
+
439
+ if (isPublishable) {
440
+ try {
441
+ const app = getApp()
442
+ const contentClient = new ContentClient({ entity, db: app.db.readWrite })
443
+ const lockLocale = isDefaultLocale ? '_' : contentLocale
444
+
445
+ const draftEntry = await contentClient.getDraft(id, lockLocale)
446
+ if (draftEntry) {
447
+ draft = {
448
+ data: draftEntry.data,
449
+ createdBy: draftEntry.createdBy,
450
+ createdByName: draftEntry.createdByName,
451
+ updatedAt: draftEntry.updatedAt,
452
+ }
453
+ }
454
+
455
+ const session = await auth.api.getSession({ headers: await headers() })
456
+ if (session?.user) {
457
+ const role = (session.user as Record<string, unknown>).role as string | undefined
458
+ if (role) {
459
+ const roles = await loadRoles(app)
460
+ const checker = buildPermissionChecker(roles)
461
+ canPublish = checker(role, entity.name, 'publish')
462
+ }
463
+
464
+ const lockService = new LockService({ db: app.db.readWrite })
465
+ const result = await lockService.acquireLock(
466
+ entity.name, id, lockLocale,
467
+ { id: session.user.id, name: session.user.name ?? undefined },
468
+ )
469
+ if (result.acquired) {
470
+ lockResult = { acquired: true }
471
+ } else {
472
+ lockResult = { acquired: false, lock: result.lock }
473
+ }
474
+ }
475
+ } catch {
476
+ // Draft/permission/lock errors — proceed without
477
+ }
478
+ }
479
+
480
+ // Block editor entity
481
+ if (blocksField) {
482
+ const record = isDefaultLocale
483
+ ? await client.findById(id, { defaultLocale })
484
+ : await client.findById(id, { locale: contentLocale, defaultLocale })
485
+ if (!record) notFound()
486
+
487
+ const rootFields = inferRootFields(entity, blocksField)
488
+ const { blocks, initialData, mediaUrls, rootData, rootFieldDefs } =
489
+ await prepareBlockEditor(
490
+ entity as Parameters<typeof prepareBlockEditor>[0],
491
+ blocksField,
492
+ record,
493
+ { rootFields },
494
+ )
495
+
496
+ return (
497
+ <div className="flex h-[calc(100vh-3.5rem)] flex-col">
498
+ <div className="flex items-center gap-3 border-b px-6 py-3">
499
+ <h1 className="text-lg font-semibold">
500
+ {(record.title as string) ?? \`Untitled \${getEntityLabelSingular(entity)}\`}
501
+ </h1>
502
+ <span className="rounded bg-muted px-2 py-0.5 text-xs text-muted-foreground uppercase">
503
+ {contentLocale}
504
+ </span>
505
+ </div>
506
+ <div className="flex-1 overflow-hidden">
507
+ <GenericBlockEditor
508
+ key={contentLocale}
509
+ entityId={id}
510
+ entityName={entity.name}
511
+ blocksField={blocksField}
512
+ blocks={blocks}
513
+ initialData={initialData}
514
+ mediaUrls={mediaUrls}
515
+ rootData={rootData}
516
+ rootFieldDefs={rootFieldDefs}
517
+ saveLabel="Save"
518
+ locale={contentLocale}
519
+ defaultLocale={defaultLocale}
520
+ versionable={isVersionable}
521
+ isPublishable={isPublishable}
522
+ draft={draft}
523
+ canPublish={canPublish}
524
+ enableLocking={enableLocking}
525
+ lockResult={lockResult}
526
+ />
527
+ </div>
528
+ </div>
529
+ )
530
+ }
531
+
532
+ // Form entity
533
+ const [record, defaultLocaleData] = await Promise.all([
534
+ client.findById(id, { locale: contentLocale }),
535
+ isNonDefaultLocale ? client.findById(id) : Promise.resolve(null),
536
+ ])
537
+ if (!record) notFound()
538
+
539
+ const refFields = getReferenceFields(entity)
540
+ const referenceOptions: Record<string, Array<{ id: string; label: string }>> = {}
541
+
542
+ if (refFields.length > 0) {
543
+ const optionPromises = refFields.map(async ({ fieldName, entityName }) => {
544
+ const refEntity = allEntities.find((e) => e.name === entityName)
545
+ if (!refEntity) return { fieldName, options: [] }
546
+ const options = await fetchReferenceOptions(refEntity, { locale: contentLocale })
547
+ return { fieldName, options }
548
+ })
549
+ const results = await Promise.all(optionPromises)
550
+ for (const { fieldName, options } of results) {
551
+ referenceOptions[fieldName] = options
552
+ }
553
+ }
554
+
555
+ return (
556
+ <div className="p-6">
557
+ <div className="mb-6">
558
+ <h1 className="text-2xl font-bold">Edit {getEntityLabelSingular(entity)}</h1>
559
+ </div>
560
+ <GenericEntityForm
561
+ entity={toEntityMeta(entity)}
562
+ id={id}
563
+ locale={contentLocale}
564
+ defaultLocale={defaultLocale}
565
+ defaultLocaleData={
566
+ isNonDefaultLocale && defaultLocaleData
567
+ ? (defaultLocaleData as Record<string, unknown>)
568
+ : undefined
569
+ }
570
+ initialData={record as Record<string, unknown>}
571
+ referenceOptions={referenceOptions}
572
+ listUrl={\`/\${locale}/admin/\${urlSlug}\`}
573
+ versionable={isVersionable}
574
+ draft={draft}
575
+ canPublish={canPublish}
576
+ enableLocking={enableLocking}
577
+ lockResult={lockResult}
578
+ />
579
+ </div>
580
+ )
581
+ })
582
+ }
583
+ `),await n(a(e,`app/[locale]/(shell)/admin/[entity]/new/page.tsx`),`import {
584
+ entityNameToSlug,
585
+ fetchReferenceOptions,
586
+ getBlocksFieldName,
587
+ getEntityLabelSingular,
588
+ getReferenceFields,
589
+ resolveEntityFromSlug,
590
+ toEntityMeta,
591
+ } from '@murumets-ee/admin-ui/server'
592
+ import { createAdminClient } from '@murumets-ee/core/clients'
593
+ import { notFound, redirect } from 'next/navigation'
594
+ import { setRequestLocale } from 'next-intl/server'
595
+ import { GenericEntityForm } from '@murumets-ee/admin-ui/entity-form'
596
+ import { allEntities } from '@/lib/admin-config'
597
+ import { withAdminContext } from '@/lib/with-admin-context'
598
+
599
+ interface NewEntityPageProps {
600
+ params: Promise<{ locale: string; entity: string }>
601
+ }
602
+
603
+ export default async function NewEntityPage({ params }: NewEntityPageProps) {
604
+ const { locale, entity: entitySlug } = await params
605
+ setRequestLocale(locale)
606
+
607
+ const entity = resolveEntityFromSlug(entitySlug, allEntities)
608
+ if (!entity) notFound()
609
+ if (entity.admin?.disableCreate) notFound()
610
+
611
+ const blocksField = getBlocksFieldName(entity)
612
+ const urlSlug = entityNameToSlug(entity.name)
613
+
614
+ return withAdminContext(locale, async ({ locale: contentLocale }) => {
615
+ // Block editor entities: create a draft and redirect to the edit page
616
+ if (blocksField) {
617
+ const client = createAdminClient(entity)
618
+ const draft = await client.create({ title: 'Untitled' })
619
+ const newId = (draft as Record<string, unknown>).id as string
620
+ redirect(\`/\${locale}/admin/\${urlSlug}/\${newId}\`)
621
+ }
622
+
623
+ // Form entities: render the create form
624
+ const refFields = getReferenceFields(entity)
625
+ const referenceOptions: Record<string, Array<{ id: string; label: string }>> = {}
626
+
627
+ if (refFields.length > 0) {
628
+ const optionPromises = refFields.map(async ({ fieldName, entityName }) => {
629
+ const refEntity = allEntities.find((e) => e.name === entityName)
630
+ if (!refEntity) return { fieldName, options: [] }
631
+ const options = await fetchReferenceOptions(refEntity, { locale: contentLocale })
632
+ return { fieldName, options }
633
+ })
634
+ const results = await Promise.all(optionPromises)
635
+ for (const { fieldName, options } of results) {
636
+ referenceOptions[fieldName] = options
637
+ }
638
+ }
639
+
640
+ return (
641
+ <div className="p-6">
642
+ <div className="mb-6">
643
+ <h1 className="text-2xl font-bold">New {getEntityLabelSingular(entity)}</h1>
644
+ </div>
645
+ <GenericEntityForm
646
+ entity={toEntityMeta(entity)}
647
+ locale={contentLocale}
648
+ referenceOptions={referenceOptions}
649
+ listUrl={\`/\${locale}/admin/\${urlSlug}\`}
650
+ />
651
+ </div>
652
+ )
653
+ })
654
+ }
655
+ `),await n(a(e,`app/[locale]/(shell)/admin/media/page.tsx`),`import { EntityList } from '@murumets-ee/admin-ui/entity-list'
656
+ import { fetchEntityList, toEntityMeta } from '@murumets-ee/admin-ui/server'
657
+ import { Media } from '@murumets-ee/media'
658
+ import { getMediaClient } from '@murumets-ee/media/client'
659
+ import { setRequestLocale } from 'next-intl/server'
660
+
661
+ export default async function MediaListPage({ params }: { params: Promise<{ locale: string }> }) {
662
+ const { locale } = await params
663
+ setRequestLocale(locale)
664
+ const initialData = await fetchEntityList(Media, { sortField: 'createdAt' })
665
+
666
+ const mediaClient = await getMediaClient()
667
+ const ids = initialData.items.map((i) => i.id as string)
668
+ const thumbMap = await mediaClient.getVariantUrls(ids, 'thumbnail')
669
+ for (const item of initialData.items) {
670
+ item.thumbnailUrl = thumbMap.get(item.id as string) ?? ''
671
+ }
672
+
673
+ return (
674
+ <div className="p-6">
675
+ <div className="mb-6">
676
+ <h1 className="text-2xl font-bold">Media</h1>
677
+ <p className="text-sm text-muted-foreground">
678
+ Manage uploaded media files.
679
+ </p>
680
+ </div>
681
+ <EntityList
682
+ entity={toEntityMeta(Media)}
683
+ entityPath="media"
684
+ image={{ field: 'thumbnailUrl', size: 'sm', shape: 'rounded' }}
685
+ allowDelete
686
+ defaultSort="createdAt"
687
+ searchPlaceholder="Search media..."
688
+ editHref={\`/\${locale}/admin/media/:id\`}
689
+ initialData={initialData}
690
+ />
691
+ </div>
692
+ )
693
+ }
694
+ `),await n(a(e,`app/[locale]/(shell)/admin/media/[id]/page.tsx`),`import { toEntityMeta } from '@murumets-ee/admin-ui/server'
695
+ import { getCurrentApp } from '@murumets-ee/core'
696
+ import { createAdminClient } from '@murumets-ee/core/clients'
697
+ import { Media } from '@murumets-ee/media'
698
+ import { findMediaUsages } from '@murumets-ee/media/usage'
699
+ import { notFound } from 'next/navigation'
700
+ import { setRequestLocale } from 'next-intl/server'
701
+ import { withAdminContext } from '@/lib/with-admin-context'
702
+ import { MediaForm } from './media-form'
703
+ import { MediaUsagePanel } from './media-usage-panel'
704
+
705
+ interface MediaEditPageProps {
706
+ params: Promise<{ locale: string; id: string }>
707
+ }
708
+
709
+ export default async function MediaEditPage({ params }: MediaEditPageProps) {
710
+ const { locale, id } = await params
711
+ setRequestLocale(locale)
712
+
713
+ return withAdminContext(locale, async ({ locale: contentLocale }) => {
714
+ const app = getCurrentApp()!
715
+ const client = createAdminClient(Media)
716
+ const [mediaItem, usages] = await Promise.all([
717
+ client.findById(id, { locale: contentLocale }),
718
+ findMediaUsages(id, app.db.readWrite),
719
+ ])
720
+ if (!mediaItem) notFound()
721
+
722
+ return (
723
+ <div className="p-6">
724
+ <div className="mb-6">
725
+ <h1 className="text-2xl font-bold">Edit Media</h1>
726
+ </div>
727
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
728
+ <div className="lg:col-span-2">
729
+ <MediaForm
730
+ entity={toEntityMeta(Media)}
731
+ id={id}
732
+ locale={contentLocale}
733
+ initialData={mediaItem as Record<string, unknown>}
734
+ />
735
+ </div>
736
+ <div>
737
+ <MediaUsagePanel usages={usages} locale={locale} />
738
+ </div>
739
+ </div>
740
+ </div>
741
+ )
742
+ })
743
+ }
744
+ `),await n(a(e,`app/[locale]/(shell)/admin/media/[id]/media-form.tsx`),`'use client'
745
+
746
+ import { EntityForm } from '@murumets-ee/admin-ui/entity-form'
747
+ import type { EntityMeta } from '@murumets-ee/admin-ui/entity-list'
748
+ import { useRouter } from 'next/navigation'
749
+
750
+ interface MediaFormProps {
751
+ entity: EntityMeta
752
+ id: string
753
+ locale?: string
754
+ initialData?: Record<string, unknown>
755
+ }
756
+
757
+ export function MediaForm({ entity, id, locale, initialData }: MediaFormProps) {
758
+ const router = useRouter()
759
+
760
+ return (
761
+ <EntityForm
762
+ entity={entity}
763
+ id={id}
764
+ locale={locale}
765
+ initialData={initialData}
766
+ entityPath="media"
767
+ layout="two-column"
768
+ fields={['title', 'alt', 'description', 'filename', 'mimeType', 'size', 'mediaType']}
769
+ fieldOverrides={{
770
+ filename: { readOnly: true, description: 'Original upload filename (read-only)' },
771
+ mimeType: { readOnly: true, label: 'MIME Type' },
772
+ size: { readOnly: true, description: 'File size in bytes' },
773
+ mediaType: { readOnly: true, label: 'Media Type' },
774
+ alt: { label: 'Alt Text', description: 'Alternative text for accessibility' },
775
+ }}
776
+ onSuccess={() => router.push(\`/\${locale}/admin/media\`)}
777
+ onCancel={() => router.back()}
778
+ />
779
+ )
780
+ }
781
+ `),await n(a(e,`app/[locale]/(shell)/admin/media/[id]/media-usage-panel.tsx`),`import type { MediaUsage } from '@murumets-ee/media/usage'
782
+
783
+ interface MediaUsagePanelProps {
784
+ usages: MediaUsage[]
785
+ locale: string
786
+ }
787
+
788
+ export function MediaUsagePanel({ usages, locale }: MediaUsagePanelProps) {
789
+ return (
790
+ <div className="rounded-lg border bg-card p-4">
791
+ <h3 className="mb-3 text-sm font-semibold">Used by</h3>
792
+
793
+ {usages.length === 0 && (
794
+ <p className="text-sm text-muted-foreground">
795
+ This media item is not referenced by any entity.
796
+ </p>
797
+ )}
798
+
799
+ {usages.length > 0 && (
800
+ <ul className="space-y-2">
801
+ {usages.map((usage) => {
802
+ const entitySlug = \`\${usage.entityName}s\`
803
+ const href = \`/\${locale}/admin/\${entitySlug}/\${usage.entityId}\`
804
+
805
+ return (
806
+ <li key={\`\${usage.entityName}-\${usage.entityId}-\${usage.fieldName}\`}>
807
+ <a
808
+ href={href}
809
+ className="block rounded-md border px-3 py-2 text-sm hover:bg-muted transition-colors"
810
+ >
811
+ <span className="font-medium capitalize">{usage.entityName}</span>
812
+ <span className="mx-1.5 text-muted-foreground">&middot;</span>
813
+ <span className="text-muted-foreground">{usage.fieldName}</span>
814
+ {usage.context === 'block' && (
815
+ <span className="ml-1.5 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
816
+ block
817
+ </span>
818
+ )}
819
+ </a>
820
+ </li>
821
+ )
822
+ })}
823
+ </ul>
824
+ )}
825
+ </div>
826
+ )
827
+ }
828
+ `),await n(a(e,`app/[locale]/(shell)/admin/media/image-styles/page.tsx`),`import { imageStylesSettings } from '@murumets-ee/media'
829
+ import { ImageStylesManager } from '@murumets-ee/media/image-styles'
830
+ import { createSettingsClient } from '@murumets-ee/settings'
831
+ import { setRequestLocale } from 'next-intl/server'
832
+ import { getToolkitApp } from '@/lib/app'
833
+
834
+ async function loadImageStyles() {
835
+ const app = await getToolkitApp()
836
+ const client = createSettingsClient(imageStylesSettings, { app })
837
+ const styles = await client.get('imageStyles')
838
+ return styles ?? {}
839
+ }
840
+
841
+ export default async function ImageStylesPage({ params }: { params: Promise<{ locale: string }> }) {
842
+ const { locale } = await params
843
+ setRequestLocale(locale)
844
+
845
+ const initialStyles = await loadImageStyles()
846
+
847
+ return (
848
+ <div className="p-6 max-w-4xl">
849
+ <ImageStylesManager initialStyles={initialStyles} />
850
+ </div>
851
+ )
852
+ }
853
+ `),await n(a(e,`app/[locale]/(shell)/admin/users/page.tsx`),`import type { UsersInitialData } from '@murumets-ee/admin-ui/users'
854
+ import { getAuth } from '@murumets-ee/auth'
855
+ import { headers } from 'next/headers'
856
+ import { setRequestLocale } from 'next-intl/server'
857
+ import { getToolkitApp } from '@/lib/app'
858
+ import { UsersPage } from './users-page'
859
+
860
+ export default async function UsersAdminPage({ params }: { params: Promise<{ locale: string }> }) {
861
+ const { locale } = await params
862
+ setRequestLocale(locale)
863
+
864
+ await getToolkitApp()
865
+ const auth = getAuth()
866
+
867
+ const result = await auth.api.listUsers({
868
+ headers: await headers(),
869
+ query: { limit: 20, sortBy: 'createdAt', sortDirection: 'desc' },
870
+ })
871
+
872
+ const initialData: UsersInitialData = {
873
+ users: result.users.map((u) => ({
874
+ id: u.id,
875
+ name: u.name,
876
+ email: u.email,
877
+ emailVerified: u.emailVerified,
878
+ image: u.image ?? null,
879
+ createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : String(u.createdAt),
880
+ updatedAt: u.updatedAt instanceof Date ? u.updatedAt.toISOString() : String(u.updatedAt),
881
+ role: u.role ?? null,
882
+ banned: u.banned ?? null,
883
+ banReason: u.banReason ?? null,
884
+ banExpires:
885
+ u.banExpires instanceof Date
886
+ ? u.banExpires.toISOString()
887
+ : u.banExpires
888
+ ? String(u.banExpires)
889
+ : null,
890
+ })),
891
+ total: result.total,
892
+ }
893
+
894
+ return (
895
+ <div className="p-6">
896
+ <div className="mb-6">
897
+ <h1 className="text-2xl font-bold">Users</h1>
898
+ <p className="text-sm text-muted-foreground">Manage user accounts, roles, and access.</p>
899
+ </div>
900
+ <UsersPage initialData={initialData} />
901
+ </div>
902
+ )
903
+ }
904
+ `),await n(a(e,`app/[locale]/(shell)/admin/users/users-page.tsx`),`'use client'
905
+
906
+ import type { UsersInitialData } from '@murumets-ee/admin-ui/users'
907
+ import { UsersManagement } from '@murumets-ee/admin-ui/users'
908
+ import { createUsersApi } from '@murumets-ee/auth/client'
909
+ import { authClient } from '@/lib/auth-client'
910
+
911
+ const usersApi = createUsersApi(authClient)
912
+
913
+ export function UsersPage({ initialData }: { initialData: UsersInitialData }) {
914
+ const session = authClient.useSession()
915
+
916
+ return (
917
+ <UsersManagement
918
+ api={usersApi}
919
+ currentUserId={session.data?.user?.id}
920
+ roles={['admin', 'editor', 'viewer']}
921
+ initialData={initialData}
922
+ />
923
+ )
924
+ }
925
+ `),await n(a(e,`app/[locale]/(shell)/admin/permissions/page.tsx`),`import { PermissionsEditor } from '@murumets-ee/admin-ui/permissions'
926
+ import { BUILT_IN_ROLES, buildResourceCatalog } from '@murumets-ee/auth'
927
+ import { setRequestLocale } from 'next-intl/server'
928
+ import { allEntities, pluginResources } from '@/lib/admin-config'
929
+ import { getToolkitApp } from '@/lib/app'
930
+ import { loadRoles } from '@/lib/load-roles'
931
+
932
+ export default async function PermissionsPage({ params }: { params: Promise<{ locale: string }> }) {
933
+ const { locale } = await params
934
+ setRequestLocale(locale)
935
+
936
+ const app = await getToolkitApp()
937
+ const savedRoles = await loadRoles(app)
938
+ const statements = buildResourceCatalog(allEntities, pluginResources)
939
+
940
+ return (
941
+ <div className="p-6">
942
+ <div className="mb-6">
943
+ <h1 className="text-2xl font-bold">Permissions</h1>
944
+ <p className="text-sm text-muted-foreground">
945
+ Configure what each role can do. Admin always has full access.
946
+ </p>
947
+ </div>
948
+ <PermissionsEditor
949
+ statements={statements}
950
+ roles={Object.keys(savedRoles)}
951
+ builtInRoles={[...BUILT_IN_ROLES]}
952
+ initialPermissions={savedRoles}
953
+ />
954
+ </div>
955
+ )
956
+ }
957
+ `),await n(a(e,`app/[locale]/(shell)/admin/roles/page.tsx`),`import { RolesEditor } from '@murumets-ee/admin-ui/permissions'
958
+ import { BUILT_IN_ROLES } from '@murumets-ee/auth'
959
+ import { setRequestLocale } from 'next-intl/server'
960
+ import { getToolkitApp } from '@/lib/app'
961
+ import { loadRoles } from '@/lib/load-roles'
962
+
963
+ export default async function RolesPage({ params }: { params: Promise<{ locale: string }> }) {
964
+ const { locale } = await params
965
+ setRequestLocale(locale)
966
+
967
+ const app = await getToolkitApp()
968
+ const savedRoles = await loadRoles(app)
969
+
970
+ const roles = [
971
+ { name: 'admin', builtIn: true, permissionCount: 0 },
972
+ ...Object.entries(savedRoles).map(([name, perms]) => ({
973
+ name,
974
+ builtIn: (BUILT_IN_ROLES as readonly string[]).includes(name),
975
+ permissionCount: Object.values(perms).flat().length,
976
+ })),
977
+ ]
978
+
979
+ return (
980
+ <div className="p-6">
981
+ <div className="mb-6">
982
+ <h1 className="text-2xl font-bold">Roles</h1>
983
+ <p className="text-sm text-muted-foreground">
984
+ Manage user roles. Built-in roles cannot be deleted.
985
+ </p>
986
+ </div>
987
+ <RolesEditor roles={roles} permissionsHref={\`/\${locale}/admin/permissions\`} />
988
+ </div>
989
+ )
990
+ }
991
+ `),await n(a(e,`app/[locale]/(shell)/admin/activity/page.tsx`),`import { AuditLog } from '@murumets-ee/admin-ui/audit-log'
992
+ import { fetchAuditLogData } from '@murumets-ee/admin-ui/server'
993
+ import { setRequestLocale } from 'next-intl/server'
994
+ import { withAdminContext } from '@/lib/with-admin-context'
995
+
996
+ export default async function ActivityPage({ params }: { params: Promise<{ locale: string }> }) {
997
+ const { locale } = await params
998
+ setRequestLocale(locale)
999
+
1000
+ return withAdminContext(locale, async () => {
1001
+ const initialData = await fetchAuditLogData()
1002
+
1003
+ return (
1004
+ <div className="p-6">
1005
+ <div className="mb-6">
1006
+ <h1 className="text-2xl font-bold">Activity Log</h1>
1007
+ <p className="text-sm text-muted-foreground">
1008
+ Track all content changes across your CMS.
1009
+ </p>
1010
+ </div>
1011
+ <AuditLog
1012
+ initialData={initialData}
1013
+ editHrefPattern={\`/\${locale}/admin/:entityType/:entityId\`}
1014
+ />
1015
+ </div>
1016
+ )
1017
+ })
1018
+ }
1019
+ `),await n(a(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
1020
+ import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
1021
+ import { permissionRoutes } from '@murumets-ee/auth/admin'
1022
+ import { ContentClient } from '@murumets-ee/content/client'
1023
+ import { LockService } from '@murumets-ee/content/lock'
1024
+ import type { PermissionChecker } from '@murumets-ee/core'
1025
+ import { getApp } from '@murumets-ee/core'
1026
+ import {
1027
+ AuditLogClient,
1028
+ createAuditDbWriter,
1029
+ createAuditLogger,
1030
+ createLogger,
1031
+ } from '@murumets-ee/logging'
1032
+ import { logRoutes } from '@murumets-ee/logging/admin'
1033
+ import { mediaRoutes } from '@murumets-ee/media/admin'
1034
+ import { createSettingsClient } from '@murumets-ee/settings'
1035
+ import { settingsRoutes } from '@murumets-ee/settings/admin'
1036
+ import { storageRoutes } from '@murumets-ee/storage/admin'
1037
+ import { taxonomyRoutes } from '@murumets-ee/taxonomy/admin'
1038
+ import {
1039
+ allEntities,
1040
+ crudEntities,
1041
+ pluginResources,
1042
+ taxonomyVocabularies,
1043
+ } from '@/lib/admin-config'
1044
+ import { getToolkitApp } from '@/lib/app'
1045
+ import { auth } from '@/lib/auth'
1046
+ import { loadRoles } from '@/lib/load-roles'
1047
+ import { permissionSettings } from '@/settings/permissions'
1048
+ import { siteSettings } from '@/settings/site'
1049
+
1050
+ await getToolkitApp()
1051
+
1052
+ let _auditLogger: ReturnType<typeof createAuditLogger> | null = null
1053
+ let _checker: PermissionChecker | null = null
1054
+
1055
+ const routes = [
1056
+ mediaRoutes(),
1057
+ storageRoutes(),
1058
+ settingsRoutes(siteSettings),
1059
+ taxonomyRoutes(taxonomyVocabularies),
1060
+ logRoutes(() => new AuditLogClient(getApp().db.readWrite)),
1061
+ permissionRoutes({
1062
+ getStatements: () => buildResourceCatalog(allEntities, pluginResources),
1063
+ loadRoles: async () => loadRoles(getApp()),
1064
+ saveRoles: async (roles) => {
1065
+ const app = getApp()
1066
+ const client = createSettingsClient(permissionSettings, { app })
1067
+ await client.set('roles', roles)
1068
+ },
1069
+ onSave: () => {
1070
+ _checker = null
1071
+ },
1072
+ }),
1073
+ ]
1074
+
1075
+ const handler = createAdminApiHandler({
1076
+ authenticate: async (req) => {
1077
+ const session = await auth.api.getSession({ headers: req.headers })
1078
+ if (!session?.user) return null
1079
+ const role = (session.user as Record<string, unknown>).role as string | undefined
1080
+ return { id: session.user.id, role, name: session.user.name, email: session.user.email }
1081
+ },
1082
+ defaultLocale: 'en',
1083
+ entities: crudEntities,
1084
+ routes,
1085
+ loadPermissions: async () => {
1086
+ if (_checker) return _checker
1087
+ const roles = await loadRoles(getApp())
1088
+ _checker = buildPermissionChecker(roles)
1089
+ return _checker
1090
+ },
1091
+ auditLogger: {
1092
+ log: async (entry) => {
1093
+ if (!_auditLogger) {
1094
+ const app = getApp()
1095
+ _auditLogger = createAuditLogger({
1096
+ logger: createLogger({ name: 'audit' }),
1097
+ dbWriter: createAuditDbWriter(app.db.readWrite),
1098
+ })
1099
+ }
1100
+ return _auditLogger.log(entry)
1101
+ },
1102
+ },
1103
+ contentClientFactory: (entity) =>
1104
+ new ContentClient({ entity, db: getApp().db.readWrite }),
1105
+ lockServiceFactory: () =>
1106
+ new LockService({ db: getApp().db.readWrite }),
1107
+ })
1108
+
1109
+ export const { GET, POST, PATCH, DELETE } = handler
1110
+ `)}async function c(e,t){await n(a(e,`lib/auth.ts`),`import { getToolkitApp } from './app'
2
1111
  import { getAuth } from '@murumets-ee/auth'
3
1112
 
4
1113
  export async function getAuthInstance() {
5
1114
  await getToolkitApp()
6
1115
  return getAuth()
7
1116
  }
8
- `),await e(s(t,"lib/auth-client.ts"),`import { createClient } from '@murumets-ee/auth/client'
1117
+ `),await n(a(e,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
9
1118
 
10
1119
  export const authClient = createClient()
11
- `),await e(s(t,"app/api/auth/[...all]/route.ts"),`import { toNextJsHandler } from 'better-auth/next-js'
1120
+ `),await n(a(e,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
12
1121
  import { getAuthInstance } from '../../../../lib/auth'
13
1122
 
14
1123
  let _handler: ReturnType<typeof toNextJsHandler> | null = null
@@ -30,7 +1139,7 @@ export async function POST(req: Request) {
30
1139
  const h = await handler()
31
1140
  return h.POST(req)
32
1141
  }
33
- `),await e(s(t,"app/[locale]/auth/layout.tsx"),`import { AuthProviders } from './providers'
1142
+ `),await n(a(e,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
34
1143
  import type { ReactNode } from 'react'
35
1144
 
36
1145
  export default function AuthLayout({ children }: { children: ReactNode }) {
@@ -42,7 +1151,7 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
42
1151
  </AuthProviders>
43
1152
  )
44
1153
  }
45
- `),await e(s(t,"app/[locale]/auth/providers.tsx"),`'use client'
1154
+ `),await n(a(e,`app/[locale]/auth/providers.tsx`),`'use client'
46
1155
 
47
1156
  import { AuthUIProvider } from '@murumets-ee/auth-ui'
48
1157
  import { authClient } from '@/lib/auth-client'
@@ -68,22 +1177,22 @@ export function AuthProviders({ children }: { children: ReactNode }) {
68
1177
  </AuthUIProvider>
69
1178
  )
70
1179
  }
71
- `),await e(s(t,"app/[locale]/auth/sign-in/page.tsx"),`import { SignInForm } from '@murumets-ee/auth-ui'
1180
+ `),await n(a(e,`app/[locale]/auth/sign-in/page.tsx`),`import { SignInForm } from '@murumets-ee/auth-ui'
72
1181
 
73
1182
  export default function SignInPage() {
74
1183
  return <SignInForm />
75
1184
  }
76
- `),await e(s(t,"app/[locale]/auth/sign-up/page.tsx"),`import { SignUpForm } from '@murumets-ee/auth-ui'
1185
+ `),await n(a(e,`app/[locale]/auth/sign-up/page.tsx`),`import { SignUpForm } from '@murumets-ee/auth-ui'
77
1186
 
78
1187
  export default function SignUpPage() {
79
1188
  return <SignUpForm />
80
1189
  }
81
- `),await e(s(t,"app/[locale]/auth/forgot-password/page.tsx"),`import { ForgotPasswordForm } from '@murumets-ee/auth-ui'
1190
+ `),await n(a(e,`app/[locale]/auth/forgot-password/page.tsx`),`import { ForgotPasswordForm } from '@murumets-ee/auth-ui'
82
1191
 
83
1192
  export default function ForgotPasswordPage() {
84
1193
  return <ForgotPasswordForm />
85
1194
  }
86
- `),await e(s(t,"app/[locale]/auth/reset-password/page.tsx"),`import { Suspense } from 'react'
1195
+ `),await n(a(e,`app/[locale]/auth/reset-password/page.tsx`),`import { Suspense } from 'react'
87
1196
  import { ResetPasswordContent } from './content'
88
1197
 
89
1198
  export default function ResetPasswordPage() {
@@ -93,7 +1202,7 @@ export default function ResetPasswordPage() {
93
1202
  </Suspense>
94
1203
  )
95
1204
  }
96
- `),await e(s(t,"app/[locale]/auth/reset-password/content.tsx"),`'use client'
1205
+ `),await n(a(e,`app/[locale]/auth/reset-password/content.tsx`),`'use client'
97
1206
 
98
1207
  import { useSearchParams } from 'next/navigation'
99
1208
  import { ResetPasswordForm } from '@murumets-ee/auth-ui'
@@ -117,7 +1226,7 @@ export function ResetPasswordContent() {
117
1226
 
118
1227
  return <ResetPasswordForm token={token} />
119
1228
  }
120
- `),await e(s(t,"app/[locale]/setup/page.tsx"),`import { redirect } from 'next/navigation'
1229
+ `),await n(a(e,`app/[locale]/setup/page.tsx`),`import { redirect } from 'next/navigation'
121
1230
  import { sql } from 'drizzle-orm'
122
1231
  import { getToolkitApp } from '@/lib/app'
123
1232
  import { SetupForm } from './form'
@@ -142,7 +1251,7 @@ export default async function SetupPage() {
142
1251
  </div>
143
1252
  )
144
1253
  }
145
- `),await e(s(t,"app/[locale]/setup/form.tsx"),`'use client'
1254
+ `),await n(a(e,`app/[locale]/setup/form.tsx`),`'use client'
146
1255
 
147
1256
  import { useState } from 'react'
148
1257
  import { createFirstAdmin } from './actions'
@@ -213,7 +1322,7 @@ export function SetupForm() {
213
1322
  </form>
214
1323
  )
215
1324
  }
216
- `),await e(s(t,"app/[locale]/setup/actions.ts"),`'use server'
1325
+ `),await n(a(e,`app/[locale]/setup/actions.ts`),`'use server'
217
1326
 
218
1327
  import { sql } from 'drizzle-orm'
219
1328
  import { getToolkitApp } from '@/lib/app'
@@ -267,17 +1376,19 @@ export async function createFirstAdmin(formData: FormData) {
267
1376
 
268
1377
  return { ok: true, email: adminUser.user.email }
269
1378
  }
270
- `)}import{join as m}from"path";async function k(t,n){let{name:r}=n;await e(m(t,".env.example"),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
1379
+ `)}async function l(e,t){let{name:r}=t;await n(a(e,`.env.example`),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
271
1380
  BETTER_AUTH_SECRET=dev-secret-change-me-in-production-min-32-chars
272
1381
  BETTER_AUTH_URL=http://localhost:3000
273
1382
  LOG_LEVEL=debug
274
1383
  NEXT_PUBLIC_APP_URL=http://localhost:3000
275
- QUEUE_WORKER=true
1384
+ # Queue worker — by default runs embedded in the web process.
1385
+ # Set to "false" in production and run "pnpm worker" as a separate process.
1386
+ # QUEUE_WORKER=false
276
1387
  RESEND_API_KEY=
277
1388
  RESEND_WEBHOOK_SECRET=
278
1389
  MAIL_FROM=noreply@example.com
279
1390
  CSAT_SECRET=
280
- `),await e(m(t,".dockerignore"),`# Dependencies (installed inside Docker)
1391
+ `),await n(a(e,`.dockerignore`),`# Dependencies (installed inside Docker)
281
1392
  node_modules/
282
1393
  .pnpm-store/
283
1394
 
@@ -321,8 +1432,8 @@ docs/
321
1432
  # OS files
322
1433
  .DS_Store
323
1434
  Thumbs.db
324
- `),await e(m(t,"Dockerfile"),`# --- Base image ---
325
- # Node 22 LTS (Debian slim \u2014 glibc required for sharp image optimization)
1435
+ `),await n(a(e,`Dockerfile`),`# --- Base image ---
1436
+ # Node 22 LTS (Debian slim — glibc required for sharp image optimization)
326
1437
  FROM node:22-slim AS base
327
1438
 
328
1439
  # --- Stage 1: Install dependencies ---
@@ -356,7 +1467,7 @@ ENV NEXT_TELEMETRY_DISABLED=1
356
1467
 
357
1468
  # Copy standalone output (includes server.js + traced node_modules)
358
1469
  COPY --from=builder --chown=node:node /app/.next/standalone ./
359
- # Copy static assets (JS/CSS chunks \u2014 excluded from standalone trace)
1470
+ # Copy static assets (JS/CSS chunks — excluded from standalone trace)
360
1471
  COPY --from=builder --chown=node:node /app/.next/static ./.next/static
361
1472
  # Copy public assets (favicon, robots.txt, images)
362
1473
  COPY --from=builder --chown=node:node /app/public ./public
@@ -367,7 +1478,7 @@ COPY --from=builder --chown=node:node /app/migrations ./migrations
367
1478
  USER node
368
1479
  EXPOSE 3000
369
1480
  CMD ["node", "server.js"]
370
- `),await e(m(t,"docker-compose.yml"),`services:
1481
+ `),await n(a(e,`docker-compose.yml`),`services:
371
1482
  postgres:
372
1483
  image: postgres:17-alpine
373
1484
  container_name: ${r}-postgres
@@ -389,7 +1500,7 @@ CMD ["node", "server.js"]
389
1500
  volumes:
390
1501
  postgres_data:
391
1502
  name: ${r}_postgres_data
392
- `),await e(m(t,"docker-compose.prod.yml"),`services:
1503
+ `),await n(a(e,`docker-compose.prod.yml`),`services:
393
1504
  app:
394
1505
  build: .
395
1506
  restart: unless-stopped
@@ -424,16 +1535,16 @@ volumes:
424
1535
 
425
1536
  networks:
426
1537
  internal:
427
- `),await y(m(t,".gitignore"),`
1538
+ `),await i(a(e,`.gitignore`),`
428
1539
  # Toolkit generated files
429
1540
  generated/
430
1541
 
431
1542
  # Environment
432
1543
  .env*
433
1544
  !.env.example
434
- `)}import{join as x}from"path";async function A(t,n){await e(x(t,"entities/index.ts"),`export { Article } from './article'
1545
+ `)}async function u(e,t){await n(a(e,`entities/index.ts`),`export { Article } from './article'
435
1546
  export { Category } from './category'
436
- `),await e(x(t,"entities/article.ts"),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
1547
+ `),await n(a(e,`entities/article.ts`),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
437
1548
 
438
1549
  export const Article = defineEntity({
439
1550
  name: 'article',
@@ -464,7 +1575,7 @@ export const Article = defineEntity({
464
1575
  delete: 'group.admin',
465
1576
  },
466
1577
  })
467
- `),await e(x(t,"entities/category.ts"),`import { defineEntity, field } from '@murumets-ee/entity'
1578
+ `),await n(a(e,`entities/category.ts`),`import { defineEntity, field } from '@murumets-ee/entity'
468
1579
 
469
1580
  export const Category = defineEntity({
470
1581
  name: 'category',
@@ -481,13 +1592,13 @@ export const Category = defineEntity({
481
1592
  delete: 'group.admin',
482
1593
  },
483
1594
  })
484
- `)}import{join as N}from"path";async function T(t,n){await e(N(t,"i18n/routing.ts"),`import { defineRouting } from 'next-intl/routing'
1595
+ `)}async function d(e,t){await n(a(e,`i18n/routing.ts`),`import { defineRouting } from 'next-intl/routing'
485
1596
 
486
1597
  export const routing = defineRouting({
487
1598
  locales: ['en'],
488
1599
  defaultLocale: 'en',
489
1600
  })
490
- `),await e(N(t,"i18n/request.ts"),`import { getRequestConfig } from 'next-intl/server'
1601
+ `),await n(a(e,`i18n/request.ts`),`import { getRequestConfig } from 'next-intl/server'
491
1602
  import { hasLocale } from 'next-intl'
492
1603
  import { routing } from './routing'
493
1604
  import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
@@ -507,15 +1618,14 @@ export default getRequestConfig(async ({ requestLocale }) => {
507
1618
  },
508
1619
  }
509
1620
  })
510
- `)}import{mkdir as S}from"fs/promises";import{join as o}from"path";var i={myorgCore:"0.1.0",myorgDb:"0.1.0",myorgEntity:"0.1.0",myorgLogging:"0.1.0",myorgAuth:"0.1.0",myorgAuthUi:"0.1.0",myorgAdminUi:"0.1.0",myorgContent:"0.1.0",myorgContentApi:"0.1.0",myorgSettings:"0.1.0",myorgStorage:"0.1.0",myorgMedia:"0.1.0",myorgTaxonomy:"0.1.0",myorgQueue:"0.1.0",myorgMail:"0.1.0",myorgTicketing:"0.1.0",myorgTicketingUi:"0.1.0",myorgEditor:"0.1.0",myorgBlocks:"0.1.0",myorgTokens:"0.1.0",myorgUi:"0.1.0",myorgCli:"0.1.0",betterAuth:"1.4.0",drizzleOrm:"0.45.1",nextIntl:"4.8.2",nextThemes:"0.4.6",lucideReact:"0.563.0",postgres:"3.4.5",reactHookForm:"7.71.1",hookformResolvers:"5.2.2",zod:"3.24.1",drizzleKit:"0.31.10",tsx:"4.19.2",babelReactCompiler:"1.0.0"};async function E(t,n,r){await L(t,n),r?.("Workspace root created"),r?.("Creating admin app..."),await $(t,n),r?.("Admin app created"),r?.("Creating web app..."),await M(t,n),r?.("Web app created"),await _(t,n),r?.("Shared config package created")}async function L(t,n){let{name:r}=n;await S(t,{recursive:!0}),await e(o(t,"pnpm-workspace.yaml"),`packages:
1621
+ `)}const f={myorgCore:`0.1.0`,myorgDb:`0.1.0`,myorgEntity:`0.1.0`,myorgLogging:`0.1.0`,myorgAuth:`0.1.0`,myorgAuthUi:`0.1.0`,myorgAdminUi:`0.1.0`,myorgContent:`0.1.0`,myorgContentApi:`0.1.0`,myorgSettings:`0.1.0`,myorgStorage:`0.1.0`,myorgMedia:`0.1.0`,myorgTaxonomy:`0.1.0`,myorgQueue:`0.1.0`,myorgMail:`0.1.0`,myorgTicketing:`0.1.0`,myorgTicketingUi:`0.1.0`,myorgEditor:`0.1.0`,myorgBlocks:`0.1.0`,myorgTokens:`0.1.0`,myorgUi:`0.1.0`,myorgCli:`0.1.0`,betterAuth:`1.4.0`,drizzleOrm:`0.45.1`,nextIntl:`4.8.2`,nextThemes:`0.4.6`,lucideReact:`0.563.0`,postgres:`3.4.5`,reactHookForm:`7.71.1`,hookformResolvers:`5.2.2`,zod:`3.24.1`,tanstackReactQuery:`5.60.5`,tanstackReactTable:`8.21.3`,drizzleKit:`0.31.10`,tsx:`4.19.2`,babelReactCompiler:`1.0.0`};async function p(e,t,n){await m(e,t),n?.(`Workspace root created`),n?.(`Creating admin app...`),await g(e,t),n?.(`Admin app created`),n?.(`Creating web app...`),await _(e,t),n?.(`Web app created`),await h(e,t),n?.(`Shared config package created`)}async function m(e,t){let{name:r}=t;await o(e,{recursive:!0}),await n(a(e,`pnpm-workspace.yaml`),`packages:
511
1622
  - 'packages/*'
512
1623
  - 'apps/*'
513
1624
 
514
1625
  ignoredBuiltDependencies:
515
1626
  - sharp
516
1627
  - unrs-resolver
517
- `),await e(o(t,"package.json"),`${JSON.stringify({name:`@${r}/monorepo`,version:"0.0.0",private:!0,type:"module",scripts:{dev:"turbo dev",build:"turbo build","db:generate":"tsx --env-file=.env packages/config/scripts/generate-schema.ts","db:migrate:generate":"drizzle-kit generate --config packages/config/drizzle.config.ts","db:migrate":"tsx --env-file=.env packages/config/scripts/migrate.ts","db:reset":"tsx --env-file=.env packages/config/scripts/reset-db.ts"},devDependencies:{turbo:"^2.3.3",typescript:"^5.7.3",tsx:`^${i.tsx}`,"drizzle-kit":`^${i.drizzleKit}`}},null,2)}
518
- `),await e(o(t,".gitignore"),`# dependencies
1628
+ `),await n(a(e,`package.json`),`${JSON.stringify({name:`@${r}/monorepo`,version:`0.0.0`,private:!0,type:`module`,scripts:{dev:`turbo dev`,build:`turbo build`,"db:generate":`tsx --env-file=.env packages/config/scripts/generate-schema.ts`,"db:migrate:generate":`drizzle-kit generate --config packages/config/drizzle.config.ts`,"db:migrate":`tsx --env-file=.env packages/config/scripts/migrate.ts`,"db:reset":`tsx --env-file=.env packages/config/scripts/reset-db.ts`},devDependencies:{turbo:`^2.3.3`,typescript:`^5.7.3`,tsx:`^${f.tsx}`,"drizzle-kit":`^${f.drizzleKit}`}},null,2)}\n`),await n(a(e,`.gitignore`),`# dependencies
519
1629
  node_modules/
520
1630
 
521
1631
  # build
@@ -546,7 +1656,7 @@ migrations/
546
1656
 
547
1657
  # turbo
548
1658
  .turbo/
549
- `),await e(o(t,".env.example"),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
1659
+ `),await n(a(e,`.env.example`),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
550
1660
  BETTER_AUTH_SECRET=dev-secret-change-me-in-production-min-32-chars
551
1661
  BETTER_AUTH_URL=http://localhost:3000
552
1662
  LOG_LEVEL=debug
@@ -556,7 +1666,7 @@ RESEND_API_KEY=
556
1666
  RESEND_WEBHOOK_SECRET=
557
1667
  MAIL_FROM=noreply@example.com
558
1668
  CSAT_SECRET=
559
- `),await e(o(t,"docker-compose.yml"),`services:
1669
+ `),await n(a(e,`docker-compose.yml`),`services:
560
1670
  postgres:
561
1671
  image: postgres:17-alpine
562
1672
  container_name: ${r}-postgres
@@ -578,9 +1688,7 @@ CSAT_SECRET=
578
1688
  volumes:
579
1689
  postgres_data:
580
1690
  name: ${r}_postgres_data
581
- `)}async function _(t,n){let{name:r}=n,a=o(t,"packages/config");await e(o(a,"package.json"),`${JSON.stringify({name:`@${r}/config`,version:"0.1.0",private:!0,type:"module",exports:{".":"./toolkit.config.ts","./entities":"./entities/index.ts","./entities/*":"./entities/*","./app":"./lib/app.ts","./auth":"./lib/auth.ts","./auth-client":"./lib/auth-client.ts"},dependencies:{"@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/db":`^${i.myorgDb}`,"@murumets-ee/entity":`^${i.myorgEntity}`,"@murumets-ee/logging":`^${i.myorgLogging}`,"@murumets-ee/auth":`^${i.myorgAuth}`,"better-auth":`^${i.betterAuth}`,"drizzle-orm":`^${i.drizzleOrm}`,postgres:`^${i.postgres}`,zod:`^${i.zod}`}},null,2)}
582
- `),await e(o(a,"tsconfig.json"),`${JSON.stringify({compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"Bundler",strict:!0,esModuleInterop:!0,skipLibCheck:!0,resolveJsonModule:!0},include:["**/*.ts"]},null,2)}
583
- `),await e(o(a,"toolkit.config.ts"),`import { auth } from '@murumets-ee/auth/plugin'
1691
+ `)}async function h(e,t){let{name:r}=t,i=a(e,`packages/config`);await n(a(i,`package.json`),`${JSON.stringify({name:`@${r}/config`,version:`0.1.0`,private:!0,type:`module`,exports:{".":`./toolkit.config.ts`,"./entities":`./entities/index.ts`,"./entities/*":`./entities/*`,"./app":`./lib/app.ts`,"./auth":`./lib/auth.ts`,"./auth-client":`./lib/auth-client.ts`},dependencies:{"@murumets-ee/core":`^${f.myorgCore}`,"@murumets-ee/db":`^${f.myorgDb}`,"@murumets-ee/entity":`^${f.myorgEntity}`,"@murumets-ee/logging":`^${f.myorgLogging}`,"@murumets-ee/auth":`^${f.myorgAuth}`,"better-auth":`^${f.betterAuth}`,"drizzle-orm":`^${f.drizzleOrm}`,postgres:`^${f.postgres}`,zod:`^${f.zod}`}},null,2)}\n`),await n(a(i,`tsconfig.json`),`${JSON.stringify({compilerOptions:{target:`ES2022`,module:`ESNext`,moduleResolution:`Bundler`,strict:!0,esModuleInterop:!0,skipLibCheck:!0,resolveJsonModule:!0},include:[`**/*.ts`]},null,2)}\n`),await n(a(i,`toolkit.config.ts`),`import { auth } from '@murumets-ee/auth/plugin'
584
1692
  import { content } from '@murumets-ee/content/plugin'
585
1693
  import { defineConfig } from '@murumets-ee/core'
586
1694
  import { logging } from '@murumets-ee/logging/plugin'
@@ -634,7 +1742,7 @@ export default defineConfig({
634
1742
  ],
635
1743
  projectRoot: import.meta.dirname,
636
1744
  })
637
- `),await e(o(a,"auth.config.ts"),`import { betterAuth } from 'better-auth'
1745
+ `),await n(a(i,`auth.config.ts`),`import { betterAuth } from 'better-auth'
638
1746
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
639
1747
  import { admin } from 'better-auth/plugins'
640
1748
  import { organization } from 'better-auth/plugins/organization'
@@ -652,7 +1760,7 @@ export const auth = betterAuth({
652
1760
  organization(),
653
1761
  ],
654
1762
  })
655
- `),await e(o(a,"drizzle.config.ts"),`import type { Config } from 'drizzle-kit'
1763
+ `),await n(a(i,`drizzle.config.ts`),`import type { Config } from 'drizzle-kit'
656
1764
 
657
1765
  if (!process.env.DATABASE_URL) {
658
1766
  throw new Error('DATABASE_URL environment variable is required')
@@ -666,9 +1774,9 @@ export default {
666
1774
  url: process.env.DATABASE_URL,
667
1775
  },
668
1776
  } satisfies Config
669
- `),await e(o(a,"entities/index.ts"),`export { Article } from './article'
1777
+ `),await n(a(i,`entities/index.ts`),`export { Article } from './article'
670
1778
  export { Category } from './category'
671
- `),await e(o(a,"entities/article.ts"),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
1779
+ `),await n(a(i,`entities/article.ts`),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
672
1780
 
673
1781
  export const Article = defineEntity({
674
1782
  name: 'article',
@@ -699,7 +1807,7 @@ export const Article = defineEntity({
699
1807
  delete: 'group.admin',
700
1808
  },
701
1809
  })
702
- `),await e(o(a,"entities/category.ts"),`import { defineEntity, field } from '@murumets-ee/entity'
1810
+ `),await n(a(i,`entities/category.ts`),`import { defineEntity, field } from '@murumets-ee/entity'
703
1811
 
704
1812
  export const Category = defineEntity({
705
1813
  name: 'category',
@@ -716,34 +1824,32 @@ export const Category = defineEntity({
716
1824
  delete: 'group.admin',
717
1825
  },
718
1826
  })
719
- `),await e(o(a,"lib/app.ts"),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
1827
+ `),await n(a(i,`lib/app.ts`),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
720
1828
  import config from '../toolkit.config'
721
1829
 
722
1830
  let appInstance: ToolkitApp | null = null
723
1831
 
724
1832
  export async function getToolkitApp(): Promise<ToolkitApp> {
725
1833
  if (!appInstance) {
726
- console.log('Initializing toolkit app...')
727
1834
  appInstance = await createApp(config)
728
1835
  setApp(appInstance)
729
- console.log('Toolkit app initialized')
730
1836
  }
731
1837
  return appInstance
732
1838
  }
733
- `),await e(o(a,"lib/auth.ts"),`import { getToolkitApp } from './app'
1839
+ `),await n(a(i,`lib/auth.ts`),`import { getToolkitApp } from './app'
734
1840
  import { getAuth } from '@murumets-ee/auth'
735
1841
 
736
1842
  export async function getAuthInstance() {
737
1843
  await getToolkitApp()
738
1844
  return getAuth()
739
1845
  }
740
- `),await e(o(a,"lib/auth-client.ts"),`import { createClient } from '@murumets-ee/auth/client'
1846
+ `),await n(a(i,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
741
1847
 
742
1848
  export const authClient = createClient()
743
- `),await e(o(a,"generated/auth-schema.ts"),`// This file is generated by better-auth CLI.
1849
+ `),await n(a(i,`generated/auth-schema.ts`),`// This file is generated by better-auth CLI.
744
1850
  // Run: npx @better-auth/cli generate --config auth.config.ts -y
745
1851
  export {}
746
- `),await e(o(a,"scripts/generate-schema.ts"),`/**
1852
+ `),await n(a(i,`scripts/generate-schema.ts`),`/**
747
1853
  * Generate Drizzle schemas from entity definitions
748
1854
  */
749
1855
 
@@ -790,7 +1896,7 @@ async function generateSchemas() {
790
1896
  }
791
1897
 
792
1898
  generateSchemas().catch(console.error)
793
- `),await e(o(a,"scripts/migrate.ts"),`/**
1899
+ `),await n(a(i,`scripts/migrate.ts`),`/**
794
1900
  * Run pending migrations
795
1901
  */
796
1902
 
@@ -816,7 +1922,7 @@ async function migrate() {
816
1922
  }
817
1923
 
818
1924
  migrate()
819
- `),await e(o(a,"scripts/reset-db.ts"),`/**
1925
+ `),await n(a(i,`scripts/reset-db.ts`),`/**
820
1926
  * Reset database (DROP all tables)
821
1927
  * WARNING: Only for development
822
1928
  */
@@ -849,11 +1955,11 @@ async function resetDb() {
849
1955
  }
850
1956
 
851
1957
  resetDb().catch(console.error)
852
- `)}async function $(t,n){let{name:r}=n,a=o(t,"apps/admin");await S(o(t,"apps"),{recursive:!0}),l(`pnpm create next-app@${"16"} ${a} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(a,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await c(o(a,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"better-auth":`^${i.betterAuth}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:h}=await import("./utils-42K2ZGUL.js");await h(o(a,".gitignore"),`
1958
+ `)}async function g(i,s){let{name:c}=s,l=a(i,`apps/admin`);await o(a(i,`apps`),{recursive:!0}),e(`pnpm create next-app@16 ${l} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await t(l,[`app/page.tsx`,`app/page.module.css`,`app/fonts`,`README.md`,`pnpm-workspace.yaml`]),await r(a(l,`package.json`),{dependencies:{[`@${c}/config`]:`workspace:*`,"@murumets-ee/auth-ui":`^${f.myorgAuthUi}`,"better-auth":`^${f.betterAuth}`,"next-intl":`^${f.nextIntl}`,"next-themes":`^${f.nextThemes}`,"lucide-react":`^${f.lucideReact}`,"react-hook-form":`^${f.reactHookForm}`,"@hookform/resolvers":`^${f.hookformResolvers}`,zod:`^${f.zod}`},devDependencies:{"babel-plugin-react-compiler":f.babelReactCompiler}});let{appendToFile:u}=await import(`./utils-Bmw75lEa.mjs`).then(e=>e.a);await u(a(l,`.gitignore`),`
853
1959
  # Environment
854
1960
  .env*
855
1961
  !.env.example
856
- `),await e(o(a,"next.config.ts"),`import type { NextConfig } from 'next'
1962
+ `),await n(a(l,`next.config.ts`),`import type { NextConfig } from 'next'
857
1963
  import createNextIntlPlugin from 'next-intl/plugin'
858
1964
 
859
1965
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
@@ -861,7 +1967,7 @@ const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
861
1967
  const nextConfig: NextConfig = {
862
1968
  reactCompiler: true,
863
1969
  transpilePackages: [
864
- '@${r}/config',
1970
+ '@${c}/config',
865
1971
  '@murumets-ee/core',
866
1972
  '@murumets-ee/entity',
867
1973
  '@murumets-ee/db',
@@ -877,7 +1983,8 @@ const nextConfig: NextConfig = {
877
1983
  }
878
1984
 
879
1985
  export default withNextIntl(nextConfig)
880
- `),await e(o(a,"proxy.ts"),`import { NextRequest, NextResponse } from 'next/server'
1986
+ `),await n(a(l,`proxy.ts`),`import { NextRequest, NextResponse } from 'next/server'
1987
+ import { getSessionCookie } from 'better-auth/cookies'
881
1988
  import createMiddleware from 'next-intl/middleware'
882
1989
  import { routing } from './i18n/routing'
883
1990
 
@@ -892,7 +1999,7 @@ export function proxy(request: NextRequest) {
892
1999
  const pathWithoutLocale = pathname.replace(localePattern, '') || '/'
893
2000
 
894
2001
  if (protectedPaths.some((p) => pathWithoutLocale.startsWith(p))) {
895
- const session = request.cookies.get('better-auth.session_token')
2002
+ const session = getSessionCookie(request)
896
2003
  if (!session) {
897
2004
  const locale = pathname.match(localePattern)?.[1] || routing.defaultLocale
898
2005
  return NextResponse.redirect(new URL(\`/\${locale}/auth/sign-in\`, request.url))
@@ -905,13 +2012,13 @@ export function proxy(request: NextRequest) {
905
2012
  export const config = {
906
2013
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
907
2014
  }
908
- `),await e(o(a,"i18n/routing.ts"),`import { defineRouting } from 'next-intl/routing'
2015
+ `),await n(a(l,`i18n/routing.ts`),`import { defineRouting } from 'next-intl/routing'
909
2016
 
910
2017
  export const routing = defineRouting({
911
2018
  locales: ['en'],
912
2019
  defaultLocale: 'en',
913
2020
  })
914
- `),await e(o(a,"i18n/request.ts"),`import { getRequestConfig } from 'next-intl/server'
2021
+ `),await n(a(l,`i18n/request.ts`),`import { getRequestConfig } from 'next-intl/server'
915
2022
  import { hasLocale } from 'next-intl'
916
2023
  import { routing } from './routing'
917
2024
  import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
@@ -931,7 +2038,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
931
2038
  },
932
2039
  }
933
2040
  })
934
- `),await e(o(a,"app/globals.css"),`@import "tailwindcss";
2041
+ `),await n(a(l,`app/globals.css`),`@import "tailwindcss";
935
2042
  @source "../node_modules/@murumets-ee/auth-ui/dist";
936
2043
 
937
2044
  @custom-variant dark (&:where(.dark, .dark *));
@@ -958,7 +2065,7 @@ body {
958
2065
  color: var(--foreground);
959
2066
  font-family: Arial, Helvetica, sans-serif;
960
2067
  }
961
- `),await e(o(a,"app/theme-provider.tsx"),`'use client'
2068
+ `),await n(a(l,`app/theme-provider.tsx`),`'use client'
962
2069
 
963
2070
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
964
2071
  import type { ReactNode } from 'react'
@@ -975,7 +2082,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
975
2082
  </NextThemesProvider>
976
2083
  )
977
2084
  }
978
- `),await e(o(a,"app/theme-toggle.tsx"),`'use client'
2085
+ `),await n(a(l,`app/theme-toggle.tsx`),`'use client'
979
2086
 
980
2087
  import { useTheme } from 'next-themes'
981
2088
  import { useState, useEffect } from 'react'
@@ -1002,7 +2109,7 @@ export function ThemeToggle() {
1002
2109
  </button>
1003
2110
  )
1004
2111
  }
1005
- `),await e(o(a,"app/nav-header.tsx"),`'use client'
2112
+ `),await n(a(l,`app/nav-header.tsx`),`'use client'
1006
2113
 
1007
2114
  import Link from 'next/link'
1008
2115
  import { usePathname } from 'next/navigation'
@@ -1032,7 +2139,7 @@ export function NavHeader() {
1032
2139
  <header className="sticky top-0 z-50 backdrop-blur-md border-b bg-white/80 border-zinc-200 dark:bg-zinc-950/80 dark:border-zinc-800">
1033
2140
  <nav className="max-w-6xl mx-auto flex items-center gap-1 px-4 h-12">
1034
2141
  <span className="font-semibold text-sm text-zinc-700 dark:text-zinc-300 mr-3 select-none">
1035
- ${r} Admin
2142
+ ${c} Admin
1036
2143
  </span>
1037
2144
  {links.map(({ href, label }) => (
1038
2145
  <Link key={href} href={href} className={linkClass(href)}>
@@ -1046,12 +2153,12 @@ export function NavHeader() {
1046
2153
  </header>
1047
2154
  )
1048
2155
  }
1049
- `),await e(o(a,"app/layout.tsx"),`import './globals.css'
2156
+ `),await n(a(l,`app/layout.tsx`),`import './globals.css'
1050
2157
 
1051
2158
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1052
2159
  return children
1053
2160
  }
1054
- `),await e(o(a,"app/[locale]/layout.tsx"),`import type { Metadata } from 'next'
2161
+ `),await n(a(l,`app/[locale]/layout.tsx`),`import type { Metadata } from 'next'
1055
2162
  import { Geist, Geist_Mono } from 'next/font/google'
1056
2163
  import { NextIntlClientProvider } from 'next-intl'
1057
2164
  import { getMessages } from 'next-intl/server'
@@ -1071,7 +2178,7 @@ const geistMono = Geist_Mono({
1071
2178
  })
1072
2179
 
1073
2180
  export const metadata: Metadata = {
1074
- title: '${r} Admin',
2181
+ title: '${c} Admin',
1075
2182
  description: 'Built with Lumi CMS Toolkit',
1076
2183
  }
1077
2184
 
@@ -1100,9 +2207,9 @@ export default async function LocaleLayout({
1100
2207
  </html>
1101
2208
  )
1102
2209
  }
1103
- `),await e(o(a,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
1104
- import { Article, Category } from '@${r}/config/entities'
1105
- import { getToolkitApp } from '@${r}/config/app'
2210
+ `),await n(a(l,`app/[locale]/page.tsx`),`import { createQueryClient } from '@murumets-ee/core/clients'
2211
+ import { Article, Category } from '@${c}/config/entities'
2212
+ import { getToolkitApp } from '@${c}/config/app'
1106
2213
 
1107
2214
  export default async function HomePage() {
1108
2215
  await getToolkitApp()
@@ -1117,7 +2224,7 @@ export default async function HomePage() {
1117
2224
  return (
1118
2225
  <div className="min-h-screen p-8">
1119
2226
  <div className="max-w-4xl mx-auto">
1120
- <h1 className="text-4xl font-bold mb-8">Welcome to ${r}</h1>
2227
+ <h1 className="text-4xl font-bold mb-8">Welcome to ${c}</h1>
1121
2228
 
1122
2229
  <section className="mb-12">
1123
2230
  <h2 className="text-2xl font-semibold mb-4">
@@ -1164,8 +2271,8 @@ export default async function HomePage() {
1164
2271
  </div>
1165
2272
  )
1166
2273
  }
1167
- `),await e(o(a,"app/api/auth/[...all]/route.ts"),`import { toNextJsHandler } from 'better-auth/next-js'
1168
- import { getAuthInstance } from '@${r}/config/auth'
2274
+ `),await n(a(l,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
2275
+ import { getAuthInstance } from '@${c}/config/auth'
1169
2276
 
1170
2277
  let _handler: ReturnType<typeof toNextJsHandler> | null = null
1171
2278
 
@@ -1184,7 +2291,7 @@ export async function GET(req: Request) {
1184
2291
  export async function POST(req: Request) {
1185
2292
  return (await handler()).POST(req)
1186
2293
  }
1187
- `),await e(o(a,"app/[locale]/auth/layout.tsx"),`import { AuthProviders } from './providers'
2294
+ `),await n(a(l,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
1188
2295
  import type { ReactNode } from 'react'
1189
2296
 
1190
2297
  export default function AuthLayout({ children }: { children: ReactNode }) {
@@ -1196,10 +2303,10 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
1196
2303
  </AuthProviders>
1197
2304
  )
1198
2305
  }
1199
- `),await e(o(a,"app/[locale]/auth/providers.tsx"),`'use client'
2306
+ `),await n(a(l,`app/[locale]/auth/providers.tsx`),`'use client'
1200
2307
 
1201
2308
  import { AuthUIProvider } from '@murumets-ee/auth-ui'
1202
- import { authClient } from '@${r}/config/auth-client'
2309
+ import { authClient } from '@${c}/config/auth-client'
1203
2310
  import Link from 'next/link'
1204
2311
  import { useRouter } from 'next/navigation'
1205
2312
  import { useLocale } from 'next-intl'
@@ -1222,22 +2329,22 @@ export function AuthProviders({ children }: { children: ReactNode }) {
1222
2329
  </AuthUIProvider>
1223
2330
  )
1224
2331
  }
1225
- `),await e(o(a,"app/[locale]/auth/sign-in/page.tsx"),`import { SignInForm } from '@murumets-ee/auth-ui'
2332
+ `),await n(a(l,`app/[locale]/auth/sign-in/page.tsx`),`import { SignInForm } from '@murumets-ee/auth-ui'
1226
2333
 
1227
2334
  export default function SignInPage() {
1228
2335
  return <SignInForm />
1229
2336
  }
1230
- `),await e(o(a,"app/[locale]/auth/sign-up/page.tsx"),`import { SignUpForm } from '@murumets-ee/auth-ui'
2337
+ `),await n(a(l,`app/[locale]/auth/sign-up/page.tsx`),`import { SignUpForm } from '@murumets-ee/auth-ui'
1231
2338
 
1232
2339
  export default function SignUpPage() {
1233
2340
  return <SignUpForm />
1234
2341
  }
1235
- `),await e(o(a,"app/[locale]/auth/forgot-password/page.tsx"),`import { ForgotPasswordForm } from '@murumets-ee/auth-ui'
2342
+ `),await n(a(l,`app/[locale]/auth/forgot-password/page.tsx`),`import { ForgotPasswordForm } from '@murumets-ee/auth-ui'
1236
2343
 
1237
2344
  export default function ForgotPasswordPage() {
1238
2345
  return <ForgotPasswordForm />
1239
2346
  }
1240
- `),await e(o(a,"app/[locale]/auth/reset-password/page.tsx"),`import { Suspense } from 'react'
2347
+ `),await n(a(l,`app/[locale]/auth/reset-password/page.tsx`),`import { Suspense } from 'react'
1241
2348
  import { ResetPasswordContent } from './content'
1242
2349
 
1243
2350
  export default function ResetPasswordPage() {
@@ -1247,7 +2354,7 @@ export default function ResetPasswordPage() {
1247
2354
  </Suspense>
1248
2355
  )
1249
2356
  }
1250
- `),await e(o(a,"app/[locale]/auth/reset-password/content.tsx"),`'use client'
2357
+ `),await n(a(l,`app/[locale]/auth/reset-password/content.tsx`),`'use client'
1251
2358
 
1252
2359
  import { useSearchParams } from 'next/navigation'
1253
2360
  import { ResetPasswordForm } from '@murumets-ee/auth-ui'
@@ -1271,9 +2378,9 @@ export function ResetPasswordContent() {
1271
2378
 
1272
2379
  return <ResetPasswordForm token={token} />
1273
2380
  }
1274
- `),await e(o(a,"app/[locale]/setup/page.tsx"),`import { redirect } from 'next/navigation'
2381
+ `),await n(a(l,`app/[locale]/setup/page.tsx`),`import { redirect } from 'next/navigation'
1275
2382
  import { sql } from 'drizzle-orm'
1276
- import { getToolkitApp } from '@${r}/config/app'
2383
+ import { getToolkitApp } from '@${c}/config/app'
1277
2384
  import { SetupForm } from './form'
1278
2385
 
1279
2386
  export default async function SetupPage() {
@@ -1296,7 +2403,7 @@ export default async function SetupPage() {
1296
2403
  </div>
1297
2404
  )
1298
2405
  }
1299
- `),await e(o(a,"app/[locale]/setup/form.tsx"),`'use client'
2406
+ `),await n(a(l,`app/[locale]/setup/form.tsx`),`'use client'
1300
2407
 
1301
2408
  import { useState } from 'react'
1302
2409
  import { createFirstAdmin } from './actions'
@@ -1367,10 +2474,10 @@ export function SetupForm() {
1367
2474
  </form>
1368
2475
  )
1369
2476
  }
1370
- `),await e(o(a,"app/[locale]/setup/actions.ts"),`'use server'
2477
+ `),await n(a(l,`app/[locale]/setup/actions.ts`),`'use server'
1371
2478
 
1372
2479
  import { sql } from 'drizzle-orm'
1373
- import { getToolkitApp } from '@${r}/config/app'
2480
+ import { getToolkitApp } from '@${c}/config/app'
1374
2481
  import { getAuth } from '@murumets-ee/auth'
1375
2482
 
1376
2483
  export async function createFirstAdmin(formData: FormData) {
@@ -1413,11 +2520,11 @@ export async function createFirstAdmin(formData: FormData) {
1413
2520
 
1414
2521
  return { ok: true, email: adminUser.user.email }
1415
2522
  }
1416
- `)}async function M(t,n){let{name:r}=n,a=o(t,"apps/web");l(`pnpm create next-app@${"16"} ${a} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(a,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await c(o(a,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:h}=await import("./utils-42K2ZGUL.js");await h(o(a,".gitignore"),`
2523
+ `)}async function _(i,o){let{name:s}=o,c=a(i,`apps/web`);e(`pnpm create next-app@16 ${c} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await t(c,[`app/page.tsx`,`app/page.module.css`,`app/fonts`,`README.md`,`pnpm-workspace.yaml`]),await r(a(c,`package.json`),{dependencies:{[`@${s}/config`]:`workspace:*`,"@murumets-ee/core":`^${f.myorgCore}`,"@murumets-ee/auth-ui":`^${f.myorgAuthUi}`,"next-intl":`^${f.nextIntl}`,"next-themes":`^${f.nextThemes}`,"lucide-react":`^${f.lucideReact}`},devDependencies:{"babel-plugin-react-compiler":f.babelReactCompiler}});let{appendToFile:l}=await import(`./utils-Bmw75lEa.mjs`).then(e=>e.a);await l(a(c,`.gitignore`),`
1417
2524
  # Environment
1418
2525
  .env*
1419
2526
  !.env.example
1420
- `),await e(o(a,"next.config.ts"),`import type { NextConfig } from 'next'
2527
+ `),await n(a(c,`next.config.ts`),`import type { NextConfig } from 'next'
1421
2528
  import createNextIntlPlugin from 'next-intl/plugin'
1422
2529
 
1423
2530
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
@@ -1425,7 +2532,7 @@ const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
1425
2532
  const nextConfig: NextConfig = {
1426
2533
  reactCompiler: true,
1427
2534
  transpilePackages: [
1428
- '@${r}/config',
2535
+ '@${s}/config',
1429
2536
  '@murumets-ee/core',
1430
2537
  '@murumets-ee/entity',
1431
2538
  '@murumets-ee/db',
@@ -1436,7 +2543,7 @@ const nextConfig: NextConfig = {
1436
2543
  }
1437
2544
 
1438
2545
  export default withNextIntl(nextConfig)
1439
- `),await e(o(a,"proxy.ts"),`import createMiddleware from 'next-intl/middleware'
2546
+ `),await n(a(c,`proxy.ts`),`import createMiddleware from 'next-intl/middleware'
1440
2547
  import { routing } from './i18n/routing'
1441
2548
 
1442
2549
  export default createMiddleware(routing)
@@ -1444,13 +2551,13 @@ export default createMiddleware(routing)
1444
2551
  export const config = {
1445
2552
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
1446
2553
  }
1447
- `),await e(o(a,"i18n/routing.ts"),`import { defineRouting } from 'next-intl/routing'
2554
+ `),await n(a(c,`i18n/routing.ts`),`import { defineRouting } from 'next-intl/routing'
1448
2555
 
1449
2556
  export const routing = defineRouting({
1450
2557
  locales: ['en'],
1451
2558
  defaultLocale: 'en',
1452
2559
  })
1453
- `),await e(o(a,"i18n/request.ts"),`import { getRequestConfig } from 'next-intl/server'
2560
+ `),await n(a(c,`i18n/request.ts`),`import { getRequestConfig } from 'next-intl/server'
1454
2561
  import { hasLocale } from 'next-intl'
1455
2562
  import { routing } from './routing'
1456
2563
  import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
@@ -1470,7 +2577,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
1470
2577
  },
1471
2578
  }
1472
2579
  })
1473
- `),await e(o(a,"app/globals.css"),`@import "tailwindcss";
2580
+ `),await n(a(c,`app/globals.css`),`@import "tailwindcss";
1474
2581
  @source "../node_modules/@murumets-ee/auth-ui/dist";
1475
2582
 
1476
2583
  @custom-variant dark (&:where(.dark, .dark *));
@@ -1497,7 +2604,7 @@ body {
1497
2604
  color: var(--foreground);
1498
2605
  font-family: Arial, Helvetica, sans-serif;
1499
2606
  }
1500
- `),await e(o(a,"app/theme-provider.tsx"),`'use client'
2607
+ `),await n(a(c,`app/theme-provider.tsx`),`'use client'
1501
2608
 
1502
2609
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
1503
2610
  import type { ReactNode } from 'react'
@@ -1514,7 +2621,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
1514
2621
  </NextThemesProvider>
1515
2622
  )
1516
2623
  }
1517
- `),await e(o(a,"app/theme-toggle.tsx"),`'use client'
2624
+ `),await n(a(c,`app/theme-toggle.tsx`),`'use client'
1518
2625
 
1519
2626
  import { useTheme } from 'next-themes'
1520
2627
  import { useState, useEffect } from 'react'
@@ -1541,7 +2648,7 @@ export function ThemeToggle() {
1541
2648
  </button>
1542
2649
  )
1543
2650
  }
1544
- `),await e(o(a,"app/nav-header.tsx"),`'use client'
2651
+ `),await n(a(c,`app/nav-header.tsx`),`'use client'
1545
2652
 
1546
2653
  import Link from 'next/link'
1547
2654
  import { usePathname } from 'next/navigation'
@@ -1569,7 +2676,7 @@ export function NavHeader() {
1569
2676
  <header className="sticky top-0 z-50 backdrop-blur-md border-b bg-white/80 border-zinc-200 dark:bg-zinc-950/80 dark:border-zinc-800">
1570
2677
  <nav className="max-w-6xl mx-auto flex items-center gap-1 px-4 h-12">
1571
2678
  <span className="font-semibold text-sm text-zinc-700 dark:text-zinc-300 mr-3 select-none">
1572
- ${r}
2679
+ ${s}
1573
2680
  </span>
1574
2681
  {links.map(({ href, label }) => (
1575
2682
  <Link key={href} href={href} className={linkClass(href)}>
@@ -1583,12 +2690,12 @@ export function NavHeader() {
1583
2690
  </header>
1584
2691
  )
1585
2692
  }
1586
- `),await e(o(a,"app/layout.tsx"),`import './globals.css'
2693
+ `),await n(a(c,`app/layout.tsx`),`import './globals.css'
1587
2694
 
1588
2695
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1589
2696
  return children
1590
2697
  }
1591
- `),await e(o(a,"app/[locale]/layout.tsx"),`import type { Metadata } from 'next'
2698
+ `),await n(a(c,`app/[locale]/layout.tsx`),`import type { Metadata } from 'next'
1592
2699
  import { Geist, Geist_Mono } from 'next/font/google'
1593
2700
  import { NextIntlClientProvider } from 'next-intl'
1594
2701
  import { getMessages } from 'next-intl/server'
@@ -1608,7 +2715,7 @@ const geistMono = Geist_Mono({
1608
2715
  })
1609
2716
 
1610
2717
  export const metadata: Metadata = {
1611
- title: '${r}',
2718
+ title: '${s}',
1612
2719
  description: 'Built with Lumi CMS Toolkit',
1613
2720
  }
1614
2721
 
@@ -1637,9 +2744,9 @@ export default async function LocaleLayout({
1637
2744
  </html>
1638
2745
  )
1639
2746
  }
1640
- `),await e(o(a,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
1641
- import { Article, Category } from '@${r}/config/entities'
1642
- import { getToolkitApp } from '@${r}/config/app'
2747
+ `),await n(a(c,`app/[locale]/page.tsx`),`import { createQueryClient } from '@murumets-ee/core/clients'
2748
+ import { Article, Category } from '@${s}/config/entities'
2749
+ import { getToolkitApp } from '@${s}/config/app'
1643
2750
 
1644
2751
  export default async function HomePage() {
1645
2752
  await getToolkitApp()
@@ -1654,7 +2761,7 @@ export default async function HomePage() {
1654
2761
  return (
1655
2762
  <div className="min-h-screen p-8">
1656
2763
  <div className="max-w-4xl mx-auto">
1657
- <h1 className="text-4xl font-bold mb-8">Welcome to ${r}</h1>
2764
+ <h1 className="text-4xl font-bold mb-8">Welcome to ${s}</h1>
1658
2765
 
1659
2766
  <section className="mb-12">
1660
2767
  <h2 className="text-2xl font-semibold mb-4">
@@ -1701,7 +2808,7 @@ export default async function HomePage() {
1701
2808
  </div>
1702
2809
  )
1703
2810
  }
1704
- `)}import{join as p}from"path";async function P(t,n){let{name:r}=n;await e(p(t,"next.config.ts"),`import type { NextConfig } from 'next'
2811
+ `)}async function v(e,t){let{name:r}=t;await n(a(e,`next.config.ts`),`import type { NextConfig } from 'next'
1705
2812
  import createNextIntlPlugin from 'next-intl/plugin'
1706
2813
 
1707
2814
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
@@ -1726,7 +2833,8 @@ const nextConfig: NextConfig = {
1726
2833
  }
1727
2834
 
1728
2835
  export default withNextIntl(nextConfig)
1729
- `),await e(p(t,"proxy.ts"),`import { NextRequest, NextResponse } from 'next/server'
2836
+ `),await n(a(e,`proxy.ts`),`import { NextRequest, NextResponse } from 'next/server'
2837
+ import { getSessionCookie } from 'better-auth/cookies'
1730
2838
  import createMiddleware from 'next-intl/middleware'
1731
2839
  import { routing } from './i18n/routing'
1732
2840
 
@@ -1743,7 +2851,7 @@ export function proxy(request: NextRequest) {
1743
2851
 
1744
2852
  // Auth check for protected paths
1745
2853
  if (protectedPaths.some((p) => pathWithoutLocale.startsWith(p))) {
1746
- const session = request.cookies.get('better-auth.session_token')
2854
+ const session = getSessionCookie(request)
1747
2855
  if (!session) {
1748
2856
  const locale = pathname.match(localePattern)?.[1] || routing.defaultLocale
1749
2857
  return NextResponse.redirect(new URL(\`/\${locale}/auth/sign-in\`, request.url))
@@ -1757,12 +2865,12 @@ export function proxy(request: NextRequest) {
1757
2865
  export const config = {
1758
2866
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
1759
2867
  }
1760
- `),await e(p(t,"app/layout.tsx"),`import './globals.css'
2868
+ `),await n(a(e,`app/layout.tsx`),`import './globals.css'
1761
2869
 
1762
2870
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1763
2871
  return children
1764
2872
  }
1765
- `),await e(p(t,"app/[locale]/layout.tsx"),`import type { Metadata } from 'next'
2873
+ `),await n(a(e,`app/[locale]/layout.tsx`),`import type { Metadata } from 'next'
1766
2874
  import { Geist, Geist_Mono } from 'next/font/google'
1767
2875
  import { NextIntlClientProvider } from 'next-intl'
1768
2876
  import { getMessages } from 'next-intl/server'
@@ -1811,7 +2919,7 @@ export default async function LocaleLayout({
1811
2919
  </html>
1812
2920
  )
1813
2921
  }
1814
- `),await e(p(t,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
2922
+ `),await n(a(e,`app/[locale]/page.tsx`),`import { createQueryClient } from '@murumets-ee/core/clients'
1815
2923
  import { Article, Category } from '@/entities'
1816
2924
  import { getToolkitApp } from '@/lib/app'
1817
2925
 
@@ -1875,13 +2983,49 @@ export default async function HomePage() {
1875
2983
  </div>
1876
2984
  )
1877
2985
  }
1878
- `)}import{join as b}from"path";async function R(t,n){await e(b(t,"scripts/generate-schema.ts"),`import { execSync } from 'node:child_process'
2986
+ `)}async function y(e,t){await n(a(e,`scripts/generate-schema.ts`),`import { execSync } from 'node:child_process'
1879
2987
  execSync('npx @murumets-ee/cli generate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1880
- `),await e(b(t,"scripts/migrate.ts"),`import { execSync } from 'node:child_process'
2988
+ `),await n(a(e,`scripts/migrate.ts`),`import { execSync } from 'node:child_process'
1881
2989
  execSync('npx @murumets-ee/cli migrate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1882
- `),await e(b(t,"scripts/reset-db.ts"),`import { execSync } from 'node:child_process'
2990
+ `),await n(a(e,`scripts/reset-db.ts`),`import { execSync } from 'node:child_process'
1883
2991
  execSync('npx @murumets-ee/cli reset --force', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1884
- `)}import{join as f}from"path";async function z(t,n){let{name:r}=n;await e(f(t,"app/globals.css"),`@import "tailwindcss";
2992
+ `),await n(a(e,`scripts/worker.ts`),`/**
2993
+ * Standalone queue worker process.
2994
+ *
2995
+ * Run separately from the Next.js web server:
2996
+ * pnpm worker (or: tsx --env-file=.env scripts/worker.ts)
2997
+ *
2998
+ * The web server should set QUEUE_WORKER=false so it only enqueues jobs.
2999
+ * This process handles job execution.
3000
+ */
3001
+
3002
+ import { createApp, setApp } from '@murumets-ee/core'
3003
+ import config from '../toolkit.config'
3004
+
3005
+ process.env.QUEUE_WORKER = 'true'
3006
+
3007
+ const app = await createApp(config)
3008
+ setApp(app)
3009
+
3010
+ app.logger.info('Queue worker process running. Press Ctrl+C to stop.')
3011
+
3012
+ const shutdown = async () => {
3013
+ app.logger.info('Shutting down queue worker...')
3014
+
3015
+ const worker = (globalThis as Record<symbol, unknown>)[
3016
+ Symbol.for('@murumets-ee/queue:worker')
3017
+ ] as { stop: () => Promise<void> } | undefined
3018
+
3019
+ if (worker) {
3020
+ await worker.stop()
3021
+ }
3022
+
3023
+ process.exit(0)
3024
+ }
3025
+
3026
+ process.on('SIGINT', shutdown)
3027
+ process.on('SIGTERM', shutdown)
3028
+ `)}async function b(e,t){let{name:r}=t;await n(a(e,`app/globals.css`),`@import "tailwindcss";
1885
3029
  @source "../node_modules/@murumets-ee/auth-ui/dist";
1886
3030
 
1887
3031
  @custom-variant dark (&:where(.dark, .dark *));
@@ -1908,7 +3052,7 @@ body {
1908
3052
  color: var(--foreground);
1909
3053
  font-family: Arial, Helvetica, sans-serif;
1910
3054
  }
1911
- `),await e(f(t,"app/theme-provider.tsx"),`'use client'
3055
+ `),await n(a(e,`app/theme-provider.tsx`),`'use client'
1912
3056
 
1913
3057
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
1914
3058
  import type { ReactNode } from 'react'
@@ -1925,7 +3069,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
1925
3069
  </NextThemesProvider>
1926
3070
  )
1927
3071
  }
1928
- `),await e(f(t,"app/theme-toggle.tsx"),`'use client'
3072
+ `),await n(a(e,`app/theme-toggle.tsx`),`'use client'
1929
3073
 
1930
3074
  import { useTheme } from 'next-themes'
1931
3075
  import { useState, useEffect } from 'react'
@@ -1952,7 +3096,7 @@ export function ThemeToggle() {
1952
3096
  </button>
1953
3097
  )
1954
3098
  }
1955
- `),await e(f(t,"app/nav-header.tsx"),`'use client'
3099
+ `),await n(a(e,`app/nav-header.tsx`),`'use client'
1956
3100
 
1957
3101
  import Link from 'next/link'
1958
3102
  import { usePathname } from 'next/navigation'
@@ -1996,7 +3140,7 @@ export function NavHeader() {
1996
3140
  </header>
1997
3141
  )
1998
3142
  }
1999
- `)}import{join as d}from"path";async function C(t,n){let{name:r}=n;await e(d(t,"toolkit.config.ts"),`import { auth } from '@murumets-ee/auth/plugin'
3143
+ `)}async function x(e,t){let{name:r}=t;await n(a(e,`toolkit.config.ts`),`import { auth } from '@murumets-ee/auth/plugin'
2000
3144
  import { content } from '@murumets-ee/content/plugin'
2001
3145
  import { defineConfig } from '@murumets-ee/core'
2002
3146
  import { logging } from '@murumets-ee/logging/plugin'
@@ -2050,21 +3194,19 @@ export default defineConfig({
2050
3194
  ],
2051
3195
  projectRoot: import.meta.dirname,
2052
3196
  })
2053
- `),await e(d(t,"lib/app.ts"),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
3197
+ `),await n(a(e,`lib/app.ts`),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
2054
3198
  import config from '../toolkit.config'
2055
3199
 
2056
3200
  let appInstance: ToolkitApp | null = null
2057
3201
 
2058
3202
  export async function getToolkitApp(): Promise<ToolkitApp> {
2059
3203
  if (!appInstance) {
2060
- console.log('Initializing toolkit app...')
2061
3204
  appInstance = await createApp(config)
2062
3205
  setApp(appInstance)
2063
- console.log('Toolkit app initialized')
2064
3206
  }
2065
3207
  return appInstance
2066
3208
  }
2067
- `),await e(d(t,"drizzle.config.ts"),`import type { Config } from 'drizzle-kit'
3209
+ `),await n(a(e,`drizzle.config.ts`),`import type { Config } from 'drizzle-kit'
2068
3210
 
2069
3211
  if (!process.env.DATABASE_URL) {
2070
3212
  throw new Error('DATABASE_URL environment variable is required')
@@ -2078,7 +3220,7 @@ export default {
2078
3220
  url: process.env.DATABASE_URL,
2079
3221
  },
2080
3222
  } satisfies Config
2081
- `),await e(d(t,"auth.config.ts"),`import { betterAuth } from 'better-auth'
3223
+ `),await n(a(e,`auth.config.ts`),`import { betterAuth } from 'better-auth'
2082
3224
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
2083
3225
  import { admin } from 'better-auth/plugins'
2084
3226
  import { organization } from 'better-auth/plugins/organization'
@@ -2096,7 +3238,8 @@ export const auth = betterAuth({
2096
3238
  organization(),
2097
3239
  ],
2098
3240
  })
2099
- `),await e(d(t,"generated/auth-schema.ts"),`// This file is generated by better-auth CLI.
3241
+ `),await n(a(e,`generated/auth-schema.ts`),`// This file is generated by better-auth CLI.
2100
3242
  // Run: npx @better-auth/cli generate --config auth.config.ts -y
2101
3243
  export {}
2102
- `)}async function O(t,n){t.mode==="single"?await q(t,n):await I(t,n)}async function q(t,n){let r=v(process.cwd(),t.name);n?.("Creating Next.js app..."),l(`pnpm create next-app@${"16"} ${t.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(r,["app/page.tsx","app/page.module.css","app/fonts","README.md"]),await c(v(r,"package.json"),{type:"module",dependencies:{"@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/db":`^${i.myorgDb}`,"@murumets-ee/entity":`^${i.myorgEntity}`,"@murumets-ee/logging":`^${i.myorgLogging}`,"@murumets-ee/auth":`^${i.myorgAuth}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"@murumets-ee/admin-ui":`^${i.myorgAdminUi}`,"@murumets-ee/content":`^${i.myorgContent}`,"@murumets-ee/settings":`^${i.myorgSettings}`,"@murumets-ee/storage":`^${i.myorgStorage}`,"@murumets-ee/media":`^${i.myorgMedia}`,"@murumets-ee/taxonomy":`^${i.myorgTaxonomy}`,"@murumets-ee/queue":`^${i.myorgQueue}`,"@murumets-ee/mail":`^${i.myorgMail}`,"@murumets-ee/ticketing":`^${i.myorgTicketing}`,"@murumets-ee/ticketing-ui":`^${i.myorgTicketingUi}`,"@murumets-ee/tokens":`^${i.myorgTokens}`,"better-auth":`^${i.betterAuth}`,"drizzle-orm":`^${i.drizzleOrm}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,postgres:`^${i.postgres}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"drizzle-kit":`^${i.drizzleKit}`,tsx:`^${i.tsx}`,"babel-plugin-react-compiler":i.babelReactCompiler},scripts:{"db:generate":"tsx --env-file=.env scripts/generate-schema.ts","db:migrate:generate":"drizzle-kit generate","db:migrate":"tsx --env-file=.env scripts/migrate.ts","db:reset":"tsx --env-file=.env scripts/reset-db.ts"}}),n?.("Adding toolkit files..."),await k(r,t),await A(r,t),await C(r,t),await w(r,t),await T(r,t),await P(r,t),await z(r,t),await R(r,t),t.installDeps&&(n?.("Installing dependencies..."),l("pnpm install",{cwd:r}))}async function I(t,n){let r=v(process.cwd(),t.name);n?.("Creating workspace..."),await E(r,t,n),t.installDeps&&(n?.("Installing dependencies..."),l("pnpm install",{cwd:r}))}export{O as scaffold};
3244
+ `)}async function S(e,t){e.mode===`single`?await C(e,t):await w(e,t)}async function C(n,i){let o=a(process.cwd(),n.name);i?.(`Creating Next.js app...`),e(`pnpm create next-app@16 ${n.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await t(o,[`app/page.tsx`,`app/page.module.css`,`app/fonts`,`README.md`]),await r(a(o,`package.json`),{type:`module`,dependencies:{"@murumets-ee/core":`^${f.myorgCore}`,"@murumets-ee/db":`^${f.myorgDb}`,"@murumets-ee/entity":`^${f.myorgEntity}`,"@murumets-ee/logging":`^${f.myorgLogging}`,"@murumets-ee/auth":`^${f.myorgAuth}`,"@murumets-ee/auth-ui":`^${f.myorgAuthUi}`,"@murumets-ee/admin-ui":`^${f.myorgAdminUi}`,"@murumets-ee/content":`^${f.myorgContent}`,"@murumets-ee/settings":`^${f.myorgSettings}`,"@murumets-ee/storage":`^${f.myorgStorage}`,"@murumets-ee/media":`^${f.myorgMedia}`,"@murumets-ee/taxonomy":`^${f.myorgTaxonomy}`,"@murumets-ee/queue":`^${f.myorgQueue}`,"@murumets-ee/mail":`^${f.myorgMail}`,"@murumets-ee/ticketing":`^${f.myorgTicketing}`,"@murumets-ee/ticketing-ui":`^${f.myorgTicketingUi}`,"@murumets-ee/tokens":`^${f.myorgTokens}`,"@murumets-ee/editor":`^${f.myorgEditor}`,"@murumets-ee/blocks":`^${f.myorgBlocks}`,"@murumets-ee/ui":`^${f.myorgUi}`,"better-auth":`^${f.betterAuth}`,"drizzle-orm":`^${f.drizzleOrm}`,"next-intl":`^${f.nextIntl}`,"next-themes":`^${f.nextThemes}`,"lucide-react":`^${f.lucideReact}`,postgres:`^${f.postgres}`,"@tanstack/react-query":`^${f.tanstackReactQuery}`,"@tanstack/react-table":`^${f.tanstackReactTable}`,"react-hook-form":`^${f.reactHookForm}`,"@hookform/resolvers":`^${f.hookformResolvers}`,zod:`^${f.zod}`},devDependencies:{"drizzle-kit":`^${f.drizzleKit}`,tsx:`^${f.tsx}`,"babel-plugin-react-compiler":f.babelReactCompiler},scripts:{"db:generate":`tsx --env-file=.env scripts/generate-schema.ts`,"db:migrate:generate":`drizzle-kit generate`,"db:migrate":`tsx --env-file=.env scripts/migrate.ts`,"db:reset":`tsx --env-file=.env scripts/reset-db.ts`,worker:`tsx --env-file=.env scripts/worker.ts`}}),i?.(`Adding toolkit files...`),await l(o,n),await u(o,n),await x(o,n),await c(o,n),await d(o,n),await v(o,n),await b(o,n),await s(o,n),await y(o,n),n.installDeps&&(i?.(`Installing dependencies...`),e(`pnpm install`,{cwd:o}))}async function w(t,n){let r=a(process.cwd(),t.name);n?.(`Creating workspace...`),await p(r,t,n),t.installDeps&&(n?.(`Installing dependencies...`),e(`pnpm install`,{cwd:r}))}export{S as scaffold};
3245
+ //# sourceMappingURL=index.mjs.map