@murumets-ee/create 0.1.13 → 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,7 @@ 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'
881
1987
  import { getSessionCookie } from 'better-auth/cookies'
882
1988
  import createMiddleware from 'next-intl/middleware'
883
1989
  import { routing } from './i18n/routing'
@@ -906,13 +2012,13 @@ export function proxy(request: NextRequest) {
906
2012
  export const config = {
907
2013
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
908
2014
  }
909
- `),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'
910
2016
 
911
2017
  export const routing = defineRouting({
912
2018
  locales: ['en'],
913
2019
  defaultLocale: 'en',
914
2020
  })
915
- `),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'
916
2022
  import { hasLocale } from 'next-intl'
917
2023
  import { routing } from './routing'
918
2024
  import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
@@ -932,7 +2038,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
932
2038
  },
933
2039
  }
934
2040
  })
935
- `),await e(o(a,"app/globals.css"),`@import "tailwindcss";
2041
+ `),await n(a(l,`app/globals.css`),`@import "tailwindcss";
936
2042
  @source "../node_modules/@murumets-ee/auth-ui/dist";
937
2043
 
938
2044
  @custom-variant dark (&:where(.dark, .dark *));
@@ -959,7 +2065,7 @@ body {
959
2065
  color: var(--foreground);
960
2066
  font-family: Arial, Helvetica, sans-serif;
961
2067
  }
962
- `),await e(o(a,"app/theme-provider.tsx"),`'use client'
2068
+ `),await n(a(l,`app/theme-provider.tsx`),`'use client'
963
2069
 
964
2070
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
965
2071
  import type { ReactNode } from 'react'
@@ -976,7 +2082,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
976
2082
  </NextThemesProvider>
977
2083
  )
978
2084
  }
979
- `),await e(o(a,"app/theme-toggle.tsx"),`'use client'
2085
+ `),await n(a(l,`app/theme-toggle.tsx`),`'use client'
980
2086
 
981
2087
  import { useTheme } from 'next-themes'
982
2088
  import { useState, useEffect } from 'react'
@@ -1003,7 +2109,7 @@ export function ThemeToggle() {
1003
2109
  </button>
1004
2110
  )
1005
2111
  }
1006
- `),await e(o(a,"app/nav-header.tsx"),`'use client'
2112
+ `),await n(a(l,`app/nav-header.tsx`),`'use client'
1007
2113
 
1008
2114
  import Link from 'next/link'
1009
2115
  import { usePathname } from 'next/navigation'
