create-kofi-stack 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2210 -146
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc3 from "picocolors";
5
+ import pc4 from "picocolors";
6
6
  import gradient from "gradient-string";
7
7
 
8
8
  // src/prompts/index.ts
@@ -713,9 +713,9 @@ ${extras.length > 0 ? `${pc.bold("Extras:")} ${extras.join(", ")}` : ""}`,
713
713
  }
714
714
 
715
715
  // src/generators/index.ts
716
- import path18 from "path";
717
- import * as p6 from "@clack/prompts";
718
- import pc2 from "picocolors";
716
+ import path20 from "path";
717
+ import * as p7 from "@clack/prompts";
718
+ import pc3 from "picocolors";
719
719
  import ora from "ora";
720
720
 
721
721
  // src/utils/fs.ts
@@ -735,6 +735,9 @@ async function writeJSON(filePath, data) {
735
735
  async function pathExists(filePath) {
736
736
  return fs.pathExists(filePath);
737
737
  }
738
+ async function readFile(filePath) {
739
+ return fs.readFile(filePath, "utf-8");
740
+ }
738
741
 
739
742
  // src/utils/git.ts
740
743
  import { execa } from "execa";
@@ -808,19 +811,19 @@ async function generatePackageJson(config, appDir) {
808
811
  next: "^16.0.0",
809
812
  react: "^19.0.0",
810
813
  "react-dom": "^19.0.0",
811
- "@convex-dev/better-auth": "^0.10.0",
812
- "better-auth": "^1.2.0",
813
- convex: "^1.17.0",
814
+ "@convex-dev/better-auth": "^0.10.9",
815
+ "better-auth": "^1.4.0",
816
+ convex: "^1.25.0",
814
817
  "@t3-oss/env-nextjs": "^0.11.0",
815
- zod: "^3.23.0",
818
+ zod: "^3.25.0",
816
819
  "date-fns": "^4.0.0",
817
820
  clsx: "^2.1.0",
818
821
  "tailwind-merge": "^2.5.0",
819
822
  "@hugeicons/react": "^0.3.0",
820
- "@base-ui-components/react": "^1.0.0-rc.0",
823
+ "@base-ui/react": "^1.0.0",
821
824
  "class-variance-authority": "^0.7.0",
822
825
  resend: "^4.0.0",
823
- "@react-email/components": "^0.0.25"
826
+ "@react-email/components": "^0.0.31"
824
827
  };
825
828
  const devDependencies = {
826
829
  typescript: "^5.0.0",
@@ -1992,8 +1995,8 @@ async function generateConvexPackageJson(convexDir) {
1992
1995
  main: "./convex/index.ts",
1993
1996
  types: "./convex/index.ts",
1994
1997
  dependencies: {
1995
- convex: "^1.17.0",
1996
- "@convex-dev/better-auth": "^0.10.0"
1998
+ convex: "^1.25.0",
1999
+ "@convex-dev/better-auth": "^0.10.9"
1997
2000
  },
1998
2001
  devDependencies: {
1999
2002
  typescript: "^5.0.0"
@@ -3151,7 +3154,7 @@ async function generateUIPackage(config, targetDir) {
3151
3154
  typecheck: "tsc --noEmit"
3152
3155
  },
3153
3156
  dependencies: {
3154
- "@base-ui-components/react": "^1.0.0-rc.0",
3157
+ "@base-ui/react": "^1.0.0",
3155
3158
  "@hugeicons/react": "^0.3.0",
3156
3159
  "class-variance-authority": "^0.7.0",
3157
3160
  clsx: "^2.1.0",
@@ -3254,7 +3257,7 @@ async function generatePayloadPackageJson(marketingDir) {
3254
3257
  "db:seed": "tsx src/seed.ts"
3255
3258
  },
3256
3259
  dependencies: {
3257
- next: "^15.0.0",
3260
+ next: "^16.0.0",
3258
3261
  react: "^19.0.0",
3259
3262
  "react-dom": "^19.0.0",
3260
3263
  payload: "^3.0.0",
@@ -3421,9 +3424,17 @@ export const Media: CollectionConfig = {
3421
3424
  mediaContent
3422
3425
  );
3423
3426
  const pagesContent = `import type { CollectionConfig } from 'payload'
3424
- import { HeroBlock } from '../blocks/Hero'
3425
- import { ContentBlock } from '../blocks/Content'
3426
- import { CTABlock } from '../blocks/CTA'
3427
+ import {
3428
+ HeroBlock,
3429
+ LogoBannerBlock,
3430
+ FeaturesBlock,
3431
+ BenefitsBlock,
3432
+ PricingBlock,
3433
+ TestimonialsBlock,
3434
+ FAQBlock,
3435
+ ContentBlock,
3436
+ CTABlock,
3437
+ } from '../blocks'
3427
3438
 
3428
3439
  export const Pages: CollectionConfig = {
3429
3440
  slug: 'pages',
@@ -3464,7 +3475,17 @@ export const Pages: CollectionConfig = {
3464
3475
  {
3465
3476
  name: 'layout',
3466
3477
  type: 'blocks',
3467
- blocks: [HeroBlock, ContentBlock, CTABlock],
3478
+ blocks: [
3479
+ HeroBlock,
3480
+ LogoBannerBlock,
3481
+ FeaturesBlock,
3482
+ BenefitsBlock,
3483
+ PricingBlock,
3484
+ TestimonialsBlock,
3485
+ FAQBlock,
3486
+ ContentBlock,
3487
+ CTABlock,
3488
+ ],
3468
3489
  },
3469
3490
  ],
3470
3491
  }
