@murumets-ee/create 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +158 -142
- package/dist/index.js +156 -140
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,b as
|
|
2
|
+
import{a as e,b as d,c as g,d as u,e as A}from"./chunk-ZAX4VH4C.js";import*as c from"@clack/prompts";import v from"picocolors";import*as s from"@clack/prompts";async function N(t){s.intro("@murumets-ee/create");let n=t??await s.text({message:"Project name:",placeholder:"my-project",validate:l=>{if(!l)return"Project name is required";if(!/^[a-z0-9-]+$/.test(l))return"Use lowercase letters, numbers, and hyphens only"}});if(s.isCancel(n))return s.cancel("Cancelled."),null;let r=await s.select({message:"App setup:",options:[{value:"single",label:"Single app",hint:"everything in one Next.js project"},{value:"multi",label:"Admin + Frontend",hint:"two apps in a pnpm workspace"}]});if(s.isCancel(r))return s.cancel("Cancelled."),null;let o=await s.confirm({message:"Install dependencies?",initialValue:!0});return s.isCancel(o)?(s.cancel("Cancelled."),null):{name:n,mode:r,installDeps:o}}import{join as k}from"path";import{join as m}from"path";async function T(t,n){await e(m(t,"lib/auth.ts"),`import { getToolkitApp } from './app'
|
|
3
3
|
import { getAuth } from '@murumets-ee/auth'
|
|
4
4
|
|
|
5
5
|
await getToolkitApp()
|
|
@@ -249,7 +249,7 @@ export async function createFirstAdmin(formData: FormData) {
|
|
|
249
249
|
|
|
250
250
|
return { ok: true, email: adminUser.user.email }
|
|
251
251
|
}
|
|
252
|
-
`)}import{join as
|
|
252
|
+
`)}import{join as p}from"path";async function S(t,n){let{name:r}=n;await e(p(t,".env.example"),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
|
|
253
253
|
BETTER_AUTH_SECRET=dev-secret-change-me-in-production-min-32-chars
|
|
254
254
|
BETTER_AUTH_URL=http://localhost:3000
|
|
255
255
|
LOG_LEVEL=debug
|
|
@@ -259,14 +259,94 @@ RESEND_API_KEY=
|
|
|
259
259
|
RESEND_WEBHOOK_SECRET=
|
|
260
260
|
MAIL_FROM=noreply@example.com
|
|
261
261
|
CSAT_SECRET=
|
|
262
|
-
`),await
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
`),await e(p(t,".dockerignore"),`# Dependencies (installed inside Docker)
|
|
263
|
+
node_modules/
|
|
264
|
+
.pnpm-store/
|
|
265
265
|
|
|
266
|
-
#
|
|
267
|
-
.
|
|
266
|
+
# Build outputs (rebuilt inside Docker)
|
|
267
|
+
.next/
|
|
268
|
+
out/
|
|
269
|
+
dist/
|
|
270
|
+
|
|
271
|
+
# Git
|
|
272
|
+
.git/
|
|
273
|
+
.gitignore
|
|
274
|
+
|
|
275
|
+
# Environment files (pass via docker run --env-file)
|
|
276
|
+
.env
|
|
277
|
+
.env.*
|
|
268
278
|
!.env.example
|
|
269
|
-
|
|
279
|
+
|
|
280
|
+
# Docker files
|
|
281
|
+
Dockerfile*
|
|
282
|
+
.dockerignore
|
|
283
|
+
docker-compose*.yml
|
|
284
|
+
|
|
285
|
+
# Tests
|
|
286
|
+
coverage/
|
|
287
|
+
**/*.test.*
|
|
288
|
+
**/*.spec.*
|
|
289
|
+
**/vitest.config.*
|
|
290
|
+
docker-compose.test.yml
|
|
291
|
+
|
|
292
|
+
# Development tooling
|
|
293
|
+
.vscode/
|
|
294
|
+
.idea/
|
|
295
|
+
.turbo/
|
|
296
|
+
.cache/
|
|
297
|
+
*.tsbuildinfo
|
|
298
|
+
|
|
299
|
+
# Documentation
|
|
300
|
+
*.md
|
|
301
|
+
docs/
|
|
302
|
+
|
|
303
|
+
# OS files
|
|
304
|
+
.DS_Store
|
|
305
|
+
Thumbs.db
|
|
306
|
+
`),await e(p(t,"Dockerfile"),`# --- Base image ---
|
|
307
|
+
# Node 22 LTS (Debian slim \u2014 glibc required for sharp image optimization)
|
|
308
|
+
FROM node:22-slim AS base
|
|
309
|
+
|
|
310
|
+
# --- Stage 1: Install dependencies ---
|
|
311
|
+
FROM base AS deps
|
|
312
|
+
WORKDIR /app
|
|
313
|
+
RUN corepack enable pnpm
|
|
314
|
+
COPY package.json pnpm-lock.yaml ./
|
|
315
|
+
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \\
|
|
316
|
+
pnpm install --frozen-lockfile
|
|
317
|
+
|
|
318
|
+
# --- Stage 2: Build the application ---
|
|
319
|
+
FROM base AS builder
|
|
320
|
+
WORKDIR /app
|
|
321
|
+
RUN corepack enable pnpm
|
|
322
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
323
|
+
COPY . .
|
|
324
|
+
ENV NODE_ENV=production
|
|
325
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
326
|
+
RUN pnpm build
|
|
327
|
+
|
|
328
|
+
# --- Stage 3: Production runner ---
|
|
329
|
+
FROM base AS runner
|
|
330
|
+
WORKDIR /app
|
|
331
|
+
ENV NODE_ENV=production
|
|
332
|
+
ENV PORT=3000
|
|
333
|
+
ENV HOSTNAME=0.0.0.0
|
|
334
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
335
|
+
|
|
336
|
+
# Copy standalone output (includes server.js + traced node_modules)
|
|
337
|
+
COPY --from=builder --chown=node:node /app/.next/standalone ./
|
|
338
|
+
# Copy static assets (JS/CSS chunks \u2014 excluded from standalone trace)
|
|
339
|
+
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
|
|
340
|
+
# Copy public assets (favicon, robots.txt, images)
|
|
341
|
+
COPY --from=builder --chown=node:node /app/public ./public
|
|
342
|
+
# Copy migrations (applied at app startup by the toolkit migration runner)
|
|
343
|
+
COPY --from=builder --chown=node:node /app/migrations ./migrations
|
|
344
|
+
|
|
345
|
+
# Run as non-root user
|
|
346
|
+
USER node
|
|
347
|
+
EXPOSE 3000
|
|
348
|
+
CMD ["node", "server.js"]
|
|
349
|
+
`),await e(p(t,"docker-compose.yml"),`services:
|
|
270
350
|
postgres:
|
|
271
351
|
image: postgres:17-alpine
|
|
272
352
|
container_name: ${r}-postgres
|
|
@@ -288,7 +368,49 @@ generated/
|
|
|
288
368
|
volumes:
|
|
289
369
|
postgres_data:
|
|
290
370
|
name: ${r}_postgres_data
|
|
291
|
-
`)
|
|
371
|
+
`),await e(p(t,"docker-compose.prod.yml"),`services:
|
|
372
|
+
app:
|
|
373
|
+
build: .
|
|
374
|
+
restart: unless-stopped
|
|
375
|
+
env_file: .env
|
|
376
|
+
depends_on:
|
|
377
|
+
postgres:
|
|
378
|
+
condition: service_healthy
|
|
379
|
+
ports:
|
|
380
|
+
- "3000:3000"
|
|
381
|
+
networks:
|
|
382
|
+
- internal
|
|
383
|
+
|
|
384
|
+
postgres:
|
|
385
|
+
image: postgres:17-alpine
|
|
386
|
+
restart: unless-stopped
|
|
387
|
+
environment:
|
|
388
|
+
POSTGRES_USER: ${r}
|
|
389
|
+
POSTGRES_PASSWORD: \${DB_PASSWORD}
|
|
390
|
+
POSTGRES_DB: ${r}
|
|
391
|
+
volumes:
|
|
392
|
+
- postgres_data:/var/lib/postgresql/data
|
|
393
|
+
healthcheck:
|
|
394
|
+
test: ["CMD-SHELL", "pg_isready -U ${r} -d ${r}"]
|
|
395
|
+
interval: 10s
|
|
396
|
+
timeout: 5s
|
|
397
|
+
retries: 5
|
|
398
|
+
networks:
|
|
399
|
+
- internal
|
|
400
|
+
|
|
401
|
+
volumes:
|
|
402
|
+
postgres_data:
|
|
403
|
+
|
|
404
|
+
networks:
|
|
405
|
+
internal:
|
|
406
|
+
`),await A(p(t,".gitignore"),`
|
|
407
|
+
# Toolkit generated files
|
|
408
|
+
generated/
|
|
409
|
+
|
|
410
|
+
# Environment
|
|
411
|
+
.env*
|
|
412
|
+
!.env.example
|
|
413
|
+
`)}import{join as y}from"path";async function E(t,n){await e(y(t,"entities/index.ts"),`export { Article } from './article'
|
|
292
414
|
export { Category } from './category'
|
|
293
415
|
`),await e(y(t,"entities/article.ts"),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
|
|
294
416
|
|
|
@@ -338,13 +460,13 @@ export const Category = defineEntity({
|
|
|
338
460
|
delete: 'group.admin',
|
|
339
461
|
},
|
|
340
462
|
})
|
|
341
|
-
`)}import{join as
|
|
463
|
+
`)}import{join as P}from"path";async function z(t,n){await e(P(t,"i18n/routing.ts"),`import { defineRouting } from 'next-intl/routing'
|
|
342
464
|
|
|
343
465
|
export const routing = defineRouting({
|
|
344
466
|
locales: ['en'],
|
|
345
467
|
defaultLocale: 'en',
|
|
346
468
|
})
|
|
347
|
-
`),await e(
|
|
469
|
+
`),await e(P(t,"i18n/request.ts"),`import { getRequestConfig } from 'next-intl/server'
|
|
348
470
|
import { hasLocale } from 'next-intl'
|
|
349
471
|
import { routing } from './routing'
|
|
350
472
|
import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
|
|
@@ -364,7 +486,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
364
486
|
},
|
|
365
487
|
}
|
|
366
488
|
})
|
|
367
|
-
`)}import{mkdir as
|
|
489
|
+
`)}import{mkdir as R}from"fs/promises";import{join as a}from"path";var i={myorgCore:"0.1.0",myorgDb:"0.1.0",myorgEntity:"0.1.0",myorgLogging:"0.1.0",myorgAuth:"0.1.0",myorgAuthUi:"0.1.0",myorgAdminUi:"0.1.0",myorgContent:"0.1.0",myorgContentApi:"0.1.0",myorgSettings:"0.1.0",myorgStorage:"0.1.0",myorgMedia:"0.1.0",myorgTaxonomy:"0.1.0",myorgQueue:"0.1.0",myorgMail:"0.1.0",myorgTicketing:"0.1.0",myorgTicketingUi:"0.1.0",myorgEditor:"0.1.0",myorgBlocks:"0.1.0",myorgTokens:"0.1.0",myorgUi:"0.1.0",myorgCli:"0.1.0",betterAuth:"1.4.0",drizzleOrm:"0.45.1",nextIntl:"4.8.2",nextThemes:"0.4.6",lucideReact:"0.563.0",postgres:"3.4.5",reactHookForm:"7.71.1",hookformResolvers:"5.2.2",zod:"3.24.1",drizzleKit:"0.27.2",tsx:"4.19.2",babelReactCompiler:"1.0.0"};async function C(t,n,r){await U(t,n),r?.("Workspace root created"),r?.("Creating admin app..."),await F(t,n),r?.("Admin app created"),r?.("Creating web app..."),await D(t,n),r?.("Web app created"),await I(t,n),r?.("Shared config package created")}async function U(t,n){let{name:r}=n;await R(t,{recursive:!0}),await e(a(t,"pnpm-workspace.yaml"),`packages:
|
|
368
490
|
- 'packages/*'
|
|
369
491
|
- 'apps/*'
|
|
370
492
|
|
|
@@ -703,7 +825,7 @@ async function resetDb() {
|
|
|
703
825
|
}
|
|
704
826
|
|
|
705
827
|
resetDb().catch(console.error)
|
|
706
|
-
`)}async function
|
|
828
|
+
`)}async function F(t,n){let{name:r}=n,o=a(t,"apps/admin");await R(a(t,"apps"),{recursive:!0}),u(`pnpm create next-app@${"16"} ${o} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await g(o,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await d(a(o,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"better-auth":`^${i.betterAuth}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:l}=await import("./utils-VX57GKZX.js");await l(a(o,".gitignore"),`
|
|
707
829
|
# Environment
|
|
708
830
|
.env*
|
|
709
831
|
!.env.example
|
|
@@ -1251,7 +1373,7 @@ export async function createFirstAdmin(formData: FormData) {
|
|
|
1251
1373
|
|
|
1252
1374
|
return { ok: true, email: adminUser.user.email }
|
|
1253
1375
|
}
|
|
1254
|
-
`)}async function
|
|
1376
|
+
`)}async function D(t,n){let{name:r}=n,o=a(t,"apps/web");u(`pnpm create next-app@${"16"} ${o} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await g(o,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await d(a(o,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:l}=await import("./utils-VX57GKZX.js");await l(a(o,".gitignore"),`
|
|
1255
1377
|
# Environment
|
|
1256
1378
|
.env*
|
|
1257
1379
|
!.env.example
|
|
@@ -1539,7 +1661,7 @@ export default async function HomePage() {
|
|
|
1539
1661
|
</div>
|
|
1540
1662
|
)
|
|
1541
1663
|
}
|
|
1542
|
-
`)}import{join as
|
|
1664
|
+
`)}import{join as f}from"path";async function L(t,n){let{name:r}=n;await e(f(t,"next.config.ts"),`import type { NextConfig } from 'next'
|
|
1543
1665
|
import createNextIntlPlugin from 'next-intl/plugin'
|
|
1544
1666
|
|
|
1545
1667
|
const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
|
|
@@ -1564,7 +1686,7 @@ const nextConfig: NextConfig = {
|
|
|
1564
1686
|
}
|
|
1565
1687
|
|
|
1566
1688
|
export default withNextIntl(nextConfig)
|
|
1567
|
-
`),await e(
|
|
1689
|
+
`),await e(f(t,"proxy.ts"),`import { NextRequest, NextResponse } from 'next/server'
|
|
1568
1690
|
import createMiddleware from 'next-intl/middleware'
|
|
1569
1691
|
import { routing } from './i18n/routing'
|
|
1570
1692
|
|
|
@@ -1595,12 +1717,12 @@ export function proxy(request: NextRequest) {
|
|
|
1595
1717
|
export const config = {
|
|
1596
1718
|
matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
|
|
1597
1719
|
}
|
|
1598
|
-
`),await e(
|
|
1720
|
+
`),await e(f(t,"app/layout.tsx"),`import './globals.css'
|
|
1599
1721
|
|
|
1600
1722
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
1601
1723
|
return children
|
|
1602
1724
|
}
|
|
1603
|
-
`),await e(
|
|
1725
|
+
`),await e(f(t,"app/[locale]/layout.tsx"),`import type { Metadata } from 'next'
|
|
1604
1726
|
import { Geist, Geist_Mono } from 'next/font/google'
|
|
1605
1727
|
import { NextIntlClientProvider } from 'next-intl'
|
|
1606
1728
|
import { getMessages } from 'next-intl/server'
|
|
@@ -1649,7 +1771,7 @@ export default async function LocaleLayout({
|
|
|
1649
1771
|
</html>
|
|
1650
1772
|
)
|
|
1651
1773
|
}
|
|
1652
|
-
`),await e(
|
|
1774
|
+
`),await e(f(t,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
|
|
1653
1775
|
import { Article, Category } from '@/entities'
|
|
1654
1776
|
import { getToolkitApp } from '@/lib/app'
|
|
1655
1777
|
|
|
@@ -1713,119 +1835,13 @@ export default async function HomePage() {
|
|
|
1713
1835
|
</div>
|
|
1714
1836
|
)
|
|
1715
1837
|
}
|
|
1716
|
-
`)}import{join as w}from"path";async function _(t,n){await e(w(t,"scripts/generate-schema.ts")
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
import
|
|
1723
|
-
import { generateSchemaCode, generateTranslationSchemaCode } from '@murumets-ee/entity'
|
|
1724
|
-
import config from '../toolkit.config'
|
|
1725
|
-
|
|
1726
|
-
async function generateSchemas() {
|
|
1727
|
-
console.log('Generating Drizzle schemas from entity definitions...')
|
|
1728
|
-
|
|
1729
|
-
const schemaDir = join(import.meta.dirname, '..', 'generated')
|
|
1730
|
-
await mkdir(schemaDir, { recursive: true })
|
|
1731
|
-
|
|
1732
|
-
const imports: string[] = []
|
|
1733
|
-
const schemas: string[] = []
|
|
1734
|
-
let hasTranslations = false
|
|
1735
|
-
|
|
1736
|
-
for (const entity of config.entities) {
|
|
1737
|
-
console.log(\` - Generating schema for \${entity.name}\`)
|
|
1738
|
-
|
|
1739
|
-
// Generate main table (returns TypeScript code string)
|
|
1740
|
-
const schemaCode = generateSchemaCode(entity)
|
|
1741
|
-
schemas.push(\`\\n// \${entity.name} table\`)
|
|
1742
|
-
schemas.push(schemaCode)
|
|
1743
|
-
|
|
1744
|
-
// Generate translation table if needed (returns TypeScript code string or null)
|
|
1745
|
-
const translationCode = generateTranslationSchemaCode(entity)
|
|
1746
|
-
if (translationCode) {
|
|
1747
|
-
hasTranslations = true
|
|
1748
|
-
schemas.push(\`\\n// \${entity.name} translations\`)
|
|
1749
|
-
schemas.push(translationCode)
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
// Add imports \u2014 include all pg-core types that entities might use
|
|
1754
|
-
const pgCoreTypes = ['pgTable', 'varchar', 'text', 'boolean', 'timestamp', 'integer', 'doublePrecision', 'jsonb', 'uuid', 'index']
|
|
1755
|
-
if (hasTranslations) pgCoreTypes.push('unique')
|
|
1756
|
-
const pgCoreImports = \`import { \${pgCoreTypes.join(', ')} } from 'drizzle-orm/pg-core'\\n\`
|
|
1757
|
-
imports.push(pgCoreImports)
|
|
1758
|
-
|
|
1759
|
-
const schemaFile = join(schemaDir, 'schema.ts')
|
|
1760
|
-
await writeFile(schemaFile, [...imports, ...schemas].join('\\n'))
|
|
1761
|
-
|
|
1762
|
-
console.log(\`\\nSchemas written to: \${schemaFile}\`)
|
|
1763
|
-
console.log('Run \\\`pnpm db:migrate:generate\\\` to create migrations')
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
generateSchemas().catch(console.error)
|
|
1767
|
-
`),await e(w(t,"scripts/migrate.ts"),`/**
|
|
1768
|
-
* Run pending migrations
|
|
1769
|
-
* Applies both toolkit migrations (.toolkit/) and project migrations
|
|
1770
|
-
*/
|
|
1771
|
-
|
|
1772
|
-
import { createDbClient, runMigrations } from '@murumets-ee/db'
|
|
1773
|
-
|
|
1774
|
-
async function migrate() {
|
|
1775
|
-
console.log('Running migrations...')
|
|
1776
|
-
|
|
1777
|
-
try {
|
|
1778
|
-
if (!process.env.DATABASE_URL) {
|
|
1779
|
-
throw new Error('DATABASE_URL environment variable is required')
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
const db = createDbClient({ url: process.env.DATABASE_URL })
|
|
1783
|
-
await runMigrations(db, import.meta.dirname + '/..')
|
|
1784
|
-
|
|
1785
|
-
console.log('Migrations completed successfully')
|
|
1786
|
-
process.exit(0)
|
|
1787
|
-
} catch (error) {
|
|
1788
|
-
console.error('Migration failed:', error)
|
|
1789
|
-
process.exit(1)
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
migrate()
|
|
1794
|
-
`),await e(w(t,"scripts/reset-db.ts"),`/**
|
|
1795
|
-
* Reset database (DROP all tables)
|
|
1796
|
-
* WARNING: Only for development
|
|
1797
|
-
*/
|
|
1798
|
-
|
|
1799
|
-
import postgres from 'postgres'
|
|
1800
|
-
|
|
1801
|
-
async function resetDb() {
|
|
1802
|
-
const DATABASE_URL = process.env.DATABASE_URL
|
|
1803
|
-
|
|
1804
|
-
if (!DATABASE_URL) {
|
|
1805
|
-
throw new Error('DATABASE_URL not set')
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
console.warn('WARNING: This will DROP ALL TABLES')
|
|
1809
|
-
console.log('Database:', DATABASE_URL.split('@')[1])
|
|
1810
|
-
|
|
1811
|
-
const sql = postgres(DATABASE_URL)
|
|
1812
|
-
|
|
1813
|
-
try {
|
|
1814
|
-
// Drop all tables (cascade to handle foreign keys)
|
|
1815
|
-
await sql\`
|
|
1816
|
-
DROP SCHEMA public CASCADE;
|
|
1817
|
-
CREATE SCHEMA public;
|
|
1818
|
-
GRANT ALL ON SCHEMA public TO PUBLIC;
|
|
1819
|
-
\`
|
|
1820
|
-
|
|
1821
|
-
console.log('Database reset complete')
|
|
1822
|
-
} finally {
|
|
1823
|
-
await sql.end()
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
resetDb().catch(console.error)
|
|
1828
|
-
`)}import{join as x}from"path";async function $(t,n){let{name:r}=n;await e(x(t,"app/globals.css"),`@import "tailwindcss";
|
|
1838
|
+
`)}import{join as w}from"path";async function _(t,n){await e(w(t,"scripts/generate-schema.ts"),`import { execSync } from 'node:child_process'
|
|
1839
|
+
execSync('npx @murumets-ee/cli generate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1840
|
+
`),await e(w(t,"scripts/migrate.ts"),`import { execSync } from 'node:child_process'
|
|
1841
|
+
execSync('npx @murumets-ee/cli migrate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1842
|
+
`),await e(w(t,"scripts/reset-db.ts"),`import { execSync } from 'node:child_process'
|
|
1843
|
+
execSync('npx @murumets-ee/cli reset --force', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1844
|
+
`)}import{join as b}from"path";async function $(t,n){let{name:r}=n;await e(b(t,"app/globals.css"),`@import "tailwindcss";
|
|
1829
1845
|
@source "../node_modules/@murumets-ee/auth-ui/dist";
|
|
1830
1846
|
|
|
1831
1847
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
@@ -1852,7 +1868,7 @@ body {
|
|
|
1852
1868
|
color: var(--foreground);
|
|
1853
1869
|
font-family: Arial, Helvetica, sans-serif;
|
|
1854
1870
|
}
|
|
1855
|
-
`),await e(
|
|
1871
|
+
`),await e(b(t,"app/theme-provider.tsx"),`'use client'
|
|
1856
1872
|
|
|
1857
1873
|
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
1858
1874
|
import type { ReactNode } from 'react'
|
|
@@ -1869,7 +1885,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
|
1869
1885
|
</NextThemesProvider>
|
|
1870
1886
|
)
|
|
1871
1887
|
}
|
|
1872
|
-
`),await e(
|
|
1888
|
+
`),await e(b(t,"app/theme-toggle.tsx"),`'use client'
|
|
1873
1889
|
|
|
1874
1890
|
import { useTheme } from 'next-themes'
|
|
1875
1891
|
import { useState, useEffect } from 'react'
|
|
@@ -1896,7 +1912,7 @@ export function ThemeToggle() {
|
|
|
1896
1912
|
</button>
|
|
1897
1913
|
)
|
|
1898
1914
|
}
|
|
1899
|
-
`),await e(
|
|
1915
|
+
`),await e(b(t,"app/nav-header.tsx"),`'use client'
|
|
1900
1916
|
|
|
1901
1917
|
import Link from 'next/link'
|
|
1902
1918
|
import { usePathname } from 'next/navigation'
|
|
@@ -1940,7 +1956,7 @@ export function NavHeader() {
|
|
|
1940
1956
|
</header>
|
|
1941
1957
|
)
|
|
1942
1958
|
}
|
|
1943
|
-
`)}import{join as
|
|
1959
|
+
`)}import{join as h}from"path";async function M(t,n){let{name:r}=n;await e(h(t,"toolkit.config.ts"),`import { auth } from '@murumets-ee/auth/plugin'
|
|
1944
1960
|
import { content } from '@murumets-ee/content/plugin'
|
|
1945
1961
|
import { defineConfig } from '@murumets-ee/core'
|
|
1946
1962
|
import { mail, ResendMailProvider } from '@murumets-ee/mail'
|
|
@@ -1992,7 +2008,7 @@ export default defineConfig({
|
|
|
1992
2008
|
],
|
|
1993
2009
|
projectRoot: import.meta.dirname,
|
|
1994
2010
|
})
|
|
1995
|
-
`),await e(
|
|
2011
|
+
`),await e(h(t,"lib/app.ts"),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
|
|
1996
2012
|
import config from '../toolkit.config'
|
|
1997
2013
|
|
|
1998
2014
|
let appInstance: ToolkitApp | null = null
|
|
@@ -2006,7 +2022,7 @@ export async function getToolkitApp(): Promise<ToolkitApp> {
|
|
|
2006
2022
|
}
|
|
2007
2023
|
return appInstance
|
|
2008
2024
|
}
|
|
2009
|
-
`),await e(
|
|
2025
|
+
`),await e(h(t,"drizzle.config.ts"),`import type { Config } from 'drizzle-kit'
|
|
2010
2026
|
|
|
2011
2027
|
if (!process.env.DATABASE_URL) {
|
|
2012
2028
|
throw new Error('DATABASE_URL environment variable is required')
|
|
@@ -2020,7 +2036,7 @@ export default {
|
|
|
2020
2036
|
url: process.env.DATABASE_URL,
|
|
2021
2037
|
},
|
|
2022
2038
|
} satisfies Config
|
|
2023
|
-
`),await e(
|
|
2039
|
+
`),await e(h(t,"auth.config.ts"),`import { betterAuth } from 'better-auth'
|
|
2024
2040
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
2025
2041
|
import { admin } from 'better-auth/plugins'
|
|
2026
2042
|
import { organization } from 'better-auth/plugins/organization'
|
|
@@ -2038,8 +2054,8 @@ export const auth = betterAuth({
|
|
|
2038
2054
|
organization(),
|
|
2039
2055
|
],
|
|
2040
2056
|
})
|
|
2041
|
-
`),await e(
|
|
2057
|
+
`),await e(h(t,"generated/auth-schema.ts"),`// This file is generated by better-auth CLI.
|
|
2042
2058
|
// Run: npx @better-auth/cli generate --config auth.config.ts -y
|
|
2043
2059
|
export {}
|
|
2044
|
-
`)}async function
|
|
2045
|
-
`),"Next steps"),c.outro(`${
|
|
2060
|
+
`)}async function O(t,n){t.mode==="single"?await B(t,n):await H(t,n)}async function B(t,n){let r=k(process.cwd(),t.name);n?.("Creating Next.js app..."),u(`pnpm create next-app@${"16"} ${t.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await g(r,["app/page.tsx","app/page.module.css","app/fonts","README.md"]),await d(k(r,"package.json"),{type:"module",dependencies:{"@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/db":`^${i.myorgDb}`,"@murumets-ee/entity":`^${i.myorgEntity}`,"@murumets-ee/logging":`^${i.myorgLogging}`,"@murumets-ee/auth":`^${i.myorgAuth}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"@murumets-ee/admin-ui":`^${i.myorgAdminUi}`,"@murumets-ee/content":`^${i.myorgContent}`,"@murumets-ee/settings":`^${i.myorgSettings}`,"@murumets-ee/storage":`^${i.myorgStorage}`,"@murumets-ee/media":`^${i.myorgMedia}`,"@murumets-ee/taxonomy":`^${i.myorgTaxonomy}`,"@murumets-ee/queue":`^${i.myorgQueue}`,"@murumets-ee/mail":`^${i.myorgMail}`,"@murumets-ee/ticketing":`^${i.myorgTicketing}`,"@murumets-ee/ticketing-ui":`^${i.myorgTicketingUi}`,"@murumets-ee/tokens":`^${i.myorgTokens}`,"better-auth":`^${i.betterAuth}`,"drizzle-orm":`^${i.drizzleOrm}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,postgres:`^${i.postgres}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"drizzle-kit":`^${i.drizzleKit}`,tsx:`^${i.tsx}`,"babel-plugin-react-compiler":i.babelReactCompiler},scripts:{"db:generate":"tsx --env-file=.env scripts/generate-schema.ts","db:migrate:generate":"drizzle-kit generate","db:migrate":"tsx --env-file=.env scripts/migrate.ts","db:reset":"tsx --env-file=.env scripts/reset-db.ts"}}),n?.("Adding toolkit files..."),await S(r,t),await E(r,t),await M(r,t),await T(r,t),await z(r,t),await L(r,t),await $(r,t),await _(r,t),t.installDeps&&(n?.("Installing dependencies..."),u("pnpm install",{cwd:r}))}async function H(t,n){let r=k(process.cwd(),t.name);n?.("Creating workspace..."),await C(r,t,n),t.installDeps&&(n?.("Installing dependencies..."),u("pnpm install",{cwd:r}))}async function W(){let t=process.argv[2],n=await N(t);if(!n)return;let r=c.spinner();try{r.start("Creating Next.js app..."),await O(n,q=>{r.stop(`${v.green("\u2713")} Done`),r.start(q)}),r.stop(`${v.green("\u2713")} Done`);let l=n.mode==="multi"?[`cd ${n.name}`,"docker compose up -d","cp .env.example .env","npx @better-auth/cli generate --config packages/config/auth.config.ts -y","pnpm db:generate","pnpm db:migrate:generate","pnpm db:migrate","pnpm dev","# Visit /setup to create your first admin user"]:[`cd ${n.name}`,"docker compose up -d","cp .env.example .env","npx @better-auth/cli generate --config auth.config.ts -y","pnpm db:generate","pnpm db:migrate:generate","pnpm db:migrate","pnpm dev","# Visit /setup to create your first admin user"];c.note(l.join(`
|
|
2061
|
+
`),"Next steps"),c.outro(`${v.green("Success!")} Your project is ready.`)}catch(o){r.stop(v.red("Failed.")),c.log.error(o instanceof Error?o.message:String(o)),process.exit(1)}}W();
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,b as
|
|
1
|
+
import{a as e,b as c,c as u,d as l,e as y}from"./chunk-CUB6XFPE.js";import{join as v}from"path";import{join as s}from"path";async function w(t,n){await e(s(t,"lib/auth.ts"),`import { getToolkitApp } from './app'
|
|
2
2
|
import { getAuth } from '@murumets-ee/auth'
|
|
3
3
|
|
|
4
4
|
await getToolkitApp()
|
|
@@ -248,7 +248,7 @@ export async function createFirstAdmin(formData: FormData) {
|
|
|
248
248
|
|
|
249
249
|
return { ok: true, email: adminUser.user.email }
|
|
250
250
|
}
|
|
251
|
-
`)}import{join as
|
|
251
|
+
`)}import{join as m}from"path";async function k(t,n){let{name:r}=n;await e(m(t,".env.example"),`DATABASE_URL=postgresql://${r}:${r}_dev_password@localhost:5432/${r}_dev
|
|
252
252
|
BETTER_AUTH_SECRET=dev-secret-change-me-in-production-min-32-chars
|
|
253
253
|
BETTER_AUTH_URL=http://localhost:3000
|
|
254
254
|
LOG_LEVEL=debug
|
|
@@ -258,14 +258,94 @@ RESEND_API_KEY=
|
|
|
258
258
|
RESEND_WEBHOOK_SECRET=
|
|
259
259
|
MAIL_FROM=noreply@example.com
|
|
260
260
|
CSAT_SECRET=
|
|
261
|
-
`),await
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
`),await e(m(t,".dockerignore"),`# Dependencies (installed inside Docker)
|
|
262
|
+
node_modules/
|
|
263
|
+
.pnpm-store/
|
|
264
264
|
|
|
265
|
-
#
|
|
266
|
-
.
|
|
265
|
+
# Build outputs (rebuilt inside Docker)
|
|
266
|
+
.next/
|
|
267
|
+
out/
|
|
268
|
+
dist/
|
|
269
|
+
|
|
270
|
+
# Git
|
|
271
|
+
.git/
|
|
272
|
+
.gitignore
|
|
273
|
+
|
|
274
|
+
# Environment files (pass via docker run --env-file)
|
|
275
|
+
.env
|
|
276
|
+
.env.*
|
|
267
277
|
!.env.example
|
|
268
|
-
|
|
278
|
+
|
|
279
|
+
# Docker files
|
|
280
|
+
Dockerfile*
|
|
281
|
+
.dockerignore
|
|
282
|
+
docker-compose*.yml
|
|
283
|
+
|
|
284
|
+
# Tests
|
|
285
|
+
coverage/
|
|
286
|
+
**/*.test.*
|
|
287
|
+
**/*.spec.*
|
|
288
|
+
**/vitest.config.*
|
|
289
|
+
docker-compose.test.yml
|
|
290
|
+
|
|
291
|
+
# Development tooling
|
|
292
|
+
.vscode/
|
|
293
|
+
.idea/
|
|
294
|
+
.turbo/
|
|
295
|
+
.cache/
|
|
296
|
+
*.tsbuildinfo
|
|
297
|
+
|
|
298
|
+
# Documentation
|
|
299
|
+
*.md
|
|
300
|
+
docs/
|
|
301
|
+
|
|
302
|
+
# OS files
|
|
303
|
+
.DS_Store
|
|
304
|
+
Thumbs.db
|
|
305
|
+
`),await e(m(t,"Dockerfile"),`# --- Base image ---
|
|
306
|
+
# Node 22 LTS (Debian slim \u2014 glibc required for sharp image optimization)
|
|
307
|
+
FROM node:22-slim AS base
|
|
308
|
+
|
|
309
|
+
# --- Stage 1: Install dependencies ---
|
|
310
|
+
FROM base AS deps
|
|
311
|
+
WORKDIR /app
|
|
312
|
+
RUN corepack enable pnpm
|
|
313
|
+
COPY package.json pnpm-lock.yaml ./
|
|
314
|
+
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \\
|
|
315
|
+
pnpm install --frozen-lockfile
|
|
316
|
+
|
|
317
|
+
# --- Stage 2: Build the application ---
|
|
318
|
+
FROM base AS builder
|
|
319
|
+
WORKDIR /app
|
|
320
|
+
RUN corepack enable pnpm
|
|
321
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
322
|
+
COPY . .
|
|
323
|
+
ENV NODE_ENV=production
|
|
324
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
325
|
+
RUN pnpm build
|
|
326
|
+
|
|
327
|
+
# --- Stage 3: Production runner ---
|
|
328
|
+
FROM base AS runner
|
|
329
|
+
WORKDIR /app
|
|
330
|
+
ENV NODE_ENV=production
|
|
331
|
+
ENV PORT=3000
|
|
332
|
+
ENV HOSTNAME=0.0.0.0
|
|
333
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
334
|
+
|
|
335
|
+
# Copy standalone output (includes server.js + traced node_modules)
|
|
336
|
+
COPY --from=builder --chown=node:node /app/.next/standalone ./
|
|
337
|
+
# Copy static assets (JS/CSS chunks \u2014 excluded from standalone trace)
|
|
338
|
+
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
|
|
339
|
+
# Copy public assets (favicon, robots.txt, images)
|
|
340
|
+
COPY --from=builder --chown=node:node /app/public ./public
|
|
341
|
+
# Copy migrations (applied at app startup by the toolkit migration runner)
|
|
342
|
+
COPY --from=builder --chown=node:node /app/migrations ./migrations
|
|
343
|
+
|
|
344
|
+
# Run as non-root user
|
|
345
|
+
USER node
|
|
346
|
+
EXPOSE 3000
|
|
347
|
+
CMD ["node", "server.js"]
|
|
348
|
+
`),await e(m(t,"docker-compose.yml"),`services:
|
|
269
349
|
postgres:
|
|
270
350
|
image: postgres:17-alpine
|
|
271
351
|
container_name: ${r}-postgres
|
|
@@ -287,6 +367,48 @@ generated/
|
|
|
287
367
|
volumes:
|
|
288
368
|
postgres_data:
|
|
289
369
|
name: ${r}_postgres_data
|
|
370
|
+
`),await e(m(t,"docker-compose.prod.yml"),`services:
|
|
371
|
+
app:
|
|
372
|
+
build: .
|
|
373
|
+
restart: unless-stopped
|
|
374
|
+
env_file: .env
|
|
375
|
+
depends_on:
|
|
376
|
+
postgres:
|
|
377
|
+
condition: service_healthy
|
|
378
|
+
ports:
|
|
379
|
+
- "3000:3000"
|
|
380
|
+
networks:
|
|
381
|
+
- internal
|
|
382
|
+
|
|
383
|
+
postgres:
|
|
384
|
+
image: postgres:17-alpine
|
|
385
|
+
restart: unless-stopped
|
|
386
|
+
environment:
|
|
387
|
+
POSTGRES_USER: ${r}
|
|
388
|
+
POSTGRES_PASSWORD: \${DB_PASSWORD}
|
|
389
|
+
POSTGRES_DB: ${r}
|
|
390
|
+
volumes:
|
|
391
|
+
- postgres_data:/var/lib/postgresql/data
|
|
392
|
+
healthcheck:
|
|
393
|
+
test: ["CMD-SHELL", "pg_isready -U ${r} -d ${r}"]
|
|
394
|
+
interval: 10s
|
|
395
|
+
timeout: 5s
|
|
396
|
+
retries: 5
|
|
397
|
+
networks:
|
|
398
|
+
- internal
|
|
399
|
+
|
|
400
|
+
volumes:
|
|
401
|
+
postgres_data:
|
|
402
|
+
|
|
403
|
+
networks:
|
|
404
|
+
internal:
|
|
405
|
+
`),await y(m(t,".gitignore"),`
|
|
406
|
+
# Toolkit generated files
|
|
407
|
+
generated/
|
|
408
|
+
|
|
409
|
+
# Environment
|
|
410
|
+
.env*
|
|
411
|
+
!.env.example
|
|
290
412
|
`)}import{join as x}from"path";async function A(t,n){await e(x(t,"entities/index.ts"),`export { Article } from './article'
|
|
291
413
|
export { Category } from './category'
|
|
292
414
|
`),await e(x(t,"entities/article.ts"),`import { behavior, defineEntity, field } from '@murumets-ee/entity'
|
|
@@ -337,13 +459,13 @@ export const Category = defineEntity({
|
|
|
337
459
|
delete: 'group.admin',
|
|
338
460
|
},
|
|
339
461
|
})
|
|
340
|
-
`)}import{join as
|
|
462
|
+
`)}import{join as N}from"path";async function T(t,n){await e(N(t,"i18n/routing.ts"),`import { defineRouting } from 'next-intl/routing'
|
|
341
463
|
|
|
342
464
|
export const routing = defineRouting({
|
|
343
465
|
locales: ['en'],
|
|
344
466
|
defaultLocale: 'en',
|
|
345
467
|
})
|
|
346
|
-
`),await e(
|
|
468
|
+
`),await e(N(t,"i18n/request.ts"),`import { getRequestConfig } from 'next-intl/server'
|
|
347
469
|
import { hasLocale } from 'next-intl'
|
|
348
470
|
import { routing } from './routing'
|
|
349
471
|
import { getAuthMessages } from '@murumets-ee/auth-ui/i18n'
|
|
@@ -363,7 +485,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
363
485
|
},
|
|
364
486
|
}
|
|
365
487
|
})
|
|
366
|
-
`)}import{mkdir as
|
|
488
|
+
`)}import{mkdir as S}from"fs/promises";import{join as o}from"path";var i={myorgCore:"0.1.0",myorgDb:"0.1.0",myorgEntity:"0.1.0",myorgLogging:"0.1.0",myorgAuth:"0.1.0",myorgAuthUi:"0.1.0",myorgAdminUi:"0.1.0",myorgContent:"0.1.0",myorgContentApi:"0.1.0",myorgSettings:"0.1.0",myorgStorage:"0.1.0",myorgMedia:"0.1.0",myorgTaxonomy:"0.1.0",myorgQueue:"0.1.0",myorgMail:"0.1.0",myorgTicketing:"0.1.0",myorgTicketingUi:"0.1.0",myorgEditor:"0.1.0",myorgBlocks:"0.1.0",myorgTokens:"0.1.0",myorgUi:"0.1.0",myorgCli:"0.1.0",betterAuth:"1.4.0",drizzleOrm:"0.45.1",nextIntl:"4.8.2",nextThemes:"0.4.6",lucideReact:"0.563.0",postgres:"3.4.5",reactHookForm:"7.71.1",hookformResolvers:"5.2.2",zod:"3.24.1",drizzleKit:"0.27.2",tsx:"4.19.2",babelReactCompiler:"1.0.0"};async function E(t,n,r){await L(t,n),r?.("Workspace root created"),r?.("Creating admin app..."),await $(t,n),r?.("Admin app created"),r?.("Creating web app..."),await M(t,n),r?.("Web app created"),await _(t,n),r?.("Shared config package created")}async function L(t,n){let{name:r}=n;await S(t,{recursive:!0}),await e(o(t,"pnpm-workspace.yaml"),`packages:
|
|
367
489
|
- 'packages/*'
|
|
368
490
|
- 'apps/*'
|
|
369
491
|
|
|
@@ -702,7 +824,7 @@ async function resetDb() {
|
|
|
702
824
|
}
|
|
703
825
|
|
|
704
826
|
resetDb().catch(console.error)
|
|
705
|
-
`)}async function $(t,n){let{name:r}=n,a=o(t,"apps/admin");await
|
|
827
|
+
`)}async function $(t,n){let{name:r}=n,a=o(t,"apps/admin");await S(o(t,"apps"),{recursive:!0}),l(`pnpm create next-app@${"16"} ${a} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(a,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await c(o(a,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"better-auth":`^${i.betterAuth}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:h}=await import("./utils-42K2ZGUL.js");await h(o(a,".gitignore"),`
|
|
706
828
|
# Environment
|
|
707
829
|
.env*
|
|
708
830
|
!.env.example
|
|
@@ -1250,7 +1372,7 @@ export async function createFirstAdmin(formData: FormData) {
|
|
|
1250
1372
|
|
|
1251
1373
|
return { ok: true, email: adminUser.user.email }
|
|
1252
1374
|
}
|
|
1253
|
-
`)}async function M(t,n){let{name:r}=n,a=o(t,"apps/web");
|
|
1375
|
+
`)}async function M(t,n){let{name:r}=n,a=o(t,"apps/web");l(`pnpm create next-app@${"16"} ${a} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(a,["app/page.tsx","app/page.module.css","app/fonts","README.md","pnpm-workspace.yaml"]),await c(o(a,"package.json"),{dependencies:{[`@${r}/config`]:"workspace:*","@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`},devDependencies:{"babel-plugin-react-compiler":i.babelReactCompiler}});let{appendToFile:h}=await import("./utils-42K2ZGUL.js");await h(o(a,".gitignore"),`
|
|
1254
1376
|
# Environment
|
|
1255
1377
|
.env*
|
|
1256
1378
|
!.env.example
|
|
@@ -1538,7 +1660,7 @@ export default async function HomePage() {
|
|
|
1538
1660
|
</div>
|
|
1539
1661
|
)
|
|
1540
1662
|
}
|
|
1541
|
-
`)}import{join as
|
|
1663
|
+
`)}import{join as p}from"path";async function P(t,n){let{name:r}=n;await e(p(t,"next.config.ts"),`import type { NextConfig } from 'next'
|
|
1542
1664
|
import createNextIntlPlugin from 'next-intl/plugin'
|
|
1543
1665
|
|
|
1544
1666
|
const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
|
|
@@ -1563,7 +1685,7 @@ const nextConfig: NextConfig = {
|
|
|
1563
1685
|
}
|
|
1564
1686
|
|
|
1565
1687
|
export default withNextIntl(nextConfig)
|
|
1566
|
-
`),await e(
|
|
1688
|
+
`),await e(p(t,"proxy.ts"),`import { NextRequest, NextResponse } from 'next/server'
|
|
1567
1689
|
import createMiddleware from 'next-intl/middleware'
|
|
1568
1690
|
import { routing } from './i18n/routing'
|
|
1569
1691
|
|
|
@@ -1594,12 +1716,12 @@ export function proxy(request: NextRequest) {
|
|
|
1594
1716
|
export const config = {
|
|
1595
1717
|
matcher: '/((?!api|_next|_vercel|.*\\\\..*).*)',
|
|
1596
1718
|
}
|
|
1597
|
-
`),await e(
|
|
1719
|
+
`),await e(p(t,"app/layout.tsx"),`import './globals.css'
|
|
1598
1720
|
|
|
1599
1721
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
1600
1722
|
return children
|
|
1601
1723
|
}
|
|
1602
|
-
`),await e(
|
|
1724
|
+
`),await e(p(t,"app/[locale]/layout.tsx"),`import type { Metadata } from 'next'
|
|
1603
1725
|
import { Geist, Geist_Mono } from 'next/font/google'
|
|
1604
1726
|
import { NextIntlClientProvider } from 'next-intl'
|
|
1605
1727
|
import { getMessages } from 'next-intl/server'
|
|
@@ -1648,7 +1770,7 @@ export default async function LocaleLayout({
|
|
|
1648
1770
|
</html>
|
|
1649
1771
|
)
|
|
1650
1772
|
}
|
|
1651
|
-
`),await e(
|
|
1773
|
+
`),await e(p(t,"app/[locale]/page.tsx"),`import { createQueryClient } from '@murumets-ee/core/clients'
|
|
1652
1774
|
import { Article, Category } from '@/entities'
|
|
1653
1775
|
import { getToolkitApp } from '@/lib/app'
|
|
1654
1776
|
|
|
@@ -1712,119 +1834,13 @@ export default async function HomePage() {
|
|
|
1712
1834
|
</div>
|
|
1713
1835
|
)
|
|
1714
1836
|
}
|
|
1715
|
-
`)}import{join as b}from"path";async function
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
import
|
|
1722
|
-
import { generateSchemaCode, generateTranslationSchemaCode } from '@murumets-ee/entity'
|
|
1723
|
-
import config from '../toolkit.config'
|
|
1724
|
-
|
|
1725
|
-
async function generateSchemas() {
|
|
1726
|
-
console.log('Generating Drizzle schemas from entity definitions...')
|
|
1727
|
-
|
|
1728
|
-
const schemaDir = join(import.meta.dirname, '..', 'generated')
|
|
1729
|
-
await mkdir(schemaDir, { recursive: true })
|
|
1730
|
-
|
|
1731
|
-
const imports: string[] = []
|
|
1732
|
-
const schemas: string[] = []
|
|
1733
|
-
let hasTranslations = false
|
|
1734
|
-
|
|
1735
|
-
for (const entity of config.entities) {
|
|
1736
|
-
console.log(\` - Generating schema for \${entity.name}\`)
|
|
1737
|
-
|
|
1738
|
-
// Generate main table (returns TypeScript code string)
|
|
1739
|
-
const schemaCode = generateSchemaCode(entity)
|
|
1740
|
-
schemas.push(\`\\n// \${entity.name} table\`)
|
|
1741
|
-
schemas.push(schemaCode)
|
|
1742
|
-
|
|
1743
|
-
// Generate translation table if needed (returns TypeScript code string or null)
|
|
1744
|
-
const translationCode = generateTranslationSchemaCode(entity)
|
|
1745
|
-
if (translationCode) {
|
|
1746
|
-
hasTranslations = true
|
|
1747
|
-
schemas.push(\`\\n// \${entity.name} translations\`)
|
|
1748
|
-
schemas.push(translationCode)
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
// Add imports \u2014 include all pg-core types that entities might use
|
|
1753
|
-
const pgCoreTypes = ['pgTable', 'varchar', 'text', 'boolean', 'timestamp', 'integer', 'doublePrecision', 'jsonb', 'uuid', 'index']
|
|
1754
|
-
if (hasTranslations) pgCoreTypes.push('unique')
|
|
1755
|
-
const pgCoreImports = \`import { \${pgCoreTypes.join(', ')} } from 'drizzle-orm/pg-core'\\n\`
|
|
1756
|
-
imports.push(pgCoreImports)
|
|
1757
|
-
|
|
1758
|
-
const schemaFile = join(schemaDir, 'schema.ts')
|
|
1759
|
-
await writeFile(schemaFile, [...imports, ...schemas].join('\\n'))
|
|
1760
|
-
|
|
1761
|
-
console.log(\`\\nSchemas written to: \${schemaFile}\`)
|
|
1762
|
-
console.log('Run \\\`pnpm db:migrate:generate\\\` to create migrations')
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
generateSchemas().catch(console.error)
|
|
1766
|
-
`),await e(b(t,"scripts/migrate.ts"),`/**
|
|
1767
|
-
* Run pending migrations
|
|
1768
|
-
* Applies both toolkit migrations (.toolkit/) and project migrations
|
|
1769
|
-
*/
|
|
1770
|
-
|
|
1771
|
-
import { createDbClient, runMigrations } from '@murumets-ee/db'
|
|
1772
|
-
|
|
1773
|
-
async function migrate() {
|
|
1774
|
-
console.log('Running migrations...')
|
|
1775
|
-
|
|
1776
|
-
try {
|
|
1777
|
-
if (!process.env.DATABASE_URL) {
|
|
1778
|
-
throw new Error('DATABASE_URL environment variable is required')
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
const db = createDbClient({ url: process.env.DATABASE_URL })
|
|
1782
|
-
await runMigrations(db, import.meta.dirname + '/..')
|
|
1783
|
-
|
|
1784
|
-
console.log('Migrations completed successfully')
|
|
1785
|
-
process.exit(0)
|
|
1786
|
-
} catch (error) {
|
|
1787
|
-
console.error('Migration failed:', error)
|
|
1788
|
-
process.exit(1)
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
migrate()
|
|
1793
|
-
`),await e(b(t,"scripts/reset-db.ts"),`/**
|
|
1794
|
-
* Reset database (DROP all tables)
|
|
1795
|
-
* WARNING: Only for development
|
|
1796
|
-
*/
|
|
1797
|
-
|
|
1798
|
-
import postgres from 'postgres'
|
|
1799
|
-
|
|
1800
|
-
async function resetDb() {
|
|
1801
|
-
const DATABASE_URL = process.env.DATABASE_URL
|
|
1802
|
-
|
|
1803
|
-
if (!DATABASE_URL) {
|
|
1804
|
-
throw new Error('DATABASE_URL not set')
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
console.warn('WARNING: This will DROP ALL TABLES')
|
|
1808
|
-
console.log('Database:', DATABASE_URL.split('@')[1])
|
|
1809
|
-
|
|
1810
|
-
const sql = postgres(DATABASE_URL)
|
|
1811
|
-
|
|
1812
|
-
try {
|
|
1813
|
-
// Drop all tables (cascade to handle foreign keys)
|
|
1814
|
-
await sql\`
|
|
1815
|
-
DROP SCHEMA public CASCADE;
|
|
1816
|
-
CREATE SCHEMA public;
|
|
1817
|
-
GRANT ALL ON SCHEMA public TO PUBLIC;
|
|
1818
|
-
\`
|
|
1819
|
-
|
|
1820
|
-
console.log('Database reset complete')
|
|
1821
|
-
} finally {
|
|
1822
|
-
await sql.end()
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
resetDb().catch(console.error)
|
|
1827
|
-
`)}import{join as g}from"path";async function C(t,n){let{name:r}=n;await e(g(t,"app/globals.css"),`@import "tailwindcss";
|
|
1837
|
+
`)}import{join as b}from"path";async function z(t,n){await e(b(t,"scripts/generate-schema.ts"),`import { execSync } from 'node:child_process'
|
|
1838
|
+
execSync('npx @murumets-ee/cli generate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1839
|
+
`),await e(b(t,"scripts/migrate.ts"),`import { execSync } from 'node:child_process'
|
|
1840
|
+
execSync('npx @murumets-ee/cli migrate', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1841
|
+
`),await e(b(t,"scripts/reset-db.ts"),`import { execSync } from 'node:child_process'
|
|
1842
|
+
execSync('npx @murumets-ee/cli reset --force', { stdio: 'inherit', cwd: import.meta.dirname + '/..' })
|
|
1843
|
+
`)}import{join as f}from"path";async function R(t,n){let{name:r}=n;await e(f(t,"app/globals.css"),`@import "tailwindcss";
|
|
1828
1844
|
@source "../node_modules/@murumets-ee/auth-ui/dist";
|
|
1829
1845
|
|
|
1830
1846
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
@@ -1851,7 +1867,7 @@ body {
|
|
|
1851
1867
|
color: var(--foreground);
|
|
1852
1868
|
font-family: Arial, Helvetica, sans-serif;
|
|
1853
1869
|
}
|
|
1854
|
-
`),await e(
|
|
1870
|
+
`),await e(f(t,"app/theme-provider.tsx"),`'use client'
|
|
1855
1871
|
|
|
1856
1872
|
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
1857
1873
|
import type { ReactNode } from 'react'
|
|
@@ -1868,7 +1884,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
|
1868
1884
|
</NextThemesProvider>
|
|
1869
1885
|
)
|
|
1870
1886
|
}
|
|
1871
|
-
`),await e(
|
|
1887
|
+
`),await e(f(t,"app/theme-toggle.tsx"),`'use client'
|
|
1872
1888
|
|
|
1873
1889
|
import { useTheme } from 'next-themes'
|
|
1874
1890
|
import { useState, useEffect } from 'react'
|
|
@@ -1895,7 +1911,7 @@ export function ThemeToggle() {
|
|
|
1895
1911
|
</button>
|
|
1896
1912
|
)
|
|
1897
1913
|
}
|
|
1898
|
-
`),await e(
|
|
1914
|
+
`),await e(f(t,"app/nav-header.tsx"),`'use client'
|
|
1899
1915
|
|
|
1900
1916
|
import Link from 'next/link'
|
|
1901
1917
|
import { usePathname } from 'next/navigation'
|
|
@@ -1939,7 +1955,7 @@ export function NavHeader() {
|
|
|
1939
1955
|
</header>
|
|
1940
1956
|
)
|
|
1941
1957
|
}
|
|
1942
|
-
`)}import{join as
|
|
1958
|
+
`)}import{join as d}from"path";async function C(t,n){let{name:r}=n;await e(d(t,"toolkit.config.ts"),`import { auth } from '@murumets-ee/auth/plugin'
|
|
1943
1959
|
import { content } from '@murumets-ee/content/plugin'
|
|
1944
1960
|
import { defineConfig } from '@murumets-ee/core'
|
|
1945
1961
|
import { mail, ResendMailProvider } from '@murumets-ee/mail'
|
|
@@ -1991,7 +2007,7 @@ export default defineConfig({
|
|
|
1991
2007
|
],
|
|
1992
2008
|
projectRoot: import.meta.dirname,
|
|
1993
2009
|
})
|
|
1994
|
-
`),await e(
|
|
2010
|
+
`),await e(d(t,"lib/app.ts"),`import { createApp, setApp, type ToolkitApp } from '@murumets-ee/core'
|
|
1995
2011
|
import config from '../toolkit.config'
|
|
1996
2012
|
|
|
1997
2013
|
let appInstance: ToolkitApp | null = null
|
|
@@ -2005,7 +2021,7 @@ export async function getToolkitApp(): Promise<ToolkitApp> {
|
|
|
2005
2021
|
}
|
|
2006
2022
|
return appInstance
|
|
2007
2023
|
}
|
|
2008
|
-
`),await e(
|
|
2024
|
+
`),await e(d(t,"drizzle.config.ts"),`import type { Config } from 'drizzle-kit'
|
|
2009
2025
|
|
|
2010
2026
|
if (!process.env.DATABASE_URL) {
|
|
2011
2027
|
throw new Error('DATABASE_URL environment variable is required')
|
|
@@ -2019,7 +2035,7 @@ export default {
|
|
|
2019
2035
|
url: process.env.DATABASE_URL,
|
|
2020
2036
|
},
|
|
2021
2037
|
} satisfies Config
|
|
2022
|
-
`),await e(
|
|
2038
|
+
`),await e(d(t,"auth.config.ts"),`import { betterAuth } from 'better-auth'
|
|
2023
2039
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
2024
2040
|
import { admin } from 'better-auth/plugins'
|
|
2025
2041
|
import { organization } from 'better-auth/plugins/organization'
|
|
@@ -2037,7 +2053,7 @@ export const auth = betterAuth({
|
|
|
2037
2053
|
organization(),
|
|
2038
2054
|
],
|
|
2039
2055
|
})
|
|
2040
|
-
`),await e(
|
|
2056
|
+
`),await e(d(t,"generated/auth-schema.ts"),`// This file is generated by better-auth CLI.
|
|
2041
2057
|
// Run: npx @better-auth/cli generate --config auth.config.ts -y
|
|
2042
2058
|
export {}
|
|
2043
|
-
`)}async function
|
|
2059
|
+
`)}async function O(t,n){t.mode==="single"?await q(t,n):await U(t,n)}async function q(t,n){let r=v(process.cwd(),t.name);n?.("Creating Next.js app..."),l(`pnpm create next-app@${"16"} ${t.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await u(r,["app/page.tsx","app/page.module.css","app/fonts","README.md"]),await c(v(r,"package.json"),{type:"module",dependencies:{"@murumets-ee/core":`^${i.myorgCore}`,"@murumets-ee/db":`^${i.myorgDb}`,"@murumets-ee/entity":`^${i.myorgEntity}`,"@murumets-ee/logging":`^${i.myorgLogging}`,"@murumets-ee/auth":`^${i.myorgAuth}`,"@murumets-ee/auth-ui":`^${i.myorgAuthUi}`,"@murumets-ee/admin-ui":`^${i.myorgAdminUi}`,"@murumets-ee/content":`^${i.myorgContent}`,"@murumets-ee/settings":`^${i.myorgSettings}`,"@murumets-ee/storage":`^${i.myorgStorage}`,"@murumets-ee/media":`^${i.myorgMedia}`,"@murumets-ee/taxonomy":`^${i.myorgTaxonomy}`,"@murumets-ee/queue":`^${i.myorgQueue}`,"@murumets-ee/mail":`^${i.myorgMail}`,"@murumets-ee/ticketing":`^${i.myorgTicketing}`,"@murumets-ee/ticketing-ui":`^${i.myorgTicketingUi}`,"@murumets-ee/tokens":`^${i.myorgTokens}`,"better-auth":`^${i.betterAuth}`,"drizzle-orm":`^${i.drizzleOrm}`,"next-intl":`^${i.nextIntl}`,"next-themes":`^${i.nextThemes}`,"lucide-react":`^${i.lucideReact}`,postgres:`^${i.postgres}`,"react-hook-form":`^${i.reactHookForm}`,"@hookform/resolvers":`^${i.hookformResolvers}`,zod:`^${i.zod}`},devDependencies:{"drizzle-kit":`^${i.drizzleKit}`,tsx:`^${i.tsx}`,"babel-plugin-react-compiler":i.babelReactCompiler},scripts:{"db:generate":"tsx --env-file=.env scripts/generate-schema.ts","db:migrate:generate":"drizzle-kit generate","db:migrate":"tsx --env-file=.env scripts/migrate.ts","db:reset":"tsx --env-file=.env scripts/reset-db.ts"}}),n?.("Adding toolkit files..."),await k(r,t),await A(r,t),await C(r,t),await w(r,t),await T(r,t),await P(r,t),await R(r,t),await z(r,t),t.installDeps&&(n?.("Installing dependencies..."),l("pnpm install",{cwd:r}))}async function U(t,n){let r=v(process.cwd(),t.name);n?.("Creating workspace..."),await E(r,t,n),t.installDeps&&(n?.("Installing dependencies..."),l("pnpm install",{cwd:r}))}export{O as scaffold};
|