@@ -1033,7 +2139,7 @@ export function NavHeader() {
1033
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">
1034
2140
  <nav className="max-w-6xl mx-auto flex items-center gap-1 px-4 h-12">
1035
2141
  <span className="font-semibold text-sm text-zinc-700 dark:text-zinc-300 mr-3 select-none">
1036
- ${r} Admin
2142
+ ${c} Admin
1037
2143
  </span>
1038
2144
  {links.map(({ href, label }) => (
1039
2145
  <Link key={href} href={href} className={linkClass(href)}>
@@ -1047,12 +2153,12 @@ export function NavHeader() {
1047
2153
  </header>
1048
2154
  )
1049
2155
  }
1050
- `),await e(o(a,"app/layout.tsx"),`import './globals.css'
2156
+ `),await n(a(l,`app/layout.tsx`),`import './globals.css'
1051
2157
 
1052
2158
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1053
2159
  return children
1054
2160
  }
1055
- `),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'
1056
2162
  import { Geist, Geist_Mono } from 'next/font/google'
1057
2163
  import { NextIntlClientProvider } from 'next-intl'
1058
2164
  import { getMessages } from 'next-intl/server'
@@ -1072,7 +2178,7 @@ const geistMono = Geist_Mono({
1072
2178
  })
1073
2179
 
1074
2180
  export const metadata: Metadata = {
1075
- title: '${r} Admin',
2181
+ title: '${c} Admin',
1076
2182
  description: 'Built with Lumi CMS Toolkit',
1077
2183
  }
1078
2184
 
@@ -1101,9 +2207,9 @@ export default async function LocaleLayout({
1101
2207
  </html>
1102
2208
  )
1103
2209
  }
1104
- `),await e(o(a,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
1105
- import { Article, Category } from '@${r}/config/entities'
1106
- 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'
1107
2213
 
1108
2214
  export default async function HomePage() {
1109
2215
  await getToolkitApp()
@@ -1118,7 +2224,7 @@ export default async function HomePage() {
1118
2224
  return (
1119
2225
  <div className="min-h-screen p-8">
1120
2226
  <div className="max-w-4xl mx-auto">
1121
- <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>
1122
2228
 
1123
2229
  <section className="mb-12">
1124
2230
  <h2 className="text-2xl font-semibold mb-4">
@@ -1165,8 +2271,8 @@ export default async function HomePage() {
1165
2271
  </div>
1166
2272
  )
1167
2273
  }
1168
- `),await e(o(a,"app/api/auth/[...all]/route.ts"),`import { toNextJsHandler } from 'better-auth/next-js'
1169
- 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'
1170
2276
 
1171
2277
  let _handler: ReturnType<typeof toNextJsHandler> | null = null
1172
2278
 
@@ -1185,7 +2291,7 @@ export async function GET(req: Request) {
1185
2291
  export async function POST(req: Request) {
1186
2292
  return (await handler()).POST(req)
1187
2293
  }
1188
- `),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'
1189
2295
  import type { ReactNode } from 'react'
1190
2296
 
1191
2297
  export default function AuthLayout({ children }: { children: ReactNode }) {
@@ -1197,10 +2303,10 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
1197
2303
  </AuthProviders>
1198
2304
  )
1199
2305
  }
1200
- `),await e(o(a,"app/[locale]/auth/providers.tsx"),`'use client'
2306
+ `),await n(a(l,`app/[locale]/auth/providers.tsx`),`'use client'
1201
2307
 
1202
2308
  import { AuthUIProvider } from '@murumets-ee/auth-ui'
1203
- import { authClient } from '@${r}/config/auth-client'
2309
+ import { authClient } from '@${c}/config/auth-client'
1204
2310
  import Link from 'next/link'
1205
2311
  import { useRouter } from 'next/navigation'
1206
2312
  import { useLocale } from 'next-intl'
@@ -1223,22 +2329,22 @@ export function AuthProviders({ children }: { children: ReactNode }) {
1223
2329
  </AuthUIProvider>
1224
2330
  )
1225
2331
  }
1226
- `),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'
1227
2333
 
1228
2334
  export default function SignInPage() {
1229
2335
  return <SignInForm />
1230
2336
  }
1231
- `),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'
1232
2338
 
1233
2339
  export default function SignUpPage() {
1234
2340
  return <SignUpForm />
1235
2341
  }
1236
- `),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'
1237
2343
 
1238
2344
  export default function ForgotPasswordPage() {
1239
2345
  return <ForgotPasswordForm />
1240
2346
  }
1241
- `),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'
1242
2348
  import { ResetPasswordContent } from './content'
1243
2349
 
1244
2350
  export default function ResetPasswordPage() {
@@ -1248,7 +2354,7 @@ export default function ResetPasswordPage() {
1248
2354
  </Suspense>
1249
2355
  )
1250
2356
  }
1251
- `),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'
1252
2358
 
1253
2359
  import { useSearchParams } from 'next/navigation'
1254
2360
  import { ResetPasswordForm } from '@murumets-ee/auth-ui'
@@ -1272,9 +2378,9 @@ export function ResetPasswordContent() {
1272
2378
 
1273
2379
  return <ResetPasswordForm token={token} />
1274
2380
  }
1275
- `),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'
1276
2382
  import { sql } from 'drizzle-orm'
1277
- import { getToolkitApp } from '@${r}/config/app'
2383
+ import { getToolkitApp } from '@${c}/config/app'
1278
2384
  import { SetupForm } from './form'
1279
2385
 
1280
2386
  export default async function SetupPage() {
@@ -1297,7 +2403,7 @@ export default async function SetupPage() {
1297
2403
  </div>
1298
2404
  )
1299
2405
  }
1300
- `),await e(o(a,"app/[locale]/setup/form.tsx"),`'use client'
2406
+ `),await n(a(l,`app/[locale]/setup/form.tsx`),`'use client'
1301
2407
 
1302
2408
  import { useState } from 'react'
1303
2409
  import { createFirstAdmin } from './actions'
@@ -1368,10 +2474,10 @@ export function SetupForm() {
1368
2474
  </form>
1369
2475
  )
1370
2476
  }
1371
- `),await e(o(a,"app/[locale]/setup/actions.ts"),`'use server'
2477
+ `),await n(a(l,`app/[locale]/setup/actions.ts`),`'use server'
1372
2478
 
1373
2479
  import { sql } from 'drizzle-orm'
1374
- import { getToolkitApp } from '@${r}/config/app'
2480
+ import { getToolkitApp } from '@${c}/config/app'
1375
2481
  import { getAuth } from '@murumets-ee/auth'
1376
2482
 
1377
2483
  export async function createFirstAdmin(formData: FormData) {
@@ -1414,11 +2520,11 @@ export async function createFirstAdmin(formData: FormData) {
1414
2520
 
1415
2521
  return { ok: true, email: adminUser.user.email }
1416
2522
  }
1417
- `)}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`),`
1418
2524
  # Environment
1419
2525
  .env*
1420
2526
  !.env.example
1421
- `),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'
1422
2528
  import createNextIntlPlugin from 'next-intl/plugin'
1423
2529
 
1424
2530
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
@@ -1426,7 +2532,7 @@ const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
1426
2532
  const nextConfig: NextConfig = {
1427
2533
  reactCompiler: true,
1428
2534
  transpilePackages: [
1429
- '@${r}/config',
2535
+ '@${s}/config',
1430
2536
  '@murumets-ee/core',
1431
2537
  '@murumets-ee/entity',
1432
2538
  '@murumets-ee/db',
@@ -1437,7 +2543,7 @@ const nextConfig: NextConfig = {
1437
2543
  }
1438
2544
 
1439
2545
  export default withNextIntl(nextConfig)
1440
- `),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'
1441
2547
  import { routing } from './i18n/routing'
