@infuro/cms-core 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +17 -13
  2. package/dist/admin.cjs +8522 -3281
  3. package/dist/admin.cjs.map +1 -1
  4. package/dist/admin.d.cts +13 -2
  5. package/dist/admin.d.ts +13 -2
  6. package/dist/admin.js +8210 -2957
  7. package/dist/admin.js.map +1 -1
  8. package/dist/api.cjs +5099 -1167
  9. package/dist/api.cjs.map +1 -1
  10. package/dist/api.d.cts +2 -2
  11. package/dist/api.d.ts +2 -2
  12. package/dist/api.js +4847 -909
  13. package/dist/api.js.map +1 -1
  14. package/dist/auth.cjs +461 -62
  15. package/dist/auth.cjs.map +1 -1
  16. package/dist/auth.d.cts +73 -47
  17. package/dist/auth.d.ts +73 -47
  18. package/dist/auth.js +425 -38
  19. package/dist/auth.js.map +1 -1
  20. package/dist/cli.cjs +21 -14
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +21 -14
  23. package/dist/cli.js.map +1 -1
  24. package/dist/{index-D9SdaDJ0.d.ts → index-BcMRGNjS.d.cts} +36 -3
  25. package/dist/{index-BPQSXgXF.d.cts → index-Bda8D1Rm.d.ts} +36 -3
  26. package/dist/index.cjs +4753 -740
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +301 -36
  29. package/dist/index.d.ts +301 -36
  30. package/dist/index.js +4750 -768
  31. package/dist/index.js.map +1 -1
  32. package/dist/migrations/1775400000001-AddDiscountAndOrderAddresses.ts +274 -0
  33. package/dist/migrations/1775800000000-VendorsAndVendorUsers.ts +74 -0
  34. package/dist/migrations/1775900000000-StoreVendorIdColumns.ts +124 -0
  35. package/dist/migrations/1775910000000-VendorOwnerRbacSeed.ts +54 -0
  36. package/dist/migrations/1775920000000-VendorOwnerSettingsPermission.ts +25 -0
  37. package/dist/migrations/1775930000000-VendorGroupStorePermissions.ts +54 -0
  38. package/dist/migrations/1775940000000-VendorRls.ts +71 -0
  39. package/dist/migrations/1775950000000-VendorCustomers.ts +87 -0
  40. package/dist/migrations/1775950000001-VendorCustomersPermissions.ts +67 -0
  41. package/dist/migrations/1778652250860-CreateCurrencyTables.ts +16 -0
  42. package/dist/migrations/1778652250861-OrderAddressTables.ts +88 -0
  43. package/dist/migrations/1778652250862-UpdateDiscountandRules.ts +100 -0
  44. package/dist/migrations/1778652250863-MakeCouponCodeNullable.ts +19 -0
  45. package/dist/migrations/1778652250864-RemoveUniqueConstraintFromDiscountCouponCode.ts +22 -0
  46. package/dist/migrations/1778652250865-FixOrdersAddressForeignKeys.ts +71 -0
  47. package/dist/migrations/1778660000000-DiscountVendorId.ts +90 -0
  48. package/dist/migrations/1778660000001-FixOrdersAddressFkToOrderAddresses.ts +79 -0
  49. package/dist/migrations/1778660000002-VendorUserIdInviteStatus.ts +61 -0
  50. package/dist/theme.d.cts +2 -2
  51. package/dist/theme.d.ts +2 -2
  52. package/dist/{types-D34wmivy.d.cts → types-DCVaXlPV.d.cts} +1 -1
  53. package/dist/{types-D34wmivy.d.ts → types-DCVaXlPV.d.ts} +1 -1
  54. package/dist/vendor-scope-DTYhJk_I.d.cts +129 -0
  55. package/dist/vendor-scope-DTYhJk_I.d.ts +129 -0
  56. package/package.json +9 -2
  57. package/dist/helpers-dlrF_49e.d.cts +0 -60
  58. package/dist/helpers-dlrF_49e.d.ts +0 -60
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/permission-entities.ts","../src/auth/helpers.ts","../src/auth/seed-permissions.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["/** API resource keys excluded from the permission matrix (internal / not admin-CRUD). */\r\nexport const PERMISSION_ENTITY_INTERNAL_EXCLUDE = new Set([\r\n 'users',\r\n 'password_reset_tokens',\r\n 'user_groups',\r\n 'permissions',\r\n 'comments',\r\n 'form_fields',\r\n 'configs',\r\n 'knowledge_base_chunks',\r\n 'carts',\r\n 'cart_items',\r\n 'wishlists',\r\n 'wishlist_items',\r\n 'message_templates',\r\n]);\r\n\r\n/** Non-CRUD admin surfaces mapped to entity keys for RBAC. */\r\nexport const PERMISSION_LOGICAL_ENTITIES = [\r\n 'users',\r\n 'forms',\r\n 'form_submissions',\r\n 'dashboard',\r\n 'upload',\r\n 'settings',\r\n 'analytics',\r\n 'chat',\r\n] as const;\r\n\r\n/** Canonical name for new installs / migrations */\r\nexport const ADMIN_GROUP_NAME = 'Administrator';\r\n\r\n/** System administrator group (roles UI + users / user_groups / permissions bypass). */\r\nexport function isSuperAdminGroupName(name: string | null | undefined): boolean {\r\n return name === ADMIN_GROUP_NAME;\r\n}\r\n\r\nexport type EntityCrudAction = 'create' | 'read' | 'update' | 'delete';\r\n\r\nexport type EntityPermissionFlags = { c: boolean; r: boolean; u: boolean; d: boolean };\r\n\r\nexport function getPermissionableEntityKeys(entityMap: Record<string, unknown>): string[] {\r\n const fromMap = Object.keys(entityMap).filter((k) => !PERMISSION_ENTITY_INTERNAL_EXCLUDE.has(k));\r\n const logical = PERMISSION_LOGICAL_ENTITIES.filter((k) => !fromMap.includes(k));\r\n return [...fromMap.sort(), ...logical].filter((k, i, a) => a.indexOf(k) === i);\r\n}\r\n\r\nexport function permissionRowsToRecord(\r\n rows: Array<{ entity: string; canCreate: boolean; canRead: boolean; canUpdate: boolean; canDelete: boolean }> | undefined\r\n): Record<string, EntityPermissionFlags> {\r\n const out: Record<string, EntityPermissionFlags> = {};\r\n if (!rows?.length) return out;\r\n for (const p of rows) {\r\n out[p.entity] = {\r\n c: !!p.canCreate,\r\n r: !!p.canRead,\r\n u: !!p.canUpdate,\r\n d: !!p.canDelete,\r\n };\r\n }\r\n return out;\r\n}\r\n\r\nexport function hasEntityPermission(\r\n record: Record<string, EntityPermissionFlags> | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): boolean {\r\n const p = record?.[entity];\r\n if (!p) return false;\r\n if (action === 'create') return p.c;\r\n if (action === 'read') return p.r;\r\n if (action === 'update') return p.u;\r\n return p.d;\r\n}\r\n","import type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\r\nimport { hasEntityPermission } from './permission-entities';\r\n\r\n/** isRBACAdmin bypasses entity checks only for these (users / roles plumbing). */\r\nexport const RBAC_ADMIN_ONLY_ENTITIES = new Set(['users', 'user_groups', 'permissions']);\r\n\r\nexport interface SessionUser {\r\n id?: string;\r\n email?: string | null;\r\n name?: string | null;\r\n groupId?: number;\r\n /** @deprecated use entityPerms / isRBACAdmin */\r\n permissions?: string[];\r\n /** Administrator group: full access only for users, user_groups, permissions */\r\n isRBACAdmin?: boolean;\r\n entityPerms?: Record<string, EntityPermissionFlags>;\r\n /** When false and not isRBACAdmin, admin API/UI is denied. */\r\n adminAccess?: boolean;\r\n}\r\n\r\nexport function sessionHasEntityAccess(\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): boolean {\r\n if (!user?.email) return false;\r\n if (user.isRBACAdmin && RBAC_ADMIN_ONLY_ENTITIES.has(entity)) return true;\r\n return hasEntityPermission(user.entityPerms, entity, action);\r\n}\r\n\r\nexport function canManageRoles(user: SessionUser | null | undefined): boolean {\r\n return !!(user?.email && user.isRBACAdmin);\r\n}\r\n\r\nexport type GetSession = () => Promise<{ user?: SessionUser } | null>;\r\n\r\nexport const OPEN_ENDPOINTS: Array<Record<string, string[]>> = [\r\n { '/api/contacts': ['POST'] },\r\n { '/api/form-submissions': ['POST'] },\r\n { '/api/blogs': ['GET'] },\r\n];\r\n\r\nexport const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]> = {};\r\n\r\nexport function isOpenEndpoint(pathname: string): boolean {\r\n return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));\r\n}\r\n\r\nexport function getRequiredPermission(pathname: string): string[] | null {\r\n return null;\r\n}\r\n\r\nexport function isPublicMethod(pathname: string, method: string): boolean {\r\n for (const endpoint of OPEN_ENDPOINTS) {\r\n const key = Object.keys(endpoint)[0];\r\n if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface AuthHelpers {\r\n requireAuth(req: Request): Promise<Response | null>;\r\n requirePermission(req: Request, permission: string): Promise<Response | null>;\r\n requireEntityPermission(req: Request, entity: string, action: EntityCrudAction): Promise<Response | null>;\r\n requireAdminAccess(req: Request): Promise<Response | null>;\r\n getAuthenticatedUser(): Promise<SessionUser | null>;\r\n}\r\n\r\nexport function createAuthHelpers(getSession: GetSession, NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }): AuthHelpers {\r\n return {\r\n async requireAuth() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n return null;\r\n },\r\n async requirePermission() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n return null;\r\n },\r\n async requireEntityPermission(_req: Request, entity: string, action: EntityCrudAction) {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n const u = session.user as SessionUser;\r\n if (sessionHasEntityAccess(u, entity, action)) return null;\r\n return NextResponse.json({ error: 'Forbidden', entity, action }, { status: 403 });\r\n },\r\n async requireAdminAccess() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n const u = session.user as SessionUser;\r\n if (u.isRBACAdmin) return null;\r\n if (u.adminAccess === false) return NextResponse.json({ error: 'Forbidden', reason: 'admin_access' }, { status: 403 });\r\n return null;\r\n },\r\n async getAuthenticatedUser() {\r\n const session = await getSession();\r\n return (session?.user as SessionUser) ?? null;\r\n },\r\n };\r\n}\r\n","import type { DataSource } from 'typeorm';\r\nimport type { EntityTarget } from 'typeorm';\r\nimport type { ObjectLiteral } from 'typeorm';\r\nimport { getPermissionableEntityKeys, ADMIN_GROUP_NAME } from './permission-entities';\r\n\r\n/**\r\n * Ensures the Administrator group has full CRUD on every permissionable entity\r\n * for the given entity map. Idempotent: upserts per entity.\r\n */\r\nexport async function seedAdministratorPermissions(\r\n dataSource: DataSource,\r\n entityMap: Record<string, unknown>\r\n): Promise<void> {\r\n const entities = getPermissionableEntityKeys(entityMap);\r\n const groupRepo = dataSource.getRepository(entityMap.user_groups as EntityTarget<ObjectLiteral>);\r\n const permRepo = dataSource.getRepository(entityMap.permissions as EntityTarget<ObjectLiteral>);\r\n\r\n const adminGroup = await groupRepo.findOne({ where: { name: ADMIN_GROUP_NAME, deleted: false } });\r\n if (!adminGroup) return;\r\n\r\n const fullCrud = { canCreate: true, canRead: true, canUpdate: true, canDelete: true };\r\n\r\n for (const entity of entities) {\r\n const existing = await permRepo.findOne({\r\n where: { groupId: adminGroup.id, entity },\r\n });\r\n if (existing) {\r\n existing.canCreate = true;\r\n existing.canRead = true;\r\n existing.canUpdate = true;\r\n existing.canDelete = true;\r\n await permRepo.save(existing);\r\n } else {\r\n await permRepo.save(\r\n permRepo.create({\r\n groupId: adminGroup.id,\r\n entity,\r\n ...fullCrud,\r\n })\r\n );\r\n }\r\n }\r\n}\r\n","export interface CmsMiddlewareConfig {\r\n publicAdminPaths?: string[];\r\n publicApiPaths?: string[];\r\n /** path -> allowed methods */\r\n publicApiMethods?: Record<string, string[]>;\r\n signInPath?: string;\r\n getSessionToken?: (request: { cookies: { get: (name: string) => { value?: string } | undefined } }) => string | undefined;\r\n}\r\n\r\n/** Default public API paths (no auth). Sites should extend this with their own routes. */\r\nexport const defaultPublicApiMethods: Record<string, string[]> = {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/health': ['GET'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n};\r\n\r\nfunction defaultGetSessionToken(request: { cookies: { get: (name: string) => { value?: string } | undefined } }): string | undefined {\r\n return (\r\n request.cookies.get('__Secure-next-auth.session-token')?.value ??\r\n request.cookies.get('next-auth.session-token')?.value\r\n );\r\n}\r\n\r\nfunction isPublicMethod(pathname: string, method: string, publicApiMethods: Record<string, string[]>): boolean {\r\n for (const [endpoint, methods] of Object.entries(publicApiMethods)) {\r\n if (pathname.startsWith(endpoint) && methods.includes(method)) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Returns middleware logic. Use from Next.js middleware:\r\n * import { createCmsMiddleware } from '@infuro/cms-core';\r\n * export const middleware = createCmsMiddleware({ ... });\r\n * export const config = { matcher: ['/admin/:path*', '/api/:path*'] };\r\n */\r\nexport function createCmsMiddleware(config: CmsMiddlewareConfig = {}) {\r\n const {\r\n publicAdminPaths = ['/admin/signin', '/admin/forgot-password', '/admin/reset-password', '/admin/invite'],\r\n publicApiMethods = defaultPublicApiMethods,\r\n signInPath = '/admin/signin',\r\n getSessionToken = defaultGetSessionToken,\r\n } = config;\r\n\r\n return function cmsMiddleware(request: {\r\n nextUrl: { pathname: string };\r\n url: string;\r\n method: string;\r\n cookies: { get: (name: string) => { value?: string } | undefined };\r\n }): { type: 'next' } | { type: 'redirect'; url: string } | { type: 'json'; status: number; body: unknown } {\r\n const pathname = request.nextUrl.pathname;\r\n const method = request.method;\r\n\r\n if (publicAdminPaths.some((p) => pathname === p || pathname.startsWith(p + '/'))) {\r\n return { type: 'next' };\r\n }\r\n\r\n if (pathname.startsWith('/admin')) {\r\n const token = getSessionToken(request);\r\n if (!token) {\r\n return { type: 'redirect', url: new URL(signInPath, request.url).toString() };\r\n }\r\n }\r\n\r\n if (pathname.startsWith('/api')) {\r\n if (isPublicMethod(pathname, method, publicApiMethods)) {\r\n return { type: 'next' };\r\n }\r\n const token = getSessionToken(request);\r\n if (!token) {\r\n return { type: 'json', status: 401, body: { error: 'Unauthorized' } };\r\n }\r\n }\r\n\r\n return { type: 'next' };\r\n };\r\n}\r\n","/**\r\n * Build NextAuth options for credentials auth. App can extend/override via extend().\r\n */\r\nimport type { NextAuthOptions } from 'next-auth';\r\nimport _CredentialsProvider from 'next-auth/providers/credentials';\r\nconst CredentialsProvider = (_CredentialsProvider as unknown as { default: typeof _CredentialsProvider }).default ?? _CredentialsProvider;\r\nimport { isSuperAdminGroupName, permissionRowsToRecord } from './permission-entities';\r\n\r\nexport interface NextAuthUser {\r\n id: number;\r\n email: string;\r\n name: string | null;\r\n password: string | null;\r\n blocked?: boolean;\r\n deleted?: boolean;\r\n groupId?: number | null;\r\n adminAccess?: boolean;\r\n group?: {\r\n name?: string;\r\n permissions?: Array<{\r\n entity: string;\r\n canCreate: boolean;\r\n canRead: boolean;\r\n canUpdate: boolean;\r\n canDelete: boolean;\r\n }>;\r\n };\r\n}\r\n\r\nexport interface AuthorizeOtpInput {\r\n identifier: string;\r\n channel: 'email' | 'sms';\r\n code: string;\r\n}\r\n\r\nexport interface NextAuthOptionsConfig {\r\n /** Resolve user by email (e.g. from TypeORM). Return null if not found. */\r\n getUserByEmail: (email: string) => Promise<NextAuthUser | null>;\r\n comparePassword: (plain: string, hash: string) => Promise<boolean>;\r\n signInPage?: string;\r\n secret?: string;\r\n extend?: (options: NextAuthOptions) => NextAuthOptions;\r\n /** When false, password CredentialsProvider is omitted. Default true. */\r\n enablePasswordLogin?: boolean;\r\n /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */\r\n enableOtpLogin?: boolean;\r\n /** Validate OTP and return user (same shape as password flow) or null. */\r\n authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;\r\n}\r\n\r\nfunction sessionUserFromNextAuthUser(user: NextAuthUser) {\r\n const g = user.group;\r\n const isRBACAdmin = isSuperAdminGroupName(g?.name);\r\n const entityPerms = permissionRowsToRecord(g?.permissions);\r\n const adminAccess = (user as NextAuthUser & { adminAccess?: boolean }).adminAccess === true;\r\n return {\r\n id: user.id.toString(),\r\n email: user.email,\r\n name: user.name,\r\n groupId: user.groupId ?? undefined,\r\n isRBACAdmin,\r\n entityPerms,\r\n adminAccess,\r\n };\r\n}\r\n\r\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\r\n const {\r\n getUserByEmail,\r\n comparePassword,\r\n signInPage = '/admin/signin',\r\n secret,\r\n extend,\r\n enablePasswordLogin = true,\r\n enableOtpLogin = false,\r\n authorizeOtp,\r\n } = config;\r\n\r\n const providers: NextAuthOptions['providers'] = [];\r\n\r\n if (enablePasswordLogin) {\r\n providers.push(\r\n CredentialsProvider({\r\n name: 'credentials',\r\n credentials: {\r\n email: { label: 'Email', type: 'email' },\r\n password: { label: 'Password', type: 'password' },\r\n },\r\n async authorize(credentials) {\r\n if (!credentials?.email || !credentials?.password) return null;\r\n try {\r\n const user = await getUserByEmail(credentials.email);\r\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted || !user.password) return null;\r\n const valid = await comparePassword(credentials.password, user.password);\r\n if (!valid) return null;\r\n return sessionUserFromNextAuthUser(user);\r\n } catch {\r\n return null;\r\n }\r\n },\r\n })\r\n );\r\n }\r\n\r\n if (enableOtpLogin && authorizeOtp) {\r\n providers.push(\r\n CredentialsProvider({\r\n id: 'otp',\r\n name: 'otp',\r\n credentials: {\r\n identifier: { label: 'Email or phone', type: 'text' },\r\n code: { label: 'Code', type: 'text' },\r\n channel: { label: 'Channel', type: 'text' },\r\n },\r\n async authorize(credentials) {\r\n const identifier = typeof credentials?.identifier === 'string' ? credentials.identifier.trim() : '';\r\n const code = typeof credentials?.code === 'string' ? credentials.code.trim() : '';\r\n const ch = credentials?.channel === 'sms' ? 'sms' : 'email';\r\n if (!identifier || !code) return null;\r\n try {\r\n const user = await authorizeOtp({ identifier, channel: ch, code });\r\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted) return null;\r\n return sessionUserFromNextAuthUser(user);\r\n } catch {\r\n return null;\r\n }\r\n },\r\n })\r\n );\r\n }\r\n\r\n const options: NextAuthOptions = {\r\n secret: secret ?? process.env.NEXTAUTH_SECRET,\r\n providers,\r\n session: { strategy: 'jwt' },\r\n pages: { signIn: signInPage },\r\n cookies: {\r\n sessionToken: {\r\n name: process.env.NEXTAUTH_URL?.startsWith('https')\r\n ? '__Secure-next-auth.session-token'\r\n : 'next-auth.session-token',\r\n options: {\r\n httpOnly: true,\r\n sameSite: 'lax',\r\n path: '/',\r\n secure: process.env.NEXTAUTH_URL?.startsWith('https') ?? false,\r\n },\r\n },\r\n },\r\n callbacks: {\r\n async jwt({ token, user, trigger, session }) {\r\n if (user) {\r\n const u = user as unknown as Record<string, unknown>;\r\n (token as Record<string, unknown>).id = u.id;\r\n (token as Record<string, unknown>).groupId = u.groupId;\r\n (token as Record<string, unknown>).isRBACAdmin = u.isRBACAdmin;\r\n (token as Record<string, unknown>).entityPerms = u.entityPerms;\r\n (token as Record<string, unknown>).adminAccess = u.adminAccess;\r\n }\r\n if (trigger === 'update' && session && typeof session === 'object') {\r\n const s = session as Record<string, unknown>;\r\n const t = token as Record<string, unknown>;\r\n if (typeof s.name === 'string') t.name = s.name;\r\n if (typeof s.email === 'string') t.email = s.email;\r\n }\r\n return token;\r\n },\r\n async session({ session, token }) {\r\n if (session.user) {\r\n const t = token as Record<string, unknown>;\r\n if (typeof t.name === 'string') session.user.name = t.name;\r\n if (typeof t.email === 'string') session.user.email = t.email;\r\n (session.user as Record<string, unknown>).id = t.id;\r\n (session.user as Record<string, unknown>).groupId = t.groupId;\r\n (session.user as Record<string, unknown>).isRBACAdmin = t.isRBACAdmin;\r\n (session.user as Record<string, unknown>).entityPerms = t.entityPerms;\r\n (session.user as Record<string, unknown>).adminAccess = t.adminAccess;\r\n }\r\n return session;\r\n },\r\n },\r\n };\r\n\r\n return extend ? extend(options) : options;\r\n}\r\n"],"mappings":"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","names":["isPublicMethod"]}
1
+ {"version":3,"sources":["../src/auth/permission-entities.ts","../src/auth/vendor-scope.ts","../src/auth/rbac-debug-db.ts","../src/auth/rbac-debug.ts","../src/auth/helpers.ts","../src/auth/index.ts","../src/auth/seed-permissions.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["/** API resource keys excluded from the permission matrix (internal / not admin-CRUD). */\r\nexport const PERMISSION_ENTITY_INTERNAL_EXCLUDE = new Set([\r\n 'users',\r\n 'password_reset_tokens',\r\n 'user_groups',\r\n 'permissions',\r\n 'comments',\r\n 'form_fields',\r\n 'configs',\r\n 'knowledge_base_chunks',\r\n 'carts',\r\n 'cart_items',\r\n 'wishlists',\r\n 'wishlist_items',\r\n 'message_templates',\r\n 'vendor_users',\r\n]);\r\n\r\n/** Non-CRUD admin surfaces mapped to entity keys for RBAC. */\r\nexport const PERMISSION_LOGICAL_ENTITIES = [\r\n 'users',\r\n 'forms',\r\n 'form_submissions',\r\n 'dashboard',\r\n 'upload',\r\n 'settings',\r\n 'analytics',\r\n 'chat',\r\n] as const;\r\n\r\n/** Canonical name for new installs / migrations */\r\nexport const ADMIN_GROUP_NAME = 'Administrator';\r\n\r\n/** System administrator group (roles UI + users / user_groups / permissions bypass). */\r\nexport function isSuperAdminGroupName(name: string | null | undefined): boolean {\r\n return name === ADMIN_GROUP_NAME;\r\n}\r\n\r\nexport type EntityCrudAction = 'create' | 'read' | 'update' | 'delete';\r\n\r\nexport type EntityPermissionFlags = { c: boolean; r: boolean; u: boolean; d: boolean };\r\n\r\nexport function getPermissionableEntityKeys(entityMap: Record<string, unknown>): string[] {\r\n const fromMap = Object.keys(entityMap).filter((k) => !PERMISSION_ENTITY_INTERNAL_EXCLUDE.has(k));\r\n const logical = PERMISSION_LOGICAL_ENTITIES.filter((k) => !fromMap.includes(k));\r\n return [...fromMap.sort(), ...logical].filter((k, i, a) => a.indexOf(k) === i);\r\n}\r\n\r\nexport function permissionRowsToRecord(\r\n rows: Array<{ entity: string; canCreate: boolean; canRead: boolean; canUpdate: boolean; canDelete: boolean }> | undefined\r\n): Record<string, EntityPermissionFlags> {\r\n const out: Record<string, EntityPermissionFlags> = {};\r\n if (!rows?.length) return out;\r\n for (const p of rows) {\r\n out[p.entity] = {\r\n c: !!p.canCreate,\r\n r: !!p.canRead,\r\n u: !!p.canUpdate,\r\n d: !!p.canDelete,\r\n };\r\n }\r\n return out;\r\n}\r\n\r\nexport function hasEntityPermission(\r\n record: Record<string, EntityPermissionFlags> | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): boolean {\r\n const p = record?.[entity];\r\n if (!p) return false;\r\n if (action === 'create') return p.c;\r\n if (action === 'read') return p.r;\r\n if (action === 'update') return p.u;\r\n return p.d;\r\n}\r\n","import type { SessionUser } from './helpers';\r\nimport { isSuperAdminGroupName } from './permission-entities';\r\n\r\n/** Entities with a `vendorId` column (store data). */\r\nexport const VENDOR_SCOPED_STORE_ENTITIES = new Set([\r\n 'products',\r\n 'collections',\r\n 'brands',\r\n 'product_categories',\r\n 'orders',\r\n 'payments',\r\n 'taxes',\r\n 'attributes',\r\n 'vendor_customers',\r\n 'discounts',\r\n]);\r\n\r\n/**\r\n * Store admin RBAC for vendor portal users (read/create/update/delete fallbacks in rbac-debug).\r\n * Includes entities without `vendorId` that are still part of the store admin UI.\r\n */\r\nexport const VENDOR_STORE_RBAC_ENTITIES = new Set<string>([\r\n ...VENDOR_SCOPED_STORE_ENTITIES,\r\n 'order_discounts',\r\n 'order_addresses',\r\n]);\r\n\r\nexport type VendorScope =\r\n | { type: 'all' }\r\n | { type: 'vendor'; vendorId: number; vendorIds: number[] }\r\n | { type: 'deny' };\r\n\r\nexport const VENDOR_OWNER_GROUP_NAME = 'Vendor Owner';\r\n\r\n/** User groups treated as vendor store staff (sidebar + scoped admin). */\r\nexport function isVendorGroupName(name: string | null | undefined): boolean {\r\n if (!name?.trim()) return false;\r\n const n = name.trim().toLowerCase();\r\n return n === VENDOR_OWNER_GROUP_NAME.toLowerCase() || n.includes('vendor');\r\n}\r\n\r\nexport function isPlatformAdministrator(user: SessionUser | null | undefined): boolean {\r\n if (!user?.email) return false;\r\n return !!(user.isRBACAdmin || isSuperAdminGroupName(user.groupName));\r\n}\r\n\r\n/** Vendor portal UI (store + store admin), not platform admin. */\r\nexport function isVendorPortalUser(user: SessionUser | null | undefined): boolean {\r\n if (user?.isVendorPortal === true) return true;\r\n if (!user?.email || isPlatformAdministrator(user)) return false;\r\n if ((user.vendorIds?.length ?? 0) > 0) return true;\r\n return isVendorGroupName(user.groupName);\r\n}\r\n\r\nexport function isVendorStaff(user: SessionUser | null | undefined): boolean {\r\n return isVendorPortalUser(user);\r\n}\r\n\r\n/** Vendor store owner (manages team + role permissions for their group). */\r\nexport function isVendorOwner(user: SessionUser | null | undefined): boolean {\r\n if (user?.isVendorOwner === true) return true;\r\n if (!user?.email || isPlatformAdministrator(user)) return false;\r\n if (user.vendorRole === 'staff') return false;\r\n if (user.vendorRole === 'owner') return true;\r\n if (!isVendorPortalUser(user)) return false;\r\n return user.groupName?.trim().toLowerCase() === VENDOR_OWNER_GROUP_NAME.toLowerCase();\r\n}\r\n\r\nexport function canManageVendorTeam(user: SessionUser | null | undefined): boolean {\r\n return isPlatformAdministrator(user) || isVendorOwner(user);\r\n}\r\n\r\nexport function canManageVendorRoles(user: SessionUser | null | undefined): boolean {\r\n return isPlatformAdministrator(user) || isVendorOwner(user);\r\n}\r\n\r\n/** Platform admins (Administrator group) can onboard vendors. */\r\nexport function canOnboardVendors(user: SessionUser | null | undefined): boolean {\r\n return isPlatformAdministrator(user);\r\n}\r\n\r\nexport function resolveVendorScopeFromSessionUser(user: SessionUser | null | undefined): VendorScope {\r\n if (!user?.email) return { type: 'deny' };\r\n if (isPlatformAdministrator(user)) return { type: 'all' };\r\n const vendorIds = user.vendorIds ?? [];\r\n if (vendorIds.length === 0) return { type: 'deny' };\r\n const preferred = user.activeVendorId ?? vendorIds[0]!;\r\n const vendorId = vendorIds.includes(preferred) ? preferred : vendorIds[0]!;\r\n return { type: 'vendor', vendorId, vendorIds };\r\n}\r\n\r\n/** Compute portal flags at login (persist on JWT). */\r\nexport function vendorPortalFlagsFromUser(input: {\r\n email: string;\r\n isRBACAdmin: boolean;\r\n groupName?: string | null;\r\n vendorIds?: number[];\r\n vendorRole?: 'owner' | 'staff' | null;\r\n}): { isVendorPortal: boolean; isVendorOwner: boolean } {\r\n const vendorIds = input.vendorIds ?? [];\r\n const isVendorPortal =\r\n !input.isRBACAdmin &&\r\n (vendorIds.length > 0 || isVendorGroupName(input.groupName));\r\n const isVendorOwner =\r\n isVendorPortal &&\r\n input.vendorRole !== 'staff' &&\r\n (input.vendorRole === 'owner' ||\r\n input.groupName?.trim().toLowerCase() === VENDOR_OWNER_GROUP_NAME.toLowerCase());\r\n return { isVendorPortal, isVendorOwner };\r\n}\r\n","import { permissionRowsToRecord } from './permission-entities';\r\nimport type { RbacDebugDataSource } from './rbac-debug';\r\nimport { summarizeEntityPerms } from './rbac-debug';\r\n\r\n/** Load permission rows from DB for debug (server/API only — do not import from `@infuro/cms-core/auth`). */\r\nexport async function loadDbPermissionsForUser(\r\n dataSource: RbacDebugDataSource,\r\n userId: number\r\n): Promise<{\r\n groupId: number | null;\r\n groupName: string | null;\r\n dbEntityPerms: Record<string, { c: boolean; r: boolean; u: boolean; d: boolean }>;\r\n}> {\r\n const rows = (await dataSource.query(\r\n `\r\n SELECT u.\"groupId\" AS \"groupId\", g.name AS \"groupName\",\r\n p.entity, p.\"canCreate\", p.\"canRead\", p.\"canUpdate\", p.\"canDelete\"\r\n FROM users u\r\n LEFT JOIN user_groups g ON g.id = u.\"groupId\" AND g.deleted = false\r\n LEFT JOIN permissions p ON p.\"groupId\" = g.id\r\n WHERE u.id = $1\r\n `,\r\n [userId]\r\n )) as Array<{\r\n groupId: number | null;\r\n groupName: string | null;\r\n entity: string | null;\r\n canCreate: boolean | null;\r\n canRead: boolean | null;\r\n canUpdate: boolean | null;\r\n canDelete: boolean | null;\r\n }>;\r\n\r\n const groupId = rows[0]?.groupId ?? null;\r\n const groupName = rows[0]?.groupName ?? null;\r\n const permRows = rows\r\n .filter((r) => r.entity)\r\n .map((r) => ({\r\n entity: r.entity!,\r\n canCreate: !!r.canCreate,\r\n canRead: !!r.canRead,\r\n canUpdate: !!r.canUpdate,\r\n canDelete: !!r.canDelete,\r\n }));\r\n\r\n return {\r\n groupId,\r\n groupName,\r\n dbEntityPerms: summarizeEntityPerms(permissionRowsToRecord(permRows)),\r\n };\r\n}\r\n","import type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\r\nimport { hasEntityPermission } from './permission-entities';\r\nimport type { SessionUser } from './helpers';\r\nimport { isVendorOwner, isVendorPortalUser, VENDOR_STORE_RBAC_ENTITIES } from './vendor-scope';\r\n\r\nconst LOG_PREFIX = '[cms-rbac]';\r\n\r\n/** Minimal DB handle for RBAC debug (avoids importing `typeorm` in the auth bundle). */\r\nexport type RbacDebugDataSource = {\r\n query: (sql: string, parameters?: unknown[]) => Promise<unknown>;\r\n};\r\n\r\n/** Set `CMS_RBAC_DEBUG=true` in `.env` to log permission decisions (server console). */\r\nexport function isRbacDebugEnabled(): boolean {\r\n const v = process.env.CMS_RBAC_DEBUG?.trim().toLowerCase();\r\n if (v === '1' || v === 'true' || v === 'yes') return true;\r\n if (v === '0' || v === 'false' || v === 'no') return false;\r\n return process.env.NODE_ENV === 'development';\r\n}\r\n\r\nexport function logRbac(message: string, data?: Record<string, unknown>): void {\r\n if (!isRbacDebugEnabled()) return;\r\n if (data) console.info(LOG_PREFIX, message, data);\r\n else console.info(LOG_PREFIX, message);\r\n}\r\n\r\nexport function summarizeEntityPerms(\r\n entityPerms: Record<string, EntityPermissionFlags> | undefined\r\n): Record<string, { c: boolean; r: boolean; u: boolean; d: boolean }> {\r\n const out: Record<string, { c: boolean; r: boolean; u: boolean; d: boolean }> = {};\r\n if (!entityPerms) return out;\r\n for (const [entity, p] of Object.entries(entityPerms)) {\r\n out[entity] = { c: p.c, r: p.r, u: p.u, d: p.d };\r\n }\r\n return out;\r\n}\r\n\r\nexport interface EntityAccessExplanation {\r\n allowed: boolean;\r\n reason: string;\r\n userSnapshot: Record<string, unknown>;\r\n}\r\n\r\nfunction vendorPortalFallbackReason(\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): string | null {\r\n if (!isVendorPortalUser(user)) return null;\r\n if (entity === 'dashboard' && action === 'read') return 'vendor_portal_dashboard_fallback';\r\n if (entity === 'analytics' && action === 'read') return 'vendor_portal_analytics_fallback';\r\n if (entity === 'settings' && action === 'read') return 'vendor_portal_settings_read_fallback';\r\n if (VENDOR_STORE_RBAC_ENTITIES.has(entity) && action === 'read') return 'vendor_portal_store_read_fallback';\r\n return null;\r\n}\r\n\r\n/** Same rules as `sessionHasEntityAccess`, but returns why access was granted or denied. */\r\nexport function explainSessionEntityAccess(\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): EntityAccessExplanation {\r\n const userSnapshot: Record<string, unknown> = {\r\n email: user?.email ?? null,\r\n id: user?.id ?? null,\r\n groupId: user?.groupId ?? null,\r\n groupName: user?.groupName ?? null,\r\n isRBACAdmin: user?.isRBACAdmin ?? false,\r\n adminAccess: user?.adminAccess ?? null,\r\n vendorIds: user?.vendorIds ?? [],\r\n activeVendorId: user?.activeVendorId ?? null,\r\n vendorRole: user?.vendorRole ?? null,\r\n isVendorPortal: user?.isVendorPortal ?? false,\r\n isVendorOwner: user?.isVendorOwner ?? false,\r\n isVendorPortalComputed: isVendorPortalUser(user),\r\n isVendorOwnerComputed: isVendorOwner(user),\r\n jwtEntityPerms: summarizeEntityPerms(user?.entityPerms),\r\n jwtEntityPermForTarget: user?.entityPerms?.[entity] ?? null,\r\n };\r\n\r\n if (!user?.email) {\r\n return { allowed: false, reason: 'no_session_email', userSnapshot };\r\n }\r\n if (user.isRBACAdmin) {\r\n return { allowed: true, reason: 'platform_administrator', userSnapshot };\r\n }\r\n\r\n const vendorIds = user.vendorIds ?? [];\r\n const isStaff = user.vendorRole === 'staff';\r\n\r\n if (vendorIds.length > 0) {\r\n if (entity === 'dashboard' && action === 'read') {\r\n return { allowed: true, reason: 'vendor_link_dashboard_read', userSnapshot };\r\n }\r\n if (entity === 'analytics' && action === 'read') {\r\n return { allowed: true, reason: 'vendor_link_analytics_read', userSnapshot };\r\n }\r\n if (entity === 'settings') {\r\n if (action === 'read') {\r\n return { allowed: true, reason: 'vendor_link_settings_read', userSnapshot };\r\n }\r\n if (action === 'update' && !isStaff) {\r\n return { allowed: true, reason: 'vendor_link_settings_update', userSnapshot };\r\n }\r\n }\r\n if (VENDOR_STORE_RBAC_ENTITIES.has(entity)) {\r\n if (isStaff && action === 'read') {\r\n return { allowed: true, reason: 'vendor_staff_store_read', userSnapshot };\r\n }\r\n if (!isStaff) {\r\n return { allowed: true, reason: 'vendor_link_store_full', userSnapshot };\r\n }\r\n return {\r\n allowed: false,\r\n reason: 'vendor_staff_store_write_denied',\r\n userSnapshot,\r\n };\r\n }\r\n }\r\n\r\n if (isVendorPortalUser(user) && isVendorOwner(user)) {\r\n if (entity === 'dashboard' && action === 'read') {\r\n return { allowed: true, reason: 'vendor_owner_group_dashboard', userSnapshot };\r\n }\r\n if (entity === 'analytics' && action === 'read') {\r\n return { allowed: true, reason: 'vendor_owner_group_analytics', userSnapshot };\r\n }\r\n if (entity === 'settings' && (action === 'read' || action === 'update')) {\r\n return { allowed: true, reason: 'vendor_owner_group_settings', userSnapshot };\r\n }\r\n if (VENDOR_STORE_RBAC_ENTITIES.has(entity)) {\r\n return { allowed: true, reason: 'vendor_owner_group_store', userSnapshot };\r\n }\r\n }\r\n\r\n const vendorPortalReason = vendorPortalFallbackReason(user, entity, action);\r\n if (vendorPortalReason) {\r\n return { allowed: true, reason: vendorPortalReason, userSnapshot };\r\n }\r\n\r\n const explicit = user.entityPerms?.[entity];\r\n if (explicit !== undefined) {\r\n const allowed = hasEntityPermission(user.entityPerms, entity, action);\r\n return {\r\n allowed,\r\n reason: allowed ? 'jwt_entity_perms_allow' : 'jwt_entity_perms_deny',\r\n userSnapshot,\r\n };\r\n }\r\n\r\n const allowed = hasEntityPermission(user.entityPerms, entity, action);\r\n return {\r\n allowed,\r\n reason: allowed ? 'entity_perms_fallback_allow' : 'no_matching_rule',\r\n userSnapshot,\r\n };\r\n}\r\n\r\nexport function sessionHasEntityAccessFromExplanation(\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): boolean {\r\n return explainSessionEntityAccess(user, entity, action).allowed;\r\n}\r\n\r\nexport async function logEntityAccessDecision(\r\n dataSource: RbacDebugDataSource | undefined,\r\n context: string,\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction,\r\n extra?: Record<string, unknown>\r\n): Promise<void> {\r\n if (!isRbacDebugEnabled()) return;\r\n\r\n const explanation = explainSessionEntityAccess(user, entity, action);\r\n let dbPermissions: Record<string, unknown> | undefined;\r\n if (dataSource && user?.id) {\r\n const id = Number(user.id);\r\n if (Number.isFinite(id)) {\r\n try {\r\n const { loadDbPermissionsForUser } = await import('./rbac-debug-db');\r\n dbPermissions = await loadDbPermissionsForUser(dataSource, id);\r\n } catch (e) {\r\n dbPermissions = { error: e instanceof Error ? e.message : String(e) };\r\n }\r\n }\r\n }\r\n\r\n logRbac(context, {\r\n entity,\r\n action,\r\n allowed: explanation.allowed,\r\n reason: explanation.reason,\r\n ...explanation.userSnapshot,\r\n dbPermissions,\r\n ...extra,\r\n });\r\n}\r\n","import type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\r\nimport { explainSessionEntityAccess, logRbac } from './rbac-debug';\r\nimport { isVendorPortalUser } from './vendor-scope';\r\n\r\n/**\r\n * @deprecated Administrator bypass is now full entity access via `isRBACAdmin` in `sessionHasEntityAccess`.\r\n * Kept for backwards-compatible imports.\r\n */\r\nexport const RBAC_ADMIN_ONLY_ENTITIES = new Set(['users', 'user_groups', 'permissions']);\r\n\r\nexport interface SessionUser {\r\n id?: string;\r\n email?: string | null;\r\n name?: string | null;\r\n groupId?: number;\r\n /** User group display name (from login). */\r\n groupName?: string | null;\r\n /** @deprecated use entityPerms / isRBACAdmin */\r\n permissions?: string[];\r\n /** Administrator group: bypasses entity-level RBAC on all resources */\r\n isRBACAdmin?: boolean;\r\n entityPerms?: Record<string, EntityPermissionFlags>;\r\n /** When false and not isRBACAdmin, admin API/UI is denied. */\r\n adminAccess?: boolean;\r\n /** Vendor stores this user can access (from vendor_users). */\r\n vendorIds?: number[];\r\n /** Active vendor for scoped CRUD (vendor staff). */\r\n activeVendorId?: number | null;\r\n /** Role for the active vendor link (`vendor_users.role`). */\r\n vendorRole?: 'owner' | 'staff' | null;\r\n /** Set at login: vendor store portal (not platform admin). */\r\n isVendorPortal?: boolean;\r\n /** Set at login: vendor store owner (team + permissions). */\r\n isVendorOwner?: boolean;\r\n}\r\n\r\nexport function sessionHasEntityAccess(\r\n user: SessionUser | null | undefined,\r\n entity: string,\r\n action: EntityCrudAction\r\n): boolean {\r\n return explainSessionEntityAccess(user, entity, action).allowed;\r\n}\r\n\r\nexport function canManageRoles(user: SessionUser | null | undefined): boolean {\r\n return !!(user?.email && user.isRBACAdmin);\r\n}\r\n\r\nexport type GetSession = () => Promise<{ user?: SessionUser } | null>;\r\n\r\nexport const OPEN_ENDPOINTS: Array<Record<string, string[]>> = [\r\n { '/api/contacts': ['POST'] },\r\n { '/api/form-submissions': ['POST'] },\r\n { '/api/blogs': ['GET'] },\r\n];\r\n\r\nexport const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]> = {};\r\n\r\nexport function isOpenEndpoint(pathname: string): boolean {\r\n return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));\r\n}\r\n\r\nexport function getRequiredPermission(pathname: string): string[] | null {\r\n return null;\r\n}\r\n\r\nexport function isPublicMethod(pathname: string, method: string): boolean {\r\n for (const endpoint of OPEN_ENDPOINTS) {\r\n const key = Object.keys(endpoint)[0];\r\n if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface AuthHelpers {\r\n requireAuth(req: Request): Promise<Response | null>;\r\n requirePermission(req: Request, permission: string): Promise<Response | null>;\r\n requireEntityPermission(req: Request, entity: string, action: EntityCrudAction): Promise<Response | null>;\r\n requireAdminAccess(req: Request): Promise<Response | null>;\r\n getAuthenticatedUser(): Promise<SessionUser | null>;\r\n}\r\n\r\nexport function createAuthHelpers(getSession: GetSession, NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }): AuthHelpers {\r\n return createAuthHelpersFromGetSession(getSession, NextResponse);\r\n}\r\n\r\n/** Pass return value into `createCmsApiHandler` (includes `getSessionUser` for vendor store APIs). */\r\nexport function createCmsAuthBundle(\r\n getSession: GetSession,\r\n NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }\r\n) {\r\n const auth = createAuthHelpers(getSession, NextResponse);\r\n return {\r\n requireAuth: auth.requireAuth,\r\n requireAdminAccess: auth.requireAdminAccess,\r\n requireEntityPermission: auth.requireEntityPermission,\r\n getSessionUser: auth.getAuthenticatedUser,\r\n getAuthenticatedUser: auth.getAuthenticatedUser,\r\n };\r\n}\r\n\r\nfunction createAuthHelpersFromGetSession(\r\n getSession: GetSession,\r\n NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }\r\n): AuthHelpers {\r\n return {\r\n async requireAuth() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n return null;\r\n },\r\n async requirePermission() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n return null;\r\n },\r\n async requireEntityPermission(_req: Request, entity: string, action: EntityCrudAction) {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n const u = session.user as SessionUser;\r\n const allowed = sessionHasEntityAccess(u, entity, action);\r\n const { reason } = explainSessionEntityAccess(u, entity, action);\r\n logRbac('authHelpers.requireEntityPermission', {\r\n entity,\r\n action,\r\n allowed,\r\n reason,\r\n email: u.email,\r\n groupName: u.groupName,\r\n vendorIds: u.vendorIds,\r\n isVendorPortal: u.isVendorPortal,\r\n isVendorOwner: u.isVendorOwner,\r\n });\r\n if (allowed) return null;\r\n return NextResponse.json({ error: 'Forbidden', entity, action, reason }, { status: 403 });\r\n },\r\n async requireAdminAccess() {\r\n const session = await getSession();\r\n if (!session?.user?.email) {\r\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\r\n }\r\n const u = session.user as SessionUser;\r\n if (u.isRBACAdmin) return null;\r\n // Vendor portal users always have admin UI access for their store — check\r\n // before adminAccess so that vendor users (who have adminAccess:false from\r\n // the DB default) are not incorrectly denied.\r\n if (isVendorPortalUser(u)) return null;\r\n if (u.adminAccess === true) return null;\r\n if (u.adminAccess === false) {\r\n logRbac('requireAdminAccess denied (adminAccess:false)', {\r\n email: u.email,\r\n groupName: u.groupName,\r\n isVendorPortal: u.isVendorPortal,\r\n });\r\n return NextResponse.json({ error: 'Forbidden', reason: 'admin_access' }, { status: 403 });\r\n }\r\n logRbac('requireAdminAccess denied (no matching rule)', {\r\n email: u.email,\r\n adminAccess: u.adminAccess,\r\n groupName: u.groupName,\r\n isVendorPortal: u.isVendorPortal,\r\n });\r\n return NextResponse.json({ error: 'Forbidden', reason: 'admin_access' }, { status: 403 });\r\n },\r\n async getAuthenticatedUser() {\r\n const session = await getSession();\r\n return (session?.user as SessionUser) ?? null;\r\n },\r\n };\r\n}\r\n","export {\r\n createAuthHelpers,\r\n createCmsAuthBundle,\r\n OPEN_ENDPOINTS,\r\n PERMISSION_REQUIRED_ENDPOINTS,\r\n isOpenEndpoint,\r\n isPublicMethod,\r\n getRequiredPermission,\r\n} from './helpers';\r\nexport type { SessionUser, GetSession, AuthHelpers } from './helpers';\r\nexport { sessionHasEntityAccess, canManageRoles, RBAC_ADMIN_ONLY_ENTITIES } from './helpers';\r\nexport {\r\n isRbacDebugEnabled,\r\n logRbac,\r\n explainSessionEntityAccess,\r\n logEntityAccessDecision,\r\n summarizeEntityPerms,\r\n} from './rbac-debug';\r\nexport {\r\n resolveVendorScopeFromSessionUser,\r\n VENDOR_SCOPED_STORE_ENTITIES,\r\n VENDOR_STORE_RBAC_ENTITIES,\r\n VENDOR_OWNER_GROUP_NAME,\r\n isVendorStaff,\r\n isVendorPortalUser,\r\n isVendorOwner,\r\n isVendorGroupName,\r\n isPlatformAdministrator,\r\n vendorPortalFlagsFromUser,\r\n canOnboardVendors,\r\n canManageVendorTeam,\r\n canManageVendorRoles,\r\n} from './vendor-scope';\r\nexport {\r\n getPermissionableEntityKeys,\r\n ADMIN_GROUP_NAME,\r\n isSuperAdminGroupName,\r\n permissionRowsToRecord,\r\n hasEntityPermission,\r\n} from './permission-entities';\r\nexport { seedAdministratorPermissions } from './seed-permissions';\r\nexport type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\r\nexport { createCmsMiddleware, defaultPublicApiMethods } from './middleware';\r\nexport type { CmsMiddlewareConfig } from './middleware';\r\nexport { getNextAuthOptions } from './nextauth-options';\r\nexport type { NextAuthOptionsConfig, NextAuthUser, AuthorizeOtpInput } from './nextauth-options';\r\n","import type { DataSource } from 'typeorm';\r\nimport type { EntityTarget } from 'typeorm';\r\nimport type { ObjectLiteral } from 'typeorm';\r\nimport { getPermissionableEntityKeys, ADMIN_GROUP_NAME } from './permission-entities';\r\n\r\n/**\r\n * Ensures the Administrator group has full CRUD on every permissionable entity\r\n * for the given entity map. Idempotent: upserts per entity.\r\n */\r\nexport async function seedAdministratorPermissions(\r\n dataSource: DataSource,\r\n entityMap: Record<string, unknown>\r\n): Promise<void> {\r\n const entities = getPermissionableEntityKeys(entityMap);\r\n const groupRepo = dataSource.getRepository(entityMap.user_groups as EntityTarget<ObjectLiteral>);\r\n const permRepo = dataSource.getRepository(entityMap.permissions as EntityTarget<ObjectLiteral>);\r\n\r\n const adminGroup = await groupRepo.findOne({ where: { name: ADMIN_GROUP_NAME, deleted: false } });\r\n console.log(\"ADMIN GROUP\", adminGroup);\r\n if (!adminGroup) return;\r\n\r\n const fullCrud = { canCreate: true, canRead: true, canUpdate: true, canDelete: true };\r\n\r\n for (const entity of entities) {\r\n const existing = await permRepo.findOne({\r\n where: { groupId: adminGroup.id, entity },\r\n });\r\n console.log(\"EXISTING\", existing);\r\n if (existing) {\r\n existing.canCreate = true;\r\n existing.canRead = true;\r\n existing.canUpdate = true;\r\n existing.canDelete = true;\r\n await permRepo.save(existing);\r\n } else {\r\n await permRepo.save(\r\n permRepo.create({\r\n groupId: adminGroup.id,\r\n entity,\r\n ...fullCrud,\r\n })\r\n );\r\n }\r\n }\r\n}\r\n","export interface CmsMiddlewareConfig {\r\n publicAdminPaths?: string[];\r\n publicApiPaths?: string[];\r\n /** path -> allowed methods */\r\n publicApiMethods?: Record<string, string[]>;\r\n signInPath?: string;\r\n getSessionToken?: (request: { cookies: { get: (name: string) => { value?: string } | undefined } }) => string | undefined;\r\n}\r\n\r\n/** Default public API paths (no auth). Sites should extend this with their own routes. */\r\nexport const defaultPublicApiMethods: Record<string, string[]> = {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/health': ['GET'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n};\r\n\r\nfunction defaultGetSessionToken(request: { cookies: { get: (name: string) => { value?: string } | undefined } }): string | undefined {\r\n return (\r\n request.cookies.get('__Secure-next-auth.session-token')?.value ??\r\n request.cookies.get('next-auth.session-token')?.value\r\n );\r\n}\r\n\r\nfunction isPublicMethod(pathname: string, method: string, publicApiMethods: Record<string, string[]>): boolean {\r\n for (const [endpoint, methods] of Object.entries(publicApiMethods)) {\r\n if (pathname.startsWith(endpoint) && methods.includes(method)) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Returns middleware logic. Use from Next.js middleware:\r\n * import { createCmsMiddleware } from '@infuro/cms-core';\r\n * export const middleware = createCmsMiddleware({ ... });\r\n * export const config = { matcher: ['/admin/:path*', '/api/:path*'] };\r\n */\r\nexport function createCmsMiddleware(config: CmsMiddlewareConfig = {}) {\r\n const {\r\n publicAdminPaths = ['/admin/signin', '/admin/forgot-password', '/admin/reset-password', '/admin/invite'],\r\n publicApiMethods = defaultPublicApiMethods,\r\n signInPath = '/admin/signin',\r\n getSessionToken = defaultGetSessionToken,\r\n } = config;\r\n\r\n return function cmsMiddleware(request: {\r\n nextUrl: { pathname: string };\r\n url: string;\r\n method: string;\r\n cookies: { get: (name: string) => { value?: string } | undefined };\r\n }): { type: 'next' } | { type: 'redirect'; url: string } | { type: 'json'; status: number; body: unknown } {\r\n const pathname = request.nextUrl.pathname;\r\n const method = request.method;\r\n\r\n if (publicAdminPaths.some((p) => pathname === p || pathname.startsWith(p + '/'))) {\r\n return { type: 'next' };\r\n }\r\n\r\n if (pathname.startsWith('/admin')) {\r\n const token = getSessionToken(request);\r\n if (!token) {\r\n return { type: 'redirect', url: new URL(signInPath, request.url).toString() };\r\n }\r\n }\r\n\r\n if (pathname.startsWith('/api')) {\r\n if (isPublicMethod(pathname, method, publicApiMethods)) {\r\n return { type: 'next' };\r\n }\r\n const token = getSessionToken(request);\r\n if (!token) {\r\n return { type: 'json', status: 401, body: { error: 'Unauthorized' } };\r\n }\r\n }\r\n\r\n return { type: 'next' };\r\n };\r\n}\r\n","/**\r\n * Build NextAuth options for credentials auth. App can extend/override via extend().\r\n */\r\nimport type { NextAuthOptions } from 'next-auth';\r\nimport _CredentialsProvider from 'next-auth/providers/credentials';\r\nconst CredentialsProvider = (_CredentialsProvider as unknown as { default: typeof _CredentialsProvider }).default ?? _CredentialsProvider;\r\nimport { isSuperAdminGroupName, permissionRowsToRecord } from './permission-entities';\r\nimport { vendorPortalFlagsFromUser } from './vendor-scope';\r\n\r\nexport interface NextAuthUser {\r\n id: number;\r\n email: string;\r\n name: string | null;\r\n password: string | null;\r\n blocked?: boolean;\r\n deleted?: boolean;\r\n groupId?: number | null;\r\n adminAccess?: boolean;\r\n group?: {\r\n name?: string;\r\n permissions?: Array<{\r\n entity: string;\r\n canCreate: boolean;\r\n canRead: boolean;\r\n canUpdate: boolean;\r\n canDelete: boolean;\r\n }>;\r\n };\r\n vendorIds?: number[];\r\n activeVendorId?: number | null;\r\n vendorRole?: 'owner' | 'staff' | null;\r\n}\r\n\r\nexport interface AuthorizeOtpInput {\r\n identifier: string;\r\n channel: 'email' | 'sms';\r\n code: string;\r\n}\r\n\r\nexport interface NextAuthOptionsConfig {\r\n /** Resolve user by email (e.g. from TypeORM). Return null if not found. */\r\n getUserByEmail: (email: string) => Promise<NextAuthUser | null>;\r\n comparePassword: (plain: string, hash: string) => Promise<boolean>;\r\n signInPage?: string;\r\n secret?: string;\r\n extend?: (options: NextAuthOptions) => NextAuthOptions;\r\n /** When false, password CredentialsProvider is omitted. Default true. */\r\n enablePasswordLogin?: boolean;\r\n /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */\r\n enableOtpLogin?: boolean;\r\n /** Validate OTP and return user (same shape as password flow) or null. */\r\n authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;\r\n}\r\n\r\nfunction sessionUserFromNextAuthUser(user: NextAuthUser) {\r\n const g = user.group;\r\n const isRBACAdmin = isSuperAdminGroupName(g?.name);\r\n const entityPerms = permissionRowsToRecord(g?.permissions);\r\n // Preserve the DB value as-is: true → true, false → false, null/undefined → undefined.\r\n // Coercing null to false would make requireAdminAccess deny vendor/unset users before\r\n // the vendor-portal check can allow them.\r\n const rawAdminAccess = (user as NextAuthUser & { adminAccess?: boolean }).adminAccess;\r\n const adminAccess: boolean | undefined =\r\n rawAdminAccess === true ? true : rawAdminAccess === false ? false : undefined;\r\n const vendorIds = user.vendorIds ?? [];\r\n const vendorRole = user.vendorRole ?? null;\r\n const { isVendorPortal, isVendorOwner } = vendorPortalFlagsFromUser({\r\n email: user.email,\r\n isRBACAdmin,\r\n groupName: g?.name,\r\n vendorIds,\r\n vendorRole,\r\n });\r\n return {\r\n id: user.id.toString(),\r\n email: user.email,\r\n name: user.name,\r\n groupId: user.groupId ?? undefined,\r\n groupName: g?.name ?? undefined,\r\n isRBACAdmin,\r\n entityPerms,\r\n adminAccess,\r\n vendorIds,\r\n activeVendorId: user.activeVendorId ?? vendorIds[0] ?? null,\r\n vendorRole,\r\n isVendorPortal,\r\n isVendorOwner,\r\n };\r\n}\r\n\r\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\r\n const {\r\n getUserByEmail,\r\n comparePassword,\r\n signInPage = '/admin/signin',\r\n secret,\r\n extend,\r\n enablePasswordLogin = true,\r\n enableOtpLogin = false,\r\n authorizeOtp,\r\n } = config;\r\n\r\n const providers: NextAuthOptions['providers'] = [];\r\n\r\n if (enablePasswordLogin) {\r\n providers.push(\r\n CredentialsProvider({\r\n name: 'credentials',\r\n credentials: {\r\n email: { label: 'Email', type: 'email' },\r\n password: { label: 'Password', type: 'password' },\r\n },\r\n async authorize(credentials) {\r\n if (!credentials?.email || !credentials?.password) return null;\r\n try {\r\n const user = await getUserByEmail(credentials.email);\r\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted || !user.password) return null;\r\n const valid = await comparePassword(credentials.password, user.password);\r\n if (!valid) return null;\r\n return sessionUserFromNextAuthUser(user);\r\n } catch (err) {\r\n // Log so infrastructure errors (DB down, missing entity, etc.) are\r\n // visible in server logs rather than silently failing as \"wrong password\".\r\n console.error('[cms-auth] authorize error (credentials):', err instanceof Error ? err.message : err);\r\n return null;\r\n }\r\n },\r\n })\r\n );\r\n }\r\n\r\n if (enableOtpLogin && authorizeOtp) {\r\n providers.push(\r\n CredentialsProvider({\r\n id: 'otp',\r\n name: 'otp',\r\n credentials: {\r\n identifier: { label: 'Email or phone', type: 'text' },\r\n code: { label: 'Code', type: 'text' },\r\n channel: { label: 'Channel', type: 'text' },\r\n },\r\n async authorize(credentials) {\r\n const identifier = typeof credentials?.identifier === 'string' ? credentials.identifier.trim() : '';\r\n const code = typeof credentials?.code === 'string' ? credentials.code.trim() : '';\r\n const ch = credentials?.channel === 'sms' ? 'sms' : 'email';\r\n if (!identifier || !code) return null;\r\n try {\r\n const user = await authorizeOtp({ identifier, channel: ch, code });\r\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted) return null;\r\n return sessionUserFromNextAuthUser(user);\r\n } catch (err) {\r\n console.error('[cms-auth] authorize error (otp):', err instanceof Error ? err.message : err);\r\n return null;\r\n }\r\n },\r\n })\r\n );\r\n }\r\n\r\n const options: NextAuthOptions = {\r\n secret: secret ?? process.env.NEXTAUTH_SECRET,\r\n providers,\r\n session: { strategy: 'jwt' },\r\n pages: { signIn: signInPage },\r\n cookies: {\r\n sessionToken: {\r\n name: process.env.NEXTAUTH_URL?.startsWith('https')\r\n ? '__Secure-next-auth.session-token'\r\n : 'next-auth.session-token',\r\n options: {\r\n httpOnly: true,\r\n sameSite: 'lax',\r\n path: '/',\r\n secure: process.env.NEXTAUTH_URL?.startsWith('https') ?? false,\r\n },\r\n },\r\n },\r\n callbacks: {\r\n async jwt({ token, user, trigger, session }) {\r\n if (user) {\r\n const u = user as unknown as Record<string, unknown>;\r\n (token as Record<string, unknown>).id = u.id;\r\n (token as Record<string, unknown>).groupId = u.groupId;\r\n (token as Record<string, unknown>).groupName = u.groupName;\r\n (token as Record<string, unknown>).isRBACAdmin = u.isRBACAdmin;\r\n (token as Record<string, unknown>).entityPerms = u.entityPerms;\r\n (token as Record<string, unknown>).adminAccess = u.adminAccess;\r\n (token as Record<string, unknown>).vendorIds = u.vendorIds;\r\n (token as Record<string, unknown>).activeVendorId = u.activeVendorId;\r\n (token as Record<string, unknown>).vendorRole = u.vendorRole;\r\n (token as Record<string, unknown>).isVendorPortal = u.isVendorPortal;\r\n (token as Record<string, unknown>).isVendorOwner = u.isVendorOwner;\r\n }\r\n if (trigger === 'update' && session && typeof session === 'object') {\r\n const s = session as Record<string, unknown>;\r\n const t = token as Record<string, unknown>;\r\n if (typeof s.name === 'string') t.name = s.name;\r\n if (typeof s.email === 'string') t.email = s.email;\r\n if (typeof s.activeVendorId === 'number') t.activeVendorId = s.activeVendorId;\r\n }\r\n return token;\r\n },\r\n async session({ session, token }) {\r\n if (session.user) {\r\n const t = token as Record<string, unknown>;\r\n if (typeof t.name === 'string') session.user.name = t.name;\r\n if (typeof t.email === 'string') session.user.email = t.email;\r\n (session.user as Record<string, unknown>).id = t.id;\r\n (session.user as Record<string, unknown>).groupId = t.groupId;\r\n (session.user as Record<string, unknown>).groupName = t.groupName;\r\n (session.user as Record<string, unknown>).isRBACAdmin = t.isRBACAdmin;\r\n (session.user as Record<string, unknown>).entityPerms = t.entityPerms;\r\n (session.user as Record<string, unknown>).adminAccess = t.adminAccess;\r\n (session.user as Record<string, unknown>).vendorIds = t.vendorIds;\r\n (session.user as Record<string, unknown>).activeVendorId = t.activeVendorId;\r\n (session.user as Record<string, unknown>).vendorRole = t.vendorRole;\r\n (session.user as Record<string, unknown>).isVendorPortal = t.isVendorPortal;\r\n (session.user as Record<string, unknown>).isVendorOwner = t.isVendorOwner;\r\n }\r\n return session;\r\n },\r\n },\r\n };\r\n\r\n return extend ? extend(options) : options;\r\n}\r\n"],"mappings":";;;;;;;;;;;AAkCO,SAAS,sBAAsB,MAA0C;AAC9E,SAAO,SAAS;AAClB;AAMO,SAAS,4BAA4B,WAA8C;AACxF,QAAM,UAAU,OAAO,KAAK,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,mCAAmC,IAAI,CAAC,CAAC;AAC/F,QAAM,UAAU,4BAA4B,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;AAC9E,SAAO,CAAC,GAAG,QAAQ,KAAK,GAAG,GAAG,OAAO,EAAE,OAAO,CAAC,GAAG,GAAG,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;AAC/E;AAEO,SAAS,uBACd,MACuC;AACvC,QAAM,MAA6C,CAAC;AACpD,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,MAAM,IAAI;AAAA,MACd,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,oBACd,QACA,QACA,QACS;AACT,QAAM,IAAI,SAAS,MAAM;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,WAAW,SAAU,QAAO,EAAE;AAClC,MAAI,WAAW,OAAQ,QAAO,EAAE;AAChC,MAAI,WAAW,SAAU,QAAO,EAAE;AAClC,SAAO,EAAE;AACX;AA3EA,IACa,oCAkBA,6BAYA;AA/Bb;AAAA;AAAA;AACO,IAAM,qCAAqC,oBAAI,IAAI;AAAA,MACxD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAGM,IAAM,8BAA8B;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGO,IAAM,mBAAmB;AAAA;AAAA;;;ACIzB,SAAS,kBAAkB,MAA0C;AAC1E,MAAI,CAAC,MAAM,KAAK,EAAG,QAAO;AAC1B,QAAM,IAAI,KAAK,KAAK,EAAE,YAAY;AAClC,SAAO,MAAM,wBAAwB,YAAY,KAAK,EAAE,SAAS,QAAQ;AAC3E;AAEO,SAAS,wBAAwB,MAA+C;AACrF,MAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAO,CAAC,EAAE,KAAK,eAAe,sBAAsB,KAAK,SAAS;AACpE;AAGO,SAAS,mBAAmB,MAA+C;AAChF,MAAI,MAAM,mBAAmB,KAAM,QAAO;AAC1C,MAAI,CAAC,MAAM,SAAS,wBAAwB,IAAI,EAAG,QAAO;AAC1D,OAAK,KAAK,WAAW,UAAU,KAAK,EAAG,QAAO;AAC9C,SAAO,kBAAkB,KAAK,SAAS;AACzC;AAEO,SAAS,cAAc,MAA+C;AAC3E,SAAO,mBAAmB,IAAI;AAChC;AAGO,SAAS,cAAc,MAA+C;AAC3E,MAAI,MAAM,kBAAkB,KAAM,QAAO;AACzC,MAAI,CAAC,MAAM,SAAS,wBAAwB,IAAI,EAAG,QAAO;AAC1D,MAAI,KAAK,eAAe,QAAS,QAAO;AACxC,MAAI,KAAK,eAAe,QAAS,QAAO;AACxC,MAAI,CAAC,mBAAmB,IAAI,EAAG,QAAO;AACtC,SAAO,KAAK,WAAW,KAAK,EAAE,YAAY,MAAM,wBAAwB,YAAY;AACtF;AAEO,SAAS,oBAAoB,MAA+C;AACjF,SAAO,wBAAwB,IAAI,KAAK,cAAc,IAAI;AAC5D;AAEO,SAAS,qBAAqB,MAA+C;AAClF,SAAO,wBAAwB,IAAI,KAAK,cAAc,IAAI;AAC5D;AAGO,SAAS,kBAAkB,MAA+C;AAC/E,SAAO,wBAAwB,IAAI;AACrC;AAEO,SAAS,kCAAkC,MAAmD;AACnG,MAAI,CAAC,MAAM,MAAO,QAAO,EAAE,MAAM,OAAO;AACxC,MAAI,wBAAwB,IAAI,EAAG,QAAO,EAAE,MAAM,MAAM;AACxD,QAAM,YAAY,KAAK,aAAa,CAAC;AACrC,MAAI,UAAU,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAClD,QAAM,YAAY,KAAK,kBAAkB,UAAU,CAAC;AACpD,QAAM,WAAW,UAAU,SAAS,SAAS,IAAI,YAAY,UAAU,CAAC;AACxE,SAAO,EAAE,MAAM,UAAU,UAAU,UAAU;AAC/C;AAGO,SAAS,0BAA0B,OAMc;AACtD,QAAM,YAAY,MAAM,aAAa,CAAC;AACtC,QAAM,iBACJ,CAAC,MAAM,gBACN,UAAU,SAAS,KAAK,kBAAkB,MAAM,SAAS;AAC5D,QAAMA,iBACJ,kBACA,MAAM,eAAe,YACpB,MAAM,eAAe,WACpB,MAAM,WAAW,KAAK,EAAE,YAAY,MAAM,wBAAwB,YAAY;AAClF,SAAO,EAAE,gBAAgB,eAAAA,eAAc;AACzC;AA7GA,IAIa,8BAiBA,4BAWA;AAhCb;AAAA;AAAA;AACA;AAGO,IAAM,+BAA+B,oBAAI,IAAI;AAAA,MAClD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAMM,IAAM,6BAA6B,oBAAI,IAAY;AAAA,MACxD,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AAOM,IAAM,0BAA0B;AAAA;AAAA;;;AChCvC;AAAA;AAAA;AAAA;AAKA,eAAsB,yBACpB,YACA,QAKC;AACD,QAAM,OAAQ,MAAM,WAAW;AAAA,IAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,CAAC,MAAM;AAAA,EACT;AAUA,QAAM,UAAU,KAAK,CAAC,GAAG,WAAW;AACpC,QAAM,YAAY,KAAK,CAAC,GAAG,aAAa;AACxC,QAAM,WAAW,KACd,OAAO,CAAC,MAAM,EAAE,MAAM,EACtB,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,CAAC,CAAC,EAAE;AAAA,IACf,SAAS,CAAC,CAAC,EAAE;AAAA,IACb,WAAW,CAAC,CAAC,EAAE;AAAA,IACf,WAAW,CAAC,CAAC,EAAE;AAAA,EACjB,EAAE;AAEJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,qBAAqB,uBAAuB,QAAQ,CAAC;AAAA,EACtE;AACF;AAlDA;AAAA;AAAA;AAAA;AAEA;AAAA;AAAA;;;ACWO,SAAS,qBAA8B;AAC5C,QAAM,IAAI,QAAQ,IAAI,gBAAgB,KAAK,EAAE,YAAY;AACzD,MAAI,MAAM,OAAO,MAAM,UAAU,MAAM,MAAO,QAAO;AACrD,MAAI,MAAM,OAAO,MAAM,WAAW,MAAM,KAAM,QAAO;AACrD,SAAO,QAAQ,IAAI,aAAa;AAClC;AAEO,SAAS,QAAQ,SAAiB,MAAsC;AAC7E,MAAI,CAAC,mBAAmB,EAAG;AAC3B,MAAI,KAAM,SAAQ,KAAK,YAAY,SAAS,IAAI;AAAA,MAC3C,SAAQ,KAAK,YAAY,OAAO;AACvC;AAEO,SAAS,qBACd,aACoE;AACpE,QAAM,MAA0E,CAAC;AACjF,MAAI,CAAC,YAAa,QAAO;AACzB,aAAW,CAAC,QAAQ,CAAC,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,QAAI,MAAM,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE;AAAA,EACjD;AACA,SAAO;AACT;AAQA,SAAS,2BACP,MACA,QACA,QACe;AACf,MAAI,CAAC,mBAAmB,IAAI,EAAG,QAAO;AACtC,MAAI,WAAW,eAAe,WAAW,OAAQ,QAAO;AACxD,MAAI,WAAW,eAAe,WAAW,OAAQ,QAAO;AACxD,MAAI,WAAW,cAAc,WAAW,OAAQ,QAAO;AACvD,MAAI,2BAA2B,IAAI,MAAM,KAAK,WAAW,OAAQ,QAAO;AACxE,SAAO;AACT;AAGO,SAAS,2BACd,MACA,QACA,QACyB;AACzB,QAAM,eAAwC;AAAA,IAC5C,OAAO,MAAM,SAAS;AAAA,IACtB,IAAI,MAAM,MAAM;AAAA,IAChB,SAAS,MAAM,WAAW;AAAA,IAC1B,WAAW,MAAM,aAAa;AAAA,IAC9B,aAAa,MAAM,eAAe;AAAA,IAClC,aAAa,MAAM,eAAe;AAAA,IAClC,WAAW,MAAM,aAAa,CAAC;AAAA,IAC/B,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,YAAY,MAAM,cAAc;AAAA,IAChC,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,eAAe,MAAM,iBAAiB;AAAA,IACtC,wBAAwB,mBAAmB,IAAI;AAAA,IAC/C,uBAAuB,cAAc,IAAI;AAAA,IACzC,gBAAgB,qBAAqB,MAAM,WAAW;AAAA,IACtD,wBAAwB,MAAM,cAAc,MAAM,KAAK;AAAA,EACzD;AAEA,MAAI,CAAC,MAAM,OAAO;AAChB,WAAO,EAAE,SAAS,OAAO,QAAQ,oBAAoB,aAAa;AAAA,EACpE;AACA,MAAI,KAAK,aAAa;AACpB,WAAO,EAAE,SAAS,MAAM,QAAQ,0BAA0B,aAAa;AAAA,EACzE;AAEA,QAAM,YAAY,KAAK,aAAa,CAAC;AACrC,QAAM,UAAU,KAAK,eAAe;AAEpC,MAAI,UAAU,SAAS,GAAG;AACxB,QAAI,WAAW,eAAe,WAAW,QAAQ;AAC/C,aAAO,EAAE,SAAS,MAAM,QAAQ,8BAA8B,aAAa;AAAA,IAC7E;AACA,QAAI,WAAW,eAAe,WAAW,QAAQ;AAC/C,aAAO,EAAE,SAAS,MAAM,QAAQ,8BAA8B,aAAa;AAAA,IAC7E;AACA,QAAI,WAAW,YAAY;AACzB,UAAI,WAAW,QAAQ;AACrB,eAAO,EAAE,SAAS,MAAM,QAAQ,6BAA6B,aAAa;AAAA,MAC5E;AACA,UAAI,WAAW,YAAY,CAAC,SAAS;AACnC,eAAO,EAAE,SAAS,MAAM,QAAQ,+BAA+B,aAAa;AAAA,MAC9E;AAAA,IACF;AACA,QAAI,2BAA2B,IAAI,MAAM,GAAG;AAC1C,UAAI,WAAW,WAAW,QAAQ;AAChC,eAAO,EAAE,SAAS,MAAM,QAAQ,2BAA2B,aAAa;AAAA,MAC1E;AACA,UAAI,CAAC,SAAS;AACZ,eAAO,EAAE,SAAS,MAAM,QAAQ,0BAA0B,aAAa;AAAA,MACzE;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,mBAAmB,IAAI,KAAK,cAAc,IAAI,GAAG;AACnD,QAAI,WAAW,eAAe,WAAW,QAAQ;AAC/C,aAAO,EAAE,SAAS,MAAM,QAAQ,gCAAgC,aAAa;AAAA,IAC/E;AACA,QAAI,WAAW,eAAe,WAAW,QAAQ;AAC/C,aAAO,EAAE,SAAS,MAAM,QAAQ,gCAAgC,aAAa;AAAA,IAC/E;AACA,QAAI,WAAW,eAAe,WAAW,UAAU,WAAW,WAAW;AACvE,aAAO,EAAE,SAAS,MAAM,QAAQ,+BAA+B,aAAa;AAAA,IAC9E;AACA,QAAI,2BAA2B,IAAI,MAAM,GAAG;AAC1C,aAAO,EAAE,SAAS,MAAM,QAAQ,4BAA4B,aAAa;AAAA,IAC3E;AAAA,EACF;AAEA,QAAM,qBAAqB,2BAA2B,MAAM,QAAQ,MAAM;AAC1E,MAAI,oBAAoB;AACtB,WAAO,EAAE,SAAS,MAAM,QAAQ,oBAAoB,aAAa;AAAA,EACnE;AAEA,QAAM,WAAW,KAAK,cAAc,MAAM;AAC1C,MAAI,aAAa,QAAW;AAC1B,UAAMC,WAAU,oBAAoB,KAAK,aAAa,QAAQ,MAAM;AACpE,WAAO;AAAA,MACL,SAAAA;AAAA,MACA,QAAQA,WAAU,2BAA2B;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,oBAAoB,KAAK,aAAa,QAAQ,MAAM;AACpE,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,UAAU,gCAAgC;AAAA,IAClD;AAAA,EACF;AACF;AAUA,eAAsB,wBACpB,YACA,SACA,MACA,QACA,QACA,OACe;AACf,MAAI,CAAC,mBAAmB,EAAG;AAE3B,QAAM,cAAc,2BAA2B,MAAM,QAAQ,MAAM;AACnE,MAAI;AACJ,MAAI,cAAc,MAAM,IAAI;AAC1B,UAAM,KAAK,OAAO,KAAK,EAAE;AACzB,QAAI,OAAO,SAAS,EAAE,GAAG;AACvB,UAAI;AACF,cAAM,EAAE,0BAAAC,0BAAyB,IAAI,MAAM;AAC3C,wBAAgB,MAAMA,0BAAyB,YAAY,EAAE;AAAA,MAC/D,SAAS,GAAG;AACV,wBAAgB,EAAE,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,SAAS;AAAA,IACf;AAAA,IACA;AAAA,IACA,SAAS,YAAY;AAAA,IACrB,QAAQ,YAAY;AAAA,IACpB,GAAG,YAAY;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EACL,CAAC;AACH;AAvMA,IAKM;AALN;AAAA;AAAA;AACA;AAEA;AAEA,IAAM,aAAa;AAAA;AAAA;;;ACJnB;AACA;AAMO,IAAM,2BAA2B,oBAAI,IAAI,CAAC,SAAS,eAAe,aAAa,CAAC;AA4BhF,SAAS,uBACd,MACA,QACA,QACS;AACT,SAAO,2BAA2B,MAAM,QAAQ,MAAM,EAAE;AAC1D;AAEO,SAAS,eAAe,MAA+C;AAC5E,SAAO,CAAC,EAAE,MAAM,SAAS,KAAK;AAChC;AAIO,IAAM,iBAAkD;AAAA,EAC7D,EAAE,iBAAiB,CAAC,MAAM,EAAE;AAAA,EAC5B,EAAE,yBAAyB,CAAC,MAAM,EAAE;AAAA,EACpC,EAAE,cAAc,CAAC,KAAK,EAAE;AAC1B;AAEO,IAAM,gCAA0D,CAAC;AAEjE,SAAS,eAAe,UAA2B;AACxD,SAAO,eAAe,KAAK,CAAC,aAAa,SAAS,WAAW,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC;AACxF;AAEO,SAAS,sBAAsB,UAAmC;AACvE,SAAO;AACT;AAEO,SAAS,eAAe,UAAkB,QAAyB;AACxE,aAAW,YAAY,gBAAgB;AACrC,UAAM,MAAM,OAAO,KAAK,QAAQ,EAAE,CAAC;AACnC,QAAI,SAAS,WAAW,GAAG,KAAK,SAAS,GAAG,EAAE,SAAS,MAAM,EAAG,QAAO;AAAA,EACzE;AACA,SAAO;AACT;AAUO,SAAS,kBAAkB,YAAwB,cAA8F;AACtJ,SAAO,gCAAgC,YAAY,YAAY;AACjE;AAGO,SAAS,oBACd,YACA,cACA;AACA,QAAM,OAAO,kBAAkB,YAAY,YAAY;AACvD,SAAO;AAAA,IACL,aAAa,KAAK;AAAA,IAClB,oBAAoB,KAAK;AAAA,IACzB,yBAAyB,KAAK;AAAA,IAC9B,gBAAgB,KAAK;AAAA,IACrB,sBAAsB,KAAK;AAAA,EAC7B;AACF;AAEA,SAAS,gCACP,YACA,cACa;AACb,SAAO;AAAA,IACL,MAAM,cAAc;AAClB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,oBAAoB;AACxB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,wBAAwB,MAAe,QAAgB,QAA0B;AACrF,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,YAAM,IAAI,QAAQ;AAClB,YAAM,UAAU,uBAAuB,GAAG,QAAQ,MAAM;AACxD,YAAM,EAAE,OAAO,IAAI,2BAA2B,GAAG,QAAQ,MAAM;AAC/D,cAAQ,uCAAuC;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,EAAE;AAAA,QACT,WAAW,EAAE;AAAA,QACb,WAAW,EAAE;AAAA,QACb,gBAAgB,EAAE;AAAA,QAClB,eAAe,EAAE;AAAA,MACnB,CAAC;AACD,UAAI,QAAS,QAAO;AACpB,aAAO,aAAa,KAAK,EAAE,OAAO,aAAa,QAAQ,QAAQ,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1F;AAAA,IACA,MAAM,qBAAqB;AACzB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,YAAM,IAAI,QAAQ;AAClB,UAAI,EAAE,YAAa,QAAO;AAI1B,UAAI,mBAAmB,CAAC,EAAG,QAAO;AAClC,UAAI,EAAE,gBAAgB,KAAM,QAAO;AACnC,UAAI,EAAE,gBAAgB,OAAO;AAC3B,gBAAQ,iDAAiD;AAAA,UACvD,OAAO,EAAE;AAAA,UACT,WAAW,EAAE;AAAA,UACb,gBAAgB,EAAE;AAAA,QACpB,CAAC;AACD,eAAO,aAAa,KAAK,EAAE,OAAO,aAAa,QAAQ,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC1F;AACA,cAAQ,gDAAgD;AAAA,QACtD,OAAO,EAAE;AAAA,QACT,aAAa,EAAE;AAAA,QACf,WAAW,EAAE;AAAA,QACb,gBAAgB,EAAE;AAAA,MACpB,CAAC;AACD,aAAO,aAAa,KAAK,EAAE,OAAO,aAAa,QAAQ,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1F;AAAA,IACA,MAAM,uBAAuB;AAC3B,YAAM,UAAU,MAAM,WAAW;AACjC,aAAQ,SAAS,QAAwB;AAAA,IAC3C;AAAA,EACF;AACF;;;ACpKA;AAOA;AAeA;;;AC9BA;AAMA,eAAsB,6BACpB,YACA,WACe;AACf,QAAM,WAAW,4BAA4B,SAAS;AACtD,QAAM,YAAY,WAAW,cAAc,UAAU,WAA0C;AAC/F,QAAM,WAAW,WAAW,cAAc,UAAU,WAA0C;AAE9F,QAAM,aAAa,MAAM,UAAU,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,MAAM,EAAE,CAAC;AAChG,UAAQ,IAAI,eAAe,UAAU;AACrC,MAAI,CAAC,WAAY;AAEjB,QAAM,WAAW,EAAE,WAAW,MAAM,SAAS,MAAM,WAAW,MAAM,WAAW,KAAK;AAEpF,aAAW,UAAU,UAAU;AAC7B,UAAM,WAAW,MAAM,SAAS,QAAQ;AAAA,MACtC,OAAO,EAAE,SAAS,WAAW,IAAI,OAAO;AAAA,IAC1C,CAAC;AACD,YAAQ,IAAI,YAAY,QAAQ;AAChC,QAAI,UAAU;AACZ,eAAS,YAAY;AACrB,eAAS,UAAU;AACnB,eAAS,YAAY;AACrB,eAAS,YAAY;AACrB,YAAM,SAAS,KAAK,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,SAAS;AAAA,QACb,SAAS,OAAO;AAAA,UACd,SAAS,WAAW;AAAA,UACpB;AAAA,UACA,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;;;AClCO,IAAM,0BAAoD;AAAA,EAC/D,iBAAiB,CAAC,MAAM;AAAA,EACxB,yBAAyB,CAAC,MAAM;AAAA,EAChC,cAAc,CAAC,KAAK;AAAA,EACpB,cAAc,CAAC,KAAK;AAAA,EACpB,aAAa,CAAC,OAAO,MAAM;AAAA,EAC3B,eAAe,CAAC,KAAK;AAAA,EACrB,8BAA8B,CAAC,MAAM;AAAA,EACrC,2BAA2B,CAAC,MAAM;AAAA,EAClC,qBAAqB,CAAC,MAAM;AAC9B;AAEA,SAAS,uBAAuB,SAAqG;AACnI,SACE,QAAQ,QAAQ,IAAI,kCAAkC,GAAG,SACzD,QAAQ,QAAQ,IAAI,yBAAyB,GAAG;AAEpD;AAEA,SAASC,gBAAe,UAAkB,QAAgB,kBAAqD;AAC7G,aAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAClE,QAAI,SAAS,WAAW,QAAQ,KAAK,QAAQ,SAAS,MAAM,EAAG,QAAO;AAAA,EACxE;AACA,SAAO;AACT;AAQO,SAAS,oBAAoB,SAA8B,CAAC,GAAG;AACpE,QAAM;AAAA,IACJ,mBAAmB,CAAC,iBAAiB,0BAA0B,yBAAyB,eAAe;AAAA,IACvG,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,kBAAkB;AAAA,EACpB,IAAI;AAEJ,SAAO,SAAS,cAAc,SAK6E;AACzG,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,SAAS,QAAQ;AAEvB,QAAI,iBAAiB,KAAK,CAAC,MAAM,aAAa,KAAK,SAAS,WAAW,IAAI,GAAG,CAAC,GAAG;AAChF,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AAEA,QAAI,SAAS,WAAW,QAAQ,GAAG;AACjC,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,YAAY,KAAK,IAAI,IAAI,YAAY,QAAQ,GAAG,EAAE,SAAS,EAAE;AAAA,MAC9E;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,MAAM,GAAG;AAC/B,UAAIA,gBAAe,UAAU,QAAQ,gBAAgB,GAAG;AACtD,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB;AACA,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,QAAQ,QAAQ,KAAK,MAAM,EAAE,OAAO,eAAe,EAAE;AAAA,MACtE;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AACF;;;AC5EA;AACA;AAHA,OAAO,0BAA0B;AACjC,IAAM,sBAAuB,qBAA6E,WAAW;AAiDrH,SAAS,4BAA4B,MAAoB;AACvD,QAAM,IAAI,KAAK;AACf,QAAM,cAAc,sBAAsB,GAAG,IAAI;AACjD,QAAM,cAAc,uBAAuB,GAAG,WAAW;AAIzD,QAAM,iBAAkB,KAAkD;AAC1E,QAAM,cACJ,mBAAmB,OAAO,OAAO,mBAAmB,QAAQ,QAAQ;AACtE,QAAM,YAAY,KAAK,aAAa,CAAC;AACrC,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,EAAE,gBAAgB,eAAAC,eAAc,IAAI,0BAA0B;AAAA,IAClE,OAAO,KAAK;AAAA,IACZ;AAAA,IACA,WAAW,GAAG;AAAA,IACd;AAAA,IACA;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL,IAAI,KAAK,GAAG,SAAS;AAAA,IACrB,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,SAAS,KAAK,WAAW;AAAA,IACzB,WAAW,GAAG,QAAQ;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,KAAK,kBAAkB,UAAU,CAAC,KAAK;AAAA,IACvD;AAAA,IACA;AAAA,IACA,eAAAA;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,QAAgD;AACjF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,iBAAiB;AAAA,IACjB;AAAA,EACF,IAAI;AAEJ,QAAM,YAA0C,CAAC;AAEjD,MAAI,qBAAqB;AACvB,cAAU;AAAA,MACR,oBAAoB;AAAA,QAClB,MAAM;AAAA,QACN,aAAa;AAAA,UACX,OAAO,EAAE,OAAO,SAAS,MAAM,QAAQ;AAAA,UACvC,UAAU,EAAE,OAAO,YAAY,MAAM,WAAW;AAAA,QAClD;AAAA,QACA,MAAM,UAAU,aAAa;AAC3B,cAAI,CAAC,aAAa,SAAS,CAAC,aAAa,SAAU,QAAO;AAC1D,cAAI;AACF,kBAAM,OAAO,MAAM,eAAe,YAAY,KAAK;AACnD,gBAAI,CAAC,QAAQ,KAAK,WAAY,KAA+B,WAAW,CAAC,KAAK,SAAU,QAAO;AAC/F,kBAAM,QAAQ,MAAM,gBAAgB,YAAY,UAAU,KAAK,QAAQ;AACvE,gBAAI,CAAC,MAAO,QAAO;AACnB,mBAAO,4BAA4B,IAAI;AAAA,UACzC,SAAS,KAAK;AAGZ,oBAAQ,MAAM,6CAA6C,eAAe,QAAQ,IAAI,UAAU,GAAG;AACnG,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,kBAAkB,cAAc;AAClC,cAAU;AAAA,MACR,oBAAoB;AAAA,QAClB,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,aAAa;AAAA,UACX,YAAY,EAAE,OAAO,kBAAkB,MAAM,OAAO;AAAA,UACpD,MAAM,EAAE,OAAO,QAAQ,MAAM,OAAO;AAAA,UACpC,SAAS,EAAE,OAAO,WAAW,MAAM,OAAO;AAAA,QAC5C;AAAA,QACA,MAAM,UAAU,aAAa;AAC3B,gBAAM,aAAa,OAAO,aAAa,eAAe,WAAW,YAAY,WAAW,KAAK,IAAI;AACjG,gBAAM,OAAO,OAAO,aAAa,SAAS,WAAW,YAAY,KAAK,KAAK,IAAI;AAC/E,gBAAM,KAAK,aAAa,YAAY,QAAQ,QAAQ;AACpD,cAAI,CAAC,cAAc,CAAC,KAAM,QAAO;AACjC,cAAI;AACF,kBAAM,OAAO,MAAM,aAAa,EAAE,YAAY,SAAS,IAAI,KAAK,CAAC;AACjE,gBAAI,CAAC,QAAQ,KAAK,WAAY,KAA+B,QAAS,QAAO;AAC7E,mBAAO,4BAA4B,IAAI;AAAA,UACzC,SAAS,KAAK;AACZ,oBAAQ,MAAM,qCAAqC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAC3F,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,UAA2B;AAAA,IAC/B,QAAQ,UAAU,QAAQ,IAAI;AAAA,IAC9B;AAAA,IACA,SAAS,EAAE,UAAU,MAAM;AAAA,IAC3B,OAAO,EAAE,QAAQ,WAAW;AAAA,IAC5B,SAAS;AAAA,MACP,cAAc;AAAA,QACZ,MAAM,QAAQ,IAAI,cAAc,WAAW,OAAO,IAC9C,qCACA;AAAA,QACJ,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,QAAQ,IAAI,cAAc,WAAW,OAAO,KAAK;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,MAAM,IAAI,EAAE,OAAO,MAAM,SAAS,QAAQ,GAAG;AAC3C,YAAI,MAAM;AACR,gBAAM,IAAI;AACV,UAAC,MAAkC,KAAK,EAAE;AAC1C,UAAC,MAAkC,UAAU,EAAE;AAC/C,UAAC,MAAkC,YAAY,EAAE;AACjD,UAAC,MAAkC,cAAc,EAAE;AACnD,UAAC,MAAkC,cAAc,EAAE;AACnD,UAAC,MAAkC,cAAc,EAAE;AACnD,UAAC,MAAkC,YAAY,EAAE;AACjD,UAAC,MAAkC,iBAAiB,EAAE;AACtD,UAAC,MAAkC,aAAa,EAAE;AAClD,UAAC,MAAkC,iBAAiB,EAAE;AACtD,UAAC,MAAkC,gBAAgB,EAAE;AAAA,QACvD;AACA,YAAI,YAAY,YAAY,WAAW,OAAO,YAAY,UAAU;AAClE,gBAAM,IAAI;AACV,gBAAM,IAAI;AACV,cAAI,OAAO,EAAE,SAAS,SAAU,GAAE,OAAO,EAAE;AAC3C,cAAI,OAAO,EAAE,UAAU,SAAU,GAAE,QAAQ,EAAE;AAC7C,cAAI,OAAO,EAAE,mBAAmB,SAAU,GAAE,iBAAiB,EAAE;AAAA,QACjE;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAG;AAChC,YAAI,QAAQ,MAAM;AAChB,gBAAM,IAAI;AACV,cAAI,OAAO,EAAE,SAAS,SAAU,SAAQ,KAAK,OAAO,EAAE;AACtD,cAAI,OAAO,EAAE,UAAU,SAAU,SAAQ,KAAK,QAAQ,EAAE;AACxD,UAAC,QAAQ,KAAiC,KAAK,EAAE;AACjD,UAAC,QAAQ,KAAiC,UAAU,EAAE;AACtD,UAAC,QAAQ,KAAiC,YAAY,EAAE;AACxD,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAC1D,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAC1D,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAC1D,UAAC,QAAQ,KAAiC,YAAY,EAAE;AACxD,UAAC,QAAQ,KAAiC,iBAAiB,EAAE;AAC7D,UAAC,QAAQ,KAAiC,aAAa,EAAE;AACzD,UAAC,QAAQ,KAAiC,iBAAiB,EAAE;AAC7D,UAAC,QAAQ,KAAiC,gBAAgB,EAAE;AAAA,QAC9D;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS,OAAO,OAAO,IAAI;AACpC;","names":["isVendorOwner","allowed","loadDbPermissionsForUser","isPublicMethod","isVendorOwner"]}
package/dist/cli.cjs CHANGED
@@ -115,21 +115,22 @@ import { getServerSession } from 'next-auth';
115
115
  import { createCmsApiHandler } from '@infuro/cms-core/api';
116
116
  import { CMS_ENTITY_MAP } from '@infuro/cms-core';
117
117
  import { getDataSourceInitialized } from '@/lib/data-source';
118
- import {
119
- requireAuth,
120
- requireAdminAccess,
121
- requireEntityPermission,
122
- getAuthenticatedUser,
123
- } from '@/lib/auth-helpers';
118
+ import { createCmsAuthBundle } from '@infuro/cms-core/auth';
119
+ import { authOptions } from '@/lib/auth-options';
124
120
  import { getCms } from '@/lib/cms';
125
121
  import bcrypt from 'bcryptjs';
126
122
 
127
123
  const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
128
124
 
125
+ const auth = createCmsAuthBundle(
126
+ () => getServerSession(authOptions).then((s) => s ?? null),
127
+ NextResponse
128
+ );
129
+
129
130
  async function requireAdminApiAuth(req: Request) {
130
- const a = await requireAuth(req);
131
+ const a = await auth.requireAuth(req);
131
132
  if (a) return a;
132
- return requireAdminAccess(req);
133
+ return auth.requireAdminAccess(req);
133
134
  }
134
135
 
135
136
  let handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;
@@ -142,8 +143,9 @@ async function getHandler() {
142
143
  dataSource,
143
144
  entityMap: CMS_ENTITY_MAP,
144
145
  requireAuth: requireAdminApiAuth,
145
- requireEntityPermission,
146
- getSessionUser: getAuthenticatedUser,
146
+ requireEntityPermission: auth.requireEntityPermission,
147
+ getSessionUser: auth.getSessionUser,
148
+ getAuthenticatedUser: auth.getAuthenticatedUser,
147
149
  json: NextResponse.json.bind(NextResponse),
148
150
  getCms,
149
151
  userAuth: {
@@ -221,7 +223,7 @@ export async function DELETE(req: Request, ctx: { params: Promise<{ path?: strin
221
223
  "src/app/api/auth/[...nextauth]/route.ts": `import NextAuth from 'next-auth';
222
224
  import { getNextAuthOptions } from '@infuro/cms-core/auth';
223
225
  import { getDataSourceInitialized } from '@/lib/data-source';
224
- import { CMS_ENTITY_MAP } from '@infuro/cms-core';
226
+ import { CMS_ENTITY_MAP, enrichUserWithVendorContext } from '@infuro/cms-core';
225
227
  import bcrypt from 'bcryptjs';
226
228
 
227
229
  async function getOptions() {
@@ -229,11 +231,12 @@ async function getOptions() {
229
231
  const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);
230
232
  return getNextAuthOptions({
231
233
  getUserByEmail: async (email: string) => {
232
- return userRepo.findOne({
234
+ const user = await userRepo.findOne({
233
235
  where: { email },
234
236
  relations: ['group', 'group.permissions'],
235
237
  select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],
236
- }) as any;
238
+ });
239
+ return enrichUserWithVendorContext(dataSource, user as any);
237
240
  },
238
241
  comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),
239
242
  signInPage: '/admin/signin',
@@ -808,6 +811,10 @@ function patchPackageJson(root, dryRun2, log) {
808
811
  deps["@infuro/cms-core"] = "^1.0.6";
809
812
  changed = true;
810
813
  }
814
+ if (!deps["pg-boss"]) {
815
+ deps["pg-boss"] = "^10.0.0";
816
+ changed = true;
817
+ }
811
818
  if (!dev.tsx) {
812
819
  dev.tsx = "^4.0.0";
813
820
  changed = true;
@@ -834,7 +841,7 @@ function patchPackageJson(root, dryRun2, log) {
834
841
  async function runNpmInstall(root, log) {
835
842
  const { spawnSync } = await import("child_process");
836
843
  log(" running: npm install (deps)...");
837
- spawnSync("npm", ["install", "@infuro/cms-core", "typeorm", "reflect-metadata", "bcryptjs", "next-auth", "next-themes", "sonner"], {
844
+ spawnSync("npm", ["install", "@infuro/cms-core", "typeorm", "reflect-metadata", "bcryptjs", "next-auth", "next-themes", "sonner", "pg-boss"], {
838
845
  cwd: root,
839
846
  stdio: "inherit",
840
847
  shell: true
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n blogGeneratorPlugin,\r\n pgBossPlugin,\r\n socialMediaPlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n blogGeneratorPlugin(),\r\n pgBossPlugin(),\r\n socialMediaPlugin(),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport {\r\n requireAuth,\r\n requireAdminAccess,\r\n requireEntityPermission,\r\n getAuthenticatedUser,\r\n} from '@/lib/auth-helpers';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await requireAuth(req);\r\n if (a) return a;\r\n return requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission,\r\n getSessionUser: getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n return userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n }) as any;\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:run / migration:revert).\r\n * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\n\r\nconst root = path.resolve(__dirname, '..');\r\nconst coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nfunction migrationGlobs() {\r\n const globs = [path.join(root, 'src', 'migrations', '*.ts')];\r\n const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');\r\n if (fs.existsSync(coreDist)) {\r\n globs.push(path.join(coreDist, '*.js'));\r\n globs.push(path.join(coreDist, '*.ts'));\r\n }\r\n return globs;\r\n}\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: migrationGlobs(),\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', 'rss-parser', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["path","fs","force","dryRun"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n blogGeneratorPlugin,\r\n pgBossPlugin,\r\n socialMediaPlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n blogGeneratorPlugin(),\r\n pgBossPlugin(),\r\n socialMediaPlugin(),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { createCmsAuthBundle } from '@infuro/cms-core/auth';\r\nimport { authOptions } from '@/lib/auth-options';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nconst auth = createCmsAuthBundle(\r\n () => getServerSession(authOptions).then((s) => s ?? null),\r\n NextResponse\r\n);\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await auth.requireAuth(req);\r\n if (a) return a;\r\n return auth.requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission: auth.requireEntityPermission,\r\n getSessionUser: auth.getSessionUser,\r\n getAuthenticatedUser: auth.getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP, enrichUserWithVendorContext } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n const user = await userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n });\r\n return enrichUserWithVendorContext(dataSource, user as any);\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:run / migration:revert).\r\n * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\n\r\nconst root = path.resolve(__dirname, '..');\r\nconst coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nfunction migrationGlobs() {\r\n const globs = [path.join(root, 'src', 'migrations', '*.ts')];\r\n const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');\r\n if (fs.existsSync(coreDist)) {\r\n globs.push(path.join(coreDist, '*.js'));\r\n globs.push(path.join(coreDist, '*.ts'));\r\n }\r\n return globs;\r\n}\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: migrationGlobs(),\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', 'rss-parser', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!deps['pg-boss']) {\r\n deps['pg-boss'] = '^10.0.0';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner', 'pg-boss'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["path","fs","force","dryRun"]}
package/dist/cli.js CHANGED
@@ -92,21 +92,22 @@ import { getServerSession } from 'next-auth';
92
92
  import { createCmsApiHandler } from '@infuro/cms-core/api';
93
93
  import { CMS_ENTITY_MAP } from '@infuro/cms-core';
94
94
  import { getDataSourceInitialized } from '@/lib/data-source';
95
- import {
96
- requireAuth,
97
- requireAdminAccess,
98
- requireEntityPermission,
99
- getAuthenticatedUser,
100
- } from '@/lib/auth-helpers';
95
+ import { createCmsAuthBundle } from '@infuro/cms-core/auth';
96
+ import { authOptions } from '@/lib/auth-options';
101
97
  import { getCms } from '@/lib/cms';
102
98
  import bcrypt from 'bcryptjs';
103
99
 
104
100
  const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
105
101
 
102
+ const auth = createCmsAuthBundle(
103
+ () => getServerSession(authOptions).then((s) => s ?? null),
104
+ NextResponse
105
+ );
106
+
106
107
  async function requireAdminApiAuth(req: Request) {
107
- const a = await requireAuth(req);
108
+ const a = await auth.requireAuth(req);
108
109
  if (a) return a;
109
- return requireAdminAccess(req);
110
+ return auth.requireAdminAccess(req);
110
111
  }
111
112
 
112
113
  let handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;
@@ -119,8 +120,9 @@ async function getHandler() {
119
120
  dataSource,
120
121
  entityMap: CMS_ENTITY_MAP,
121
122
  requireAuth: requireAdminApiAuth,
122
- requireEntityPermission,
123
- getSessionUser: getAuthenticatedUser,
123
+ requireEntityPermission: auth.requireEntityPermission,
124
+ getSessionUser: auth.getSessionUser,
125
+ getAuthenticatedUser: auth.getAuthenticatedUser,
124
126
  json: NextResponse.json.bind(NextResponse),
125
127
  getCms,
126
128
  userAuth: {
@@ -198,7 +200,7 @@ export async function DELETE(req: Request, ctx: { params: Promise<{ path?: strin
198
200
  "src/app/api/auth/[...nextauth]/route.ts": `import NextAuth from 'next-auth';
199
201
  import { getNextAuthOptions } from '@infuro/cms-core/auth';
200
202
  import { getDataSourceInitialized } from '@/lib/data-source';
201
- import { CMS_ENTITY_MAP } from '@infuro/cms-core';
203
+ import { CMS_ENTITY_MAP, enrichUserWithVendorContext } from '@infuro/cms-core';
202
204
  import bcrypt from 'bcryptjs';
203
205
 
204
206
  async function getOptions() {
@@ -206,11 +208,12 @@ async function getOptions() {
206
208
  const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);
207
209
  return getNextAuthOptions({
208
210
  getUserByEmail: async (email: string) => {
209
- return userRepo.findOne({
211
+ const user = await userRepo.findOne({
210
212
  where: { email },
211
213
  relations: ['group', 'group.permissions'],
212
214
  select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],
213
- }) as any;
215
+ });
216
+ return enrichUserWithVendorContext(dataSource, user as any);
214
217
  },
215
218
  comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),
216
219
  signInPage: '/admin/signin',
@@ -785,6 +788,10 @@ function patchPackageJson(root, dryRun2, log) {
785
788
  deps["@infuro/cms-core"] = "^1.0.6";
786
789
  changed = true;
787
790
  }
791
+ if (!deps["pg-boss"]) {
792
+ deps["pg-boss"] = "^10.0.0";
793
+ changed = true;
794
+ }
788
795
  if (!dev.tsx) {
789
796
  dev.tsx = "^4.0.0";
790
797
  changed = true;
@@ -811,7 +818,7 @@ function patchPackageJson(root, dryRun2, log) {
811
818
  async function runNpmInstall(root, log) {
812
819
  const { spawnSync } = await import("child_process");
813
820
  log(" running: npm install (deps)...");
814
- spawnSync("npm", ["install", "@infuro/cms-core", "typeorm", "reflect-metadata", "bcryptjs", "next-auth", "next-themes", "sonner"], {
821
+ spawnSync("npm", ["install", "@infuro/cms-core", "typeorm", "reflect-metadata", "bcryptjs", "next-auth", "next-themes", "sonner", "pg-boss"], {
815
822
  cwd: root,
816
823
  stdio: "inherit",
817
824
  shell: true