create-kofi-stack 1.2.17 → 1.2.19
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 +77 -616
- 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,17 @@ export const {
|
|
|
1061
1037
|
} = authClient
|
|
1062
1038
|
`;
|
|
1063
1039
|
await writeFile(path3.join(appDir, "src/lib/auth.ts"), authClientContent);
|
|
1064
|
-
const
|
|
1065
|
-
const convexContent = isMonorepo ? `import { ConvexProvider, ConvexReactClient } from 'convex/react'
|
|
1040
|
+
const convexContent = `import { ConvexProvider, ConvexReactClient } from 'convex/react'
|
|
1066
1041
|
import { env } from '@/env'
|
|
1067
1042
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1043
|
+
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
|
1044
|
+
if (!convexUrl) {
|
|
1045
|
+
throw new Error(
|
|
1046
|
+
'NEXT_PUBLIC_CONVEX_URL is not set. Run "npx convex dev" to set up Convex.'
|
|
1047
|
+
)
|
|
1048
|
+
}
|
|
1073
1049
|
|
|
1074
|
-
export const convex = new ConvexReactClient(
|
|
1050
|
+
export const convex = new ConvexReactClient(convexUrl)
|
|
1075
1051
|
|
|
1076
1052
|
export { ConvexProvider }
|
|
1077
1053
|
`;
|
|
@@ -2058,12 +2034,19 @@ import { convexAdapter } from '@convex-dev/better-auth/adapter'
|
|
|
2058
2034
|
import { ConvexHttpClient } from 'convex/browser'
|
|
2059
2035
|
import { env } from '@/env'
|
|
2060
2036
|
|
|
2061
|
-
const
|
|
2037
|
+
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
|
2038
|
+
if (!convexUrl) {
|
|
2039
|
+
throw new Error(
|
|
2040
|
+
'NEXT_PUBLIC_CONVEX_URL is not set. Run "npx convex dev" to set up Convex.'
|
|
2041
|
+
)
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
const convex = new ConvexHttpClient(convexUrl)
|
|
2062
2045
|
|
|
2063
2046
|
export const auth = betterAuth({
|
|
2064
2047
|
database: convexAdapter(convex),
|
|
2065
|
-
secret: env.BETTER_AUTH_SECRET,
|
|
2066
|
-
baseURL: env.NEXT_PUBLIC_APP_URL,
|
|
2048
|
+
secret: env.BETTER_AUTH_SECRET || '',
|
|
2049
|
+
baseURL: env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
2067
2050
|
emailAndPassword: {
|
|
2068
2051
|
enabled: true,
|
|
2069
2052
|
sendResetPassword: async ({ user, url }) => {
|
|
@@ -2762,19 +2745,18 @@ async function generateRateLimiting(config, appDir) {
|
|
|
2762
2745
|
}
|
|
2763
2746
|
}
|
|
2764
2747
|
async function generateArcjet(appDir) {
|
|
2765
|
-
const content = `import arcjet, { shield,
|
|
2748
|
+
const content = `import arcjet, { shield, fixedWindow, detectBot } from '@arcjet/next'
|
|
2766
2749
|
import { env } from '@/env'
|
|
2767
2750
|
|
|
2768
2751
|
export const aj = arcjet({
|
|
2769
|
-
key: env.ARCJET_KEY
|
|
2770
|
-
characteristics: ['ip.src'],
|
|
2752
|
+
key: env.ARCJET_KEY!,
|
|
2771
2753
|
rules: [
|
|
2772
2754
|
// Shield protects against common attacks
|
|
2773
2755
|
shield({ mode: 'LIVE' }),
|
|
2774
|
-
// Rate limit API requests
|
|
2775
|
-
|
|
2756
|
+
// Rate limit API requests - 100 requests per minute
|
|
2757
|
+
fixedWindow({
|
|
2776
2758
|
mode: 'LIVE',
|
|
2777
|
-
|
|
2759
|
+
window: '1m',
|
|
2778
2760
|
max: 100,
|
|
2779
2761
|
}),
|
|
2780
2762
|
// Bot detection
|
|
@@ -4263,517 +4245,8 @@ S3_ENDPOINT="https://[PROJECT].supabase.co/storage/v1/s3"
|
|
|
4263
4245
|
await writeFile(path15.join(marketingDir, ".env.local"), envContent);
|
|
4264
4246
|
}
|
|
4265
4247
|
|
|
4266
|
-
// src/generators/fumadocs.ts
|
|
4267
|
-
import path16 from "path";
|
|
4268
|
-
async function generateFumadocs(config, docsDir) {
|
|
4269
|
-
await ensureDir(path16.join(docsDir, "content/docs"));
|
|
4270
|
-
await ensureDir(path16.join(docsDir, "src/app/docs/[[...slug]]"));
|
|
4271
|
-
await ensureDir(path16.join(docsDir, "src/lib"));
|
|
4272
|
-
await generateFumadocsPackageJson(docsDir);
|
|
4273
|
-
await generateFumadocsNextConfig(docsDir);
|
|
4274
|
-
await generateFumadocsTsConfig(docsDir);
|
|
4275
|
-
await generateFumadocsSource(docsDir);
|
|
4276
|
-
await generateFumadocsAppFiles(config, docsDir);
|
|
4277
|
-
await generateFumadocsContent(config, docsDir);
|
|
4278
|
-
}
|
|
4279
|
-
async function generateFumadocsPackageJson(docsDir) {
|
|
4280
|
-
const packageJson2 = {
|
|
4281
|
-
name: "@repo/docs",
|
|
4282
|
-
version: "0.1.0",
|
|
4283
|
-
private: true,
|
|
4284
|
-
scripts: {
|
|
4285
|
-
dev: "next dev --turbopack -p 3002",
|
|
4286
|
-
build: "next build",
|
|
4287
|
-
start: "next start",
|
|
4288
|
-
lint: "biome check .",
|
|
4289
|
-
"lint:fix": "biome check --write .",
|
|
4290
|
-
typecheck: "tsc --noEmit"
|
|
4291
|
-
},
|
|
4292
|
-
dependencies: {
|
|
4293
|
-
next: "^16.0.0",
|
|
4294
|
-
react: "^19.0.0",
|
|
4295
|
-
"react-dom": "^19.0.0",
|
|
4296
|
-
"fumadocs-core": "^16.0.0",
|
|
4297
|
-
"fumadocs-mdx": "^14.0.0",
|
|
4298
|
-
"fumadocs-ui": "^16.0.0",
|
|
4299
|
-
"@repo/ui": "workspace:*"
|
|
4300
|
-
},
|
|
4301
|
-
devDependencies: {
|
|
4302
|
-
"@repo/config-typescript": "workspace:*",
|
|
4303
|
-
"@types/node": "^20.0.0",
|
|
4304
|
-
"@types/react": "^19.0.0",
|
|
4305
|
-
"@types/react-dom": "^19.0.0",
|
|
4306
|
-
"@types/mdx": "^2.0.0",
|
|
4307
|
-
tailwindcss: "^4.0.0",
|
|
4308
|
-
"@tailwindcss/postcss": "^4.0.0",
|
|
4309
|
-
postcss: "^8.4.0",
|
|
4310
|
-
typescript: "^5.0.0"
|
|
4311
|
-
}
|
|
4312
|
-
};
|
|
4313
|
-
await writeJSON(path16.join(docsDir, "package.json"), packageJson2);
|
|
4314
|
-
}
|
|
4315
|
-
async function generateFumadocsNextConfig(docsDir) {
|
|
4316
|
-
const content = `import { createMDX } from 'fumadocs-mdx/next'
|
|
4317
|
-
import type { NextConfig } from 'next'
|
|
4318
|
-
|
|
4319
|
-
const withMDX = createMDX()
|
|
4320
|
-
|
|
4321
|
-
const config: NextConfig = {
|
|
4322
|
-
reactStrictMode: true,
|
|
4323
|
-
transpilePackages: ['@repo/ui'],
|
|
4324
|
-
}
|
|
4325
|
-
|
|
4326
|
-
export default withMDX(config)
|
|
4327
|
-
`;
|
|
4328
|
-
await writeFile(path16.join(docsDir, "next.config.ts"), content);
|
|
4329
|
-
}
|
|
4330
|
-
async function generateFumadocsTsConfig(docsDir) {
|
|
4331
|
-
const tsConfig = {
|
|
4332
|
-
compilerOptions: {
|
|
4333
|
-
target: "ES2020",
|
|
4334
|
-
lib: ["dom", "dom.iterable", "esnext"],
|
|
4335
|
-
allowJs: true,
|
|
4336
|
-
skipLibCheck: true,
|
|
4337
|
-
strict: true,
|
|
4338
|
-
noEmit: true,
|
|
4339
|
-
esModuleInterop: true,
|
|
4340
|
-
module: "esnext",
|
|
4341
|
-
moduleResolution: "bundler",
|
|
4342
|
-
resolveJsonModule: true,
|
|
4343
|
-
isolatedModules: true,
|
|
4344
|
-
jsx: "preserve",
|
|
4345
|
-
incremental: true,
|
|
4346
|
-
plugins: [{ name: "next" }],
|
|
4347
|
-
paths: {
|
|
4348
|
-
"@/*": ["./src/*"],
|
|
4349
|
-
"@/.source": ["./.source"]
|
|
4350
|
-
}
|
|
4351
|
-
},
|
|
4352
|
-
include: [
|
|
4353
|
-
"next-env.d.ts",
|
|
4354
|
-
"**/*.ts",
|
|
4355
|
-
"**/*.tsx",
|
|
4356
|
-
"**/*.mdx",
|
|
4357
|
-
".next/types/**/*.ts",
|
|
4358
|
-
".source/**/*.ts"
|
|
4359
|
-
],
|
|
4360
|
-
exclude: ["node_modules"]
|
|
4361
|
-
};
|
|
4362
|
-
await writeJSON(path16.join(docsDir, "tsconfig.json"), tsConfig);
|
|
4363
|
-
}
|
|
4364
|
-
async function generateFumadocsSource(docsDir) {
|
|
4365
|
-
const content = `import { docs } from '@/.source'
|
|
4366
|
-
import { loader } from 'fumadocs-core/source'
|
|
4367
|
-
|
|
4368
|
-
export const source = loader({
|
|
4369
|
-
baseUrl: '/docs',
|
|
4370
|
-
source: docs.toFumadocsSource(),
|
|
4371
|
-
})
|
|
4372
|
-
`;
|
|
4373
|
-
await writeFile(path16.join(docsDir, "src/lib/source.ts"), content);
|
|
4374
|
-
}
|
|
4375
|
-
async function generateFumadocsAppFiles(config, docsDir) {
|
|
4376
|
-
const layoutContent = `import { RootProvider } from 'fumadocs-ui/provider/next'
|
|
4377
|
-
import { Inter } from 'next/font/google'
|
|
4378
|
-
import type { Metadata } from 'next'
|
|
4379
|
-
import type { ReactNode } from 'react'
|
|
4380
|
-
import './global.css'
|
|
4381
|
-
|
|
4382
|
-
const inter = Inter({
|
|
4383
|
-
subsets: ['latin'],
|
|
4384
|
-
})
|
|
4385
|
-
|
|
4386
|
-
export const metadata: Metadata = {
|
|
4387
|
-
title: {
|
|
4388
|
-
template: '%s | ${config.name} Docs',
|
|
4389
|
-
default: '${config.name} Documentation',
|
|
4390
|
-
},
|
|
4391
|
-
description: 'Documentation for ${config.name}',
|
|
4392
|
-
}
|
|
4393
|
-
|
|
4394
|
-
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
4395
|
-
return (
|
|
4396
|
-
<html lang="en" className={inter.className} suppressHydrationWarning>
|
|
4397
|
-
<body className="flex flex-col min-h-screen">
|
|
4398
|
-
<RootProvider>{children}</RootProvider>
|
|
4399
|
-
</body>
|
|
4400
|
-
</html>
|
|
4401
|
-
)
|
|
4402
|
-
}
|
|
4403
|
-
`;
|
|
4404
|
-
await writeFile(path16.join(docsDir, "src/app/layout.tsx"), layoutContent);
|
|
4405
|
-
const docsLayoutContent = `import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
|
4406
|
-
import type { ReactNode } from 'react'
|
|
4407
|
-
import { source } from '@/lib/source'
|
|
4408
|
-
|
|
4409
|
-
export default function Layout({ children }: { children: ReactNode }) {
|
|
4410
|
-
return (
|
|
4411
|
-
<DocsLayout
|
|
4412
|
-
tree={source.pageTree}
|
|
4413
|
-
nav={{
|
|
4414
|
-
title: '${config.name} Docs',
|
|
4415
|
-
}}
|
|
4416
|
-
>
|
|
4417
|
-
{children}
|
|
4418
|
-
</DocsLayout>
|
|
4419
|
-
)
|
|
4420
|
-
}
|
|
4421
|
-
`;
|
|
4422
|
-
await writeFile(
|
|
4423
|
-
path16.join(docsDir, "src/app/docs/layout.tsx"),
|
|
4424
|
-
docsLayoutContent
|
|
4425
|
-
);
|
|
4426
|
-
const docsPageContent = `import { source } from '@/lib/source'
|
|
4427
|
-
import {
|
|
4428
|
-
DocsPage,
|
|
4429
|
-
DocsBody,
|
|
4430
|
-
DocsDescription,
|
|
4431
|
-
DocsTitle,
|
|
4432
|
-
} from 'fumadocs-ui/page'
|
|
4433
|
-
import { notFound } from 'next/navigation'
|
|
4434
|
-
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
|
4435
|
-
|
|
4436
|
-
export default async function Page(props: {
|
|
4437
|
-
params: Promise<{ slug?: string[] }>
|
|
4438
|
-
}) {
|
|
4439
|
-
const params = await props.params
|
|
4440
|
-
const page = source.getPage(params.slug)
|
|
4441
|
-
if (!page) notFound()
|
|
4442
|
-
|
|
4443
|
-
const MDX = page.data.body
|
|
4444
|
-
|
|
4445
|
-
return (
|
|
4446
|
-
<DocsPage toc={page.data.toc} full={page.data.full}>
|
|
4447
|
-
<DocsTitle>{page.data.title}</DocsTitle>
|
|
4448
|
-
<DocsDescription>{page.data.description}</DocsDescription>
|
|
4449
|
-
<DocsBody>
|
|
4450
|
-
<MDX components={{ ...defaultMdxComponents }} />
|
|
4451
|
-
</DocsBody>
|
|
4452
|
-
</DocsPage>
|
|
4453
|
-
)
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
|
-
export async function generateStaticParams() {
|
|
4457
|
-
return source.generateParams()
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4460
|
-
export async function generateMetadata(props: {
|
|
4461
|
-
params: Promise<{ slug?: string[] }>
|
|
4462
|
-
}) {
|
|
4463
|
-
const params = await props.params
|
|
4464
|
-
const page = source.getPage(params.slug)
|
|
4465
|
-
if (!page) notFound()
|
|
4466
|
-
|
|
4467
|
-
return {
|
|
4468
|
-
title: page.data.title,
|
|
4469
|
-
description: page.data.description,
|
|
4470
|
-
}
|
|
4471
|
-
}
|
|
4472
|
-
`;
|
|
4473
|
-
await writeFile(
|
|
4474
|
-
path16.join(docsDir, "src/app/docs/[[...slug]]/page.tsx"),
|
|
4475
|
-
docsPageContent
|
|
4476
|
-
);
|
|
4477
|
-
const homePageContent = `import { redirect } from 'next/navigation'
|
|
4478
|
-
|
|
4479
|
-
export default function HomePage() {
|
|
4480
|
-
redirect('/docs')
|
|
4481
|
-
}
|
|
4482
|
-
`;
|
|
4483
|
-
await writeFile(path16.join(docsDir, "src/app/page.tsx"), homePageContent);
|
|
4484
|
-
const globalCssContent = `@import 'tailwindcss';
|
|
4485
|
-
@import 'fumadocs-ui/css/neutral.css';
|
|
4486
|
-
@import 'fumadocs-ui/css/preset.css';
|
|
4487
|
-
|
|
4488
|
-
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
|
4489
|
-
`;
|
|
4490
|
-
await writeFile(path16.join(docsDir, "src/app/global.css"), globalCssContent);
|
|
4491
|
-
const sourceConfigContent = `import { defineDocs, defineConfig } from 'fumadocs-mdx/config'
|
|
4492
|
-
|
|
4493
|
-
export const { docs, meta } = defineDocs({
|
|
4494
|
-
dir: 'content/docs',
|
|
4495
|
-
})
|
|
4496
|
-
|
|
4497
|
-
export default defineConfig()
|
|
4498
|
-
`;
|
|
4499
|
-
await writeFile(path16.join(docsDir, "source.config.ts"), sourceConfigContent);
|
|
4500
|
-
const postcssContent = `export default {
|
|
4501
|
-
plugins: {
|
|
4502
|
-
'@tailwindcss/postcss': {},
|
|
4503
|
-
},
|
|
4504
|
-
}
|
|
4505
|
-
`;
|
|
4506
|
-
await writeFile(path16.join(docsDir, "postcss.config.mjs"), postcssContent);
|
|
4507
|
-
}
|
|
4508
|
-
async function generateFumadocsContent(config, docsDir) {
|
|
4509
|
-
const metaJson = {
|
|
4510
|
-
title: "Documentation",
|
|
4511
|
-
pages: ["index", "getting-started", "---", "guides", "api"]
|
|
4512
|
-
};
|
|
4513
|
-
await writeJSON(path16.join(docsDir, "content/docs/meta.json"), metaJson);
|
|
4514
|
-
const indexContent = `---
|
|
4515
|
-
title: Introduction
|
|
4516
|
-
description: Welcome to ${config.name} documentation
|
|
4517
|
-
---
|
|
4518
|
-
|
|
4519
|
-
# Welcome to ${config.name}
|
|
4520
|
-
|
|
4521
|
-
This is the documentation for ${config.name}, built with [Fumadocs](https://fumadocs.vercel.app).
|
|
4522
|
-
|
|
4523
|
-
## Features
|
|
4524
|
-
|
|
4525
|
-
- Built with Next.js 15 and React 19
|
|
4526
|
-
- Convex for real-time database
|
|
4527
|
-
- Better-Auth for authentication
|
|
4528
|
-
- shadcn/ui components
|
|
4529
|
-
- Tailwind CSS v4
|
|
4530
|
-
|
|
4531
|
-
## Getting Started
|
|
4532
|
-
|
|
4533
|
-
Check out the [Getting Started](/docs/getting-started) guide to begin.
|
|
4534
|
-
`;
|
|
4535
|
-
await writeFile(
|
|
4536
|
-
path16.join(docsDir, "content/docs/index.mdx"),
|
|
4537
|
-
indexContent
|
|
4538
|
-
);
|
|
4539
|
-
const gettingStartedContent = `---
|
|
4540
|
-
title: Getting Started
|
|
4541
|
-
description: Get started with ${config.name}
|
|
4542
|
-
---
|
|
4543
|
-
|
|
4544
|
-
# Getting Started
|
|
4545
|
-
|
|
4546
|
-
This guide will help you get ${config.name} up and running.
|
|
4547
|
-
|
|
4548
|
-
## Prerequisites
|
|
4549
|
-
|
|
4550
|
-
- Node.js 18 or higher
|
|
4551
|
-
- pnpm package manager
|
|
4552
|
-
|
|
4553
|
-
## Installation
|
|
4554
|
-
|
|
4555
|
-
1. Clone the repository
|
|
4556
|
-
2. Install dependencies:
|
|
4557
|
-
|
|
4558
|
-
\`\`\`bash
|
|
4559
|
-
pnpm install
|
|
4560
|
-
\`\`\`
|
|
4561
|
-
|
|
4562
|
-
3. Copy environment variables:
|
|
4563
|
-
|
|
4564
|
-
\`\`\`bash
|
|
4565
|
-
cp .env.example .env.local
|
|
4566
|
-
\`\`\`
|
|
4567
|
-
|
|
4568
|
-
4. Set up Convex:
|
|
4569
|
-
|
|
4570
|
-
\`\`\`bash
|
|
4571
|
-
pnpm convex dev
|
|
4572
|
-
\`\`\`
|
|
4573
|
-
|
|
4574
|
-
5. Start the development server:
|
|
4575
|
-
|
|
4576
|
-
\`\`\`bash
|
|
4577
|
-
pnpm dev
|
|
4578
|
-
\`\`\`
|
|
4579
|
-
|
|
4580
|
-
## Next Steps
|
|
4581
|
-
|
|
4582
|
-
- [Configure authentication](/docs/guides/authentication)
|
|
4583
|
-
- [Set up your database schema](/docs/guides/database)
|
|
4584
|
-
- [Customize the UI](/docs/guides/customization)
|
|
4585
|
-
`;
|
|
4586
|
-
await writeFile(
|
|
4587
|
-
path16.join(docsDir, "content/docs/getting-started.mdx"),
|
|
4588
|
-
gettingStartedContent
|
|
4589
|
-
);
|
|
4590
|
-
await ensureDir(path16.join(docsDir, "content/docs/guides"));
|
|
4591
|
-
const guidesMetaJson = {
|
|
4592
|
-
title: "Guides",
|
|
4593
|
-
pages: ["authentication", "database", "customization"]
|
|
4594
|
-
};
|
|
4595
|
-
await writeJSON(
|
|
4596
|
-
path16.join(docsDir, "content/docs/guides/meta.json"),
|
|
4597
|
-
guidesMetaJson
|
|
4598
|
-
);
|
|
4599
|
-
const authGuideContent = `---
|
|
4600
|
-
title: Authentication
|
|
4601
|
-
description: Learn how to configure authentication in ${config.name}
|
|
4602
|
-
---
|
|
4603
|
-
|
|
4604
|
-
# Authentication
|
|
4605
|
-
|
|
4606
|
-
${config.name} uses Better-Auth with Convex for authentication.
|
|
4607
|
-
|
|
4608
|
-
## Supported Providers
|
|
4609
|
-
|
|
4610
|
-
- Email/Password (always enabled)
|
|
4611
|
-
- Google OAuth (always enabled)
|
|
4612
|
-
${config.auth.providers.map((p8) => `- ${p8.charAt(0).toUpperCase() + p8.slice(1)}`).join("\n")}
|
|
4613
|
-
|
|
4614
|
-
## Configuration
|
|
4615
|
-
|
|
4616
|
-
Authentication is configured in \`convex/auth.ts\`.
|
|
4617
|
-
|
|
4618
|
-
\`\`\`typescript
|
|
4619
|
-
export const { auth, createAuth } = new BetterAuth<DataModel>(components.betterAuth, {
|
|
4620
|
-
emailAndPassword: {
|
|
4621
|
-
enabled: true,
|
|
4622
|
-
},
|
|
4623
|
-
socialProviders: {
|
|
4624
|
-
google: {
|
|
4625
|
-
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
4626
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
4627
|
-
},
|
|
4628
|
-
},
|
|
4629
|
-
})
|
|
4630
|
-
\`\`\`
|
|
4631
|
-
|
|
4632
|
-
## Using Authentication
|
|
4633
|
-
|
|
4634
|
-
Use the auth client in your components:
|
|
4635
|
-
|
|
4636
|
-
\`\`\`typescript
|
|
4637
|
-
import { signIn, signOut, useSession } from '@/lib/auth'
|
|
4638
|
-
|
|
4639
|
-
// Sign in
|
|
4640
|
-
await signIn.email({ email, password })
|
|
4641
|
-
|
|
4642
|
-
// Sign out
|
|
4643
|
-
await signOut()
|
|
4644
|
-
|
|
4645
|
-
// Get session
|
|
4646
|
-
const { data: session } = useSession()
|
|
4647
|
-
\`\`\`
|
|
4648
|
-
`;
|
|
4649
|
-
await writeFile(
|
|
4650
|
-
path16.join(docsDir, "content/docs/guides/authentication.mdx"),
|
|
4651
|
-
authGuideContent
|
|
4652
|
-
);
|
|
4653
|
-
const databaseGuideContent = `---
|
|
4654
|
-
title: Database
|
|
4655
|
-
description: Working with Convex database
|
|
4656
|
-
---
|
|
4657
|
-
|
|
4658
|
-
# Database
|
|
4659
|
-
|
|
4660
|
-
${config.name} uses Convex as its database.
|
|
4661
|
-
|
|
4662
|
-
## Schema
|
|
4663
|
-
|
|
4664
|
-
The database schema is defined in \`convex/schema.ts\`.
|
|
4665
|
-
|
|
4666
|
-
## Queries
|
|
4667
|
-
|
|
4668
|
-
Create queries to read data:
|
|
4669
|
-
|
|
4670
|
-
\`\`\`typescript
|
|
4671
|
-
import { query } from './_generated/server'
|
|
4672
|
-
|
|
4673
|
-
export const getUsers = query({
|
|
4674
|
-
handler: async (ctx) => {
|
|
4675
|
-
return await ctx.db.query('users').collect()
|
|
4676
|
-
},
|
|
4677
|
-
})
|
|
4678
|
-
\`\`\`
|
|
4679
|
-
|
|
4680
|
-
## Mutations
|
|
4681
|
-
|
|
4682
|
-
Create mutations to write data:
|
|
4683
|
-
|
|
4684
|
-
\`\`\`typescript
|
|
4685
|
-
import { mutation } from './_generated/server'
|
|
4686
|
-
import { v } from 'convex/values'
|
|
4687
|
-
|
|
4688
|
-
export const createUser = mutation({
|
|
4689
|
-
args: {
|
|
4690
|
-
name: v.string(),
|
|
4691
|
-
email: v.string(),
|
|
4692
|
-
},
|
|
4693
|
-
handler: async (ctx, args) => {
|
|
4694
|
-
return await ctx.db.insert('users', args)
|
|
4695
|
-
},
|
|
4696
|
-
})
|
|
4697
|
-
\`\`\`
|
|
4698
|
-
`;
|
|
4699
|
-
await writeFile(
|
|
4700
|
-
path16.join(docsDir, "content/docs/guides/database.mdx"),
|
|
4701
|
-
databaseGuideContent
|
|
4702
|
-
);
|
|
4703
|
-
const customizationGuideContent = `---
|
|
4704
|
-
title: Customization
|
|
4705
|
-
description: Customize the look and feel of ${config.name}
|
|
4706
|
-
---
|
|
4707
|
-
|
|
4708
|
-
# Customization
|
|
4709
|
-
|
|
4710
|
-
## Theme
|
|
4711
|
-
|
|
4712
|
-
The theme is configured in \`src/app/globals.css\` using Tailwind CSS v4.
|
|
4713
|
-
|
|
4714
|
-
### Colors
|
|
4715
|
-
|
|
4716
|
-
Update the theme colors in the CSS:
|
|
4717
|
-
|
|
4718
|
-
\`\`\`css
|
|
4719
|
-
@theme {
|
|
4720
|
-
--color-primary: oklch(0.6 0.2 250);
|
|
4721
|
-
--color-primary-foreground: oklch(0.98 0 0);
|
|
4722
|
-
}
|
|
4723
|
-
\`\`\`
|
|
4724
|
-
|
|
4725
|
-
### Dark Mode
|
|
4726
|
-
|
|
4727
|
-
Dark mode is supported out of the box. Define dark mode styles with:
|
|
4728
|
-
|
|
4729
|
-
\`\`\`css
|
|
4730
|
-
.dark {
|
|
4731
|
-
--color-background: oklch(0.145 0 0);
|
|
4732
|
-
}
|
|
4733
|
-
\`\`\`
|
|
4734
|
-
|
|
4735
|
-
## Components
|
|
4736
|
-
|
|
4737
|
-
UI components are in \`src/components/ui\`. Customize or add new components as needed.
|
|
4738
|
-
`;
|
|
4739
|
-
await writeFile(
|
|
4740
|
-
path16.join(docsDir, "content/docs/guides/customization.mdx"),
|
|
4741
|
-
customizationGuideContent
|
|
4742
|
-
);
|
|
4743
|
-
await ensureDir(path16.join(docsDir, "content/docs/api"));
|
|
4744
|
-
const apiMetaJson = {
|
|
4745
|
-
title: "API Reference",
|
|
4746
|
-
pages: ["overview"]
|
|
4747
|
-
};
|
|
4748
|
-
await writeJSON(
|
|
4749
|
-
path16.join(docsDir, "content/docs/api/meta.json"),
|
|
4750
|
-
apiMetaJson
|
|
4751
|
-
);
|
|
4752
|
-
const apiOverviewContent = `---
|
|
4753
|
-
title: API Overview
|
|
4754
|
-
description: API reference for ${config.name}
|
|
4755
|
-
---
|
|
4756
|
-
|
|
4757
|
-
# API Reference
|
|
4758
|
-
|
|
4759
|
-
This section contains the API reference for ${config.name}.
|
|
4760
|
-
|
|
4761
|
-
## Authentication API
|
|
4762
|
-
|
|
4763
|
-
See the [Better-Auth documentation](https://www.better-auth.com) for detailed API reference.
|
|
4764
|
-
|
|
4765
|
-
## Database API
|
|
4766
|
-
|
|
4767
|
-
See the [Convex documentation](https://docs.convex.dev) for database API reference.
|
|
4768
|
-
`;
|
|
4769
|
-
await writeFile(
|
|
4770
|
-
path16.join(docsDir, "content/docs/api/overview.mdx"),
|
|
4771
|
-
apiOverviewContent
|
|
4772
|
-
);
|
|
4773
|
-
}
|
|
4774
|
-
|
|
4775
4248
|
// src/generators/design-system.ts
|
|
4776
|
-
import
|
|
4249
|
+
import path16 from "path";
|
|
4777
4250
|
var fontImports = {
|
|
4778
4251
|
inter: {
|
|
4779
4252
|
import: "import { Inter } from 'next/font/google'",
|
|
@@ -4841,17 +4314,17 @@ var spacingMultipliers = {
|
|
|
4841
4314
|
relaxed: 1.125
|
|
4842
4315
|
};
|
|
4843
4316
|
async function generateDesignSystemApp(config, targetDir) {
|
|
4844
|
-
const appDir =
|
|
4845
|
-
await ensureDir(
|
|
4846
|
-
await ensureDir(
|
|
4847
|
-
await ensureDir(
|
|
4848
|
-
await ensureDir(
|
|
4849
|
-
await ensureDir(
|
|
4850
|
-
await ensureDir(
|
|
4851
|
-
await ensureDir(
|
|
4852
|
-
await ensureDir(
|
|
4853
|
-
await ensureDir(
|
|
4854
|
-
await ensureDir(
|
|
4317
|
+
const appDir = path16.join(targetDir, "apps/design-system");
|
|
4318
|
+
await ensureDir(path16.join(appDir, "src/app"));
|
|
4319
|
+
await ensureDir(path16.join(appDir, "src/app/colors"));
|
|
4320
|
+
await ensureDir(path16.join(appDir, "src/app/typography"));
|
|
4321
|
+
await ensureDir(path16.join(appDir, "src/app/spacing"));
|
|
4322
|
+
await ensureDir(path16.join(appDir, "src/app/brand"));
|
|
4323
|
+
await ensureDir(path16.join(appDir, "src/app/blocks"));
|
|
4324
|
+
await ensureDir(path16.join(appDir, "src/app/components"));
|
|
4325
|
+
await ensureDir(path16.join(appDir, "src/app/components/[slug]"));
|
|
4326
|
+
await ensureDir(path16.join(appDir, "src/lib"));
|
|
4327
|
+
await ensureDir(path16.join(appDir, "public/brand"));
|
|
4855
4328
|
await generatePackageJson2(config, appDir);
|
|
4856
4329
|
await generateTsConfig2(appDir);
|
|
4857
4330
|
await generateNextConfig2(appDir);
|
|
@@ -4904,7 +4377,7 @@ async function generatePackageJson2(config, appDir) {
|
|
|
4904
4377
|
typescript: "^5.0.0"
|
|
4905
4378
|
}
|
|
4906
4379
|
};
|
|
4907
|
-
await writeJSON(
|
|
4380
|
+
await writeJSON(path16.join(appDir, "package.json"), packageJson2);
|
|
4908
4381
|
}
|
|
4909
4382
|
async function generateTsConfig2(appDir) {
|
|
4910
4383
|
const tsConfig = {
|
|
@@ -4932,7 +4405,7 @@ async function generateTsConfig2(appDir) {
|
|
|
4932
4405
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
4933
4406
|
exclude: ["node_modules"]
|
|
4934
4407
|
};
|
|
4935
|
-
await writeJSON(
|
|
4408
|
+
await writeJSON(path16.join(appDir, "tsconfig.json"), tsConfig);
|
|
4936
4409
|
}
|
|
4937
4410
|
async function generateNextConfig2(appDir) {
|
|
4938
4411
|
const content = `import type { NextConfig } from 'next'
|
|
@@ -4943,7 +4416,7 @@ const nextConfig: NextConfig = {
|
|
|
4943
4416
|
|
|
4944
4417
|
export default nextConfig
|
|
4945
4418
|
`;
|
|
4946
|
-
await writeFile(
|
|
4419
|
+
await writeFile(path16.join(appDir, "next.config.ts"), content);
|
|
4947
4420
|
}
|
|
4948
4421
|
async function generatePostCssConfig2(appDir) {
|
|
4949
4422
|
const content = `export default {
|
|
@@ -4952,7 +4425,7 @@ async function generatePostCssConfig2(appDir) {
|
|
|
4952
4425
|
},
|
|
4953
4426
|
}
|
|
4954
4427
|
`;
|
|
4955
|
-
await writeFile(
|
|
4428
|
+
await writeFile(path16.join(appDir, "postcss.config.mjs"), content);
|
|
4956
4429
|
}
|
|
4957
4430
|
async function generateGlobalsCss2(config, appDir) {
|
|
4958
4431
|
const { designSystem } = config;
|
|
@@ -5212,7 +4685,7 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
5212
4685
|
}
|
|
5213
4686
|
}
|
|
5214
4687
|
`;
|
|
5215
|
-
await writeFile(
|
|
4688
|
+
await writeFile(path16.join(appDir, "src/app/globals.css"), content);
|
|
5216
4689
|
}
|
|
5217
4690
|
async function generateLayoutTsx(config, appDir) {
|
|
5218
4691
|
const { designSystem } = config;
|
|
@@ -5411,7 +4884,7 @@ function ThemeToggle() {
|
|
|
5411
4884
|
)
|
|
5412
4885
|
}
|
|
5413
4886
|
`;
|
|
5414
|
-
await writeFile(
|
|
4887
|
+
await writeFile(path16.join(appDir, "src/app/layout.tsx"), content);
|
|
5415
4888
|
}
|
|
5416
4889
|
async function generateHomePage(config, appDir) {
|
|
5417
4890
|
const content = `export default function DesignSystemHome() {
|
|
@@ -5577,7 +5050,7 @@ function Principle({
|
|
|
5577
5050
|
)
|
|
5578
5051
|
}
|
|
5579
5052
|
`;
|
|
5580
|
-
await writeFile(
|
|
5053
|
+
await writeFile(path16.join(appDir, "src/app/page.tsx"), content);
|
|
5581
5054
|
}
|
|
5582
5055
|
async function generateColorsPage(config, appDir) {
|
|
5583
5056
|
const content = `export default function ColorsPage() {
|
|
@@ -5746,7 +5219,7 @@ function ColorCard({
|
|
|
5746
5219
|
)
|
|
5747
5220
|
}
|
|
5748
5221
|
`;
|
|
5749
|
-
await writeFile(
|
|
5222
|
+
await writeFile(path16.join(appDir, "src/app/colors/page.tsx"), content);
|
|
5750
5223
|
}
|
|
5751
5224
|
async function generateTypographyPage(config, appDir) {
|
|
5752
5225
|
const { designSystem } = config;
|
|
@@ -5903,7 +5376,7 @@ function WeightCard({ weight, name }: { weight: string; name: string }) {
|
|
|
5903
5376
|
)
|
|
5904
5377
|
}
|
|
5905
5378
|
`;
|
|
5906
|
-
await writeFile(
|
|
5379
|
+
await writeFile(path16.join(appDir, "src/app/typography/page.tsx"), content);
|
|
5907
5380
|
}
|
|
5908
5381
|
async function generateSpacingPage(config, appDir) {
|
|
5909
5382
|
const { designSystem } = config;
|
|
@@ -6036,7 +5509,7 @@ function PatternCard({
|
|
|
6036
5509
|
)
|
|
6037
5510
|
}
|
|
6038
5511
|
`;
|
|
6039
|
-
await writeFile(
|
|
5512
|
+
await writeFile(path16.join(appDir, "src/app/spacing/page.tsx"), content);
|
|
6040
5513
|
}
|
|
6041
5514
|
async function generateBrandPage(config, appDir) {
|
|
6042
5515
|
const content = `export default function BrandPage() {
|
|
@@ -6174,7 +5647,7 @@ function DownloadCard({
|
|
|
6174
5647
|
)
|
|
6175
5648
|
}
|
|
6176
5649
|
`;
|
|
6177
|
-
await writeFile(
|
|
5650
|
+
await writeFile(path16.join(appDir, "src/app/brand/page.tsx"), content);
|
|
6178
5651
|
}
|
|
6179
5652
|
async function generateBlocksPage(appDir) {
|
|
6180
5653
|
const content = `export default function BlocksPage() {
|
|
@@ -6236,7 +5709,7 @@ function BlockCard({ title, description }: { title: string; description: string
|
|
|
6236
5709
|
)
|
|
6237
5710
|
}
|
|
6238
5711
|
`;
|
|
6239
|
-
await writeFile(
|
|
5712
|
+
await writeFile(path16.join(appDir, "src/app/blocks/page.tsx"), content);
|
|
6240
5713
|
}
|
|
6241
5714
|
async function generateComponentsPage(appDir) {
|
|
6242
5715
|
const content = `export default function ComponentsPage() {
|
|
@@ -6316,7 +5789,7 @@ function CategoryCard({
|
|
|
6316
5789
|
)
|
|
6317
5790
|
}
|
|
6318
5791
|
`;
|
|
6319
|
-
await writeFile(
|
|
5792
|
+
await writeFile(path16.join(appDir, "src/app/components/page.tsx"), content);
|
|
6320
5793
|
}
|
|
6321
5794
|
async function generateComponentSlugPage(appDir) {
|
|
6322
5795
|
const content = `import { notFound } from 'next/navigation'
|
|
@@ -6420,7 +5893,7 @@ export function generateStaticParams() {
|
|
|
6420
5893
|
return components.map((slug) => ({ slug }))
|
|
6421
5894
|
}
|
|
6422
5895
|
`;
|
|
6423
|
-
await writeFile(
|
|
5896
|
+
await writeFile(path16.join(appDir, "src/app/components/[slug]/page.tsx"), content);
|
|
6424
5897
|
}
|
|
6425
5898
|
async function generateUtils(appDir) {
|
|
6426
5899
|
const content = `import { clsx, type ClassValue } from 'clsx'
|
|
@@ -6430,7 +5903,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6430
5903
|
return twMerge(clsx(inputs))
|
|
6431
5904
|
}
|
|
6432
5905
|
`;
|
|
6433
|
-
await writeFile(
|
|
5906
|
+
await writeFile(path16.join(appDir, "src/lib/utils.ts"), content);
|
|
6434
5907
|
}
|
|
6435
5908
|
|
|
6436
5909
|
// src/setup/env-wizard.ts
|
|
@@ -6438,7 +5911,7 @@ import * as p6 from "@clack/prompts";
|
|
|
6438
5911
|
import pc2 from "picocolors";
|
|
6439
5912
|
import { exec } from "child_process";
|
|
6440
5913
|
import { promisify } from "util";
|
|
6441
|
-
import
|
|
5914
|
+
import path17 from "path";
|
|
6442
5915
|
var execAsync = promisify(exec);
|
|
6443
5916
|
async function runEnvSetupWizard(config) {
|
|
6444
5917
|
console.log();
|
|
@@ -6519,7 +5992,7 @@ This will open your browser to create a new project on Convex.
|
|
|
6519
5992
|
${pc2.dim("Note: Make sure you have a Convex account at https://convex.dev")}
|
|
6520
5993
|
`);
|
|
6521
5994
|
try {
|
|
6522
|
-
const convexDir = config.structure === "monorepo" ?
|
|
5995
|
+
const convexDir = config.structure === "monorepo" ? path17.join(config.targetDir, "packages/backend") : config.targetDir;
|
|
6523
5996
|
const { spawn } = await import("child_process");
|
|
6524
5997
|
p6.log.info(`Installing dependencies in ${pc2.dim(convexDir)}...`);
|
|
6525
5998
|
const installSuccess = await new Promise((resolve) => {
|
|
@@ -6795,7 +6268,7 @@ async function writeEnvFiles(config, envValues) {
|
|
|
6795
6268
|
return;
|
|
6796
6269
|
}
|
|
6797
6270
|
p6.log.step("Writing environment files...");
|
|
6798
|
-
const webEnvPath = config.structure === "monorepo" ?
|
|
6271
|
+
const webEnvPath = config.structure === "monorepo" ? path17.join(config.targetDir, "apps/web/.env.local") : path17.join(config.targetDir, ".env.local");
|
|
6799
6272
|
if (await pathExists(webEnvPath)) {
|
|
6800
6273
|
let content = await readFile(webEnvPath);
|
|
6801
6274
|
for (const [key, value] of Object.entries(envValues)) {
|
|
@@ -6814,7 +6287,7 @@ ${key}="${value}"`;
|
|
|
6814
6287
|
p6.log.success(`Updated ${config.structure === "monorepo" ? "apps/web/.env.local" : ".env.local"}`);
|
|
6815
6288
|
}
|
|
6816
6289
|
if (config.marketingSite === "payload" && config.structure === "monorepo") {
|
|
6817
|
-
const marketingEnvPath =
|
|
6290
|
+
const marketingEnvPath = path17.join(config.targetDir, "apps/marketing/.env.local");
|
|
6818
6291
|
if (await pathExists(marketingEnvPath)) {
|
|
6819
6292
|
let content = await readFile(marketingEnvPath);
|
|
6820
6293
|
const payloadEnvKeys = ["PAYLOAD_SECRET", "DATABASE_URL", "S3_ENDPOINT", "S3_REGION", "S3_BUCKET", "S3_ACCESS_KEY_ID", "S3_SECRET_ACCESS_KEY", "CRON_SECRET", "PREVIEW_SECRET"];
|
|
@@ -6875,7 +6348,7 @@ async function generateSingleProject(config, spinner) {
|
|
|
6875
6348
|
await generateShadcn(config, targetDir);
|
|
6876
6349
|
spinner.succeed("shadcn/ui configured");
|
|
6877
6350
|
spinner.start("Setting up Convex...");
|
|
6878
|
-
await generateConvex(config,
|
|
6351
|
+
await generateConvex(config, path18.join(targetDir, "convex"));
|
|
6879
6352
|
spinner.succeed("Convex configured");
|
|
6880
6353
|
spinner.start("Configuring Better-Auth...");
|
|
6881
6354
|
await generateBetterAuth(config, targetDir);
|
|
@@ -6918,7 +6391,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6918
6391
|
spinner.start("Creating shared UI package...");
|
|
6919
6392
|
await generateUIPackage(config, targetDir);
|
|
6920
6393
|
spinner.succeed("Shared UI package created");
|
|
6921
|
-
const webDir =
|
|
6394
|
+
const webDir = path18.join(targetDir, "apps/web");
|
|
6922
6395
|
spinner.start("Generating web application...");
|
|
6923
6396
|
await generateBaseNextjs(config, webDir);
|
|
6924
6397
|
await generateTailwind(config, webDir);
|
|
@@ -6928,10 +6401,10 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6928
6401
|
await generateEmail(config, webDir);
|
|
6929
6402
|
await updateWebTsConfig(webDir);
|
|
6930
6403
|
spinner.succeed("Web application generated");
|
|
6931
|
-
const backendDir =
|
|
6404
|
+
const backendDir = path18.join(targetDir, "packages/backend");
|
|
6932
6405
|
spinner.start("Setting up Convex backend...");
|
|
6933
6406
|
await ensureDir(backendDir);
|
|
6934
|
-
await generateConvex(config,
|
|
6407
|
+
await generateConvex(config, path18.join(backendDir, "convex"));
|
|
6935
6408
|
spinner.succeed("Convex backend configured");
|
|
6936
6409
|
if (config.integrations.analytics !== "none") {
|
|
6937
6410
|
spinner.start(`Setting up ${config.integrations.analytics} analytics...`);
|
|
@@ -6954,7 +6427,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6954
6427
|
spinner.succeed(`${config.integrations.monitoring} monitoring configured`);
|
|
6955
6428
|
}
|
|
6956
6429
|
if (config.marketingSite !== "none") {
|
|
6957
|
-
const marketingDir =
|
|
6430
|
+
const marketingDir = path18.join(targetDir, "apps/marketing");
|
|
6958
6431
|
spinner.start(`Generating ${config.marketingSite} marketing site...`);
|
|
6959
6432
|
if (config.marketingSite === "payload") {
|
|
6960
6433
|
await generatePayload(config, marketingDir);
|
|
@@ -6963,12 +6436,6 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6963
6436
|
}
|
|
6964
6437
|
spinner.succeed(`${config.marketingSite} marketing site generated`);
|
|
6965
6438
|
}
|
|
6966
|
-
if (config.includeDocs) {
|
|
6967
|
-
const docsDir = path19.join(targetDir, "apps/docs");
|
|
6968
|
-
spinner.start("Generating Fumadocs documentation site...");
|
|
6969
|
-
await generateFumadocs(config, docsDir);
|
|
6970
|
-
spinner.succeed("Fumadocs documentation site generated");
|
|
6971
|
-
}
|
|
6972
6439
|
spinner.start("Generating design system app...");
|
|
6973
6440
|
await generateDesignSystemApp(config, targetDir);
|
|
6974
6441
|
spinner.succeed("Design system app generated");
|
|
@@ -6985,7 +6452,7 @@ async function runPostGenerationSteps(config, spinner) {
|
|
|
6985
6452
|
}
|
|
6986
6453
|
spinner.start("Installing shadcn/ui components...");
|
|
6987
6454
|
try {
|
|
6988
|
-
const shadcnDir = config.structure === "monorepo" ?
|
|
6455
|
+
const shadcnDir = config.structure === "monorepo" ? path18.join(targetDir, "packages/ui") : targetDir;
|
|
6989
6456
|
await runShadcnAdd(shadcnDir);
|
|
6990
6457
|
spinner.succeed("shadcn/ui components installed");
|
|
6991
6458
|
} catch (error) {
|
|
@@ -7045,7 +6512,7 @@ async function generateBiomeConfig(targetDir) {
|
|
|
7045
6512
|
}
|
|
7046
6513
|
};
|
|
7047
6514
|
await writeFile(
|
|
7048
|
-
|
|
6515
|
+
path18.join(targetDir, "biome.json"),
|
|
7049
6516
|
JSON.stringify(biomeJson, null, 2)
|
|
7050
6517
|
);
|
|
7051
6518
|
}
|
|
@@ -7096,13 +6563,13 @@ yarn-error.log*
|
|
|
7096
6563
|
*.pem
|
|
7097
6564
|
.cache
|
|
7098
6565
|
`;
|
|
7099
|
-
await writeFile(
|
|
6566
|
+
await writeFile(path18.join(targetDir, ".gitignore"), gitignoreContent);
|
|
7100
6567
|
}
|
|
7101
6568
|
async function generateHuskyHooks(targetDir) {
|
|
7102
6569
|
const preCommitContent = `pnpm lint-staged
|
|
7103
6570
|
`;
|
|
7104
|
-
await ensureDir(
|
|
7105
|
-
await writeFile(
|
|
6571
|
+
await ensureDir(path18.join(targetDir, ".husky"));
|
|
6572
|
+
await writeFile(path18.join(targetDir, ".husky/pre-commit"), preCommitContent);
|
|
7106
6573
|
}
|
|
7107
6574
|
async function updateWebTsConfig(webDir) {
|
|
7108
6575
|
const tsConfig = {
|
|
@@ -7129,14 +6596,14 @@ async function updateWebTsConfig(webDir) {
|
|
|
7129
6596
|
exclude: ["node_modules"]
|
|
7130
6597
|
};
|
|
7131
6598
|
await writeFile(
|
|
7132
|
-
|
|
6599
|
+
path18.join(webDir, "tsconfig.json"),
|
|
7133
6600
|
JSON.stringify(tsConfig, null, 2)
|
|
7134
6601
|
);
|
|
7135
6602
|
}
|
|
7136
6603
|
async function generateNextjsMarketing(config, marketingDir) {
|
|
7137
|
-
await ensureDir(
|
|
7138
|
-
await ensureDir(
|
|
7139
|
-
await ensureDir(
|
|
6604
|
+
await ensureDir(path18.join(marketingDir, "src/app"));
|
|
6605
|
+
await ensureDir(path18.join(marketingDir, "src/components"));
|
|
6606
|
+
await ensureDir(path18.join(marketingDir, "public"));
|
|
7140
6607
|
const packageJson2 = {
|
|
7141
6608
|
name: "@repo/marketing",
|
|
7142
6609
|
version: "0.1.0",
|
|
@@ -7167,7 +6634,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7167
6634
|
}
|
|
7168
6635
|
};
|
|
7169
6636
|
await writeFile(
|
|
7170
|
-
|
|
6637
|
+
path18.join(marketingDir, "package.json"),
|
|
7171
6638
|
JSON.stringify(packageJson2, null, 2)
|
|
7172
6639
|
);
|
|
7173
6640
|
const homePageContent = `export default function HomePage() {
|
|
@@ -7190,7 +6657,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7190
6657
|
}
|
|
7191
6658
|
`;
|
|
7192
6659
|
await writeFile(
|
|
7193
|
-
|
|
6660
|
+
path18.join(marketingDir, "src/app/page.tsx"),
|
|
7194
6661
|
homePageContent
|
|
7195
6662
|
);
|
|
7196
6663
|
const layoutContent = `import type { Metadata } from 'next'
|
|
@@ -7214,7 +6681,7 @@ export default function RootLayout({
|
|
|
7214
6681
|
}
|
|
7215
6682
|
`;
|
|
7216
6683
|
await writeFile(
|
|
7217
|
-
|
|
6684
|
+
path18.join(marketingDir, "src/app/layout.tsx"),
|
|
7218
6685
|
layoutContent
|
|
7219
6686
|
);
|
|
7220
6687
|
const globalsCss = `@import "tailwindcss";
|
|
@@ -7226,7 +6693,7 @@ export default function RootLayout({
|
|
|
7226
6693
|
}
|
|
7227
6694
|
`;
|
|
7228
6695
|
await writeFile(
|
|
7229
|
-
|
|
6696
|
+
path18.join(marketingDir, "src/app/globals.css"),
|
|
7230
6697
|
globalsCss
|
|
7231
6698
|
);
|
|
7232
6699
|
const tsConfig = {
|
|
@@ -7253,7 +6720,7 @@ export default function RootLayout({
|
|
|
7253
6720
|
exclude: ["node_modules"]
|
|
7254
6721
|
};
|
|
7255
6722
|
await writeFile(
|
|
7256
|
-
|
|
6723
|
+
path18.join(marketingDir, "tsconfig.json"),
|
|
7257
6724
|
JSON.stringify(tsConfig, null, 2)
|
|
7258
6725
|
);
|
|
7259
6726
|
const nextConfig = `import type { NextConfig } from 'next'
|
|
@@ -7264,14 +6731,14 @@ const nextConfig: NextConfig = {
|
|
|
7264
6731
|
|
|
7265
6732
|
export default nextConfig
|
|
7266
6733
|
`;
|
|
7267
|
-
await writeFile(
|
|
6734
|
+
await writeFile(path18.join(marketingDir, "next.config.ts"), nextConfig);
|
|
7268
6735
|
const postcssConfig = `export default {
|
|
7269
6736
|
plugins: {
|
|
7270
6737
|
'@tailwindcss/postcss': {},
|
|
7271
6738
|
},
|
|
7272
6739
|
}
|
|
7273
6740
|
`;
|
|
7274
|
-
await writeFile(
|
|
6741
|
+
await writeFile(path18.join(marketingDir, "postcss.config.mjs"), postcssConfig);
|
|
7275
6742
|
}
|
|
7276
6743
|
function displaySuccessMessage(config) {
|
|
7277
6744
|
const apps = ["web"];
|
|
@@ -7279,9 +6746,6 @@ function displaySuccessMessage(config) {
|
|
|
7279
6746
|
if (config.marketingSite !== "none") {
|
|
7280
6747
|
apps.push("marketing");
|
|
7281
6748
|
}
|
|
7282
|
-
if (config.includeDocs) {
|
|
7283
|
-
apps.push("docs");
|
|
7284
|
-
}
|
|
7285
6749
|
apps.push("design-system");
|
|
7286
6750
|
}
|
|
7287
6751
|
console.log();
|
|
@@ -7314,9 +6778,6 @@ function displaySuccessMessage(config) {
|
|
|
7314
6778
|
if (config.marketingSite === "payload") {
|
|
7315
6779
|
console.log(` ${pc3.dim("-")} Payload CMS: ${pc3.cyan("https://payloadcms.com/docs")}`);
|
|
7316
6780
|
}
|
|
7317
|
-
if (config.includeDocs) {
|
|
7318
|
-
console.log(` ${pc3.dim("-")} Fumadocs: ${pc3.cyan("https://fumadocs.vercel.app")}`);
|
|
7319
|
-
}
|
|
7320
6781
|
console.log();
|
|
7321
6782
|
}
|
|
7322
6783
|
|
|
@@ -7344,7 +6805,7 @@ program.name("create-kofi-stack").description(
|
|
|
7344
6805
|
).version(packageJson.version).argument("[project-name]", "Name of the project").option("--monorepo", "Use monorepo structure with Turborepo").option("--single", "Use single app structure").option(
|
|
7345
6806
|
"--marketing <type>",
|
|
7346
6807
|
"Marketing site type: payload, nextjs, none"
|
|
7347
|
-
).option(
|
|
6808
|
+
).option(
|
|
7348
6809
|
"--component-library <library>",
|
|
7349
6810
|
"Component library: base (Base UI), radix (Radix UI)"
|
|
7350
6811
|
).option(
|