1442
2548
 
1443
2549
  export default createMiddleware(routing)
@@ -1445,13 +2551,13 @@ export default createMiddleware(routing)
1445
2551
  export const config = {
1446
2552
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
1447
2553
  }
1448
- `),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'
1449
2555
 
1450
2556
  export const routing = defineRouting({
1451
2557
  locales: ['en'],
1452
2558
  defaultLocale: 'en',
1453
2559
  })
1454
- `),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'
1455
2561
  import { hasLocale } from 'next-intl'
1456
2562
  import { routing } from './routing'
1457
2563
  import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
@@ -1471,7 +2577,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
1471
2577
  },
1472
2578
  }
1473
2579
  })
1474
- `),await e(o(a,"app/globals.css"),`@import "tailwindcss";
2580
+ `),await n(a(c,`app/globals.css`),`@import "tailwindcss";
1475
2581
  @source "../node_modules/@murumets-ee/auth-ui/dist";
1476
2582
 
1477
2583
  @custom-variant dark (&:where(.dark, .dark *));
@@ -1498,7 +2604,7 @@ body {
1498
2604
  color: var(--foreground);
1499
2605
  font-family: Arial, Helvetica, sans-serif;
1500
2606
  }
1501
- `),await e(o(a,"app/theme-provider.tsx"),`'use client'
2607
+ `),await n(a(c,`app/theme-provider.tsx`),`'use client'
1502
2608
 
1503
2609
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
1504
2610
  import type { ReactNode } from 'react'
@@ -1515,7 +2621,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
1515
2621
  </NextThemesProvider>
1516
2622
  )
1517
2623
  }
1518
- `),await e(o(a,"app/theme-toggle.tsx"),`'use client'
2624
+ `),await n(a(c,`app/theme-toggle.tsx`),`'use client'
1519
2625
 
1520
2626
  import { useTheme } from 'next-themes'
1521
2627
  import { useState, useEffect } from 'react'
@@ -1542,7 +2648,7 @@ export function ThemeToggle() {
1542
2648
  </button>
1543
2649
  )
1544
2650
  }
