create-kofi-stack 1.2.18 → 1.2.20
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/index.js +105 -628
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -80,16 +80,6 @@ async function promptMarketingSite(defaultValue) {
|
|
|
80
80
|
}
|
|
81
81
|
return marketing;
|
|
82
82
|
}
|
|
83
|
-
async function promptIncludeDocs(defaultValue) {
|
|
84
|
-
const includeDocs = await p.confirm({
|
|
85
|
-
message: "Include documentation site? (Fumadocs)",
|
|
86
|
-
initialValue: defaultValue ?? true
|
|
87
|
-
});
|
|
88
|
-
if (p.isCancel(includeDocs)) {
|
|
89
|
-
throw new Error("Operation cancelled");
|
|
90
|
-
}
|
|
91
|
-
return includeDocs;
|
|
92
|
-
}
|
|
93
83
|
|
|
94
84
|
// src/prompts/shadcn.ts
|
|
95
85
|
import * as p2 from "@clack/prompts";
|
|
@@ -471,7 +461,6 @@ function parseOptions(options) {
|
|
|
471
461
|
monorepo: options.monorepo,
|
|
472
462
|
single: options.single,
|
|
473
463
|
marketing: options.marketing,
|
|
474
|
-
docs: options.docs,
|
|
475
464
|
componentLibrary: options.componentLibrary,
|
|
476
465
|
style: options.style,
|
|
477
466
|
baseColor: options.baseColor,
|
|
@@ -505,18 +494,12 @@ async function runPrompts(projectName, rawOptions) {
|
|
|
505
494
|
structure = await promptProjectStructure();
|
|
506
495
|
}
|
|
507
496
|
let marketingSite = "none";
|
|
508
|
-
let includeDocs = false;
|
|
509
497
|
if (structure === "monorepo") {
|
|
510
498
|
if (options.marketing) {
|
|
511
499
|
marketingSite = options.marketing;
|
|
512
500
|
} else {
|
|
513
501
|
marketingSite = await promptMarketingSite();
|
|
514
502
|
}
|
|
515
|
-
if (options.docs !== void 0) {
|
|
516
|
-
includeDocs = options.docs;
|
|
517
|
-
} else {
|
|
518
|
-
includeDocs = await promptIncludeDocs();
|
|
519
|
-
}
|
|
520
503
|
}
|
|
521
504
|
const shadcn = await promptShadcnConfig({
|
|
522
505
|
componentLibrary: options.componentLibrary,
|
|
@@ -569,7 +552,6 @@ async function runPrompts(projectName, rawOptions) {
|
|
|
569
552
|
name,
|
|
570
553
|
structure,
|
|
571
554
|
marketingSite,
|
|
572
|
-
includeDocs,
|
|
573
555
|
shadcn,
|
|
574
556
|
designSystem,
|
|
575
557
|
auth,
|
|
@@ -614,9 +596,6 @@ function showConfigSummary(config) {
|
|
|
614
596
|
if (config.marketingSite !== "none") {
|
|
615
597
|
apps.push(`marketing (${config.marketingSite})`);
|
|
616
598
|
}
|
|
617
|
-
if (config.includeDocs) {
|
|
618
|
-
apps.push("docs");
|
|
619
|
-
}
|
|
620
599
|
}
|
|
621
600
|
const authProviders = ["Email/Password", "Google", ...config.auth.providers];
|
|
622
601
|
const extras = [];
|
|
@@ -660,9 +639,6 @@ async function showConfirmation(config) {
|
|
|
660
639
|
if (config.marketingSite !== "none") {
|
|
661
640
|
apps.push(`marketing (${config.marketingSite})`);
|
|
662
641
|
}
|
|
663
|
-
if (config.includeDocs) {
|
|
664
|
-
apps.push("docs");
|
|
665
|
-
}
|
|
666
642
|
}
|
|
667
643
|
const authProviders = ["Email/Password", "Google", ...config.auth.providers];
|
|
668
644
|
const extras = [];
|
|
@@ -709,7 +685,7 @@ ${extras.length > 0 ? `${pc.bold("Extras:")} ${extras.join(", ")}` : ""}`,
|
|
|
709
685
|
}
|
|
710
686
|
|
|
711
687
|
// src/generators/index.ts
|
|
712
|
-
import
|
|
688
|
+
import path18 from "path";
|
|
713
689
|
import * as p7 from "@clack/prompts";
|
|
714
690
|
import pc3 from "picocolors";
|
|
715
691
|
import ora from "ora";
|
|
@@ -1061,17 +1037,36 @@ export const {
|
|
|
1061
1037
|
} = authClient
|
|
1062
1038
|
`;
|
|
1063
1039
|
await writeFile(path3.join(appDir, "src/lib/auth.ts"), authClientContent);
|
|
1064
|
-
const convexContent = `
|
|
1040
|
+
const convexContent = `'use client'
|
|
1041
|
+
|
|
1042
|
+
import { ConvexProvider, ConvexReactClient } from 'convex/react'
|
|
1065
1043
|
import { env } from '@/env'
|
|
1066
1044
|
|
|
1067
1045
|
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
|
1068
|
-
if (!convexUrl) {
|
|
1069
|
-
throw new Error(
|
|
1070
|
-
'NEXT_PUBLIC_CONVEX_URL is not set. Run "npx convex dev" to set up Convex.'
|
|
1071
|
-
)
|
|
1072
|
-
}
|
|
1073
1046
|
|
|
1074
|
-
|
|
1047
|
+
// Create client only if URL is available
|
|
1048
|
+
export const convex = convexUrl ? new ConvexReactClient(convexUrl) : null
|
|
1049
|
+
|
|
1050
|
+
// Wrapper provider that handles missing Convex configuration
|
|
1051
|
+
export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
|
|
1052
|
+
if (!convex) {
|
|
1053
|
+
return (
|
|
1054
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
1055
|
+
<div className="text-center p-8 max-w-md">
|
|
1056
|
+
<h1 className="text-2xl font-bold mb-4">Convex Not Configured</h1>
|
|
1057
|
+
<p className="text-muted-foreground mb-4">
|
|
1058
|
+
Run <code className="bg-muted px-2 py-1 rounded">npx convex dev</code> in your terminal to set up Convex.
|
|
1059
|
+
</p>
|
|
1060
|
+
<p className="text-sm text-muted-foreground">
|
|
1061
|
+
This will create your Convex project and add the required environment variables.
|
|
1062
|
+
</p>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
)
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return <ConvexProvider client={convex}>{children}</ConvexProvider>
|
|
1069
|
+
}
|
|
1075
1070
|
|
|
1076
1071
|
export { ConvexProvider }
|
|
1077
1072
|
`;
|
|
@@ -1198,15 +1193,14 @@ export const env = createEnv({
|
|
|
1198
1193
|
async function generateComponentFiles(config, appDir) {
|
|
1199
1194
|
let providersContent = `'use client'
|
|
1200
1195
|
|
|
1201
|
-
import {
|
|
1202
|
-
import { convex } from '@/lib/convex'`;
|
|
1196
|
+
import { ConvexClientProvider } from '@/lib/convex'`;
|
|
1203
1197
|
if (config.integrations.analytics === "posthog") {
|
|
1204
1198
|
providersContent += `
|
|
1205
1199
|
import posthog from 'posthog-js'
|
|
1206
1200
|
import { PostHogProvider } from 'posthog-js/react'
|
|
1207
1201
|
import { env } from '@/env'
|
|
1208
1202
|
|
|
1209
|
-
if (typeof window !== 'undefined') {
|
|
1203
|
+
if (typeof window !== 'undefined' && env.NEXT_PUBLIC_POSTHOG_KEY) {
|
|
1210
1204
|
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
1211
1205
|
api_host: env.NEXT_PUBLIC_POSTHOG_HOST,
|
|
1212
1206
|
person_profiles: 'identified_only',
|
|
@@ -1222,9 +1216,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
1222
1216
|
<PostHogProvider client={posthog}>`;
|
|
1223
1217
|
}
|
|
1224
1218
|
providersContent += `
|
|
1225
|
-
<
|
|
1219
|
+
<ConvexClientProvider>
|
|
1226
1220
|
{children}
|
|
1227
|
-
</
|
|
1221
|
+
</ConvexClientProvider>`;
|
|
1228
1222
|
if (config.integrations.analytics === "posthog") {
|
|
1229
1223
|
providersContent += `
|
|
1230
1224
|
</PostHogProvider>`;
|
|
@@ -2059,18 +2053,19 @@ import { ConvexHttpClient } from 'convex/browser'
|
|
|
2059
2053
|
import { env } from '@/env'
|
|
2060
2054
|
|
|
2061
2055
|
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
|
2062
|
-
if (!convexUrl) {
|
|
2063
|
-
throw new Error(
|
|
2064
|
-
'NEXT_PUBLIC_CONVEX_URL is not set. Run "npx convex dev" to set up Convex.'
|
|
2065
|
-
)
|
|
2066
|
-
}
|
|
2067
2056
|
|
|
2068
|
-
|
|
2057
|
+
// Auth is only functional when Convex is configured
|
|
2058
|
+
const convex = convexUrl ? new ConvexHttpClient(convexUrl) : null
|
|
2059
|
+
|
|
2060
|
+
function createAuth() {
|
|
2061
|
+
if (!convex) {
|
|
2062
|
+
throw new Error('NEXT_PUBLIC_CONVEX_URL is not set. Run "npx convex dev" to set up Convex.')
|
|
2063
|
+
}
|
|
2069
2064
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2065
|
+
return betterAuth({
|
|
2066
|
+
database: convexAdapter(convex),
|
|
2067
|
+
secret: env.BETTER_AUTH_SECRET || '',
|
|
2068
|
+
baseURL: env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
2074
2069
|
emailAndPassword: {
|
|
2075
2070
|
enabled: true,
|
|
2076
2071
|
sendResetPassword: async ({ user, url }) => {
|
|
@@ -2100,7 +2095,11 @@ export const auth = betterAuth({
|
|
|
2100
2095
|
],`;
|
|
2101
2096
|
}
|
|
2102
2097
|
content += `
|
|
2103
|
-
})
|
|
2098
|
+
})
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// Lazy initialization - only create auth when needed
|
|
2102
|
+
export const auth = convex ? createAuth() : null
|
|
2104
2103
|
`;
|
|
2105
2104
|
await writeFile(path7.join(appDir, "src/lib/auth-server.ts"), content);
|
|
2106
2105
|
}
|
|
@@ -2769,19 +2768,18 @@ async function generateRateLimiting(config, appDir) {
|
|
|
2769
2768
|
}
|
|
2770
2769
|
}
|
|
2771
2770
|
async function generateArcjet(appDir) {
|
|
2772
|
-
const content = `import arcjet, { shield,
|
|
2771
|
+
const content = `import arcjet, { shield, fixedWindow, detectBot } from '@arcjet/next'
|
|
2773
2772
|
import { env } from '@/env'
|
|
2774
2773
|
|
|
2775
2774
|
export const aj = arcjet({
|
|
2776
|
-
key: env.ARCJET_KEY
|
|
2777
|
-
characteristics: ['ip.src'],
|
|
2775
|
+
key: env.ARCJET_KEY!,
|
|
2778
2776
|
rules: [
|
|
2779
2777
|
// Shield protects against common attacks
|
|
2780
2778
|
shield({ mode: 'LIVE' }),
|
|
2781
|
-
// Rate limit API requests
|
|
2782
|
-
|
|
2779
|
+
// Rate limit API requests - 100 requests per minute
|
|
2780
|
+
fixedWindow({
|
|
2783
2781
|
mode: 'LIVE',
|
|
2784
|
-
|
|
2782
|
+
window: '1m',
|
|
2785
2783
|
max: 100,
|
|
2786
2784
|
}),
|
|
2787
2785
|
// Bot detection
|
|
@@ -4270,517 +4268,8 @@ S3_ENDPOINT="https://[PROJECT].supabase.co/storage/v1/s3"
|
|
|
4270
4268
|
await writeFile(path15.join(marketingDir, ".env.local"), envContent);
|
|
4271
4269
|
}
|
|
4272
4270
|
|
|
4273
|
-
// src/generators/fumadocs.ts
|
|
4274
|
-
import path16 from "path";
|
|
4275
|
-
async function generateFumadocs(config, docsDir) {
|
|
4276
|
-
await ensureDir(path16.join(docsDir, "content/docs"));
|
|
4277
|
-
await ensureDir(path16.join(docsDir, "src/app/docs/[[...slug]]"));
|
|
4278
|
-
await ensureDir(path16.join(docsDir, "src/lib"));
|
|
4279
|
-
await generateFumadocsPackageJson(docsDir);
|
|
4280
|
-
await generateFumadocsNextConfig(docsDir);
|
|
4281
|
-
await generateFumadocsTsConfig(docsDir);
|
|
4282
|
-
await generateFumadocsSource(docsDir);
|
|
4283
|
-
await generateFumadocsAppFiles(config, docsDir);
|
|
4284
|
-
await generateFumadocsContent(config, docsDir);
|
|
4285
|
-
}
|
|
4286
|
-
async function generateFumadocsPackageJson(docsDir) {
|
|
4287
|
-
const packageJson2 = {
|
|
4288
|
-
name: "@repo/docs",
|
|
4289
|
-
version: "0.1.0",
|
|
4290
|
-
private: true,
|
|
4291
|
-
scripts: {
|
|
4292
|
-
dev: "next dev --turbopack -p 3002",
|
|
4293
|
-
build: "next build",
|
|
4294
|
-
start: "next start",
|
|
4295
|
-
lint: "biome check .",
|
|
4296
|
-
"lint:fix": "biome check --write .",
|
|
4297
|
-
typecheck: "tsc --noEmit"
|
|
4298
|
-
},
|
|
4299
|
-
dependencies: {
|
|
4300
|
-
next: "^16.0.0",
|
|
4301
|
-
react: "^19.0.0",
|
|
4302
|
-
"react-dom": "^19.0.0",
|
|
4303
|
-
"fumadocs-core": "^16.0.0",
|
|
4304
|
-
"fumadocs-mdx": "^14.0.0",
|
|
4305
|
-
"fumadocs-ui": "^16.0.0",
|
|
4306
|
-
"@repo/ui": "workspace:*"
|
|
4307
|
-
},
|
|
4308
|
-
devDependencies: {
|
|
4309
|
-
"@repo/config-typescript": "workspace:*",
|
|
4310
|
-
"@types/node": "^20.0.0",
|
|
4311
|
-
"@types/react": "^19.0.0",
|
|
4312
|
-
"@types/react-dom": "^19.0.0",
|
|
4313
|
-
"@types/mdx": "^2.0.0",
|
|
4314
|
-
tailwindcss: "^4.0.0",
|
|
4315
|
-
"@tailwindcss/postcss": "^4.0.0",
|
|
4316
|
-
postcss: "^8.4.0",
|
|
4317
|
-
typescript: "^5.0.0"
|
|
4318
|
-
}
|
|
4319
|
-
};
|
|
4320
|
-
await writeJSON(path16.join(docsDir, "package.json"), packageJson2);
|
|
4321
|
-
}
|
|
4322
|
-
async function generateFumadocsNextConfig(docsDir) {
|
|
4323
|
-
const content = `import { createMDX } from 'fumadocs-mdx/next'
|
|
4324
|
-
import type { NextConfig } from 'next'
|
|
4325
|
-
|
|
4326
|
-
const withMDX = createMDX()
|
|
4327
|
-
|
|
4328
|
-
const config: NextConfig = {
|
|
4329
|
-
reactStrictMode: true,
|
|
4330
|
-
transpilePackages: ['@repo/ui'],
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
export default withMDX(config)
|
|
4334
|
-
`;
|
|
4335
|
-
await writeFile(path16.join(docsDir, "next.config.ts"), content);
|
|
4336
|
-
}
|
|
4337
|
-
async function generateFumadocsTsConfig(docsDir) {
|
|
4338
|
-
const tsConfig = {
|
|
4339
|
-
compilerOptions: {
|
|
4340
|
-
target: "ES2020",
|
|
4341
|
-
lib: ["dom", "dom.iterable", "esnext"],
|
|
4342
|
-
allowJs: true,
|
|
4343
|
-
skipLibCheck: true,
|
|
4344
|
-
strict: true,
|
|
4345
|
-
noEmit: true,
|
|
4346
|
-
esModuleInterop: true,
|
|
4347
|
-
module: "esnext",
|
|
4348
|
-
moduleResolution: "bundler",
|
|
4349
|
-
resolveJsonModule: true,
|
|
4350
|
-
isolatedModules: true,
|
|
4351
|
-
jsx: "preserve",
|
|
4352
|
-
incremental: true,
|
|
4353
|
-
plugins: [{ name: "next" }],
|
|
4354
|
-
paths: {
|
|
4355
|
-
"@/*": ["./src/*"],
|
|
4356
|
-
"fumadocs-mdx:collections/*": ["./.source/*"]
|
|
4357
|
-
}
|
|
4358
|
-
},
|
|
4359
|
-
include: [
|
|
4360
|
-
"next-env.d.ts",
|
|
4361
|
-
"**/*.ts",
|
|
4362
|
-
"**/*.tsx",
|
|
4363
|
-
"**/*.mdx",
|
|
4364
|
-
".next/types/**/*.ts",
|
|
4365
|
-
".source/**/*.ts"
|
|
4366
|
-
],
|
|
4367
|
-
exclude: ["node_modules"]
|
|
4368
|
-
};
|
|
4369
|
-
await writeJSON(path16.join(docsDir, "tsconfig.json"), tsConfig);
|
|
4370
|
-
}
|
|
4371
|
-
async function generateFumadocsSource(docsDir) {
|
|
4372
|
-
const content = `import { docs } from 'fumadocs-mdx:collections/server'
|
|
4373
|
-
import { loader } from 'fumadocs-core/source'
|
|
4374
|
-
|
|
4375
|
-
export const source = loader({
|
|
4376
|
-
baseUrl: '/docs',
|
|
4377
|
-
source: docs.toFumadocsSource(),
|
|
4378
|
-
})
|
|
4379
|
-
`;
|
|
4380
|
-
await writeFile(path16.join(docsDir, "src/lib/source.ts"), content);
|
|
4381
|
-
}
|
|
4382
|
-
async function generateFumadocsAppFiles(config, docsDir) {
|
|
4383
|
-
const layoutContent = `import { RootProvider } from 'fumadocs-ui/provider/next'
|
|
4384
|
-
import { Inter } from 'next/font/google'
|
|
4385
|
-
import type { Metadata } from 'next'
|
|
4386
|
-
import type { ReactNode } from 'react'
|
|
4387
|
-
import './global.css'
|
|
4388
|
-
|
|
4389
|
-
const inter = Inter({
|
|
4390
|
-
subsets: ['latin'],
|
|
4391
|
-
})
|
|
4392
|
-
|
|
4393
|
-
export const metadata: Metadata = {
|
|
4394
|
-
title: {
|
|
4395
|
-
template: '%s | ${config.name} Docs',
|
|
4396
|
-
default: '${config.name} Documentation',
|
|
4397
|
-
},
|
|
4398
|
-
description: 'Documentation for ${config.name}',
|
|
4399
|
-
}
|
|
4400
|
-
|
|
4401
|
-
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
4402
|
-
return (
|
|
4403
|
-
<html lang="en" className={inter.className} suppressHydrationWarning>
|
|
4404
|
-
<body className="flex flex-col min-h-screen">
|
|
4405
|
-
<RootProvider>{children}</RootProvider>
|
|
4406
|
-
</body>
|
|
4407
|
-
</html>
|
|
4408
|
-
)
|
|
4409
|
-
}
|
|
4410
|
-
`;
|
|
4411
|
-
await writeFile(path16.join(docsDir, "src/app/layout.tsx"), layoutContent);
|
|
4412
|
-
const docsLayoutContent = `import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
|
4413
|
-
import type { ReactNode } from 'react'
|
|
4414
|
-
import { source } from '@/lib/source'
|
|
4415
|
-
|
|
4416
|
-
export default function Layout({ children }: { children: ReactNode }) {
|
|
4417
|
-
return (
|
|
4418
|
-
<DocsLayout
|
|
4419
|
-
tree={source.pageTree}
|
|
4420
|
-
nav={{
|
|
4421
|
-
title: '${config.name} Docs',
|
|
4422
|
-
}}
|
|
4423
|
-
>
|
|
4424
|
-
{children}
|
|
4425
|
-
</DocsLayout>
|
|
4426
|
-
)
|
|
4427
|
-
}
|
|
4428
|
-
`;
|
|
4429
|
-
await writeFile(
|
|
4430
|
-
path16.join(docsDir, "src/app/docs/layout.tsx"),
|
|
4431
|
-
docsLayoutContent
|
|
4432
|
-
);
|
|
4433
|
-
const docsPageContent = `import { source } from '@/lib/source'
|
|
4434
|
-
import {
|
|
4435
|
-
DocsPage,
|
|
4436
|
-
DocsBody,
|
|
4437
|
-
DocsDescription,
|
|
4438
|
-
DocsTitle,
|
|
4439
|
-
} from 'fumadocs-ui/page'
|
|
4440
|
-
import { notFound } from 'next/navigation'
|
|
4441
|
-
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
|
4442
|
-
|
|
4443
|
-
export default async function Page(props: {
|
|
4444
|
-
params: Promise<{ slug?: string[] }>
|
|
4445
|
-
}) {
|
|
4446
|
-
const params = await props.params
|
|
4447
|
-
const page = source.getPage(params.slug)
|
|
4448
|
-
if (!page) notFound()
|
|
4449
|
-
|
|
4450
|
-
const MDX = page.data.body
|
|
4451
|
-
|
|
4452
|
-
return (
|
|
4453
|
-
<DocsPage toc={page.data.toc} full={page.data.full}>
|
|
4454
|
-
<DocsTitle>{page.data.title}</DocsTitle>
|
|
4455
|
-
<DocsDescription>{page.data.description}</DocsDescription>
|
|
4456
|
-
<DocsBody>
|
|
4457
|
-
<MDX components={{ ...defaultMdxComponents }} />
|
|
4458
|
-
</DocsBody>
|
|
4459
|
-
</DocsPage>
|
|
4460
|
-
)
|
|
4461
|
-
}
|
|
4462
|
-
|
|
4463
|
-
export async function generateStaticParams() {
|
|
4464
|
-
return source.generateParams()
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
export async function generateMetadata(props: {
|
|
4468
|
-
params: Promise<{ slug?: string[] }>
|
|
4469
|
-
}) {
|
|
4470
|
-
const params = await props.params
|
|
4471
|
-
const page = source.getPage(params.slug)
|
|
4472
|
-
if (!page) notFound()
|
|
4473
|
-
|
|
4474
|
-
return {
|
|
4475
|
-
title: page.data.title,
|
|
4476
|
-
description: page.data.description,
|
|
4477
|
-
}
|
|
4478
|
-
}
|
|
4479
|
-
`;
|
|
4480
|
-
await writeFile(
|
|
4481
|
-
path16.join(docsDir, "src/app/docs/[[...slug]]/page.tsx"),
|
|
4482
|
-
docsPageContent
|
|
4483
|
-
);
|
|
4484
|
-
const homePageContent = `import { redirect } from 'next/navigation'
|
|
4485
|
-
|
|
4486
|
-
export default function HomePage() {
|
|
4487
|
-
redirect('/docs')
|
|
4488
|
-
}
|
|
4489
|
-
`;
|
|
4490
|
-
await writeFile(path16.join(docsDir, "src/app/page.tsx"), homePageContent);
|
|
4491
|
-
const globalCssContent = `@import 'tailwindcss';
|
|
4492
|
-
@import 'fumadocs-ui/css/neutral.css';
|
|
4493
|
-
@import 'fumadocs-ui/css/preset.css';
|
|
4494
|
-
|
|
4495
|
-
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
|
4496
|
-
`;
|
|
4497
|
-
await writeFile(path16.join(docsDir, "src/app/global.css"), globalCssContent);
|
|
4498
|
-
const sourceConfigContent = `import { defineDocs, defineConfig } from 'fumadocs-mdx/config'
|
|
4499
|
-
|
|
4500
|
-
export const { docs, meta } = defineDocs({
|
|
4501
|
-
dir: 'content/docs',
|
|
4502
|
-
})
|
|
4503
|
-
|
|
4504
|
-
export default defineConfig()
|
|
4505
|
-
`;
|
|
4506
|
-
await writeFile(path16.join(docsDir, "source.config.ts"), sourceConfigContent);
|
|
4507
|
-
const postcssContent = `export default {
|
|
4508
|
-
plugins: {
|
|
4509
|
-
'@tailwindcss/postcss': {},
|
|
4510
|
-
},
|
|
4511
|
-
}
|
|
4512
|
-
`;
|
|
4513
|
-
await writeFile(path16.join(docsDir, "postcss.config.mjs"), postcssContent);
|
|
4514
|
-
}
|
|
4515
|
-
async function generateFumadocsContent(config, docsDir) {
|
|
4516
|
-
const metaJson = {
|
|
4517
|
-
title: "Documentation",
|
|
4518
|
-
pages: ["index", "getting-started", "---", "guides", "api"]
|
|
4519
|
-
};
|
|
4520
|
-
await writeJSON(path16.join(docsDir, "content/docs/meta.json"), metaJson);
|
|
4521
|
-
const indexContent = `---
|
|
4522
|
-
title: Introduction
|
|
4523
|
-
description: Welcome to ${config.name} documentation
|
|
4524
|
-
---
|
|
4525
|
-
|
|
4526
|
-
# Welcome to ${config.name}
|
|
4527
|
-
|
|
4528
|
-
This is the documentation for ${config.name}, built with [Fumadocs](https://fumadocs.vercel.app).
|
|
4529
|
-
|
|
4530
|
-
## Features
|
|
4531
|
-
|
|
4532
|
-
- Built with Next.js 15 and React 19
|
|
4533
|
-
- Convex for real-time database
|
|
4534
|
-
- Better-Auth for authentication
|
|
4535
|
-
- shadcn/ui components
|
|
4536
|
-
- Tailwind CSS v4
|
|
4537
|
-
|
|
4538
|
-
## Getting Started
|
|
4539
|
-
|
|
4540
|
-
Check out the [Getting Started](/docs/getting-started) guide to begin.
|
|
4541
|
-
`;
|
|
4542
|
-
await writeFile(
|
|
4543
|
-
path16.join(docsDir, "content/docs/index.mdx"),
|
|
4544
|
-
indexContent
|
|
4545
|
-
);
|
|
4546
|
-
const gettingStartedContent = `---
|
|
4547
|
-
title: Getting Started
|
|
4548
|
-
description: Get started with ${config.name}
|
|
4549
|
-
---
|
|
4550
|
-
|
|
4551
|
-
# Getting Started
|
|
4552
|
-
|
|
4553
|
-
This guide will help you get ${config.name} up and running.
|
|
4554
|
-
|
|
4555
|
-
## Prerequisites
|
|
4556
|
-
|
|
4557
|
-
- Node.js 18 or higher
|
|
4558
|
-
- pnpm package manager
|
|
4559
|
-
|
|
4560
|
-
## Installation
|
|
4561
|
-
|
|
4562
|
-
1. Clone the repository
|
|
4563
|
-
2. Install dependencies:
|
|
4564
|
-
|
|
4565
|
-
\`\`\`bash
|
|
4566
|
-
pnpm install
|
|
4567
|
-
\`\`\`
|
|
4568
|
-
|
|
4569
|
-
3. Copy environment variables:
|
|
4570
|
-
|
|
4571
|
-
\`\`\`bash
|
|
4572
|
-
cp .env.example .env.local
|
|
4573
|
-
\`\`\`
|
|
4574
|
-
|
|
4575
|
-
4. Set up Convex:
|
|
4576
|
-
|
|
4577
|
-
\`\`\`bash
|
|
4578
|
-
pnpm convex dev
|
|
4579
|
-
\`\`\`
|
|
4580
|
-
|
|
4581
|
-
5. Start the development server:
|
|
4582
|
-
|
|
4583
|
-
\`\`\`bash
|
|
4584
|
-
pnpm dev
|
|
4585
|
-
\`\`\`
|
|
4586
|
-
|
|
4587
|
-
## Next Steps
|
|
4588
|
-
|
|
4589
|
-
- [Configure authentication](/docs/guides/authentication)
|
|
4590
|
-
- [Set up your database schema](/docs/guides/database)
|
|
4591
|
-
- [Customize the UI](/docs/guides/customization)
|
|
4592
|
-
`;
|
|
4593
|
-
await writeFile(
|
|
4594
|
-
path16.join(docsDir, "content/docs/getting-started.mdx"),
|
|
4595
|
-
gettingStartedContent
|
|
4596
|
-
);
|
|
4597
|
-
await ensureDir(path16.join(docsDir, "content/docs/guides"));
|
|
4598
|
-
const guidesMetaJson = {
|
|
4599
|
-
title: "Guides",
|
|
4600
|
-
pages: ["authentication", "database", "customization"]
|
|
4601
|
-
};
|
|
4602
|
-
await writeJSON(
|
|
4603
|
-
path16.join(docsDir, "content/docs/guides/meta.json"),
|
|
4604
|
-
guidesMetaJson
|
|
4605
|
-
);
|
|
4606
|
-
const authGuideContent = `---
|
|
4607
|
-
title: Authentication
|
|
4608
|
-
description: Learn how to configure authentication in ${config.name}
|
|
4609
|
-
---
|
|
4610
|
-
|
|
4611
|
-
# Authentication
|
|
4612
|
-
|
|
4613
|
-
${config.name} uses Better-Auth with Convex for authentication.
|
|
4614
|
-
|
|
4615
|
-
## Supported Providers
|
|
4616
|
-
|
|
4617
|
-
- Email/Password (always enabled)
|
|
4618
|
-
- Google OAuth (always enabled)
|
|
4619
|
-
${config.auth.providers.map((p8) => `- ${p8.charAt(0).toUpperCase() + p8.slice(1)}`).join("\n")}
|
|
4620
|
-
|
|
4621
|
-
## Configuration
|
|
4622
|
-
|
|
4623
|
-
Authentication is configured in \`convex/auth.ts\`.
|
|
4624
|
-
|
|
4625
|
-
\`\`\`typescript
|
|
4626
|
-
export const { auth, createAuth } = new BetterAuth<DataModel>(components.betterAuth, {
|
|
4627
|
-
emailAndPassword: {
|
|
4628
|
-
enabled: true,
|
|
4629
|
-
},
|
|
4630
|
-
socialProviders: {
|
|
4631
|
-
google: {
|
|
4632
|
-
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
4633
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
4634
|
-
},
|
|
4635
|
-
},
|
|
4636
|
-
})
|
|
4637
|
-
\`\`\`
|
|
4638
|
-
|
|
4639
|
-
## Using Authentication
|
|
4640
|
-
|
|
4641
|
-
Use the auth client in your components:
|
|
4642
|
-
|
|
4643
|
-
\`\`\`typescript
|
|
4644
|
-
import { signIn, signOut, useSession } from '@/lib/auth'
|
|
4645
|
-
|
|
4646
|
-
// Sign in
|
|
4647
|
-
await signIn.email({ email, password })
|
|
4648
|
-
|
|
4649
|
-
// Sign out
|
|
4650
|
-
await signOut()
|
|
4651
|
-
|
|
4652
|
-
// Get session
|
|
4653
|
-
const { data: session } = useSession()
|
|
4654
|
-
\`\`\`
|
|
4655
|
-
`;
|
|
4656
|
-
await writeFile(
|
|
4657
|
-
path16.join(docsDir, "content/docs/guides/authentication.mdx"),
|
|
4658
|
-
authGuideContent
|
|
4659
|
-
);
|
|
4660
|
-
const databaseGuideContent = `---
|
|
4661
|
-
title: Database
|
|
4662
|
-
description: Working with Convex database
|
|
4663
|
-
---
|
|
4664
|
-
|
|
4665
|
-
# Database
|
|
4666
|
-
|
|
4667
|
-
${config.name} uses Convex as its database.
|
|
4668
|
-
|
|
4669
|
-
## Schema
|
|
4670
|
-
|
|
4671
|
-
The database schema is defined in \`convex/schema.ts\`.
|
|
4672
|
-
|
|
4673
|
-
## Queries
|
|
4674
|
-
|
|
4675
|
-
Create queries to read data:
|
|
4676
|
-
|
|
4677
|
-
\`\`\`typescript
|
|
4678
|
-
import { query } from './_generated/server'
|
|
4679
|
-
|
|
4680
|
-
export const getUsers = query({
|
|
4681
|
-
handler: async (ctx) => {
|
|
4682
|
-
return await ctx.db.query('users').collect()
|
|
4683
|
-
},
|
|
4684
|
-
})
|
|
4685
|
-
\`\`\`
|
|
4686
|
-
|
|
4687
|
-
## Mutations
|
|
4688
|
-
|
|
4689
|
-
Create mutations to write data:
|
|
4690
|
-
|
|
4691
|
-
\`\`\`typescript
|
|
4692
|
-
import { mutation } from './_generated/server'
|
|
4693
|
-
import { v } from 'convex/values'
|
|
4694
|
-
|
|
4695
|
-
export const createUser = mutation({
|
|
4696
|
-
args: {
|
|
4697
|
-
name: v.string(),
|
|
4698
|
-
email: v.string(),
|
|
4699
|
-
},
|
|
4700
|
-
handler: async (ctx, args) => {
|
|
4701
|
-
return await ctx.db.insert('users', args)
|
|
4702
|
-
},
|
|
4703
|
-
})
|
|
4704
|
-
\`\`\`
|
|
4705
|
-
`;
|
|
4706
|
-
await writeFile(
|
|
4707
|
-
path16.join(docsDir, "content/docs/guides/database.mdx"),
|
|
4708
|
-
databaseGuideContent
|
|
4709
|
-
);
|
|
4710
|
-
const customizationGuideContent = `---
|
|
4711
|
-
title: Customization
|
|
4712
|
-
description: Customize the look and feel of ${config.name}
|
|
4713
|
-
---
|
|
4714
|
-
|
|
4715
|
-
# Customization
|
|
4716
|
-
|
|
4717
|
-
## Theme
|
|
4718
|
-
|
|
4719
|
-
The theme is configured in \`src/app/globals.css\` using Tailwind CSS v4.
|
|
4720
|
-
|
|
4721
|
-
### Colors
|
|
4722
|
-
|
|
4723
|
-
Update the theme colors in the CSS:
|
|
4724
|
-
|
|
4725
|
-
\`\`\`css
|
|
4726
|
-
@theme {
|
|
4727
|
-
--color-primary: oklch(0.6 0.2 250);
|
|
4728
|
-
--color-primary-foreground: oklch(0.98 0 0);
|
|
4729
|
-
}
|
|
4730
|
-
\`\`\`
|
|
4731
|
-
|
|
4732
|
-
### Dark Mode
|
|
4733
|
-
|
|
4734
|
-
Dark mode is supported out of the box. Define dark mode styles with:
|
|
4735
|
-
|
|
4736
|
-
\`\`\`css
|
|
4737
|
-
.dark {
|
|
4738
|
-
--color-background: oklch(0.145 0 0);
|
|
4739
|
-
}
|
|
4740
|
-
\`\`\`
|
|
4741
|
-
|
|
4742
|
-
## Components
|
|
4743
|
-
|
|
4744
|
-
UI components are in \`src/components/ui\`. Customize or add new components as needed.
|
|
4745
|
-
`;
|
|
4746
|
-
await writeFile(
|
|
4747
|
-
path16.join(docsDir, "content/docs/guides/customization.mdx"),
|
|
4748
|
-
customizationGuideContent
|
|
4749
|
-
);
|
|
4750
|
-
await ensureDir(path16.join(docsDir, "content/docs/api"));
|
|
4751
|
-
const apiMetaJson = {
|
|
4752
|
-
title: "API Reference",
|
|
4753
|
-
pages: ["overview"]
|
|
4754
|
-
};
|
|
4755
|
-
await writeJSON(
|
|
4756
|
-
path16.join(docsDir, "content/docs/api/meta.json"),
|
|
4757
|
-
apiMetaJson
|
|
4758
|
-
);
|
|
4759
|
-
const apiOverviewContent = `---
|
|
4760
|
-
title: API Overview
|
|
4761
|
-
description: API reference for ${config.name}
|
|
4762
|
-
---
|
|
4763
|
-
|
|
4764
|
-
# API Reference
|
|
4765
|
-
|
|
4766
|
-
This section contains the API reference for ${config.name}.
|
|
4767
|
-
|
|
4768
|
-
## Authentication API
|
|
4769
|
-
|
|
4770
|
-
See the [Better-Auth documentation](https://www.better-auth.com) for detailed API reference.
|
|
4771
|
-
|
|
4772
|
-
## Database API
|
|
4773
|
-
|
|
4774
|
-
See the [Convex documentation](https://docs.convex.dev) for database API reference.
|
|
4775
|
-
`;
|
|
4776
|
-
await writeFile(
|
|
4777
|
-
path16.join(docsDir, "content/docs/api/overview.mdx"),
|
|
4778
|
-
apiOverviewContent
|
|
4779
|
-
);
|
|
4780
|
-
}
|
|
4781
|
-
|
|
4782
4271
|
// src/generators/design-system.ts
|
|
4783
|
-
import
|
|
4272
|
+
import path16 from "path";
|
|
4784
4273
|
var fontImports = {
|
|
4785
4274
|
inter: {
|
|
4786
4275
|
import: "import { Inter } from 'next/font/google'",
|
|
@@ -4848,17 +4337,17 @@ var spacingMultipliers = {
|
|
|
4848
4337
|
relaxed: 1.125
|
|
4849
4338
|
};
|
|
4850
4339
|
async function generateDesignSystemApp(config, targetDir) {
|
|
4851
|
-
const appDir =
|
|
4852
|
-
await ensureDir(
|
|
4853
|
-
await ensureDir(
|
|
4854
|
-
await ensureDir(
|
|
4855
|
-
await ensureDir(
|
|
4856
|
-
await ensureDir(
|
|
4857
|
-
await ensureDir(
|
|
4858
|
-
await ensureDir(
|
|
4859
|
-
await ensureDir(
|
|
4860
|
-
await ensureDir(
|
|
4861
|
-
await ensureDir(
|
|
4340
|
+
const appDir = path16.join(targetDir, "apps/design-system");
|
|
4341
|
+
await ensureDir(path16.join(appDir, "src/app"));
|
|
4342
|
+
await ensureDir(path16.join(appDir, "src/app/colors"));
|
|
4343
|
+
await ensureDir(path16.join(appDir, "src/app/typography"));
|
|
4344
|
+
await ensureDir(path16.join(appDir, "src/app/spacing"));
|
|
4345
|
+
await ensureDir(path16.join(appDir, "src/app/brand"));
|
|
4346
|
+
await ensureDir(path16.join(appDir, "src/app/blocks"));
|
|
4347
|
+
await ensureDir(path16.join(appDir, "src/app/components"));
|
|
4348
|
+
await ensureDir(path16.join(appDir, "src/app/components/[slug]"));
|
|
4349
|
+
await ensureDir(path16.join(appDir, "src/lib"));
|
|
4350
|
+
await ensureDir(path16.join(appDir, "public/brand"));
|
|
4862
4351
|
await generatePackageJson2(config, appDir);
|
|
4863
4352
|
await generateTsConfig2(appDir);
|
|
4864
4353
|
await generateNextConfig2(appDir);
|
|
@@ -4911,7 +4400,7 @@ async function generatePackageJson2(config, appDir) {
|
|
|
4911
4400
|
typescript: "^5.0.0"
|
|
4912
4401
|
}
|
|
4913
4402
|
};
|
|
4914
|
-
await writeJSON(
|
|
4403
|
+
await writeJSON(path16.join(appDir, "package.json"), packageJson2);
|
|
4915
4404
|
}
|
|
4916
4405
|
async function generateTsConfig2(appDir) {
|
|
4917
4406
|
const tsConfig = {
|
|
@@ -4939,7 +4428,7 @@ async function generateTsConfig2(appDir) {
|
|
|
4939
4428
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
4940
4429
|
exclude: ["node_modules"]
|
|
4941
4430
|
};
|
|
4942
|
-
await writeJSON(
|
|
4431
|
+
await writeJSON(path16.join(appDir, "tsconfig.json"), tsConfig);
|
|
4943
4432
|
}
|
|
4944
4433
|
async function generateNextConfig2(appDir) {
|
|
4945
4434
|
const content = `import type { NextConfig } from 'next'
|
|
@@ -4950,7 +4439,7 @@ const nextConfig: NextConfig = {
|
|
|
4950
4439
|
|
|
4951
4440
|
export default nextConfig
|
|
4952
4441
|
`;
|
|
4953
|
-
await writeFile(
|
|
4442
|
+
await writeFile(path16.join(appDir, "next.config.ts"), content);
|
|
4954
4443
|
}
|
|
4955
4444
|
async function generatePostCssConfig2(appDir) {
|
|
4956
4445
|
const content = `export default {
|
|
@@ -4959,7 +4448,7 @@ async function generatePostCssConfig2(appDir) {
|
|
|
4959
4448
|
},
|
|
4960
4449
|
}
|
|
4961
4450
|
`;
|
|
4962
|
-
await writeFile(
|
|
4451
|
+
await writeFile(path16.join(appDir, "postcss.config.mjs"), content);
|
|
4963
4452
|
}
|
|
4964
4453
|
async function generateGlobalsCss2(config, appDir) {
|
|
4965
4454
|
const { designSystem } = config;
|
|
@@ -5219,7 +4708,7 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
5219
4708
|
}
|
|
5220
4709
|
}
|
|
5221
4710
|
`;
|
|
5222
|
-
await writeFile(
|
|
4711
|
+
await writeFile(path16.join(appDir, "src/app/globals.css"), content);
|
|
5223
4712
|
}
|
|
5224
4713
|
async function generateLayoutTsx(config, appDir) {
|
|
5225
4714
|
const { designSystem } = config;
|
|
@@ -5418,7 +4907,7 @@ function ThemeToggle() {
|
|
|
5418
4907
|
)
|
|
5419
4908
|
}
|
|
5420
4909
|
`;
|
|
5421
|
-
await writeFile(
|
|
4910
|
+
await writeFile(path16.join(appDir, "src/app/layout.tsx"), content);
|
|
5422
4911
|
}
|
|
5423
4912
|
async function generateHomePage(config, appDir) {
|
|
5424
4913
|
const content = `export default function DesignSystemHome() {
|
|
@@ -5584,7 +5073,7 @@ function Principle({
|
|
|
5584
5073
|
)
|
|
5585
5074
|
}
|
|
5586
5075
|
`;
|
|
5587
|
-
await writeFile(
|
|
5076
|
+
await writeFile(path16.join(appDir, "src/app/page.tsx"), content);
|
|
5588
5077
|
}
|
|
5589
5078
|
async function generateColorsPage(config, appDir) {
|
|
5590
5079
|
const content = `export default function ColorsPage() {
|
|
@@ -5753,7 +5242,7 @@ function ColorCard({
|
|
|
5753
5242
|
)
|
|
5754
5243
|
}
|
|
5755
5244
|
`;
|
|
5756
|
-
await writeFile(
|
|
5245
|
+
await writeFile(path16.join(appDir, "src/app/colors/page.tsx"), content);
|
|
5757
5246
|
}
|
|
5758
5247
|
async function generateTypographyPage(config, appDir) {
|
|
5759
5248
|
const { designSystem } = config;
|
|
@@ -5910,7 +5399,7 @@ function WeightCard({ weight, name }: { weight: string; name: string }) {
|
|
|
5910
5399
|
)
|
|
5911
5400
|
}
|
|
5912
5401
|
`;
|
|
5913
|
-
await writeFile(
|
|
5402
|
+
await writeFile(path16.join(appDir, "src/app/typography/page.tsx"), content);
|
|
5914
5403
|
}
|
|
5915
5404
|
async function generateSpacingPage(config, appDir) {
|
|
5916
5405
|
const { designSystem } = config;
|
|
@@ -6043,7 +5532,7 @@ function PatternCard({
|
|
|
6043
5532
|
)
|
|
6044
5533
|
}
|
|
6045
5534
|
`;
|
|
6046
|
-
await writeFile(
|
|
5535
|
+
await writeFile(path16.join(appDir, "src/app/spacing/page.tsx"), content);
|
|
6047
5536
|
}
|
|
6048
5537
|
async function generateBrandPage(config, appDir) {
|
|
6049
5538
|
const content = `export default function BrandPage() {
|
|
@@ -6181,7 +5670,7 @@ function DownloadCard({
|
|
|
6181
5670
|
)
|
|
6182
5671
|
}
|
|
6183
5672
|
`;
|
|
6184
|
-
await writeFile(
|
|
5673
|
+
await writeFile(path16.join(appDir, "src/app/brand/page.tsx"), content);
|
|
6185
5674
|
}
|
|
6186
5675
|
async function generateBlocksPage(appDir) {
|
|
6187
5676
|
const content = `export default function BlocksPage() {
|
|
@@ -6243,7 +5732,7 @@ function BlockCard({ title, description }: { title: string; description: string
|
|
|
6243
5732
|
)
|
|
6244
5733
|
}
|
|
6245
5734
|
`;
|
|
6246
|
-
await writeFile(
|
|
5735
|
+
await writeFile(path16.join(appDir, "src/app/blocks/page.tsx"), content);
|
|
6247
5736
|
}
|
|
6248
5737
|
async function generateComponentsPage(appDir) {
|
|
6249
5738
|
const content = `export default function ComponentsPage() {
|
|
@@ -6323,7 +5812,7 @@ function CategoryCard({
|
|
|
6323
5812
|
)
|
|
6324
5813
|
}
|
|
6325
5814
|
`;
|
|
6326
|
-
await writeFile(
|
|
5815
|
+
await writeFile(path16.join(appDir, "src/app/components/page.tsx"), content);
|
|
6327
5816
|
}
|
|
6328
5817
|
async function generateComponentSlugPage(appDir) {
|
|
6329
5818
|
const content = `import { notFound } from 'next/navigation'
|
|
@@ -6427,7 +5916,7 @@ export function generateStaticParams() {
|
|
|
6427
5916
|
return components.map((slug) => ({ slug }))
|
|
6428
5917
|
}
|
|
6429
5918
|
`;
|
|
6430
|
-
await writeFile(
|
|
5919
|
+
await writeFile(path16.join(appDir, "src/app/components/[slug]/page.tsx"), content);
|
|
6431
5920
|
}
|
|
6432
5921
|
async function generateUtils(appDir) {
|
|
6433
5922
|
const content = `import { clsx, type ClassValue } from 'clsx'
|
|
@@ -6437,7 +5926,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6437
5926
|
return twMerge(clsx(inputs))
|
|
6438
5927
|
}
|
|
6439
5928
|
`;
|
|
6440
|
-
await writeFile(
|
|
5929
|
+
await writeFile(path16.join(appDir, "src/lib/utils.ts"), content);
|
|
6441
5930
|
}
|
|
6442
5931
|
|
|
6443
5932
|
// src/setup/env-wizard.ts
|
|
@@ -6445,7 +5934,7 @@ import * as p6 from "@clack/prompts";
|
|
|
6445
5934
|
import pc2 from "picocolors";
|
|
6446
5935
|
import { exec } from "child_process";
|
|
6447
5936
|
import { promisify } from "util";
|
|
6448
|
-
import
|
|
5937
|
+
import path17 from "path";
|
|
6449
5938
|
var execAsync = promisify(exec);
|
|
6450
5939
|
async function runEnvSetupWizard(config) {
|
|
6451
5940
|
console.log();
|
|
@@ -6526,7 +6015,7 @@ This will open your browser to create a new project on Convex.
|
|
|
6526
6015
|
${pc2.dim("Note: Make sure you have a Convex account at https://convex.dev")}
|
|
6527
6016
|
`);
|
|
6528
6017
|
try {
|
|
6529
|
-
const convexDir = config.structure === "monorepo" ?
|
|
6018
|
+
const convexDir = config.structure === "monorepo" ? path17.join(config.targetDir, "packages/backend") : config.targetDir;
|
|
6530
6019
|
const { spawn } = await import("child_process");
|
|
6531
6020
|
p6.log.info(`Installing dependencies in ${pc2.dim(convexDir)}...`);
|
|
6532
6021
|
const installSuccess = await new Promise((resolve) => {
|
|
@@ -6802,7 +6291,7 @@ async function writeEnvFiles(config, envValues) {
|
|
|
6802
6291
|
return;
|
|
6803
6292
|
}
|
|
6804
6293
|
p6.log.step("Writing environment files...");
|
|
6805
|
-
const webEnvPath = config.structure === "monorepo" ?
|
|
6294
|
+
const webEnvPath = config.structure === "monorepo" ? path17.join(config.targetDir, "apps/web/.env.local") : path17.join(config.targetDir, ".env.local");
|
|
6806
6295
|
if (await pathExists(webEnvPath)) {
|
|
6807
6296
|
let content = await readFile(webEnvPath);
|
|
6808
6297
|
for (const [key, value] of Object.entries(envValues)) {
|
|
@@ -6821,7 +6310,7 @@ ${key}="${value}"`;
|
|
|
6821
6310
|
p6.log.success(`Updated ${config.structure === "monorepo" ? "apps/web/.env.local" : ".env.local"}`);
|
|
6822
6311
|
}
|
|
6823
6312
|
if (config.marketingSite === "payload" && config.structure === "monorepo") {
|
|
6824
|
-
const marketingEnvPath =
|
|
6313
|
+
const marketingEnvPath = path17.join(config.targetDir, "apps/marketing/.env.local");
|
|
6825
6314
|
if (await pathExists(marketingEnvPath)) {
|
|
6826
6315
|
let content = await readFile(marketingEnvPath);
|
|
6827
6316
|
const payloadEnvKeys = ["PAYLOAD_SECRET", "DATABASE_URL", "S3_ENDPOINT", "S3_REGION", "S3_BUCKET", "S3_ACCESS_KEY_ID", "S3_SECRET_ACCESS_KEY", "CRON_SECRET", "PREVIEW_SECRET"];
|
|
@@ -6882,7 +6371,7 @@ async function generateSingleProject(config, spinner) {
|
|
|
6882
6371
|
await generateShadcn(config, targetDir);
|
|
6883
6372
|
spinner.succeed("shadcn/ui configured");
|
|
6884
6373
|
spinner.start("Setting up Convex...");
|
|
6885
|
-
await generateConvex(config,
|
|
6374
|
+
await generateConvex(config, path18.join(targetDir, "convex"));
|
|
6886
6375
|
spinner.succeed("Convex configured");
|
|
6887
6376
|
spinner.start("Configuring Better-Auth...");
|
|
6888
6377
|
await generateBetterAuth(config, targetDir);
|
|
@@ -6925,7 +6414,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6925
6414
|
spinner.start("Creating shared UI package...");
|
|
6926
6415
|
await generateUIPackage(config, targetDir);
|
|
6927
6416
|
spinner.succeed("Shared UI package created");
|
|
6928
|
-
const webDir =
|
|
6417
|
+
const webDir = path18.join(targetDir, "apps/web");
|
|
6929
6418
|
spinner.start("Generating web application...");
|
|
6930
6419
|
await generateBaseNextjs(config, webDir);
|
|
6931
6420
|
await generateTailwind(config, webDir);
|
|
@@ -6935,10 +6424,10 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6935
6424
|
await generateEmail(config, webDir);
|
|
6936
6425
|
await updateWebTsConfig(webDir);
|
|
6937
6426
|
spinner.succeed("Web application generated");
|
|
6938
|
-
const backendDir =
|
|
6427
|
+
const backendDir = path18.join(targetDir, "packages/backend");
|
|
6939
6428
|
spinner.start("Setting up Convex backend...");
|
|
6940
6429
|
await ensureDir(backendDir);
|
|
6941
|
-
await generateConvex(config,
|
|
6430
|
+
await generateConvex(config, path18.join(backendDir, "convex"));
|
|
6942
6431
|
spinner.succeed("Convex backend configured");
|
|
6943
6432
|
if (config.integrations.analytics !== "none") {
|
|
6944
6433
|
spinner.start(`Setting up ${config.integrations.analytics} analytics...`);
|
|
@@ -6961,7 +6450,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6961
6450
|
spinner.succeed(`${config.integrations.monitoring} monitoring configured`);
|
|
6962
6451
|
}
|
|
6963
6452
|
if (config.marketingSite !== "none") {
|
|
6964
|
-
const marketingDir =
|
|
6453
|
+
const marketingDir = path18.join(targetDir, "apps/marketing");
|
|
6965
6454
|
spinner.start(`Generating ${config.marketingSite} marketing site...`);
|
|
6966
6455
|
if (config.marketingSite === "payload") {
|
|
6967
6456
|
await generatePayload(config, marketingDir);
|
|
@@ -6970,12 +6459,6 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6970
6459
|
}
|
|
6971
6460
|
spinner.succeed(`${config.marketingSite} marketing site generated`);
|
|
6972
6461
|
}
|
|
6973
|
-
if (config.includeDocs) {
|
|
6974
|
-
const docsDir = path19.join(targetDir, "apps/docs");
|
|
6975
|
-
spinner.start("Generating Fumadocs documentation site...");
|
|
6976
|
-
await generateFumadocs(config, docsDir);
|
|
6977
|
-
spinner.succeed("Fumadocs documentation site generated");
|
|
6978
|
-
}
|
|
6979
6462
|
spinner.start("Generating design system app...");
|
|
6980
6463
|
await generateDesignSystemApp(config, targetDir);
|
|
6981
6464
|
spinner.succeed("Design system app generated");
|
|
@@ -6992,7 +6475,7 @@ async function runPostGenerationSteps(config, spinner) {
|
|
|
6992
6475
|
}
|
|
6993
6476
|
spinner.start("Installing shadcn/ui components...");
|
|
6994
6477
|
try {
|
|
6995
|
-
const shadcnDir = config.structure === "monorepo" ?
|
|
6478
|
+
const shadcnDir = config.structure === "monorepo" ? path18.join(targetDir, "packages/ui") : targetDir;
|
|
6996
6479
|
await runShadcnAdd(shadcnDir);
|
|
6997
6480
|
spinner.succeed("shadcn/ui components installed");
|
|
6998
6481
|
} catch (error) {
|
|
@@ -7052,7 +6535,7 @@ async function generateBiomeConfig(targetDir) {
|
|
|
7052
6535
|
}
|
|
7053
6536
|
};
|
|
7054
6537
|
await writeFile(
|
|
7055
|
-
|
|
6538
|
+
path18.join(targetDir, "biome.json"),
|
|
7056
6539
|
JSON.stringify(biomeJson, null, 2)
|
|
7057
6540
|
);
|
|
7058
6541
|
}
|
|
@@ -7103,13 +6586,13 @@ yarn-error.log*
|
|
|
7103
6586
|
*.pem
|
|
7104
6587
|
.cache
|
|
7105
6588
|
`;
|
|
7106
|
-
await writeFile(
|
|
6589
|
+
await writeFile(path18.join(targetDir, ".gitignore"), gitignoreContent);
|
|
7107
6590
|
}
|
|
7108
6591
|
async function generateHuskyHooks(targetDir) {
|
|
7109
6592
|
const preCommitContent = `pnpm lint-staged
|
|
7110
6593
|
`;
|
|
7111
|
-
await ensureDir(
|
|
7112
|
-
await writeFile(
|
|
6594
|
+
await ensureDir(path18.join(targetDir, ".husky"));
|
|
6595
|
+
await writeFile(path18.join(targetDir, ".husky/pre-commit"), preCommitContent);
|
|
7113
6596
|
}
|
|
7114
6597
|
async function updateWebTsConfig(webDir) {
|
|
7115
6598
|
const tsConfig = {
|
|
@@ -7136,14 +6619,14 @@ async function updateWebTsConfig(webDir) {
|
|
|
7136
6619
|
exclude: ["node_modules"]
|
|
7137
6620
|
};
|
|
7138
6621
|
await writeFile(
|
|
7139
|
-
|
|
6622
|
+
path18.join(webDir, "tsconfig.json"),
|
|
7140
6623
|
JSON.stringify(tsConfig, null, 2)
|
|
7141
6624
|
);
|
|
7142
6625
|
}
|
|
7143
6626
|
async function generateNextjsMarketing(config, marketingDir) {
|
|
7144
|
-
await ensureDir(
|
|
7145
|
-
await ensureDir(
|
|
7146
|
-
await ensureDir(
|
|
6627
|
+
await ensureDir(path18.join(marketingDir, "src/app"));
|
|
6628
|
+
await ensureDir(path18.join(marketingDir, "src/components"));
|
|
6629
|
+
await ensureDir(path18.join(marketingDir, "public"));
|
|
7147
6630
|
const packageJson2 = {
|
|
7148
6631
|
name: "@repo/marketing",
|
|
7149
6632
|
version: "0.1.0",
|
|
@@ -7174,7 +6657,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7174
6657
|
}
|
|
7175
6658
|
};
|
|
7176
6659
|
await writeFile(
|
|
7177
|
-
|
|
6660
|
+
path18.join(marketingDir, "package.json"),
|
|
7178
6661
|
JSON.stringify(packageJson2, null, 2)
|
|
7179
6662
|
);
|
|
7180
6663
|
const homePageContent = `export default function HomePage() {
|
|
@@ -7197,7 +6680,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7197
6680
|
}
|
|
7198
6681
|
`;
|
|
7199
6682
|
await writeFile(
|
|
7200
|
-
|
|
6683
|
+
path18.join(marketingDir, "src/app/page.tsx"),
|
|
7201
6684
|
homePageContent
|
|
7202
6685
|
);
|
|
7203
6686
|
const layoutContent = `import type { Metadata } from 'next'
|
|
@@ -7221,7 +6704,7 @@ export default function RootLayout({
|
|
|
7221
6704
|
}
|
|
7222
6705
|
`;
|
|
7223
6706
|
await writeFile(
|
|
7224
|
-
|
|
6707
|
+
path18.join(marketingDir, "src/app/layout.tsx"),
|
|
7225
6708
|
layoutContent
|
|
7226
6709
|
);
|
|
7227
6710
|
const globalsCss = `@import "tailwindcss";
|
|
@@ -7233,7 +6716,7 @@ export default function RootLayout({
|
|
|
7233
6716
|
}
|
|
7234
6717
|
`;
|
|
7235
6718
|
await writeFile(
|
|
7236
|
-
|
|
6719
|
+
path18.join(marketingDir, "src/app/globals.css"),
|
|
7237
6720
|
globalsCss
|
|
7238
6721
|
);
|
|
7239
6722
|
const tsConfig = {
|
|
@@ -7260,7 +6743,7 @@ export default function RootLayout({
|
|
|
7260
6743
|
exclude: ["node_modules"]
|
|
7261
6744
|
};
|
|
7262
6745
|
await writeFile(
|
|
7263
|
-
|
|
6746
|
+
path18.join(marketingDir, "tsconfig.json"),
|
|
7264
6747
|
JSON.stringify(tsConfig, null, 2)
|
|
7265
6748
|
);
|
|
7266
6749
|
const nextConfig = `import type { NextConfig } from 'next'
|
|
@@ -7271,14 +6754,14 @@ const nextConfig: NextConfig = {
|
|
|
7271
6754
|
|
|
7272
6755
|
export default nextConfig
|
|
7273
6756
|
`;
|
|
7274
|
-
await writeFile(
|
|
6757
|
+
await writeFile(path18.join(marketingDir, "next.config.ts"), nextConfig);
|
|
7275
6758
|
const postcssConfig = `export default {
|
|
7276
6759
|
plugins: {
|
|
7277
6760
|
'@tailwindcss/postcss': {},
|
|
7278
6761
|
},
|
|
7279
6762
|
}
|
|
7280
6763
|
`;
|
|
7281
|
-
await writeFile(
|
|
6764
|
+
await writeFile(path18.join(marketingDir, "postcss.config.mjs"), postcssConfig);
|
|
7282
6765
|
}
|
|
7283
6766
|
function displaySuccessMessage(config) {
|
|
7284
6767
|
const apps = ["web"];
|
|
@@ -7286,9 +6769,6 @@ function displaySuccessMessage(config) {
|
|
|
7286
6769
|
if (config.marketingSite !== "none") {
|
|
7287
6770
|
apps.push("marketing");
|
|
7288
6771
|
}
|
|
7289
|
-
if (config.includeDocs) {
|
|
7290
|
-
apps.push("docs");
|
|
7291
|
-
}
|
|
7292
6772
|
apps.push("design-system");
|
|
7293
6773
|
}
|
|
7294
6774
|
console.log();
|
|
@@ -7321,9 +6801,6 @@ function displaySuccessMessage(config) {
|
|
|
7321
6801
|
if (config.marketingSite === "payload") {
|
|
7322
6802
|
console.log(` ${pc3.dim("-")} Payload CMS: ${pc3.cyan("https://payloadcms.com/docs")}`);
|
|
7323
6803
|
}
|
|
7324
|
-
if (config.includeDocs) {
|
|
7325
|
-
console.log(` ${pc3.dim("-")} Fumadocs: ${pc3.cyan("https://fumadocs.vercel.app")}`);
|
|
7326
|
-
}
|
|
7327
6804
|
console.log();
|
|
7328
6805
|
}
|
|
7329
6806
|
|
|
@@ -7351,7 +6828,7 @@ program.name("create-kofi-stack").description(
|
|
|
7351
6828
|
).version(packageJson.version).argument("[project-name]", "Name of the project").option("--monorepo", "Use monorepo structure with Turborepo").option("--single", "Use single app structure").option(
|
|
7352
6829
|
"--marketing <type>",
|
|
7353
6830
|
"Marketing site type: payload, nextjs, none"
|
|
7354
|
-
).option(
|
|
6831
|
+
).option(
|
|
7355
6832
|
"--component-library <library>",
|
|
7356
6833
|
"Component library: base (Base UI), radix (Radix UI)"
|
|
7357
6834
|
).option(
|