@@ -3677,6 +3698,7 @@ async function generatePayloadBlocks(marketingDir) {
3677
3698
 
3678
3699
  export const HeroBlock: Block = {
3679
3700
  slug: 'hero',
3701
+ labels: { singular: 'Hero', plural: 'Heroes' },
3680
3702
  fields: [
3681
3703
  {
3682
3704
  name: 'heading',
@@ -3692,74 +3714,242 @@ export const HeroBlock: Block = {
3692
3714
  type: 'upload',
3693
3715
  relationTo: 'media',
3694
3716
  },
3717
+ {
3718
+ name: 'buttons',
3719
+ type: 'array',
3720
+ maxRows: 2,
3721
+ fields: [
3722
+ { name: 'label', type: 'text', required: true },
3723
+ { name: 'url', type: 'text', required: true },
3724
+ {
3725
+ name: 'variant',
3726
+ type: 'select',
3727
+ options: [
3728
+ { label: 'Primary', value: 'primary' },
3729
+ { label: 'Secondary', value: 'secondary' },
3730
+ ],
3731
+ defaultValue: 'primary',
3732
+ },
3733
+ ],
3734
+ },
3735
+ ],
3736
+ }
3737
+ `;
3738
+ await writeFile(path15.join(marketingDir, "src/blocks/Hero.ts"), heroContent);
3739
+ const logoBannerContent = `import type { Block } from 'payload'
3740
+
3741
+ export const LogoBannerBlock: Block = {
3742
+ slug: 'logoBanner',
3743
+ labels: { singular: 'Logo Banner', plural: 'Logo Banners' },
3744
+ fields: [
3745
+ { name: 'heading', type: 'text' },
3746
+ {
3747
+ name: 'logos',
3748
+ type: 'array',
3749
+ fields: [
3750
+ { name: 'name', type: 'text', required: true },
3751
+ { name: 'logo', type: 'upload', relationTo: 'media' },
3752
+ ],
3753
+ },
3754
+ ],
3755
+ }
3756
+ `;
3757
+ await writeFile(path15.join(marketingDir, "src/blocks/LogoBanner.ts"), logoBannerContent);
3758
+ const featuresContent = `import type { Block } from 'payload'
3759
+
3760
+ export const FeaturesBlock: Block = {
3761
+ slug: 'features',
3762
+ labels: { singular: 'Features', plural: 'Features' },
3763
+ fields: [
3764
+ { name: 'heading', type: 'text' },
3765
+ { name: 'subheading', type: 'textarea' },
3766
+ {
3767
+ name: 'features',
3768
+ type: 'array',
3769
+ fields: [
3770
+ {
3771
+ name: 'icon',
3772
+ type: 'select',
3773
+ options: [
3774
+ { label: 'Zap', value: 'zap' },
3775
+ { label: 'Shield', value: 'shield' },
3776
+ { label: 'Globe', value: 'globe' },
3777
+ { label: 'Layers', value: 'layers' },
3778
+ { label: 'Settings', value: 'settings' },
3779
+ { label: 'Users', value: 'users' },
3780
+ { label: 'Lock', value: 'lock' },
3781
+ { label: 'Star', value: 'star' },
3782
+ ],
3783
+ },
3784
+ { name: 'title', type: 'text', required: true },
3785
+ { name: 'description', type: 'textarea' },
3786
+ ],
3787
+ },
3788
+ ],
3789
+ }
3790
+ `;
3791
+ await writeFile(path15.join(marketingDir, "src/blocks/Features.ts"), featuresContent);
3792
+ const benefitsContent = `import type { Block } from 'payload'
3793
+
3794
+ export const BenefitsBlock: Block = {
3795
+ slug: 'benefits',
3796
+ labels: { singular: 'Benefits', plural: 'Benefits' },
3797
+ fields: [
3798
+ { name: 'label', type: 'text' },
3799
+ { name: 'heading', type: 'text', required: true },
3800
+ { name: 'description', type: 'textarea' },
3801
+ { name: 'image', type: 'upload', relationTo: 'media' },
3802
+ {
3803
+ name: 'imagePosition',
3804
+ type: 'select',
3805
+ options: [
3806
+ { label: 'Left', value: 'left' },
3807
+ { label: 'Right', value: 'right' },
3808
+ ],
3809
+ defaultValue: 'right',
3810
+ },
3811
+ {
3812
+ name: 'benefits',
3813
+ type: 'array',
3814
+ fields: [{ name: 'text', type: 'text', required: true }],
3815
+ },
3695
3816
  {
3696
3817
  name: 'cta',
3697
3818
  type: 'group',
3698
3819
  fields: [
3820
+ { name: 'label', type: 'text' },
3821
+ { name: 'url', type: 'text' },
3822
+ ],
3823
+ },
3824
+ ],
3825
+ }
3826
+ `;
3827
+ await writeFile(path15.join(marketingDir, "src/blocks/Benefits.ts"), benefitsContent);
3828
+ const pricingContent = `import type { Block } from 'payload'
3829
+
3830
+ export const PricingBlock: Block = {
3831
+ slug: 'pricing',
3832
+ labels: { singular: 'Pricing', plural: 'Pricing' },
3833
+ fields: [
3834
+ { name: 'heading', type: 'text' },
3835
+ { name: 'subheading', type: 'textarea' },
3836
+ {
3837
+ name: 'plans',
3838
+ type: 'array',
3839
+ fields: [
3840
+ { name: 'name', type: 'text', required: true },
3841
+ { name: 'price', type: 'text', required: true },
3842
+ { name: 'period', type: 'text', defaultValue: '/month' },
3843
+ { name: 'description', type: 'textarea' },
3844
+ { name: 'featured', type: 'checkbox', defaultValue: false },
3699
3845
  {
3700
- name: 'label',
3701
- type: 'text',
3846
+ name: 'features',
3847
+ type: 'array',
3848
+ fields: [
3849
+ { name: 'feature', type: 'text', required: true },
3850
+ { name: 'included', type: 'checkbox', defaultValue: true },
3851
+ ],
3702
3852
  },
3703
3853
  {
3704
- name: 'url',
3705
- type: 'text',
3854
+ name: 'cta',
3855
+ type: 'group',
3856
+ fields: [
3857
+ { name: 'label', type: 'text' },
3858
+ { name: 'url', type: 'text' },
3859
+ ],
3706
3860
  },
3707
3861
  ],
3708
3862
  },
3709
3863
  ],
3710
3864
  }
3711
3865
  `;
3712
- await writeFile(path15.join(marketingDir, "src/blocks/Hero.ts"), heroContent);
3866
+ await writeFile(path15.join(marketingDir, "src/blocks/Pricing.ts"), pricingContent);
3867
+ const testimonialsContent = `import type { Block } from 'payload'
3868
+
3869
+ export const TestimonialsBlock: Block = {
3870
+ slug: 'testimonials',
3871
+ labels: { singular: 'Testimonials', plural: 'Testimonials' },
3872
+ fields: [
3873
+ { name: 'heading', type: 'text' },
3874
+ { name: 'subheading', type: 'textarea' },
3875
+ {
3876
+ name: 'testimonials',
3877
+ type: 'array',
3878
+ fields: [
3879
+ { name: 'quote', type: 'textarea', required: true },
3880
+ { name: 'author', type: 'text', required: true },
3881
+ { name: 'role', type: 'text' },
3882
+ { name: 'company', type: 'text' },
3883
+ { name: 'image', type: 'upload', relationTo: 'media' },
3884
+ ],
3885
+ },
3886
+ ],
3887
+ }
3888
+ `;
3889
+ await writeFile(path15.join(marketingDir, "src/blocks/Testimonials.ts"), testimonialsContent);
3890
+ const faqContent = `import type { Block } from 'payload'
3891
+
3892
+ export const FAQBlock: Block = {
3893
+ slug: 'faq',
3894
+ labels: { singular: 'FAQ', plural: 'FAQs' },
3895
+ fields: [
3896
+ { name: 'heading', type: 'text' },
3897
+ { name: 'subheading', type: 'textarea' },
3898
+ {
3899
+ name: 'items',
3900
+ type: 'array',
3901
+ fields: [
3902
+ { name: 'question', type: 'text', required: true },
3903
+ { name: 'answer', type: 'textarea', required: true },
3904
+ ],
3905
+ },
3906
+ ],
3907
+ }
3908
+ `;
3909
+ await writeFile(path15.join(marketingDir, "src/blocks/FAQ.ts"), faqContent);
3713
3910
  const contentContent = `import type { Block } from 'payload'
3714
3911
 
3715
3912
  export const ContentBlock: Block = {
3716
3913
  slug: 'content',
3914
+ labels: { singular: 'Content', plural: 'Content' },
3717
3915
  fields: [
3718
- {
3719
- name: 'content',
3720
- type: 'richText',
3721
- },
3916
+ { name: 'content', type: 'richText' },
3722
3917
  ],
3723
3918
  }
3724
3919
  `;
3725
- await writeFile(
3726
- path15.join(marketingDir, "src/blocks/Content.ts"),
3727
- contentContent
3728
- );
3920
+ await writeFile(path15.join(marketingDir, "src/blocks/Content.ts"), contentContent);
3729
3921
  const ctaContent = `import type { Block } from 'payload'
3730
3922
 
3731
3923
  export const CTABlock: Block = {
3732
3924
  slug: 'cta',
3925
+ labels: { singular: 'CTA', plural: 'CTAs' },
3733
3926
  fields: [
3927
+ { name: 'heading', type: 'text' },
3928
+ { name: 'description', type: 'textarea' },
3734
3929
  {
3735
- name: 'heading',
3736
- type: 'text',
3737
- },
3738
- {
3739
- name: 'description',
3740
- type: 'textarea',
3930
+ name: 'style',
3931
+ type: 'select',
3932
+ options: [
3933
+ { label: 'Light', value: 'light' },
3934
+ { label: 'Dark', value: 'dark' },
3935
+ { label: 'Primary', value: 'primary' },
3936
+ ],
3937
+ defaultValue: 'dark',
3741
3938
  },
3742
3939
  {
3743
3940
  name: 'buttons',
3744
3941
  type: 'array',
3745
3942
  maxRows: 2,
3746
3943
  fields: [
3747
- {
3748
- name: 'label',
3749
- type: 'text',
3750
- required: true,
3751
- },
3752
- {
3753
- name: 'url',
3754
- type: 'text',
3755
- required: true,
3756
- },
3944
+ { name: 'label', type: 'text', required: true },
3945
+ { name: 'url', type: 'text', required: true },
3757
3946
  {
3758
3947
  name: 'variant',
3759
3948
  type: 'select',
3760
3949
  options: [
3761
3950
  { label: 'Primary', value: 'primary' },
3762
3951
  { label: 'Secondary', value: 'secondary' },
3952
+ { label: 'Outline', value: 'outline' },
3763
3953
  ],
3764
3954
  defaultValue: 'primary',
3765
3955
  },
@@ -3770,6 +3960,12 @@ export const CTABlock: Block = {
3770
3960
  `;
3771
3961
  await writeFile(path15.join(marketingDir, "src/blocks/CTA.ts"), ctaContent);
3772
3962
  const indexContent = `export { HeroBlock } from './Hero'
3963
+ export { LogoBannerBlock } from './LogoBanner'
3964
+ export { FeaturesBlock } from './Features'
3965
+ export { BenefitsBlock } from './Benefits'
3966
+ export { PricingBlock } from './Pricing'
3967
+ export { TestimonialsBlock } from './Testimonials'
3968
+ export { FAQBlock } from './FAQ'
3773
3969
  export { ContentBlock } from './Content'
3774
3970
  export { CTABlock } from './CTA'
3775
3971
  `;
@@ -3921,17 +4117,23 @@ async function generatePayloadTsConfig(marketingDir) {
3921
4117
  await writeJSON(path15.join(marketingDir, "tsconfig.json"), tsConfig);
3922
4118
  }
3923
4119
  async function generatePayloadEnv(marketingDir) {
3924
- const envContent = `# Database
4120
+ const envContent = `# Database (Supabase PostgreSQL)
3925
4121
  DATABASE_URL=postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres
3926
4122
 
3927
- # Payload
3928
- PAYLOAD_SECRET=
4123
+ # Payload CMS
4124
+ PAYLOAD_SECRET= # Generate with: openssl rand -base64 32
4125
+
4126
+ # Scheduled Jobs
4127
+ CRON_SECRET= # Generate with: openssl rand -base64 32
3929
4128
 
3930
- # S3 Storage (Supabase)
3931
- S3_BUCKET=
4129
+ # Draft Previews
4130
+ PREVIEW_SECRET= # Generate with: openssl rand -base64 32
4131
+
4132
+ # S3 Storage (Supabase Storage)
4133
+ S3_BUCKET=media
3932
4134
  S3_ACCESS_KEY_ID=
3933
4135
  S3_SECRET_ACCESS_KEY=
3934
- S3_REGION=
4136
+ S3_REGION=auto
3935
4137
  S3_ENDPOINT=https://[PROJECT].supabase.co/storage/v1/s3
3936
4138
  `;
3937
4139
  await writeFile(path15.join(marketingDir, ".env.example"), envContent);
@@ -3965,12 +4167,12 @@ async function generateFumadocsPackageJson(docsDir) {
3965
4167
  typecheck: "tsc --noEmit"
3966
4168
  },
3967
4169
  dependencies: {
3968
- next: "^15.0.0",
4170
+ next: "^16.0.0",
3969
4171
  react: "^19.0.0",
3970
4172
  "react-dom": "^19.0.0",
3971
- "fumadocs-core": "^14.0.0",
3972
- "fumadocs-mdx": "^10.0.0",
3973
- "fumadocs-ui": "^14.0.0",
4173
+ "fumadocs-core": "^15.0.0",
4174
+ "fumadocs-mdx": "^15.0.0",
4175
+ "fumadocs-ui": "^15.0.0",
3974
4176
  "@repo/ui": "workspace:*"
3975
4177
  },
3976
4178
  devDependencies: {
@@ -4148,48 +4350,11 @@ export default function HomePage() {
4148
4350
  }
4149
4351
  `;
4150
4352
  await writeFile(path16.join(docsDir, "src/app/page.tsx"), homePageContent);
4151
- const globalCssContent = `@import 'fumadocs-ui/style.css';
4152
- @import "tailwindcss";
4353
+ const globalCssContent = `@import 'tailwindcss';
4354
+ @import 'fumadocs-ui/css/neutral.css';
4355
+ @import 'fumadocs-ui/css/preset.css';
4153
4356
 
4154
- :root {
4155
- --fd-background: 0 0% 100%;
4156
- --fd-foreground: 0 0% 3.9%;
4157
- --fd-muted: 0 0% 96.1%;
4158
- --fd-muted-foreground: 0 0% 45.1%;
4159
- --fd-popover: 0 0% 100%;
4160
- --fd-popover-foreground: 0 0% 3.9%;
4161
- --fd-card: 0 0% 100%;
4162
- --fd-card-foreground: 0 0% 3.9%;
4163
- --fd-border: 0 0% 89.8%;
4164
- --fd-input: 0 0% 89.8%;
4165
- --fd-primary: 0 0% 9%;
4166
- --fd-primary-foreground: 0 0% 98%;
4167
- --fd-secondary: 0 0% 96.1%;
4168
- --fd-secondary-foreground: 0 0% 9%;
4169
- --fd-accent: 0 0% 96.1%;
4170
- --fd-accent-foreground: 0 0% 9%;
4171
- --fd-ring: 0 0% 63.9%;
4172
- }
4173
-
4174
- .dark {
4175
- --fd-background: 0 0% 3.9%;
4176
- --fd-foreground: 0 0% 98%;
4177
- --fd-muted: 0 0% 14.9%;
4178
- --fd-muted-foreground: 0 0% 63.9%;
4179
- --fd-popover: 0 0% 3.9%;
4180
- --fd-popover-foreground: 0 0% 98%;
4181
- --fd-card: 0 0% 3.9%;
4182
- --fd-card-foreground: 0 0% 98%;
4183
- --fd-border: 0 0% 14.9%;
4184
- --fd-input: 0 0% 14.9%;
4185
- --fd-primary: 0 0% 98%;
4186
- --fd-primary-foreground: 0 0% 9%;
4187
- --fd-secondary: 0 0% 14.9%;
4188
- --fd-secondary-foreground: 0 0% 98%;
4189
- --fd-accent: 0 0% 14.9%;
4190
- --fd-accent-foreground: 0 0% 98%;
4191
- --fd-ring: 0 0% 14.9%;
4192
- }
4357
+ @source '../node_modules/fumadocs-ui/dist/**/*.js';
4193
4358
  `;
4194
4359
  await writeFile(path16.join(docsDir, "src/app/global.css"), globalCssContent);
4195
4360
  const sourceConfigContent = `import { defineCollections, defineConfig } from 'fumadocs-mdx/config'
@@ -4315,7 +4480,7 @@ ${config.name} uses Better-Auth with Convex for authentication.
4315
4480
 
4316
4481
  - Email/Password (always enabled)
4317
4482
  - Google OAuth (always enabled)
4318
- ${config.auth.providers.map((p7) => `- ${p7.charAt(0).toUpperCase() + p7.slice(1)}`).join("\n")}
4483
+ ${config.auth.providers.map((p8) => `- ${p8.charAt(0).toUpperCase() + p8.slice(1)}`).join("\n")}
4319
4484
 
4320
4485
  ## Configuration
4321
4486
 
@@ -5009,19 +5174,1913 @@ Follow our blog for updates on new features and best practices.
5009
5174
  );
5010
5175
  }
5011
5176
 
5177
+ // src/generators/design-system.ts
5178
+ import path18 from "path";
5179
+ var fontImports = {
5180
+ inter: {
5181
+ import: "import { Inter } from 'next/font/google'",
5182
+ variable: "--font-inter",
5183
+ config: `const inter = Inter({
5184
+ variable: '--font-inter',
5185
+ subsets: ['latin'],
5186
+ })`
5187
+ },
5188
+ "dm-sans": {
5189
+ import: "import { DM_Sans } from 'next/font/google'",
5190
+ variable: "--font-dm-sans",
5191
+ config: `const dmSans = DM_Sans({
5192
+ variable: '--font-dm-sans',
5193
+ subsets: ['latin'],
5194
+ weight: ['400', '500', '600', '700'],
5195
+ })`
5196
+ },
5197
+ geist: {
5198
+ import: "import { Geist } from 'next/font/google'",
5199
+ variable: "--font-geist",
5200
+ config: `const geist = Geist({
5201
+ variable: '--font-geist',
5202
+ subsets: ['latin'],
5203
+ })`
5204
+ },
5205
+ "plus-jakarta": {
5206
+ import: "import { Plus_Jakarta_Sans } from 'next/font/google'",
5207
+ variable: "--font-plus-jakarta",
5208
+ config: `const plusJakarta = Plus_Jakarta_Sans({
5209
+ variable: '--font-plus-jakarta',
5210
+ subsets: ['latin'],
5211
+ weight: ['400', '500', '600', '700'],
5212
+ })`
5213
+ },
5214
+ outfit: {
5215
+ import: "import { Outfit } from 'next/font/google'",
5216
+ variable: "--font-outfit",
5217
+ config: `const outfit = Outfit({
5218
+ variable: '--font-outfit',
5219
+ subsets: ['latin'],
5220
+ })`
5221
+ },
5222
+ "space-grotesk": {
5223
+ import: "import { Space_Grotesk } from 'next/font/google'",
5224
+ variable: "--font-space-grotesk",
5225
+ config: `const spaceGrotesk = Space_Grotesk({
5226
+ variable: '--font-space-grotesk',
5227
+ subsets: ['latin'],
5228
+ weight: ['400', '500', '600', '700'],
5229
+ })`
5230
+ }
5231
+ };
5232
+ var radiusValues = {
5233
+ none: "0px",
5234
+ sm: "4px",
5235
+ md: "8px",
5236
+ lg: "12px",
5237
+ xl: "16px",
5238
+ full: "9999px"
5239
+ };
5240
+ var spacingMultipliers = {
5241
+ compact: 0.875,
5242
+ default: 1,
5243
+ relaxed: 1.125
5244
+ };
5245
+ async function generateDesignSystemApp(config, targetDir) {
5246
+ const appDir = path18.join(targetDir, "apps/design-system");
5247
+ await ensureDir(path18.join(appDir, "src/app"));
5248
+ await ensureDir(path18.join(appDir, "src/app/colors"));
5249
+ await ensureDir(path18.join(appDir, "src/app/typography"));
5250
+ await ensureDir(path18.join(appDir, "src/app/spacing"));
5251
+ await ensureDir(path18.join(appDir, "src/app/brand"));
5252
+ await ensureDir(path18.join(appDir, "src/app/blocks"));
5253
+ await ensureDir(path18.join(appDir, "src/app/components"));
5254
+ await ensureDir(path18.join(appDir, "src/lib"));
5255
+ await ensureDir(path18.join(appDir, "public/brand"));
5256
+ await generatePackageJson2(config, appDir);
5257
+ await generateTsConfig2(appDir);
5258
+ await generateNextConfig2(appDir);
5259
+ await generatePostCssConfig2(appDir);
5260
+ await generateGlobalsCss2(config, appDir);
5261
+ await generateLayoutTsx(config, appDir);
5262
+ await generateHomePage(config, appDir);
5263
+ await generateColorsPage(config, appDir);
5264
+ await generateTypographyPage(config, appDir);
5265
+ await generateSpacingPage(config, appDir);
5266
+ await generateBrandPage(config, appDir);
5267
+ await generateBlocksPage(appDir);
5268
+ await generateComponentsPage(appDir);
5269
+ await generateUtils(appDir);
5270
+ }
5271
+ async function generatePackageJson2(config, appDir) {
5272
+ const packageJson = {
5273
+ name: "@repo/design-system",
5274
+ version: "0.1.0",
5275
+ private: true,
5276
+ scripts: {
5277
+ dev: "next dev --port 3003",
5278
+ build: "next build",
5279
+ start: "next start",
5280
+ lint: "biome check .",
5281
+ "lint:fix": "biome check --write .",
5282
+ typecheck: "tsc --noEmit"
5283
+ },
5284
+ dependencies: {
5285
+ "@repo/ui": "workspace:*",
5286
+ "class-variance-authority": "^0.7.1",
5287
+ clsx: "^2.1.1",
5288
+ next: "^16.0.0",
5289
+ "next-themes": "^0.4.6",
5290
+ react: "^19.0.0",
5291
+ "react-dom": "^19.0.0",
5292
+ recharts: "^2.15.0",
5293
+ sonner: "^2.0.0",
5294
+ "tailwind-merge": "^3.0.0",
5295
+ "tw-animate-css": "^1.3.0"
5296
+ },
5297
+ devDependencies: {
5298
+ "@repo/config-typescript": "workspace:*",
5299
+ "@tailwindcss/postcss": "^4.0.0",
5300
+ "@types/node": "^20.0.0",
5301
+ "@types/react": "^19.0.0",
5302
+ "@types/react-dom": "^19.0.0",
5303
+ tailwindcss: "^4.0.0",
5304
+ typescript: "^5.0.0"
5305
+ }
5306
+ };
5307
+ await writeJSON(path18.join(appDir, "package.json"), packageJson);
5308
+ }
5309
+ async function generateTsConfig2(appDir) {
5310
+ const tsConfig = {
5311
+ extends: "@repo/config-typescript/nextjs.json",
5312
+ compilerOptions: {
5313
+ jsx: "preserve",
5314
+ paths: {
5315
+ "@/*": ["./src/*"],
5316
+ "@repo/ui": ["../../packages/ui/src"],
5317
+ "@repo/ui/*": ["../../packages/ui/src/*"]
5318
+ }
5319
+ },
5320
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
5321
+ exclude: ["node_modules"]
5322
+ };
5323
+ await writeJSON(path18.join(appDir, "tsconfig.json"), tsConfig);
5324
+ }
5325
+ async function generateNextConfig2(appDir) {
5326
+ const content = `import type { NextConfig } from 'next'
5327
+
5328
+ const nextConfig: NextConfig = {
5329
+ transpilePackages: ['@repo/ui'],
5330
+ }
5331
+
5332
+ export default nextConfig
5333
+ `;
5334
+ await writeFile(path18.join(appDir, "next.config.ts"), content);
5335
+ }
5336
+ async function generatePostCssConfig2(appDir) {
5337
+ const content = `export default {
5338
+ plugins: {
5339
+ '@tailwindcss/postcss': {},
5340
+ },
5341
+ }
5342
+ `;
5343
+ await writeFile(path18.join(appDir, "postcss.config.mjs"), content);
5344
+ }
5345
+ async function generateGlobalsCss2(config, appDir) {
5346
+ const { designSystem } = config;
5347
+ const radiusValue = radiusValues[designSystem.borderRadius];
5348
+ const spacingMult = spacingMultipliers[designSystem.spacingScale];
5349
+ const content = `@import "tailwindcss";
5350
+ @import "tw-animate-css";
5351
+
5352
+ @custom-variant dark (&:is(.dark *));
5353
+
5354
+ /* Design System - CSS Variables */
5355
+
5356
+ :root {
5357
+ /* Border Radius */
5358
+ --radius: ${radiusValue};
5359
+ --radius-xs: calc(${radiusValue} * 0.5);
5360
+ --radius-sm: calc(${radiusValue} * 0.75);
5361
+ --radius-md: ${radiusValue};
5362
+ --radius-lg: calc(${radiusValue} * 1.5);
5363
+ --radius-pill: 9999px;
5364
+
5365
+ /* Spacing Scale Multiplier: ${designSystem.spacingScale} (${spacingMult}x) */
5366
+
5367
+ /* Color Tokens - Light Mode */
5368
+ --background: #ffffff;
5369
+ --card: #ffffff;
5370
+ --popover: #ffffff;
5371
+ --muted: #f5f7fa;
5372
+
5373
+ /* Foreground tokens */
5374
+ --foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5375
+ --card-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5376
+ --popover-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5377
+ --muted-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "40%" : i === 1 ? "20%" : v).join(" ")});
5378
+
5379
+ /* Brand colors */
5380
+ --primary: hsl(${designSystem.primaryColor});
5381
+ --primary-foreground: #ffffff;
5382
+ --secondary: hsl(${designSystem.secondaryColor});
5383
+ --secondary-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5384
+ --accent: hsl(${designSystem.accentColor});
5385
+ --accent-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5386
+
5387
+ /* Semantic colors */
5388
+ --success: #22c55e;
5389
+ --warning: #f59e0b;
5390
+ --destructive: #ef4444;
5391
+ --destructive-foreground: #ffffff;
5392
+
5393
+ /* Border and input */
5394
+ --border: #e2e8f0;
5395
+ --input: #e2e8f0;
5396
+ --ring: hsl(${designSystem.primaryColor});
5397
+
5398
+ /* Chart colors */
5399
+ --chart-1: hsl(${designSystem.primaryColor});
5400
+ --chart-2: #22c55e;
5401
+ --chart-3: hsl(${designSystem.accentColor});
5402
+ --chart-4: #ef4444;
5403
+ --chart-5: #6b7280;
5404
+
5405
+ /* Sidebar tokens */
5406
+ --sidebar: #ffffff;
5407
+ --sidebar-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5408
+ --sidebar-primary: hsl(${designSystem.primaryColor});
5409
+ --sidebar-primary-foreground: #ffffff;
5410
+ --sidebar-accent: hsla(${designSystem.primaryColor}, 0.1);
5411
+ --sidebar-accent-foreground: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "10%" : v).join(" ")});
5412
+ --sidebar-border: #e2e8f0;
5413
+ --sidebar-ring: hsl(${designSystem.primaryColor});
5414
+ }
5415
+
5416
+ .dark {
5417
+ /* Color Tokens - Dark Mode */
5418
+ --background: #0b0d11;
5419
+ --card: #111418;
5420
+ --popover: #111418;
5421
+ --muted: #161a20;
5422
+
5423
+ --foreground: #f5f7fa;
5424
+ --card-foreground: #f5f7fa;
5425
+ --popover-foreground: #f5f7fa;
5426
+ --muted-foreground: #9ba3b0;
5427
+
5428
+ --primary: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5429
+ --primary-foreground: #ffffff;
5430
+ --secondary: #1e293b;
5431
+ --secondary-foreground: #f8fafc;
5432
+ --accent: #1e293b;
5433
+ --accent-foreground: #f8fafc;
5434
+
5435
+ --success: #4ade80;
5436
+ --warning: #fbbf24;
5437
+ --destructive: #fca5a5;
5438
+ --destructive-foreground: #0f172a;
5439
+
5440
+ --border: rgba(255, 255, 255, 0.1);
5441
+ --input: rgba(255, 255, 255, 0.1);
5442
+ --ring: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5443
+
5444
+ --chart-1: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5445
+ --chart-2: #4ade80;
5446
+ --chart-3: hsl(${designSystem.accentColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5447
+ --chart-4: #fca5a5;
5448
+ --chart-5: #9ba3b0;
5449
+
5450
+ --sidebar: #0b0d11;
5451
+ --sidebar-foreground: #f5f7fa;
5452
+ --sidebar-primary: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5453
+ --sidebar-primary-foreground: #ffffff;
5454
+ --sidebar-accent: hsla(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")}, 0.1);
5455
+ --sidebar-accent-foreground: #f5f7fa;
5456
+ --sidebar-border: rgba(255, 255, 255, 0.08);
5457
+ --sidebar-ring: hsl(${designSystem.primaryColor.replace("%", "").split(" ").map((v, i) => i === 2 ? "60%" : v).join(" ")});
5458
+ }
5459
+
5460
+ /* Tailwind Theme Mapping */
5461
+ @theme inline {
5462
+ --radius-sm: calc(var(--radius) - 4px);
5463
+ --radius-md: calc(var(--radius) - 2px);
5464
+ --radius-lg: var(--radius);
5465
+ --radius-xl: calc(var(--radius) + 4px);
5466
+
5467
+ --color-background: var(--background);
5468
+ --color-foreground: var(--foreground);
5469
+ --color-card: var(--card);
5470
+ --color-card-foreground: var(--card-foreground);
5471
+ --color-popover: var(--popover);
5472
+ --color-popover-foreground: var(--popover-foreground);
5473
+ --color-primary: var(--primary);
5474
+ --color-primary-foreground: var(--primary-foreground);
5475
+ --color-secondary: var(--secondary);
5476
+ --color-secondary-foreground: var(--secondary-foreground);
5477
+ --color-muted: var(--muted);
5478
+ --color-muted-foreground: var(--muted-foreground);
5479
+ --color-accent: var(--accent);
5480
+ --color-accent-foreground: var(--accent-foreground);
5481
+ --color-destructive: var(--destructive);
5482
+ --color-destructive-foreground: var(--destructive-foreground);
5483
+ --color-border: var(--border);
5484
+ --color-input: var(--input);
5485
+ --color-ring: var(--ring);
5486
+ --color-success: var(--success);
5487
+ --color-warning: var(--warning);
5488
+
5489
+ --color-chart-1: var(--chart-1);
5490
+ --color-chart-2: var(--chart-2);
5491
+ --color-chart-3: var(--chart-3);
5492
+ --color-chart-4: var(--chart-4);
5493
+ --color-chart-5: var(--chart-5);
5494
+
5495
+ --color-sidebar: var(--sidebar);
5496
+ --color-sidebar-foreground: var(--sidebar-foreground);
5497
+ --color-sidebar-primary: var(--sidebar-primary);
5498
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
5499
+ --color-sidebar-accent: var(--sidebar-accent);
5500
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
5501
+ --color-sidebar-border: var(--sidebar-border);
5502
+ --color-sidebar-ring: var(--sidebar-ring);
5503
+ }
5504
+
5505
+ /* Typography */
5506
+ @theme {
5507
+ --font-size-display-hero: 3.5rem;
5508
+ --font-size-display-02: 3rem;
5509
+ --font-size-display-01: 2.5rem;
5510
+ --font-size-h1: 2rem;
5511
+ --font-size-h2: 1.5rem;
5512
+ --font-size-h3: 1.25rem;
5513
+ --font-size-h4: 1.125rem;
5514
+ --font-size-body-lg: 1.125rem;
5515
+ --font-size-body: 1rem;
5516
+ --font-size-body-sm: 0.875rem;
5517
+
5518
+ --line-height-display-hero: 1.1;
5519
+ --line-height-display: 1.2;
5520
+ --line-height-heading: 1.2;
5521
+ --line-height-body: 1.5;
5522
+ --line-height-body-relaxed: 1.75;
5523
+ }
5524
+
5525
+ /* Base styles */
5526
+ html, body {
5527
+ min-height: 100%;
5528
+ overflow-x: hidden;
5529
+ max-width: 100vw;
5530
+ }
5531
+
5532
+ h1, h2, h3, h4, h5, h6 {
5533
+ font-family: var(--font-heading), system-ui, sans-serif;
5534
+ }
5535
+
5536
+ /* Typography Utility Classes */
5537
+ .text-display-hero {
5538
+ font-size: var(--font-size-display-hero);
5539
+ line-height: var(--line-height-display-hero);
5540
+ font-weight: 600;
5541
+ }
5542
+
5543
+ .text-display-02 {
5544
+ font-size: var(--font-size-display-02);
5545
+ line-height: var(--line-height-display);
5546
+ font-weight: 600;
5547
+ }
5548
+
5549
+ .text-display-01 {
5550
+ font-size: var(--font-size-display-01);
5551
+ line-height: var(--line-height-display);
5552
+ font-weight: 600;
5553
+ }
5554
+
5555
+ .text-h1 {
5556
+ font-size: var(--font-size-h1);
5557
+ line-height: var(--line-height-heading);
5558
+ font-weight: 600;
5559
+ }
5560
+
5561
+ .text-h2 {
5562
+ font-size: var(--font-size-h2);
5563
+ line-height: var(--line-height-heading);
5564
+ font-weight: 600;
5565
+ }
5566
+
5567
+ .text-h3 {
5568
+ font-size: var(--font-size-h3);
5569
+ line-height: var(--line-height-heading);
5570
+ font-weight: 500;
5571
+ }
5572
+
5573
+ .text-h4 {
5574
+ font-size: var(--font-size-h4);
5575
+ line-height: var(--line-height-heading);
5576
+ font-weight: 500;
5577
+ }
5578
+
5579
+ .text-body-lg {
5580
+ font-size: var(--font-size-body-lg);
5581
+ line-height: var(--line-height-body-relaxed);
5582
+ }
5583
+
5584
+ .text-body {
5585
+ font-size: var(--font-size-body);
5586
+ line-height: var(--line-height-body);
5587
+ }
5588
+
5589
+ .text-body-sm {
5590
+ font-size: var(--font-size-body-sm);
5591
+ line-height: var(--line-height-body);
5592
+ }
5593
+
5594
+ @layer base {
5595
+ * {
5596
+ @apply border-border outline-ring/50;
5597
+ }
5598
+ body {
5599
+ @apply bg-background text-foreground;
5600
+ }
5601
+ }
5602
+ `;
5603
+ await writeFile(path18.join(appDir, "src/app/globals.css"), content);
5604
+ }
5605
+ async function generateLayoutTsx(config, appDir) {
5606
+ const { designSystem } = config;
5607
+ const headingFont = fontImports[designSystem.headingFont];
5608
+ const bodyFont = fontImports[designSystem.bodyFont];
5609
+ const imports = [];
5610
+ const fontConfigs = [];
5611
+ const fontVars = [];
5612
+ if (headingFont) {
5613
+ imports.push(headingFont.import);
5614
+ fontConfigs.push(headingFont.config.replace(/const \w+/, "const headingFont"));
5615
+ fontVars.push("${headingFont.variable}");
5616
+ }
5617
+ if (bodyFont && bodyFont.variable !== headingFont?.variable) {
5618
+ imports.push(bodyFont.import);
5619
+ fontConfigs.push(bodyFont.config.replace(/const \w+/, "const bodyFont"));
5620
+ fontVars.push("${bodyFont.variable}");
5621
+ }
5622
+ const uniqueImports = [...new Set(imports)];
5623
+ const content = `import type { Metadata } from 'next'
5624
+ ${uniqueImports.join("\n")}
5625
+ import { ThemeProvider } from 'next-themes'
5626
+ import './globals.css'
5627
+
5628
+ ${fontConfigs.join("\n\n")}
5629
+
5630
+ export const metadata: Metadata = {
5631
+ title: '${config.name} Design System',
5632
+ description: 'Design system documentation for ${config.name}',
5633
+ }
5634
+
5635
+ export default function RootLayout({
5636
+ children,
5637
+ }: Readonly<{
5638
+ children: React.ReactNode
5639
+ }>) {
5640
+ return (
5641
+ <html lang="en" suppressHydrationWarning>
5642
+ <body
5643
+ className={\`\${headingFont.variable}${bodyFont && bodyFont.variable !== headingFont?.variable ? " ${bodyFont.variable}" : ""} font-sans antialiased bg-background text-foreground\`}
5644
+ style={{ '--font-heading': 'var(${headingFont.variable})' } as React.CSSProperties}
5645
+ >
5646
+ <ThemeProvider attribute="class" defaultTheme="light" enableSystem>
5647
+ <div className="min-h-screen flex">
5648
+ <Sidebar projectName="${config.name}" />
5649
+ <main className="flex-1 overflow-auto">{children}</main>
5650
+ </div>
5651
+ </ThemeProvider>
5652
+ </body>
5653
+ </html>
5654
+ )
5655
+ }
5656
+
5657
+ const components = [
5658
+ 'Accordion', 'Alert', 'Alert Dialog', 'Aspect Ratio', 'Avatar', 'Badge',
5659
+ 'Breadcrumb', 'Button', 'Calendar', 'Card', 'Carousel', 'Chart', 'Checkbox',
5660
+ 'Collapsible', 'Command', 'Context Menu', 'Dialog', 'Drawer', 'Dropdown Menu',
5661
+ 'Form', 'Hover Card', 'Input', 'Input OTP', 'Label', 'Menubar', 'Navigation Menu',
5662
+ 'Pagination', 'Popover', 'Progress', 'Radio Group', 'Resizable', 'Scroll Area',
5663
+ 'Select', 'Separator', 'Sheet', 'Sidebar', 'Skeleton', 'Slider', 'Sonner',
5664
+ 'Switch', 'Table', 'Tabs', 'Textarea', 'Toast', 'Toggle', 'Toggle Group', 'Tooltip',
5665
+ ]
5666
+
5667
+ function Sidebar({ projectName }: { projectName: string }) {
5668
+ const foundationItems = [
5669
+ { href: '/', label: 'Overview', icon: 'home' },
5670
+ { href: '/colors', label: 'Colors', icon: 'palette' },
5671
+ { href: '/typography', label: 'Typography', icon: 'type' },
5672
+ { href: '/spacing', label: 'Spacing', icon: 'ruler' },
5673
+ { href: '/brand', label: 'Brand', icon: 'badge' },
5674
+ ]
5675
+
5676
+ return (
5677
+ <aside className="w-64 border-r border-border bg-card hidden md:flex md:flex-col h-screen sticky top-0">
5678
+ <div className="p-6">
5679
+ <a href="/" className="flex items-center gap-3">
5680
+ <div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-semibold">
5681
+ {projectName.charAt(0).toUpperCase()}
5682
+ </div>
5683
+ <span className="text-h4 font-semibold text-foreground">
5684
+ {projectName} Design
5685
+ </span>
5686
+ </a>
5687
+ </div>
5688
+ <nav className="flex-1 overflow-y-auto px-3 pb-6">
5689
+ <div className="mb-4">
5690
+ <p className="px-3 mb-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
5691
+ Foundation
5692
+ </p>
5693
+ <div className="space-y-0.5">
5694
+ {foundationItems.map((item) => (
5695
+ <a
5696
+ key={item.href}
5697
+ href={item.href}
5698
+ className="flex items-center gap-3 px-3 py-2 rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
5699
+ >
5700
+ <NavIcon name={item.icon} />
5701
+ <span>{item.label}</span>
5702
+ </a>
5703
+ ))}
5704
+ </div>
5705
+ </div>
5706
+
5707
+ <div className="mb-4">
5708
+ <p className="px-3 mb-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
5709
+ Blocks
5710
+ </p>
5711
+ <div className="space-y-0.5">
5712
+ <a
5713
+ href="/blocks"
5714
+ className="flex items-center gap-3 px-3 py-2 rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
5715
+ >
5716
+ <NavIcon name="grid" />
5717
+ <span>All Blocks</span>
5718
+ </a>
5719
+ </div>
5720
+ </div>
5721
+
5722
+ <div>
5723
+ <p className="px-3 mb-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
5724
+ Components
5725
+ </p>
5726
+ <div className="space-y-0.5">
5727
+ {components.map((component) => {
5728
+ const slug = component.toLowerCase().replace(/\\s+/g, '-')
5729
+ return (
5730
+ <a
5731
+ key={slug}
5732
+ href={\`/components/\${slug}\`}
5733
+ className="block px-3 py-1.5 rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
5734
+ >
5735
+ {component}
5736
+ </a>
5737
+ )
5738
+ })}
5739
+ </div>
5740
+ </div>
5741
+ </nav>
5742
+ <div className="p-4 border-t border-border">
5743
+ <ThemeToggle />
5744
+ </div>
5745
+ </aside>
5746
+ )
5747
+ }
5748
+
5749
+ function NavIcon({ name }: { name: string }) {
5750
+ const icons: Record<string, React.ReactNode> = {
5751
+ home: (
5752
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5753
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
5754
+ </svg>
5755
+ ),
5756
+ palette: (
5757
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5758
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
5759
+ </svg>
5760
+ ),
5761
+ type: (
5762
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5763
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6h16M4 12h16m-7 6h7" />
5764
+ </svg>
5765
+ ),
5766
+ ruler: (
5767
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5768
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
5769
+ </svg>
5770
+ ),
5771
+ badge: (
5772
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5773
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
5774
+ </svg>
5775
+ ),
5776
+ grid: (
5777
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5778
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
5779
+ </svg>
5780
+ ),
5781
+ }
5782
+ return icons[name] || null
5783
+ }
5784
+
5785
+ function ThemeToggle() {
5786
+ return (
5787
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
5788
+ <button className="p-2 rounded-lg hover:bg-muted transition-colors" title="Light mode">
5789
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5790
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
5791
+ </svg>
5792
+ </button>
5793
+ <button className="p-2 rounded-lg hover:bg-muted transition-colors" title="Dark mode">
5794
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5795
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
5796
+ </svg>
5797
+ </button>
5798
+ </div>
5799
+ )
5800
+ }
5801
+ `;
5802
+ await writeFile(path18.join(appDir, "src/app/layout.tsx"), content);
5803
+ }
5804
+ async function generateHomePage(config, appDir) {
5805
+ const content = `export default function DesignSystemHome() {
5806
+ return (
5807
+ <div className="p-8 max-w-4xl">
5808
+ <header className="mb-12">
5809
+ <h1 className="text-display-01 text-foreground mb-4">
5810
+ ${config.name} Design System
5811
+ </h1>
5812
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
5813
+ A comprehensive design system for building consistent, accessible, and
5814
+ beautiful interfaces across all ${config.name} products.
5815
+ </p>
5816
+ </header>
5817
+
5818
+ <section className="mb-12">
5819
+ <h2 className="text-h2 text-foreground mb-6">Quick Start</h2>
5820
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
5821
+ <QuickLink
5822
+ href="/colors"
5823
+ title="Colors"
5824
+ description="Brand colors, semantic tokens, and usage guidelines"
5825
+ icon="palette"
5826
+ />
5827
+ <QuickLink
5828
+ href="/typography"
5829
+ title="Typography"
5830
+ description="Font families, type scale, and text styles"
5831
+ icon="type"
5832
+ />
5833
+ <QuickLink
5834
+ href="/spacing"
5835
+ title="Spacing"
5836
+ description="Spacing scale, layout guidelines, and border radius"
5837
+ icon="ruler"
5838
+ />
5839
+ <QuickLink
5840
+ href="/components"
5841
+ title="Components"
5842
+ description="UI components with examples and code"
5843
+ icon="box"
5844
+ />
5845
+ </div>
5846
+ </section>
5847
+
5848
+ <section className="mb-12">
5849
+ <h2 className="text-h2 text-foreground mb-6">Design Principles</h2>
5850
+ <div className="space-y-4">
5851
+ <Principle
5852
+ title="Consistency"
5853
+ description="Use the defined design tokens consistently across all interfaces for a cohesive experience."
5854
+ />
5855
+ <Principle
5856
+ title="Accessibility"
5857
+ description="All color combinations meet WCAG AA contrast requirements. Design for everyone."
5858
+ />
5859
+ <Principle
5860
+ title="Simplicity"
5861
+ description="Keep interfaces clean and focused. Every element should serve a purpose."
5862
+ />
5863
+ <Principle
5864
+ title="Flexibility"
5865
+ description="Components are designed to be adaptable while maintaining visual harmony."
5866
+ />
5867
+ </div>
5868
+ </section>
5869
+
5870
+ <section>
5871
+ <h2 className="text-h2 text-foreground mb-6">Resources</h2>
5872
+ <div className="flex flex-wrap gap-4">
5873
+ <a
5874
+ href="/brand"
5875
+ className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-border hover:bg-muted transition-colors text-sm"
5876
+ >
5877
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5878
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
5879
+ </svg>
5880
+ Brand Assets
5881
+ </a>
5882
+ <a
5883
+ href="/blocks"
5884
+ className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-border hover:bg-muted transition-colors text-sm"
5885
+ >
5886
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5887
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6z" />
5888
+ </svg>
5889
+ UI Blocks
5890
+ </a>
5891
+ </div>
5892
+ </section>
5893
+ </div>
5894
+ )
5895
+ }
5896
+
5897
+ function QuickLink({
5898
+ href,
5899
+ title,
5900
+ description,
5901
+ icon,
5902
+ }: {
5903
+ href: string
5904
+ title: string
5905
+ description: string
5906
+ icon: string
5907
+ }) {
5908
+ const icons: Record<string, React.ReactNode> = {
5909
+ palette: (
5910
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5911
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
5912
+ </svg>
5913
+ ),
5914
+ type: (
5915
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5916
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6h16M4 12h16m-7 6h7" />
5917
+ </svg>
5918
+ ),
5919
+ ruler: (
5920
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5921
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
5922
+ </svg>
5923
+ ),
5924
+ box: (
5925
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5926
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
5927
+ </svg>
5928
+ ),
5929
+ }
5930
+
5931
+ return (
5932
+ <a
5933
+ href={href}
5934
+ className="group block p-6 rounded-xl border border-border hover:border-primary/30 hover:bg-muted/50 transition-all"
5935
+ >
5936
+ <div className="flex items-start gap-4">
5937
+ <div className="p-2 rounded-lg bg-primary/10 text-primary">
5938
+ {icons[icon]}
5939
+ </div>
5940
+ <div>
5941
+ <h3 className="text-h4 text-foreground group-hover:text-primary transition-colors">
5942
+ {title}
5943
+ </h3>
5944
+ <p className="text-body-sm text-muted-foreground mt-1">
5945
+ {description}
5946
+ </p>
5947
+ </div>
5948
+ </div>
5949
+ </a>
5950
+ )
5951
+ }
5952
+
5953
+ function Principle({
5954
+ title,
5955
+ description,
5956
+ }: {
5957
+ title: string
5958
+ description: string
5959
+ }) {
5960
+ return (
5961
+ <div className="p-4 rounded-lg border border-border">
5962
+ <h3 className="text-h4 text-foreground mb-1">{title}</h3>
5963
+ <p className="text-body-sm text-muted-foreground">{description}</p>
5964
+ </div>
5965
+ )
5966
+ }
5967
+ `;
5968
+ await writeFile(path18.join(appDir, "src/app/page.tsx"), content);
5969
+ }
5970
+ async function generateColorsPage(config, appDir) {
5971
+ const content = `export default function ColorsPage() {
5972
+ return (
5973
+ <div className="p-8 max-w-5xl">
5974
+ <header className="mb-12">
5975
+ <h1 className="text-display-01 text-foreground mb-4">Colors</h1>
5976
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
5977
+ The color system is built on a foundation of brand colors
5978
+ with semantic tokens for consistent application across all interfaces.
5979
+ </p>
5980
+ </header>
5981
+
5982
+ <section className="mb-12">
5983
+ <h2 className="text-h2 text-foreground mb-6">Brand Colors</h2>
5984
+ <p className="text-body text-muted-foreground mb-6">
5985
+ Core brand colors used across all products.
5986
+ </p>
5987
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
5988
+ <ColorCard
5989
+ name="Primary"
5990
+ cssVar="--primary"
5991
+ usage="CTAs, buttons, links, interactive elements"
5992
+ />
5993
+ <ColorCard
5994
+ name="Secondary"
5995
+ cssVar="--secondary"
5996
+ usage="Secondary buttons, subtle backgrounds"
5997
+ />
5998
+ <ColorCard
5999
+ name="Accent"
6000
+ cssVar="--accent"
6001
+ usage="Highlights, badges, special elements"
6002
+ />
6003
+ </div>
6004
+ </section>
6005
+
6006
+ <section className="mb-12">
6007
+ <h2 className="text-h2 text-foreground mb-6">Semantic Colors</h2>
6008
+ <p className="text-body text-muted-foreground mb-6">
6009
+ Colors with specific meaning used for feedback and status indication.
6010
+ </p>
6011
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
6012
+ <ColorCard
6013
+ name="Success"
6014
+ cssVar="--success"
6015
+ usage="Success states, confirmations"
6016
+ />
6017
+ <ColorCard
6018
+ name="Warning"
6019
+ cssVar="--warning"
6020
+ usage="Warnings, caution states"
6021
+ />
6022
+ <ColorCard
6023
+ name="Destructive"
6024
+ cssVar="--destructive"
6025
+ usage="Errors, destructive actions"
6026
+ />
6027
+ </div>
6028
+ </section>
6029
+
6030
+ <section className="mb-12">
6031
+ <h2 className="text-h2 text-foreground mb-6">Background Colors</h2>
6032
+ <p className="text-body text-muted-foreground mb-6">
6033
+ Surface colors for different elevation levels and contexts.
6034
+ </p>
6035
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
6036
+ <ColorCard
6037
+ name="Background"
6038
+ cssVar="--background"
6039
+ usage="Page background"
6040
+ />
6041
+ <ColorCard
6042
+ name="Card"
6043
+ cssVar="--card"
6044
+ usage="Card surfaces"
6045
+ />
6046
+ <ColorCard
6047
+ name="Muted"
6048
+ cssVar="--muted"
6049
+ usage="Subtle backgrounds"
6050
+ />
6051
+ </div>
6052
+ </section>
6053
+
6054
+ <section className="mb-12">
6055
+ <h2 className="text-h2 text-foreground mb-6">Text Colors</h2>
6056
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
6057
+ <ColorCard
6058
+ name="Foreground"
6059
+ cssVar="--foreground"
6060
+ usage="Primary text, headings"
6061
+ />
6062
+ <ColorCard
6063
+ name="Muted Foreground"
6064
+ cssVar="--muted-foreground"
6065
+ usage="Secondary text, descriptions"
6066
+ />
6067
+ <ColorCard
6068
+ name="Primary"
6069
+ cssVar="--primary"
6070
+ usage="Links, emphasis"
6071
+ />
6072
+ </div>
6073
+ </section>
6074
+
6075
+ <section>
6076
+ <h2 className="text-h2 text-foreground mb-6">CSS Variables</h2>
6077
+ <p className="text-body text-muted-foreground mb-4">
6078
+ Use these CSS variables in your styles for consistent theming:
6079
+ </p>
6080
+ <div className="bg-muted rounded-lg p-4 overflow-x-auto">
6081
+ <pre className="text-sm text-foreground">
6082
+ {\`/* Primary colors */
6083
+ var(--primary)
6084
+ var(--secondary)
6085
+ var(--accent)
6086
+
6087
+ /* Backgrounds */
6088
+ var(--background)
6089
+ var(--card)
6090
+ var(--muted)
6091
+
6092
+ /* Text */
6093
+ var(--foreground)
6094
+ var(--muted-foreground)
6095
+
6096
+ /* Semantic */
6097
+ var(--success)
6098
+ var(--warning)
6099
+ var(--destructive)
6100
+
6101
+ /* Borders */
6102
+ var(--border)
6103
+ var(--ring)\`}
6104
+ </pre>
6105
+ </div>
6106
+ </section>
6107
+ </div>
6108
+ )
6109
+ }
6110
+
6111
+ function ColorCard({
6112
+ name,
6113
+ cssVar,
6114
+ usage,
6115
+ }: {
6116
+ name: string
6117
+ cssVar: string
6118
+ usage: string
6119
+ }) {
6120
+ return (
6121
+ <div className="rounded-xl border border-border overflow-hidden">
6122
+ <div
6123
+ className="h-24"
6124
+ style={{ backgroundColor: \`var(\${cssVar})\` }}
6125
+ />
6126
+ <div className="p-4 bg-card">
6127
+ <h3 className="text-h4 text-foreground mb-1">{name}</h3>
6128
+ <p className="text-xs text-muted-foreground mb-2">
6129
+ <code className="bg-muted px-1 rounded">{cssVar}</code>
6130
+ </p>
6131
+ <p className="text-xs text-muted-foreground">{usage}</p>
6132
+ </div>
6133
+ </div>
6134
+ )
6135
+ }
6136
+ `;
6137
+ await writeFile(path18.join(appDir, "src/app/colors/page.tsx"), content);
6138
+ }
6139
+ async function generateTypographyPage(config, appDir) {
6140
+ const { designSystem } = config;
6141
+ const headingFontName = designSystem.headingFont.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
6142
+ const bodyFontName = designSystem.bodyFont.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
6143
+ const content = `export default function TypographyPage() {
6144
+ return (
6145
+ <div className="p-8 max-w-5xl">
6146
+ <header className="mb-12">
6147
+ <h1 className="text-display-01 text-foreground mb-4">Typography</h1>
6148
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
6149
+ The typography system uses ${headingFontName} for headings and ${bodyFontName}
6150
+ for body text, creating a modern and professional aesthetic.
6151
+ </p>
6152
+ </header>
6153
+
6154
+ <section className="mb-12">
6155
+ <h2 className="text-h2 text-foreground mb-6">Font Families</h2>
6156
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
6157
+ <FontFamilyCard
6158
+ name="${headingFontName}"
6159
+ variable="--font-heading"
6160
+ usage="Headings, display text"
6161
+ sample="The quick brown fox jumps over the lazy dog"
6162
+ />
6163
+ <FontFamilyCard
6164
+ name="${bodyFontName}"
6165
+ variable="--font-body"
6166
+ usage="Body text, UI elements"
6167
+ sample="The quick brown fox jumps over the lazy dog"
6168
+ />
6169
+ </div>
6170
+ </section>
6171
+
6172
+ <section className="mb-12">
6173
+ <h2 className="text-h2 text-foreground mb-6">Type Scale</h2>
6174
+ <p className="text-body text-muted-foreground mb-6">
6175
+ A comprehensive scale from display headings down to small body text.
6176
+ </p>
6177
+ <div className="space-y-6">
6178
+ <TypeScaleItem name="Display Hero" size="56px (3.5rem)" className="text-display-hero" />
6179
+ <TypeScaleItem name="Display 02" size="48px (3rem)" className="text-display-02" />
6180
+ <TypeScaleItem name="Display 01" size="40px (2.5rem)" className="text-display-01" />
6181
+ <TypeScaleItem name="Heading 1" size="32px (2rem)" className="text-h1" />
6182
+ <TypeScaleItem name="Heading 2" size="24px (1.5rem)" className="text-h2" />
6183
+ <TypeScaleItem name="Heading 3" size="20px (1.25rem)" className="text-h3" />
6184
+ <TypeScaleItem name="Heading 4" size="18px (1.125rem)" className="text-h4" />
6185
+ <TypeScaleItem name="Body Large" size="18px (1.125rem)" className="text-body-lg" />
6186
+ <TypeScaleItem name="Body" size="16px (1rem)" className="text-body" />
6187
+ <TypeScaleItem name="Body Small" size="14px (0.875rem)" className="text-body-sm" />
6188
+ </div>
6189
+ </section>
6190
+
6191
+ <section className="mb-12">
6192
+ <h2 className="text-h2 text-foreground mb-6">Font Weights</h2>
6193
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
6194
+ <WeightCard weight="400" name="Regular" />
6195
+ <WeightCard weight="500" name="Medium" />
6196
+ <WeightCard weight="600" name="Semibold" />
6197
+ <WeightCard weight="700" name="Bold" />
6198
+ </div>
6199
+ </section>
6200
+
6201
+ <section>
6202
+ <h2 className="text-h2 text-foreground mb-6">CSS Classes</h2>
6203
+ <p className="text-body text-muted-foreground mb-4">
6204
+ Use these utility classes for consistent typography:
6205
+ </p>
6206
+ <div className="bg-muted rounded-lg p-4 overflow-x-auto">
6207
+ <pre className="text-sm text-foreground">
6208
+ {\`/* Display & Headings */
6209
+ .text-display-hero /* 56px, weight 600 */
6210
+ .text-display-02 /* 48px, weight 600 */
6211
+ .text-display-01 /* 40px, weight 600 */
6212
+ .text-h1 /* 32px, weight 600 */
6213
+ .text-h2 /* 24px, weight 600 */
6214
+ .text-h3 /* 20px, weight 500 */
6215
+ .text-h4 /* 18px, weight 500 */
6216
+
6217
+ /* Body Text */
6218
+ .text-body-lg /* 18px, line-height 175% */
6219
+ .text-body /* 16px, line-height 150% */
6220
+ .text-body-sm /* 14px, line-height 150% */
6221
+
6222
+ /* Colors */
6223
+ .text-foreground /* Primary text color */
6224
+ .text-muted-foreground /* Secondary text color */
6225
+ .text-primary /* Brand primary color */\`}
6226
+ </pre>
6227
+ </div>
6228
+ </section>
6229
+ </div>
6230
+ )
6231
+ }
6232
+
6233
+ function FontFamilyCard({
6234
+ name,
6235
+ variable,
6236
+ usage,
6237
+ sample,
6238
+ }: {
6239
+ name: string
6240
+ variable: string
6241
+ usage: string
6242
+ sample: string
6243
+ }) {
6244
+ return (
6245
+ <div className="rounded-xl border border-border p-6">
6246
+ <h3 className="text-h3 text-foreground mb-2">{name}</h3>
6247
+ <p className="text-body-sm text-muted-foreground mb-4">{usage}</p>
6248
+ <div className="p-4 bg-muted rounded-lg mb-4">
6249
+ <p className="text-xl text-foreground">{sample}</p>
6250
+ </div>
6251
+ <code className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
6252
+ {variable}
6253
+ </code>
6254
+ </div>
6255
+ )
6256
+ }
6257
+
6258
+ function TypeScaleItem({
6259
+ name,
6260
+ size,
6261
+ className,
6262
+ }: {
6263
+ name: string
6264
+ size: string
6265
+ className: string
6266
+ }) {
6267
+ return (
6268
+ <div className="flex flex-col md:flex-row md:items-center gap-4 pb-6 border-b border-border">
6269
+ <div className="md:w-48 shrink-0">
6270
+ <p className="text-body-sm font-medium text-foreground">{name}</p>
6271
+ <p className="text-xs text-muted-foreground">{size}</p>
6272
+ </div>
6273
+ <div className="flex-1 overflow-hidden">
6274
+ <p className={\`text-foreground truncate \${className}\`}>
6275
+ The quick brown fox jumps
6276
+ </p>
6277
+ </div>
6278
+ </div>
6279
+ )
6280
+ }
6281
+
6282
+ function WeightCard({ weight, name }: { weight: string; name: string }) {
6283
+ return (
6284
+ <div className="rounded-lg border border-border p-4 text-center">
6285
+ <p className="text-2xl text-foreground mb-2" style={{ fontWeight: weight }}>
6286
+ Aa
6287
+ </p>
6288
+ <p className="text-body-sm font-medium text-foreground">{name}</p>
6289
+ <p className="text-xs text-muted-foreground">{weight}</p>
6290
+ </div>
6291
+ )
6292
+ }
6293
+ `;
6294
+ await writeFile(path18.join(appDir, "src/app/typography/page.tsx"), content);
6295
+ }
6296
+ async function generateSpacingPage(config, appDir) {
6297
+ const { designSystem } = config;
6298
+ const radiusValue = radiusValues[designSystem.borderRadius];
6299
+ const content = `export default function SpacingPage() {
6300
+ return (
6301
+ <div className="p-8 max-w-5xl">
6302
+ <header className="mb-12">
6303
+ <h1 className="text-display-01 text-foreground mb-4">Spacing</h1>
6304
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
6305
+ Consistent spacing creates visual harmony and improves readability.
6306
+ Uses Tailwind&apos;s default spacing scale with specific guidelines for common patterns.
6307
+ </p>
6308
+ </header>
6309
+
6310
+ <section className="mb-12">
6311
+ <h2 className="text-h2 text-foreground mb-6">Spacing Scale</h2>
6312
+ <p className="text-body text-muted-foreground mb-6">
6313
+ Based on a 4px base unit. Use these values for margins, padding, and gaps.
6314
+ </p>
6315
+ <div className="space-y-3">
6316
+ <SpacingRow value="1" pixels="4px" />
6317
+ <SpacingRow value="2" pixels="8px" />
6318
+ <SpacingRow value="3" pixels="12px" />
6319
+ <SpacingRow value="4" pixels="16px" />
6320
+ <SpacingRow value="6" pixels="24px" />
6321
+ <SpacingRow value="8" pixels="32px" />
6322
+ <SpacingRow value="12" pixels="48px" />
6323
+ <SpacingRow value="16" pixels="64px" />
6324
+ <SpacingRow value="24" pixels="96px" />
6325
+ </div>
6326
+ </section>
6327
+
6328
+ <section className="mb-12">
6329
+ <h2 className="text-h2 text-foreground mb-6">Border Radius</h2>
6330
+ <p className="text-body text-muted-foreground mb-6">
6331
+ Default border radius: <code className="bg-muted px-1 rounded">${radiusValue}</code>
6332
+ </p>
6333
+ <div className="grid grid-cols-2 md:grid-cols-5 gap-4">
6334
+ <RadiusCard name="XS" value="calc(var(--radius) * 0.5)" />
6335
+ <RadiusCard name="SM" value="calc(var(--radius) * 0.75)" />
6336
+ <RadiusCard name="Default" value="var(--radius)" />
6337
+ <RadiusCard name="LG" value="calc(var(--radius) * 1.5)" />
6338
+ <RadiusCard name="Pill" value="9999px" />
6339
+ </div>
6340
+ </section>
6341
+
6342
+ <section className="mb-12">
6343
+ <h2 className="text-h2 text-foreground mb-6">Common Patterns</h2>
6344
+ <div className="space-y-6">
6345
+ <PatternCard
6346
+ title="Page Padding"
6347
+ description="Standard page content padding"
6348
+ code="p-8 or px-8 py-12"
6349
+ />
6350
+ <PatternCard
6351
+ title="Section Spacing"
6352
+ description="Space between major sections"
6353
+ code="mb-12 or space-y-12"
6354
+ />
6355
+ <PatternCard
6356
+ title="Card Padding"
6357
+ description="Internal card padding"
6358
+ code="p-4 or p-6"
6359
+ />
6360
+ <PatternCard
6361
+ title="Grid Gap"
6362
+ description="Space between grid items"
6363
+ code="gap-4 or gap-6"
6364
+ />
6365
+ </div>
6366
+ </section>
6367
+
6368
+ <section>
6369
+ <h2 className="text-h2 text-foreground mb-6">CSS Variables</h2>
6370
+ <div className="bg-muted rounded-lg p-4 overflow-x-auto">
6371
+ <pre className="text-sm text-foreground">
6372
+ {\`/* Border Radius */
6373
+ --radius: ${radiusValue};
6374
+ --radius-xs: calc(var(--radius) * 0.5);
6375
+ --radius-sm: calc(var(--radius) * 0.75);
6376
+ --radius-lg: calc(var(--radius) * 1.5);
6377
+ --radius-pill: 9999px;\`}
6378
+ </pre>
6379
+ </div>
6380
+ </section>
6381
+ </div>
6382
+ )
6383
+ }
6384
+
6385
+ function SpacingRow({ value, pixels }: { value: string; pixels: string }) {
6386
+ return (
6387
+ <div className="flex items-center gap-4">
6388
+ <div className="w-16 text-right">
6389
+ <code className="text-sm text-muted-foreground">{value}</code>
6390
+ </div>
6391
+ <div className="flex-1 flex items-center gap-2">
6392
+ <div className="h-6 bg-primary rounded" style={{ width: pixels }} />
6393
+ <span className="text-sm text-muted-foreground">{pixels}</span>
6394
+ </div>
6395
+ </div>
6396
+ )
6397
+ }
6398
+
6399
+ function RadiusCard({ name, value }: { name: string; value: string }) {
6400
+ return (
6401
+ <div className="text-center">
6402
+ <div className="h-20 w-full bg-primary mb-3" style={{ borderRadius: value }} />
6403
+ <p className="text-body-sm font-medium text-foreground">{name}</p>
6404
+ <code className="text-xs text-muted-foreground">{value}</code>
6405
+ </div>
6406
+ )
6407
+ }
6408
+
6409
+ function PatternCard({
6410
+ title,
6411
+ description,
6412
+ code,
6413
+ }: {
6414
+ title: string
6415
+ description: string
6416
+ code: string
6417
+ }) {
6418
+ return (
6419
+ <div className="rounded-lg border border-border p-4">
6420
+ <h3 className="text-h4 text-foreground mb-1">{title}</h3>
6421
+ <p className="text-body-sm text-muted-foreground mb-2">{description}</p>
6422
+ <code className="text-xs text-primary bg-primary/10 px-2 py-1 rounded">{code}</code>
6423
+ </div>
6424
+ )
6425
+ }
6426
+ `;
6427
+ await writeFile(path18.join(appDir, "src/app/spacing/page.tsx"), content);
6428
+ }
6429
+ async function generateBrandPage(config, appDir) {
6430
+ const content = `export default function BrandPage() {
6431
+ return (
6432
+ <div className="p-8 max-w-5xl">
6433
+ <header className="mb-12">
6434
+ <h1 className="text-display-01 text-foreground mb-4">Brand</h1>
6435
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
6436
+ Brand assets and guidelines for ${config.name}. Download logos, icons,
6437
+ and learn about proper usage.
6438
+ </p>
6439
+ </header>
6440
+
6441
+ <section className="mb-12">
6442
+ <h2 className="text-h2 text-foreground mb-6">Logo</h2>
6443
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
6444
+ <div className="rounded-xl border border-border p-8 bg-white flex items-center justify-center">
6445
+ <div className="h-16 w-16 rounded-xl bg-primary flex items-center justify-center text-primary-foreground text-2xl font-bold">
6446
+ ${config.name.charAt(0).toUpperCase()}
6447
+ </div>
6448
+ </div>
6449
+ <div className="rounded-xl border border-border p-8 bg-zinc-900 flex items-center justify-center">
6450
+ <div className="h-16 w-16 rounded-xl bg-primary flex items-center justify-center text-primary-foreground text-2xl font-bold">
6451
+ ${config.name.charAt(0).toUpperCase()}
6452
+ </div>
6453
+ </div>
6454
+ </div>
6455
+ <p className="text-body-sm text-muted-foreground mt-4">
6456
+ The logo should maintain adequate spacing from other elements. Minimum clear space
6457
+ equals the height of the logo mark.
6458
+ </p>
6459
+ </section>
6460
+
6461
+ <section className="mb-12">
6462
+ <h2 className="text-h2 text-foreground mb-6">Logo Usage</h2>
6463
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
6464
+ <UsageCard
6465
+ title="Do"
6466
+ items={[
6467
+ 'Use the logo on clean backgrounds',
6468
+ 'Maintain the aspect ratio',
6469
+ 'Use approved color variants',
6470
+ 'Keep adequate clear space',
6471
+ ]}
6472
+ type="do"
6473
+ />
6474
+ <UsageCard
6475
+ title="Don\\'t"
6476
+ items={[
6477
+ 'Stretch or distort the logo',
6478
+ 'Add effects like shadows or gradients',
6479
+ 'Place on busy backgrounds',
6480
+ 'Change the logo colors',
6481
+ ]}
6482
+ type="dont"
6483
+ />
6484
+ </div>
6485
+ </section>
6486
+
6487
+ <section>
6488
+ <h2 className="text-h2 text-foreground mb-6">Downloads</h2>
6489
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
6490
+ <DownloadCard
6491
+ title="Logo Pack"
6492
+ description="SVG, PNG in various sizes"
6493
+ icon="image"
6494
+ />
6495
+ <DownloadCard
6496
+ title="Icon Set"
6497
+ description="App icons for all platforms"
6498
+ icon="grid"
6499
+ />
6500
+ <DownloadCard
6501
+ title="Brand Guidelines"
6502
+ description="Complete brand documentation"
6503
+ icon="file"
6504
+ />
6505
+ </div>
6506
+ </section>
6507
+ </div>
6508
+ )
6509
+ }
6510
+
6511
+ function UsageCard({
6512
+ title,
6513
+ items,
6514
+ type,
6515
+ }: {
6516
+ title: string
6517
+ items: string[]
6518
+ type: 'do' | 'dont'
6519
+ }) {
6520
+ const borderColor = type === 'do' ? 'border-l-success' : 'border-l-destructive'
6521
+
6522
+ return (
6523
+ <div className={\`rounded-lg border border-border border-l-4 \${borderColor} p-4\`}>
6524
+ <h3 className="text-h4 text-foreground mb-3">{title}</h3>
6525
+ <ul className="space-y-2">
6526
+ {items.map((item, i) => (
6527
+ <li key={i} className="flex items-start gap-2 text-body-sm text-muted-foreground">
6528
+ <span className={type === 'do' ? 'text-success' : 'text-destructive'}>
6529
+ {type === 'do' ? '\u2713' : '\u2717'}
6530
+ </span>
6531
+ {item}
6532
+ </li>
6533
+ ))}
6534
+ </ul>
6535
+ </div>
6536
+ )
6537
+ }
6538
+
6539
+ function DownloadCard({
6540
+ title,
6541
+ description,
6542
+ icon,
6543
+ }: {
6544
+ title: string
6545
+ description: string
6546
+ icon: string
6547
+ }) {
6548
+ return (
6549
+ <button className="rounded-lg border border-border p-4 text-left hover:bg-muted transition-colors w-full">
6550
+ <div className="flex items-center gap-3">
6551
+ <div className="p-2 rounded-lg bg-primary/10 text-primary">
6552
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6553
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
6554
+ </svg>
6555
+ </div>
6556
+ <div>
6557
+ <h3 className="text-h4 text-foreground">{title}</h3>
6558
+ <p className="text-body-sm text-muted-foreground">{description}</p>
6559
+ </div>
6560
+ </div>
6561
+ </button>
6562
+ )
6563
+ }
6564
+ `;
6565
+ await writeFile(path18.join(appDir, "src/app/brand/page.tsx"), content);
6566
+ }
6567
+ async function generateBlocksPage(appDir) {
6568
+ const content = `export default function BlocksPage() {
6569
+ return (
6570
+ <div className="p-8 max-w-5xl">
6571
+ <header className="mb-12">
6572
+ <h1 className="text-display-01 text-foreground mb-4">Blocks</h1>
6573
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
6574
+ Pre-built UI blocks for common patterns. Copy and paste into your projects.
6575
+ </p>
6576
+ </header>
6577
+
6578
+ <section className="mb-12">
6579
+ <h2 className="text-h2 text-foreground mb-6">Marketing</h2>
6580
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
6581
+ <BlockCard title="Hero" description="Hero sections with CTA" />
6582
+ <BlockCard title="Features" description="Feature grids and lists" />
6583
+ <BlockCard title="Pricing" description="Pricing tables and cards" />
6584
+ <BlockCard title="Testimonials" description="Customer testimonials" />
6585
+ <BlockCard title="CTA" description="Call-to-action sections" />
6586
+ <BlockCard title="FAQ" description="FAQ accordions" />
6587
+ </div>
6588
+ </section>
6589
+
6590
+ <section className="mb-12">
6591
+ <h2 className="text-h2 text-foreground mb-6">Dashboard</h2>
6592
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
6593
+ <BlockCard title="Stats" description="Metric and stat cards" />
6594
+ <BlockCard title="Tables" description="Data tables with actions" />
6595
+ <BlockCard title="Charts" description="Chart components" />
6596
+ <BlockCard title="Sidebar" description="Navigation sidebars" />
6597
+ <BlockCard title="Header" description="Dashboard headers" />
6598
+ </div>
6599
+ </section>
6600
+
6601
+ <section>
6602
+ <h2 className="text-h2 text-foreground mb-6">Widgets</h2>
6603
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
6604
+ <BlockCard title="Chat" description="Chat windows and bubbles" />
6605
+ <BlockCard title="Forms" description="Contact and input forms" />
6606
+ <BlockCard title="Cards" description="Content cards" />
6607
+ </div>
6608
+ </section>
6609
+ </div>
6610
+ )
6611
+ }
6612
+
6613
+ function BlockCard({ title, description }: { title: string; description: string }) {
6614
+ return (
6615
+ <a
6616
+ href="#"
6617
+ className="group block rounded-lg border border-border p-4 hover:border-primary/30 hover:bg-muted/50 transition-all"
6618
+ >
6619
+ <h3 className="text-h4 text-foreground group-hover:text-primary transition-colors">
6620
+ {title}
6621
+ </h3>
6622
+ <p className="text-body-sm text-muted-foreground mt-1">{description}</p>
6623
+ </a>
6624
+ )
6625
+ }
6626
+ `;
6627
+ await writeFile(path18.join(appDir, "src/app/blocks/page.tsx"), content);
6628
+ }
6629
+ async function generateComponentsPage(appDir) {
6630
+ const content = `export default function ComponentsPage() {
6631
+ return (
6632
+ <div className="p-8 max-w-5xl">
6633
+ <header className="mb-12">
6634
+ <h1 className="text-display-01 text-foreground mb-4">Components</h1>
6635
+ <p className="text-body-lg text-muted-foreground max-w-2xl">
6636
+ A collection of reusable UI components built with shadcn/ui.
6637
+ Browse the sidebar to explore individual components.
6638
+ </p>
6639
+ </header>
6640
+
6641
+ <section className="mb-12">
6642
+ <h2 className="text-h2 text-foreground mb-6">Getting Started</h2>
6643
+ <div className="bg-muted rounded-lg p-6">
6644
+ <p className="text-body text-muted-foreground mb-4">
6645
+ Components are available from the @repo/ui package. Import them directly:
6646
+ </p>
6647
+ <pre className="text-sm text-foreground bg-background rounded p-4 overflow-x-auto">
6648
+ {\`import { Button } from '@repo/ui/button'
6649
+ import { Card, CardHeader, CardContent } from '@repo/ui/card'\`}
6650
+ </pre>
6651
+ </div>
6652
+ </section>
6653
+
6654
+ <section>
6655
+ <h2 className="text-h2 text-foreground mb-6">Categories</h2>
6656
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
6657
+ <CategoryCard
6658
+ title="Inputs"
6659
+ components={['Button', 'Input', 'Textarea', 'Select', 'Checkbox', 'Radio Group', 'Switch', 'Slider']}
6660
+ />
6661
+ <CategoryCard
6662
+ title="Display"
6663
+ components={['Card', 'Badge', 'Avatar', 'Alert', 'Progress', 'Skeleton', 'Table']}
6664
+ />
6665
+ <CategoryCard
6666
+ title="Navigation"
6667
+ components={['Tabs', 'Breadcrumb', 'Pagination', 'Navigation Menu', 'Sidebar']}
6668
+ />
6669
+ <CategoryCard
6670
+ title="Overlays"
6671
+ components={['Dialog', 'Drawer', 'Sheet', 'Popover', 'Tooltip', 'Dropdown Menu']}
6672
+ />
6673
+ </div>
6674
+ </section>
6675
+ </div>
6676
+ )
6677
+ }
6678
+
6679
+ function CategoryCard({
6680
+ title,
6681
+ components,
6682
+ }: {
6683
+ title: string
6684
+ components: string[]
6685
+ }) {
6686
+ return (
6687
+ <div className="rounded-xl border border-border p-6">
6688
+ <h3 className="text-h3 text-foreground mb-4">{title}</h3>
6689
+ <div className="flex flex-wrap gap-2">
6690
+ {components.map((component) => {
6691
+ const slug = component.toLowerCase().replace(/\\s+/g, '-')
6692
+ return (
6693
+ <a
6694
+ key={slug}
6695
+ href={\`/components/\${slug}\`}
6696
+ className="px-3 py-1 rounded-full bg-muted text-sm text-muted-foreground hover:text-foreground hover:bg-muted/80 transition-colors"
6697
+ >
6698
+ {component}
6699
+ </a>
6700
+ )
6701
+ })}
6702
+ </div>
6703
+ </div>
6704
+ )
6705
+ }
6706
+ `;
6707
+ await writeFile(path18.join(appDir, "src/app/components/page.tsx"), content);
6708
+ }
6709
+ async function generateUtils(appDir) {
6710
+ const content = `import { clsx, type ClassValue } from 'clsx'
6711
+ import { twMerge } from 'tailwind-merge'
6712
+
6713
+ export function cn(...inputs: ClassValue[]) {
6714
+ return twMerge(clsx(inputs))
6715
+ }
6716
+ `;
6717
+ await writeFile(path18.join(appDir, "src/lib/utils.ts"), content);
6718
+ }
6719
+
6720
+ // src/setup/env-wizard.ts
6721
+ import * as p6 from "@clack/prompts";
6722
+ import pc2 from "picocolors";
6723
+ import { exec } from "child_process";
6724
+ import { promisify } from "util";
6725
+ import path19 from "path";
6726
+ var execAsync = promisify(exec);
6727
+ async function runEnvSetupWizard(config) {
6728
+ console.log();
6729
+ p6.intro(pc2.bgMagenta(pc2.black(" Environment Setup ")));
6730
+ const shouldSetup = await p6.confirm({
6731
+ message: "Would you like to set up environment variables now?",
6732
+ initialValue: true
6733
+ });
6734
+ if (p6.isCancel(shouldSetup) || !shouldSetup) {
6735
+ p6.log.info("Skipping environment setup. You can configure .env.local manually later.");
6736
+ return;
6737
+ }
6738
+ const envValues = {};
6739
+ await setupSecrets(config, envValues);
6740
+ await setupConvex(config, envValues);
6741
+ if (config.marketingSite === "payload") {
6742
+ await setupSupabase(config, envValues);
6743
+ }
6744
+ await showOAuthSetupGuide(config);
6745
+ await showRemainingEnvGuide(config);
6746
+ await writeEnvFiles(config, envValues);
6747
+ p6.outro(pc2.green("Environment setup complete!"));
6748
+ }
6749
+ async function setupSecrets(config, envValues) {
6750
+ p6.log.step("Generating secure secrets...");
6751
+ try {
6752
+ const { stdout: authSecret } = await execAsync("openssl rand -base64 32");
6753
+ envValues.BETTER_AUTH_SECRET = authSecret.trim();
6754
+ p6.log.success("Generated BETTER_AUTH_SECRET");
6755
+ if (config.marketingSite === "payload") {
6756
+ const { stdout: payloadSecret } = await execAsync("openssl rand -base64 32");
6757
+ envValues.PAYLOAD_SECRET = payloadSecret.trim();
6758
+ p6.log.success("Generated PAYLOAD_SECRET");
6759
+ const { stdout: cronSecret } = await execAsync("openssl rand -base64 32");
6760
+ envValues.CRON_SECRET = cronSecret.trim();
6761
+ p6.log.success("Generated CRON_SECRET");
6762
+ const { stdout: previewSecret } = await execAsync("openssl rand -base64 32");
6763
+ envValues.PREVIEW_SECRET = previewSecret.trim();
6764
+ p6.log.success("Generated PREVIEW_SECRET");
6765
+ }
6766
+ } catch (error) {
6767
+ p6.log.warn("Could not generate secrets automatically. Please generate them manually:");
6768
+ p6.log.info("Run: openssl rand -base64 32");
6769
+ }
6770
+ }
6771
+ async function setupConvex(config, envValues) {
6772
+ console.log();
6773
+ p6.log.step("Convex Setup");
6774
+ const convexChoice = await p6.select({
6775
+ message: "How would you like to set up Convex?",
6776
+ options: [
6777
+ {
6778
+ value: "new",
6779
+ label: "Create a new Convex project",
6780
+ hint: "Will open Convex dashboard in browser"
6781
+ },
6782
+ {
6783
+ value: "existing",
6784
+ label: "Connect to an existing Convex project",
6785
+ hint: "Enter your Convex deployment URL"
6786
+ },
6787
+ {
6788
+ value: "skip",
6789
+ label: "Skip for now",
6790
+ hint: "Configure manually later"
6791
+ }
6792
+ ]
6793
+ });
6794
+ if (p6.isCancel(convexChoice) || convexChoice === "skip") {
6795
+ p6.log.info("Skipping Convex setup. Run `pnpm convex dev` to set up later.");
6796
+ return;
6797
+ }
6798
+ if (convexChoice === "new") {
6799
+ p6.log.info(`
6800
+ ${pc2.bold("To create a new Convex project:")}
6801
+
6802
+ 1. Run ${pc2.cyan("pnpm convex dev")} in your project directory
6803
+ 2. It will open your browser to create a new project
6804
+ 3. Follow the prompts to set up your Convex project
6805
+ 4. The CLI will automatically add the deployment URL to .env.local
6806
+
6807
+ ${pc2.dim("Note: Make sure you have a Convex account at https://convex.dev")}
6808
+ `);
6809
+ } else if (convexChoice === "existing") {
6810
+ const deploymentName = await p6.text({
6811
+ message: "Enter your Convex deployment name (e.g., my-app-123):",
6812
+ placeholder: "my-app-123",
6813
+ validate: (value) => {
6814
+ if (!value) return "Deployment name is required";
6815
+ return void 0;
6816
+ }
6817
+ });
6818
+ if (!p6.isCancel(deploymentName)) {
6819
+ envValues.CONVEX_DEPLOYMENT = `prod:${deploymentName}`;
6820
+ envValues.NEXT_PUBLIC_CONVEX_URL = `https://${deploymentName}.convex.cloud`;
6821
+ p6.log.success("Convex deployment configured");
6822
+ }
6823
+ }
6824
+ }
6825
+ async function setupSupabase(config, envValues) {
6826
+ console.log();
6827
+ p6.log.step("Supabase Database Setup (for Payload CMS)");
6828
+ const supabaseChoice = await p6.select({
6829
+ message: "How would you like to set up Supabase?",
6830
+ options: [
6831
+ {
6832
+ value: "guide",
6833
+ label: "Show setup guide",
6834
+ hint: "Step-by-step instructions"
6835
+ },
6836
+ {
6837
+ value: "existing",
6838
+ label: "Enter existing Supabase credentials",
6839
+ hint: "I already have a Supabase project"
6840
+ },
6841
+ {
6842
+ value: "skip",
6843
+ label: "Skip for now",
6844
+ hint: "Configure manually later"
6845
+ }
6846
+ ]
6847
+ });
6848
+ if (p6.isCancel(supabaseChoice) || supabaseChoice === "skip") {
6849
+ p6.log.info("Skipping Supabase setup. Configure DATABASE_URL in .env.local later.");
6850
+ return;
6851
+ }
6852
+ if (supabaseChoice === "guide") {
6853
+ p6.log.info(`
6854
+ ${pc2.bold("To set up Supabase for Payload CMS:")}
6855
+
6856
+ 1. Go to ${pc2.cyan("https://supabase.com")} and create/sign in to your account
6857
+ 2. Create a new project (or select an existing one)
6858
+ 3. Go to ${pc2.bold("Project Settings > Database")}
6859
+ 4. Under ${pc2.bold("Connection string")}, select ${pc2.bold("URI")} tab
6860
+ 5. Copy the connection string (starts with postgresql://)
6861
+ 6. Replace ${pc2.dim("[YOUR-PASSWORD]")} with your database password
6862
+
6863
+ ${pc2.bold("For S3 Storage (images/media):")}
6864
+ 1. Go to ${pc2.bold("Project Settings > Storage")}
6865
+ 2. Create a new bucket called "media"
6866
+ 3. Go to ${pc2.bold("Project Settings > API")}
6867
+ 4. Copy the ${pc2.bold("service_role key")} for S3_SECRET_ACCESS_KEY
6868
+ 5. Note your project reference for the S3 endpoint
6869
+
6870
+ ${pc2.dim("Add these to your apps/marketing/.env.local file")}
6871
+ `);
6872
+ const enterNow = await p6.confirm({
6873
+ message: "Would you like to enter your Supabase credentials now?",
6874
+ initialValue: false
6875
+ });
6876
+ if (!p6.isCancel(enterNow) && enterNow) {
6877
+ await collectSupabaseCredentials(envValues);
6878
+ }
6879
+ } else if (supabaseChoice === "existing") {
6880
+ await collectSupabaseCredentials(envValues);
6881
+ }
6882
+ }
6883
+ async function collectSupabaseCredentials(envValues) {
6884
+ const databaseUrl = await p6.text({
6885
+ message: "Enter your DATABASE_URL (PostgreSQL connection string):",
6886
+ placeholder: "postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres",
6887
+ validate: (value) => {
6888
+ if (!value) return "Database URL is required";
6889
+ if (!value.startsWith("postgresql://")) return "Must be a PostgreSQL connection string";
6890
+ return void 0;
6891
+ }
6892
+ });
6893
+ if (!p6.isCancel(databaseUrl)) {
6894
+ envValues.DATABASE_URL = databaseUrl;
6895
+ p6.log.success("Database URL configured");
6896
+ }
6897
+ const setupS3 = await p6.confirm({
6898
+ message: "Would you like to configure S3 storage for media uploads?",
6899
+ initialValue: false
6900
+ });
6901
+ if (!p6.isCancel(setupS3) && setupS3) {
6902
+ const projectRef = await p6.text({
6903
+ message: "Enter your Supabase project reference (e.g., abcdefghijklmnop):",
6904
+ placeholder: "abcdefghijklmnop"
6905
+ });
6906
+ if (!p6.isCancel(projectRef)) {
6907
+ envValues.S3_ENDPOINT = `https://${projectRef}.supabase.co/storage/v1/s3`;
6908
+ envValues.S3_REGION = "auto";
6909
+ envValues.S3_BUCKET = "media";
6910
+ const accessKeyId = await p6.text({
6911
+ message: "Enter your S3 Access Key ID (from Supabase API settings):",
6912
+ placeholder: "your-access-key-id"
6913
+ });
6914
+ if (!p6.isCancel(accessKeyId)) {
6915
+ envValues.S3_ACCESS_KEY_ID = accessKeyId;
6916
+ }
6917
+ const secretKey = await p6.password({
6918
+ message: "Enter your S3 Secret Access Key (service_role key):"
6919
+ });
6920
+ if (!p6.isCancel(secretKey)) {
6921
+ envValues.S3_SECRET_ACCESS_KEY = secretKey;
6922
+ }
6923
+ p6.log.success("S3 storage configured");
6924
+ }
6925
+ }
6926
+ }
6927
+ async function showOAuthSetupGuide(config) {
6928
+ console.log();
6929
+ p6.log.step("OAuth Provider Setup");
6930
+ const allProviders = ["google", ...config.auth.providers];
6931
+ p6.log.info(`
6932
+ ${pc2.bold("Configure OAuth providers for authentication:")}
6933
+
6934
+ ${pc2.cyan("Google OAuth")} (required):
6935
+ 1. Go to ${pc2.dim("https://console.cloud.google.com")}
6936
+ 2. Create a new project or select existing
6937
+ 3. Go to APIs & Services > Credentials
6938
+ 4. Create OAuth 2.0 Client ID (Web application)
6939
+ 5. Add authorized redirect: ${pc2.dim("http://localhost:3000/api/auth/callback/google")}
6940
+ 6. Copy Client ID and Client Secret to .env.local
6941
+ `);
6942
+ for (const provider of config.auth.providers) {
6943
+ const guides = {
6944
+ github: `${pc2.cyan("GitHub OAuth")}:
6945
+ 1. Go to ${pc2.dim("https://github.com/settings/developers")}
6946
+ 2. Create a new OAuth App
6947
+ 3. Set callback URL: ${pc2.dim("http://localhost:3000/api/auth/callback/github")}`,
6948
+ discord: `${pc2.cyan("Discord OAuth")}:
6949
+ 1. Go to ${pc2.dim("https://discord.com/developers/applications")}
6950
+ 2. Create a new application
6951
+ 3. Go to OAuth2 and add redirect: ${pc2.dim("http://localhost:3000/api/auth/callback/discord")}`,
6952
+ twitter: `${pc2.cyan("Twitter/X OAuth")}:
6953
+ 1. Go to ${pc2.dim("https://developer.twitter.com/en/portal/dashboard")}
6954
+ 2. Create a project and app
6955
+ 3. Set callback URL: ${pc2.dim("http://localhost:3000/api/auth/callback/twitter")}`,
6956
+ apple: `${pc2.cyan("Apple OAuth")}:
6957
+ 1. Go to ${pc2.dim("https://developer.apple.com")}
6958
+ 2. Create an App ID and Service ID
6959
+ 3. Configure Sign in with Apple`,
6960
+ microsoft: `${pc2.cyan("Microsoft OAuth")}:
6961
+ 1. Go to ${pc2.dim("https://portal.azure.com")}
6962
+ 2. Register a new application in Azure AD
6963
+ 3. Add redirect: ${pc2.dim("http://localhost:3000/api/auth/callback/microsoft")}`,
6964
+ linkedin: `${pc2.cyan("LinkedIn OAuth")}:
6965
+ 1. Go to ${pc2.dim("https://www.linkedin.com/developers")}
6966
+ 2. Create a new app
6967
+ 3. Add redirect: ${pc2.dim("http://localhost:3000/api/auth/callback/linkedin")}`
6968
+ };
6969
+ if (guides[provider]) {
6970
+ console.log(guides[provider]);
6971
+ console.log();
6972
+ }
6973
+ }
6974
+ }
6975
+ async function showRemainingEnvGuide(config) {
6976
+ console.log();
6977
+ p6.log.step("Additional Configuration");
6978
+ const guides = [];
6979
+ guides.push(`${pc2.cyan("Resend (Email)")}:
6980
+ 1. Sign up at ${pc2.dim("https://resend.com")}
6981
+ 2. Create an API key
6982
+ 3. Add RESEND_API_KEY to .env.local`);
6983
+ if (config.integrations.analytics === "posthog") {
6984
+ guides.push(`${pc2.cyan("PostHog Analytics")}:
6985
+ 1. Sign up at ${pc2.dim("https://posthog.com")}
6986
+ 2. Create a project and get your API key
6987
+ 3. Add NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST`);
6988
+ }
6989
+ if (config.integrations.uploads === "uploadthing") {
6990
+ guides.push(`${pc2.cyan("Uploadthing")}:
6991
+ 1. Sign up at ${pc2.dim("https://uploadthing.com")}
6992
+ 2. Create an app and get your credentials
6993
+ 3. Add UPLOADTHING_SECRET and UPLOADTHING_APP_ID`);
6994
+ }
6995
+ if (config.integrations.uploads === "s3") {
6996
+ guides.push(`${pc2.cyan("AWS S3")}:
6997
+ 1. Create an S3 bucket in AWS Console
6998
+ 2. Create an IAM user with S3 access
6999
+ 3. Add AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_BUCKET_NAME`);
7000
+ }
7001
+ if (config.integrations.rateLimiting === "arcjet") {
7002
+ guides.push(`${pc2.cyan("Arcjet")}:
7003
+ 1. Sign up at ${pc2.dim("https://arcjet.com")}
7004
+ 2. Create a site and get your API key
7005
+ 3. Add ARCJET_KEY to .env.local`);
7006
+ }
7007
+ if (config.integrations.rateLimiting === "upstash") {
7008
+ guides.push(`${pc2.cyan("Upstash Redis")}:
7009
+ 1. Sign up at ${pc2.dim("https://upstash.com")}
7010
+ 2. Create a Redis database
7011
+ 3. Add UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN`);
7012
+ }
7013
+ if (config.integrations.monitoring === "sentry") {
7014
+ guides.push(`${pc2.cyan("Sentry")}:
7015
+ 1. Sign up at ${pc2.dim("https://sentry.io")}
7016
+ 2. Create a project (Next.js)
7017
+ 3. Add SENTRY_DSN and SENTRY_AUTH_TOKEN`);
7018
+ }
7019
+ if (guides.length > 0) {
7020
+ p6.log.info(`
7021
+ ${pc2.bold("Configure these services to complete your setup:")}
7022
+
7023
+ ${guides.join("\n\n")}
7024
+ `);
7025
+ }
7026
+ }
7027
+ async function writeEnvFiles(config, envValues) {
7028
+ if (Object.keys(envValues).length === 0) {
7029
+ return;
7030
+ }
7031
+ p6.log.step("Writing environment files...");
7032
+ const webEnvPath = config.structure === "monorepo" ? path19.join(config.targetDir, "apps/web/.env.local") : path19.join(config.targetDir, ".env.local");
7033
+ if (await pathExists(webEnvPath)) {
7034
+ let content = await readFile(webEnvPath);
7035
+ for (const [key, value] of Object.entries(envValues)) {
7036
+ if (["PAYLOAD_SECRET", "DATABASE_URL", "S3_ENDPOINT", "S3_REGION", "S3_BUCKET", "S3_ACCESS_KEY_ID", "S3_SECRET_ACCESS_KEY", "CRON_SECRET", "PREVIEW_SECRET"].includes(key)) {
7037
+ continue;
7038
+ }
7039
+ const regex = new RegExp(`^${key}=.*$`, "m");
7040
+ if (regex.test(content)) {
7041
+ content = content.replace(regex, `${key}=${value}`);
7042
+ } else {
7043
+ content += `
7044
+ ${key}=${value}`;
7045
+ }
7046
+ }
7047
+ await writeFile(webEnvPath, content);
7048
+ p6.log.success(`Updated ${config.structure === "monorepo" ? "apps/web/.env.local" : ".env.local"}`);
7049
+ }
7050
+ if (config.marketingSite === "payload" && config.structure === "monorepo") {
7051
+ const marketingEnvPath = path19.join(config.targetDir, "apps/marketing/.env.local");
7052
+ if (await pathExists(marketingEnvPath)) {
7053
+ let content = await readFile(marketingEnvPath);
7054
+ const payloadEnvKeys = ["PAYLOAD_SECRET", "DATABASE_URL", "S3_ENDPOINT", "S3_REGION", "S3_BUCKET", "S3_ACCESS_KEY_ID", "S3_SECRET_ACCESS_KEY", "CRON_SECRET", "PREVIEW_SECRET"];
7055
+ for (const [key, value] of Object.entries(envValues)) {
7056
+ if (!payloadEnvKeys.includes(key)) continue;
7057
+ const regex = new RegExp(`^${key}=.*$`, "m");
7058
+ if (regex.test(content)) {
7059
+ content = content.replace(regex, `${key}=${value}`);
7060
+ } else {
7061
+ content += `
7062
+ ${key}=${value}`;
7063
+ }
7064
+ }
7065
+ await writeFile(marketingEnvPath, content);
7066
+ p6.log.success("Updated apps/marketing/.env.local");
7067
+ }
7068
+ }
7069
+ }
7070
+
5012
7071
  // src/generators/index.ts
5013
7072
  async function generateProject(config) {
5014
7073
  const spinner = ora();
5015
7074
  if (!await isPnpmInstalled()) {
5016
- p6.cancel("pnpm is not installed. Please install pnpm first: npm install -g pnpm");
7075
+ p7.cancel("pnpm is not installed. Please install pnpm first: npm install -g pnpm");
5017
7076
  process.exit(1);
5018
7077
  }
5019
7078
  if (await pathExists(config.targetDir)) {
5020
- p6.cancel(`Directory ${config.name} already exists`);
7079
+ p7.cancel(`Directory ${config.name} already exists`);
5021
7080
  process.exit(1);
5022
7081
  }
5023
7082
  console.log();
5024
- p6.log.info(`Creating project in ${pc2.cyan(config.targetDir)}`);
7083
+ p7.log.info(`Creating project in ${pc3.cyan(config.targetDir)}`);
5025
7084
  console.log();
5026
7085
  try {
5027
7086
  if (config.structure === "monorepo") {
@@ -5030,6 +7089,7 @@ async function generateProject(config) {
5030
7089
  await generateSingleProject(config, spinner);
5031
7090
  }
5032
7091
  await runPostGenerationSteps(config, spinner);
7092
+ await runEnvSetupWizard(config);
5033
7093
  displaySuccessMessage(config);
5034
7094
  } catch (error) {
5035
7095
  spinner.fail("An error occurred during project generation");
@@ -5049,7 +7109,7 @@ async function generateSingleProject(config, spinner) {
5049
7109
  await generateShadcn(config, targetDir);
5050
7110
  spinner.succeed("shadcn/ui configured");
5051
7111
  spinner.start("Setting up Convex...");
5052
- await generateConvex(config, path18.join(targetDir, "convex"));
7112
+ await generateConvex(config, path20.join(targetDir, "convex"));
5053
7113
  spinner.succeed("Convex configured");
5054
7114
  spinner.start("Configuring Better-Auth...");
5055
7115
  await generateBetterAuth(config, targetDir);
@@ -5092,7 +7152,7 @@ async function generateMonorepoProject(config, spinner) {
5092
7152
  spinner.start("Creating shared UI package...");
5093
7153
  await generateUIPackage(config, targetDir);
5094
7154
  spinner.succeed("Shared UI package created");
5095
- const webDir = path18.join(targetDir, "apps/web");
7155
+ const webDir = path20.join(targetDir, "apps/web");
5096
7156
  spinner.start("Generating web application...");
5097
7157
  await generateBaseNextjs(config, webDir);
5098
7158
  await generateTailwind(config, webDir);
@@ -5102,10 +7162,10 @@ async function generateMonorepoProject(config, spinner) {
5102
7162
  await generateEmail(config, webDir);
5103
7163
  await updateWebTsConfig(webDir);
5104
7164
  spinner.succeed("Web application generated");
5105
- const backendDir = path18.join(targetDir, "packages/backend");
7165
+ const backendDir = path20.join(targetDir, "packages/backend");
5106
7166
  spinner.start("Setting up Convex backend...");
5107
7167
  await ensureDir(backendDir);
5108
- await generateConvex(config, path18.join(backendDir, "convex"));
7168
+ await generateConvex(config, path20.join(backendDir, "convex"));
5109
7169
  spinner.succeed("Convex backend configured");
5110
7170
  if (config.integrations.analytics !== "none") {
5111
7171
  spinner.start(`Setting up ${config.integrations.analytics} analytics...`);
@@ -5128,7 +7188,7 @@ async function generateMonorepoProject(config, spinner) {
5128
7188
  spinner.succeed(`${config.integrations.monitoring} monitoring configured`);
5129
7189
  }
5130
7190
  if (config.marketingSite !== "none") {
5131
- const marketingDir = path18.join(targetDir, "apps/marketing");
7191
+ const marketingDir = path20.join(targetDir, "apps/marketing");
5132
7192
  spinner.start(`Generating ${config.marketingSite} marketing site...`);
5133
7193
  if (config.marketingSite === "payload") {
5134
7194
  await generatePayload(config, marketingDir);
@@ -5140,11 +7200,14 @@ async function generateMonorepoProject(config, spinner) {
5140
7200
  spinner.succeed(`${config.marketingSite} marketing site generated`);
5141
7201
  }
5142
7202
  if (config.includeDocs) {
5143
- const docsDir = path18.join(targetDir, "apps/docs");
7203
+ const docsDir = path20.join(targetDir, "apps/docs");
5144
7204
  spinner.start("Generating Fumadocs documentation site...");
5145
7205
  await generateFumadocs(config, docsDir);
5146
7206
  spinner.succeed("Fumadocs documentation site generated");
5147
7207
  }
7208
+ spinner.start("Generating design system app...");
7209
+ await generateDesignSystemApp(config, targetDir);
7210
+ spinner.succeed("Design system app generated");
5148
7211
  await generateGitignore(targetDir);
5149
7212
  }
5150
7213
  async function runPostGenerationSteps(config, spinner) {
@@ -5158,7 +7221,7 @@ async function runPostGenerationSteps(config, spinner) {
5158
7221
  }
5159
7222
  spinner.start("Installing shadcn/ui components...");
5160
7223
  try {
5161
- const shadcnDir = config.structure === "monorepo" ? path18.join(targetDir, "packages/ui") : targetDir;
7224
+ const shadcnDir = config.structure === "monorepo" ? path20.join(targetDir, "packages/ui") : targetDir;
5162
7225
  await runShadcnAdd(shadcnDir);
5163
7226
  spinner.succeed("shadcn/ui components installed");
5164
7227
  } catch (error) {
@@ -5218,7 +7281,7 @@ async function generateBiomeConfig(targetDir) {
5218
7281
  }
5219
7282
  };
5220
7283
  await writeFile(
5221
- path18.join(targetDir, "biome.json"),
7284
+ path20.join(targetDir, "biome.json"),
5222
7285
  JSON.stringify(biomeJson, null, 2)
5223
7286
  );
5224
7287
  }
@@ -5269,13 +7332,13 @@ yarn-error.log*
5269
7332
  *.pem
5270
7333
  .cache
5271
7334
  `;
5272
- await writeFile(path18.join(targetDir, ".gitignore"), gitignoreContent);
7335
+ await writeFile(path20.join(targetDir, ".gitignore"), gitignoreContent);
5273
7336
  }
5274
7337
  async function generateHuskyHooks(targetDir) {
5275
7338
  const preCommitContent = `pnpm lint-staged
5276
7339
  `;
5277
- await ensureDir(path18.join(targetDir, ".husky"));
5278
- await writeFile(path18.join(targetDir, ".husky/pre-commit"), preCommitContent);
7340
+ await ensureDir(path20.join(targetDir, ".husky"));
7341
+ await writeFile(path20.join(targetDir, ".husky/pre-commit"), preCommitContent);
5279
7342
  }
5280
7343
  async function updateWebTsConfig(webDir) {
5281
7344
  const tsConfig = {
@@ -5289,14 +7352,14 @@ async function updateWebTsConfig(webDir) {
5289
7352
  exclude: ["node_modules"]
5290
7353
  };
5291
7354
  await writeFile(
5292
- path18.join(webDir, "tsconfig.json"),
7355
+ path20.join(webDir, "tsconfig.json"),
5293
7356
  JSON.stringify(tsConfig, null, 2)
5294
7357
  );
5295
7358
  }
5296
7359
  async function generateNextjsMarketing(config, marketingDir) {
5297
- await ensureDir(path18.join(marketingDir, "src/app"));
5298
- await ensureDir(path18.join(marketingDir, "src/components"));
5299
- await ensureDir(path18.join(marketingDir, "public"));
7360
+ await ensureDir(path20.join(marketingDir, "src/app"));
7361
+ await ensureDir(path20.join(marketingDir, "src/components"));
7362
+ await ensureDir(path20.join(marketingDir, "public"));
5300
7363
  const packageJson = {
5301
7364
  name: "@repo/marketing",
5302
7365
  version: "0.1.0",
@@ -5327,7 +7390,7 @@ async function generateNextjsMarketing(config, marketingDir) {
5327
7390
  }
5328
7391
  };
5329
7392
  await writeFile(
5330
- path18.join(marketingDir, "package.json"),
7393
+ path20.join(marketingDir, "package.json"),
5331
7394
  JSON.stringify(packageJson, null, 2)
5332
7395
  );
5333
7396
  const homePageContent = `export default function HomePage() {
@@ -5350,7 +7413,7 @@ async function generateNextjsMarketing(config, marketingDir) {
5350
7413
  }
5351
7414
  `;
5352
7415
  await writeFile(
5353
- path18.join(marketingDir, "src/app/page.tsx"),
7416
+ path20.join(marketingDir, "src/app/page.tsx"),
5354
7417
  homePageContent
5355
7418
  );
5356
7419
  const layoutContent = `import type { Metadata } from 'next'
@@ -5374,7 +7437,7 @@ export default function RootLayout({
5374
7437
  }
5375
7438
  `;
5376
7439
  await writeFile(
5377
- path18.join(marketingDir, "src/app/layout.tsx"),
7440
+ path20.join(marketingDir, "src/app/layout.tsx"),
5378
7441
  layoutContent
5379
7442
  );
5380
7443
  const globalsCss = `@import "tailwindcss";
@@ -5386,7 +7449,7 @@ export default function RootLayout({
5386
7449
  }
5387
7450
  `;
5388
7451
  await writeFile(
5389
- path18.join(marketingDir, "src/app/globals.css"),
7452
+ path20.join(marketingDir, "src/app/globals.css"),
5390
7453
  globalsCss
5391
7454
  );
5392
7455
  const tsConfig = {
@@ -5400,7 +7463,7 @@ export default function RootLayout({
5400
7463
  exclude: ["node_modules"]
5401
7464
  };
5402
7465
  await writeFile(
5403
- path18.join(marketingDir, "tsconfig.json"),
7466
+ path20.join(marketingDir, "tsconfig.json"),
5404
7467
  JSON.stringify(tsConfig, null, 2)
5405
7468
  );
5406
7469
  const nextConfig = `import type { NextConfig } from 'next'
@@ -5411,14 +7474,14 @@ const nextConfig: NextConfig = {
5411
7474
 
5412
7475
  export default nextConfig
5413
7476
  `;
5414
- await writeFile(path18.join(marketingDir, "next.config.ts"), nextConfig);
7477
+ await writeFile(path20.join(marketingDir, "next.config.ts"), nextConfig);
5415
7478
  const postcssConfig = `export default {
5416
7479
  plugins: {
5417
7480
  '@tailwindcss/postcss': {},
5418
7481
  },
5419
7482
  }
5420
7483
  `;
5421
- await writeFile(path18.join(marketingDir, "postcss.config.mjs"), postcssConfig);
7484
+ await writeFile(path20.join(marketingDir, "postcss.config.mjs"), postcssConfig);
5422
7485
  }
5423
7486
  function displaySuccessMessage(config) {
5424
7487
  const apps = ["web"];
@@ -5429,39 +7492,40 @@ function displaySuccessMessage(config) {
5429
7492
  if (config.includeDocs) {
5430
7493
  apps.push("docs");
5431
7494
  }
7495
+ apps.push("design-system");
5432
7496
  }
5433
7497
  console.log();
5434
- p6.outro(pc2.green("Project created successfully!"));
7498
+ p7.outro(pc3.green("Project created successfully!"));
5435
7499
  console.log();
5436
- console.log(pc2.bold("Next steps:"));
7500
+ console.log(pc3.bold("Next steps:"));
5437
7501
  console.log();
5438
- console.log(` ${pc2.cyan("cd")} ${config.name}`);
7502
+ console.log(` ${pc3.cyan("cd")} ${config.name}`);
5439
7503
  console.log();
5440
- console.log(` ${pc2.dim("# Set up your environment variables")}`);
5441
- console.log(` ${pc2.cyan("cp")} .env.example .env.local`);
7504
+ console.log(` ${pc3.dim("# Set up your environment variables")}`);
7505
+ console.log(` ${pc3.cyan("cp")} .env.example .env.local`);
5442
7506
  console.log();
5443
- console.log(` ${pc2.dim("# Start Convex development server")}`);
5444
- console.log(` ${pc2.cyan("pnpm")} convex dev`);
7507
+ console.log(` ${pc3.dim("# Start Convex development server")}`);
7508
+ console.log(` ${pc3.cyan("pnpm")} convex dev`);
5445
7509
  console.log();
5446
- console.log(` ${pc2.dim("# In another terminal, start the app")}`);
5447
- console.log(` ${pc2.cyan("pnpm")} dev`);
7510
+ console.log(` ${pc3.dim("# In another terminal, start the app")}`);
7511
+ console.log(` ${pc3.cyan("pnpm")} dev`);
5448
7512
  if (config.marketingSite === "payload") {
5449
7513
  console.log();
5450
- console.log(pc2.dim("# If using Payload CMS:"));
5451
- console.log(pc2.dim("# 1. Create a Supabase project at https://supabase.com"));
5452
- console.log(pc2.dim("# 2. Copy your database connection strings to .env.local"));
5453
- console.log(pc2.dim("# 3. Run migrations: pnpm --filter marketing db:push"));
7514
+ console.log(pc3.dim("# If using Payload CMS:"));
7515
+ console.log(pc3.dim("# 1. Create a Supabase project at https://supabase.com"));
7516
+ console.log(pc3.dim("# 2. Copy your database connection strings to .env.local"));
7517
+ console.log(pc3.dim("# 3. Run migrations: pnpm --filter marketing db:push"));
5454
7518
  }
5455
7519
  console.log();
5456
- console.log(pc2.bold("Documentation:"));
5457
- console.log(` ${pc2.dim("-")} Convex: ${pc2.cyan("https://docs.convex.dev")}`);
5458
- console.log(` ${pc2.dim("-")} Better-Auth: ${pc2.cyan("https://www.better-auth.com")}`);
5459
- console.log(` ${pc2.dim("-")} shadcn/ui: ${pc2.cyan("https://ui.shadcn.com")}`);
7520
+ console.log(pc3.bold("Documentation:"));
7521
+ console.log(` ${pc3.dim("-")} Convex: ${pc3.cyan("https://docs.convex.dev")}`);
7522
+ console.log(` ${pc3.dim("-")} Better-Auth: ${pc3.cyan("https://www.better-auth.com")}`);
7523
+ console.log(` ${pc3.dim("-")} shadcn/ui: ${pc3.cyan("https://ui.shadcn.com")}`);
5460
7524
  if (config.marketingSite === "payload") {
5461
- console.log(` ${pc2.dim("-")} Payload CMS: ${pc2.cyan("https://payloadcms.com/docs")}`);
7525
+ console.log(` ${pc3.dim("-")} Payload CMS: ${pc3.cyan("https://payloadcms.com/docs")}`);
5462
7526
  }
5463
7527
  if (config.includeDocs) {
5464
- console.log(` ${pc2.dim("-")} Fumadocs: ${pc2.cyan("https://fumadocs.vercel.app")}`);
7528
+ console.log(` ${pc3.dim("-")} Fumadocs: ${pc3.cyan("https://fumadocs.vercel.app")}`);
5465
7529
  }
5466
7530
  console.log();
5467
7531
  }
@@ -5479,7 +7543,7 @@ var kofiGradient = gradient(["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]);
5479
7543
  function printBanner() {
5480
7544
  console.log(kofiGradient(BANNER));
5481
7545
  console.log(
5482
- pc3.dim(" Scaffold opinionated full-stack projects with ease\n")
7546
+ pc4.dim(" Scaffold opinionated full-stack projects with ease\n")
5483
7547
  );
5484
7548
  }
5485
7549
  var program = new Command();
@@ -5513,10 +7577,10 @@ program.name("create-kofi-stack").description(
5513
7577
  await generateProject(config);
5514
7578
  } catch (error) {
5515
7579
  if (error instanceof Error && error.message.includes("cancelled")) {
5516
- console.log(pc3.yellow("\nOperation cancelled."));
7580
+ console.log(pc4.yellow("\nOperation cancelled."));
5517
7581
  process.exit(0);
5518
7582
  }
5519
- console.error(pc3.red("\nAn error occurred:"), error);
7583
+ console.error(pc4.red("\nAn error occurred:"), error);
5520
7584
  process.exit(1);
5521
7585
  }
5522
7586
  });