1545
- `),await e(o(a,"app/nav-header.tsx"),`'use client'
2651
+ `),await n(a(c,`app/nav-header.tsx`),`'use client'
1546
2652
 
1547
2653
  import Link from 'next/link'
1548
2654
  import { usePathname } from 'next/navigation'
@@ -1570,7 +2676,7 @@ export function NavHeader() {
1570
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">
1571
2677
  <nav className="max-w-6xl mx-auto flex items-center gap-1 px-4 h-12">
1572
2678
  <span className="font-semibold text-sm text-zinc-700 dark:text-zinc-300 mr-3 select-none">
1573
- ${r}
2679
+ ${s}
1574
2680
  </span>
1575
2681
  {links.map(({ href, label }) => (
1576
2682
  <Link key={href} href={href} className={linkClass(href)}>
@@ -1584,12 +2690,12 @@ export function NavHeader() {
1584
2690
  </header>
1585
2691
  )
1586
2692
  }
1587
- `),await e(o(a,"app/layout.tsx"),`import './globals.css'
2693
+ `),await n(a(c,`app/layout.tsx`),`import './globals.css'
1588
2694
 
1589
2695
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1590
2696
  return children
1591
2697
  }
1592
- `),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'
1593
2699
  import { Geist, Geist_Mono } from 'next/font/google'
1594
2700
  import { NextIntlClientProvider } from 'next-intl'
1595
2701
  import { getMessages } from 'next-intl/server'
@@ -1609,7 +2715,7 @@ const geistMono = Geist_Mono({
1609
2715
  })
1610
2716
 
1611
2717
  export const metadata: Metadata = {
1612
- title: '${r}',
2718
+ title: '${s}',
1613
2719
  description: 'Built with Lumi CMS Toolkit',
1614
2720
  }
1615
2721
 
@@ -1638,9 +2744,9 @@ export default async function LocaleLayout({
1638
2744
  </html>
1639
2745
  )
1640
2746
  }
1641
- `),await e(o(a,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
1642
- import { Article, Category } from '@${r}/config/entities'
1643
- 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'
1644
2750
 
1645
2751
  export default async function HomePage() {
1646
2752
  await getToolkitApp()
@@ -1655,7 +2761,7 @@ export default async function HomePage() {
1655
2761
  return (
1656
2762
  <div className="min-h-screen p-8">
1657
2763
  <div className="max-w-4xl mx-auto">
1658
- <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>
1659
2765
 
1660
2766
  <section className="mb-12">
1661
2767
  <h2 className="text-2xl font-semibold mb-4">
@@ -1702,7 +2808,7 @@ export default async function HomePage() {
1702
2808
  </div>
1703
2809
  )
1704
2810
  }
1705
- `)}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'
1706
2812
  import createNextIntlPlugin from 'next-intl/plugin'
1707
2813
 
1708
2814
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
@@ -1727,7 +2833,7 @@ const nextConfig: NextConfig = {
1727
2833
  }
1728
2834
 
1729
2835
  export default withNextIntl(nextConfig)
1730
- `),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'
1731
2837
  import { getSessionCookie } from 'better-auth/cookies'
1732
2838
  import createMiddleware from 'next-intl/middleware'
1733
2839
  import { routing } from './i18n/routing'
@@ -1759,12 +2865,12 @@ export function proxy(request: NextRequest) {
1759
2865
  export const config = {
1760
2866
  matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
1761
2867
  }
1762
- `),await e(p(t,"app/layout.tsx"),`import './globals.css'
2868
+ `),await n(a(e,`app/layout.tsx`),`import './globals.css'
1763
2869
 
1764
2870
  export default function RootLayout({ children }: { children: React.ReactNode }) {
1765
2871
  return children
1766
2872
  }
1767
- `),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'
1768
2874
  import { Geist, Geist_Mono } from 'next/font/google'
1769
2875
  import { NextIntlClientProvider } from 'next-intl'
1770
2876
  import { getMessages } from 'next-intl/server'
@@ -1813,7 +2919,7 @@ export default async function LocaleLayout({
1813
2919
  </html>
1814
2920
  )
1815
2921
  }
1816
- `),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'
1817
2923
  import { Article, Category } from '@/entities'
1818
2924
  import { getToolkitApp } from '@/lib/app'
1819
2925
 
@@ -1877,13 +2983,49 @@ export default async function HomePage() {
1877
2983
  </div>
1878
2984
  )
1879
2985
  }
1880
- `)}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'
1881
2987
  execSync('npx @murumets-ee/cli generate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1882
- `),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'
1883
2989
  execSync('npx @murumets-ee/cli migrate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1884
- `),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'
1885
2991
  execSync('npx @murumets-ee/cli reset --force', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
1886
- `)}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";
1887
3029
  @source "../node_modules/@murumets-ee/auth-ui/dist";
1888
3030
 
1889
3031
  @custom-variant dark (&:where(.dark, .dark *));
@@ -1910,7 +3052,7 @@ body {
1910
3052
  color: var(--foreground);
1911
3053
  font-family: Arial, Helvetica, sans-serif;
1912
3054
  }
1913
- `),await e(f(t,"app/theme-provider.tsx"),`'use client'
3055
+ `),await n(a(e,`app/theme-provider.tsx`),`'use client'
1914
3056
 
1915
3057
  import { ThemeProvider as NextThemesProvider } from 'next-themes'
1916
3058
  import type { ReactNode } from 'react'
@@ -1927,7 +3069,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
1927
3069
  </NextThemesProvider>
1928
3070
  )
1929
3071
  }
1930
- `),await e(f(t,"app/theme-toggle.tsx"),`'use client'
3072
+ `),await n(a(e,`app/theme-toggle.tsx`),`'use client'
1931
3073
 
1932
3074
  import { useTheme } from 'next-themes'
1933
3075
  import { useState, useEffect } from 'react'
@@ -1954,7 +3096,7 @@ export function ThemeToggle() {
1954
3096
  </button>
1955
3097
  )
1956
3098
  }
1957
- `),await e(f(t,"app/nav-header.tsx"),`'use client'
3099
+ `),await n(a(e,`app/nav-header.tsx`),`'use client'
1958
3100
 
1959
3101
  import Link from 'next/link'
1960
3102
  import { usePathname } from 'next/navigation'
@@ -1998,7 +3140,7 @@ export function NavHeader() {
1998
3140
  </header>
1999
3141
  )
2000
3142
  }
2001
- `)}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'
2002
3144
  import { content } from '@murumets-ee/content/plugin'
2003
3145
  import { defineConfig } from '@murumets-ee/core'
2004
3146
  import { logging } from '@murumets-ee/logging/plugin'
@@ -2052,21 +3194,19 @@ export default defineConfig({
2052
3194
  ],
2053
3195
  projectRoot: import.meta.dirname,
2054
3196
  })
2055
- `),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'
2056
3198
  import config from '../toolkit.config'
2057
3199
 
2058
3200
  let appInstance: ToolkitApp | null = null
2059
3201
 
2060
3202
  export async function getToolkitApp(): Promise<ToolkitApp> {
2061
3203
  if (!appInstance) {
2062
- console.log('Initializing toolkit app...')
2063
3204
  appInstance = await createApp(config)
2064
3205
  setApp(appInstance)
2065
- console.log('Toolkit app initialized')
2066
3206
  }
2067
3207
  return appInstance
2068
3208
  }
2069
- `),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'
2070
3210
 
2071
3211
  if (!process.env.DATABASE_URL) {
2072
3212
  throw new Error('DATABASE_URL environment variable is required')
@@ -2080,7 +3220,7 @@ export default {
2080
3220
  url: process.env.DATABASE_URL,
2081
3221
  },
2082
3222
  } satisfies Config
2083
- `),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'
2084
3224
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
2085
3225
  import { admin } from 'better-auth/plugins'
2086
3226
  import { organization } from 'better-auth/plugins/organization'
@@ -2098,7 +3238,8 @@ export const auth = betterAuth({
2098
3238
  organization(),
2099
3239
  ],
2100
3240
  })
2101
- `),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.
2102
3242
  // Run: npx @better-auth/cli generate --config auth.config.ts -y
2103
3243
  export {}
2104
- `)}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