create-kofi-stack 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +436 -112
- 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
|
|
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
|
|
717
|
-
import * as
|
|
718
|
-
import
|
|
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.
|
|
812
|
-
"better-auth": "^1.
|
|
813
|
-
convex: "^1.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
1996
|
-
"@convex-dev/better-auth": "^0.10.
|
|
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
|
|
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",
|
|
@@ -4114,17 +4117,23 @@ async function generatePayloadTsConfig(marketingDir) {
|
|
|
4114
4117
|
await writeJSON(path15.join(marketingDir, "tsconfig.json"), tsConfig);
|
|
4115
4118
|
}
|
|
4116
4119
|
async function generatePayloadEnv(marketingDir) {
|
|
4117
|
-
const envContent = `# Database
|
|
4120
|
+
const envContent = `# Database (Supabase PostgreSQL)
|
|
4118
4121
|
DATABASE_URL=postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres
|
|
4119
4122
|
|
|
4120
|
-
# Payload
|
|
4121
|
-
PAYLOAD_SECRET=
|
|
4123
|
+
# Payload CMS
|
|
4124
|
+
PAYLOAD_SECRET= # Generate with: openssl rand -base64 32
|
|
4122
4125
|
|
|
4123
|
-
#
|
|
4124
|
-
|
|
4126
|
+
# Scheduled Jobs
|
|
4127
|
+
CRON_SECRET= # Generate with: openssl rand -base64 32
|
|
4128
|
+
|
|
4129
|
+
# Draft Previews
|
|
4130
|
+
PREVIEW_SECRET= # Generate with: openssl rand -base64 32
|
|
4131
|
+
|
|
4132
|
+
# S3 Storage (Supabase Storage)
|
|
4133
|
+
S3_BUCKET=media
|
|
4125
4134
|
S3_ACCESS_KEY_ID=
|
|
4126
4135
|
S3_SECRET_ACCESS_KEY=
|
|
4127
|
-
S3_REGION=
|
|
4136
|
+
S3_REGION=auto
|
|
4128
4137
|
S3_ENDPOINT=https://[PROJECT].supabase.co/storage/v1/s3
|
|
4129
4138
|
`;
|
|
4130
4139
|
await writeFile(path15.join(marketingDir, ".env.example"), envContent);
|
|
@@ -4158,12 +4167,12 @@ async function generateFumadocsPackageJson(docsDir) {
|
|
|
4158
4167
|
typecheck: "tsc --noEmit"
|
|
4159
4168
|
},
|
|
4160
4169
|
dependencies: {
|
|
4161
|
-
next: "^
|
|
4170
|
+
next: "^16.0.0",
|
|
4162
4171
|
react: "^19.0.0",
|
|
4163
4172
|
"react-dom": "^19.0.0",
|
|
4164
|
-
"fumadocs-core": "^
|
|
4165
|
-
"fumadocs-mdx": "^
|
|
4166
|
-
"fumadocs-ui": "^
|
|
4173
|
+
"fumadocs-core": "^16.0.0",
|
|
4174
|
+
"fumadocs-mdx": "^14.0.0",
|
|
4175
|
+
"fumadocs-ui": "^16.0.0",
|
|
4167
4176
|
"@repo/ui": "workspace:*"
|
|
4168
4177
|
},
|
|
4169
4178
|
devDependencies: {
|
|
@@ -4341,48 +4350,11 @@ export default function HomePage() {
|
|
|
4341
4350
|
}
|
|
4342
4351
|
`;
|
|
4343
4352
|
await writeFile(path16.join(docsDir, "src/app/page.tsx"), homePageContent);
|
|
4344
|
-
const globalCssContent = `@import '
|
|
4345
|
-
@import
|
|
4346
|
-
|
|
4347
|
-
:root {
|
|
4348
|
-
--fd-background: 0 0% 100%;
|
|
4349
|
-
--fd-foreground: 0 0% 3.9%;
|
|
4350
|
-
--fd-muted: 0 0% 96.1%;
|
|
4351
|
-
--fd-muted-foreground: 0 0% 45.1%;
|
|
4352
|
-
--fd-popover: 0 0% 100%;
|
|
4353
|
-
--fd-popover-foreground: 0 0% 3.9%;
|
|
4354
|
-
--fd-card: 0 0% 100%;
|
|
4355
|
-
--fd-card-foreground: 0 0% 3.9%;
|
|
4356
|
-
--fd-border: 0 0% 89.8%;
|
|
4357
|
-
--fd-input: 0 0% 89.8%;
|
|
4358
|
-
--fd-primary: 0 0% 9%;
|
|
4359
|
-
--fd-primary-foreground: 0 0% 98%;
|
|
4360
|
-
--fd-secondary: 0 0% 96.1%;
|
|
4361
|
-
--fd-secondary-foreground: 0 0% 9%;
|
|
4362
|
-
--fd-accent: 0 0% 96.1%;
|
|
4363
|
-
--fd-accent-foreground: 0 0% 9%;
|
|
4364
|
-
--fd-ring: 0 0% 63.9%;
|
|
4365
|
-
}
|
|
4353
|
+
const globalCssContent = `@import 'tailwindcss';
|
|
4354
|
+
@import 'fumadocs-ui/css/neutral.css';
|
|
4355
|
+
@import 'fumadocs-ui/css/preset.css';
|
|
4366
4356
|
|
|
4367
|
-
|
|
4368
|
-
--fd-background: 0 0% 3.9%;
|
|
4369
|
-
--fd-foreground: 0 0% 98%;
|
|
4370
|
-
--fd-muted: 0 0% 14.9%;
|
|
4371
|
-
--fd-muted-foreground: 0 0% 63.9%;
|
|
4372
|
-
--fd-popover: 0 0% 3.9%;
|
|
4373
|
-
--fd-popover-foreground: 0 0% 98%;
|
|
4374
|
-
--fd-card: 0 0% 3.9%;
|
|
4375
|
-
--fd-card-foreground: 0 0% 98%;
|
|
4376
|
-
--fd-border: 0 0% 14.9%;
|
|
4377
|
-
--fd-input: 0 0% 14.9%;
|
|
4378
|
-
--fd-primary: 0 0% 98%;
|
|
4379
|
-
--fd-primary-foreground: 0 0% 9%;
|
|
4380
|
-
--fd-secondary: 0 0% 14.9%;
|
|
4381
|
-
--fd-secondary-foreground: 0 0% 98%;
|
|
4382
|
-
--fd-accent: 0 0% 14.9%;
|
|
4383
|
-
--fd-accent-foreground: 0 0% 98%;
|
|
4384
|
-
--fd-ring: 0 0% 14.9%;
|
|
4385
|
-
}
|
|
4357
|
+
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
|
4386
4358
|
`;
|
|
4387
4359
|
await writeFile(path16.join(docsDir, "src/app/global.css"), globalCssContent);
|
|
4388
4360
|
const sourceConfigContent = `import { defineCollections, defineConfig } from 'fumadocs-mdx/config'
|
|
@@ -4508,7 +4480,7 @@ ${config.name} uses Better-Auth with Convex for authentication.
|
|
|
4508
4480
|
|
|
4509
4481
|
- Email/Password (always enabled)
|
|
4510
4482
|
- Google OAuth (always enabled)
|
|
4511
|
-
${config.auth.providers.map((
|
|
4483
|
+
${config.auth.providers.map((p8) => `- ${p8.charAt(0).toUpperCase() + p8.slice(1)}`).join("\n")}
|
|
4512
4484
|
|
|
4513
4485
|
## Configuration
|
|
4514
4486
|
|
|
@@ -6745,19 +6717,370 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6745
6717
|
await writeFile(path18.join(appDir, "src/lib/utils.ts"), content);
|
|
6746
6718
|
}
|
|
6747
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
|
+
|
|
6748
7071
|
// src/generators/index.ts
|
|
6749
7072
|
async function generateProject(config) {
|
|
6750
7073
|
const spinner = ora();
|
|
6751
7074
|
if (!await isPnpmInstalled()) {
|
|
6752
|
-
|
|
7075
|
+
p7.cancel("pnpm is not installed. Please install pnpm first: npm install -g pnpm");
|
|
6753
7076
|
process.exit(1);
|
|
6754
7077
|
}
|
|
6755
7078
|
if (await pathExists(config.targetDir)) {
|
|
6756
|
-
|
|
7079
|
+
p7.cancel(`Directory ${config.name} already exists`);
|
|
6757
7080
|
process.exit(1);
|
|
6758
7081
|
}
|
|
6759
7082
|
console.log();
|
|
6760
|
-
|
|
7083
|
+
p7.log.info(`Creating project in ${pc3.cyan(config.targetDir)}`);
|
|
6761
7084
|
console.log();
|
|
6762
7085
|
try {
|
|
6763
7086
|
if (config.structure === "monorepo") {
|
|
@@ -6766,6 +7089,7 @@ async function generateProject(config) {
|
|
|
6766
7089
|
await generateSingleProject(config, spinner);
|
|
6767
7090
|
}
|
|
6768
7091
|
await runPostGenerationSteps(config, spinner);
|
|
7092
|
+
await runEnvSetupWizard(config);
|
|
6769
7093
|
displaySuccessMessage(config);
|
|
6770
7094
|
} catch (error) {
|
|
6771
7095
|
spinner.fail("An error occurred during project generation");
|
|
@@ -6785,7 +7109,7 @@ async function generateSingleProject(config, spinner) {
|
|
|
6785
7109
|
await generateShadcn(config, targetDir);
|
|
6786
7110
|
spinner.succeed("shadcn/ui configured");
|
|
6787
7111
|
spinner.start("Setting up Convex...");
|
|
6788
|
-
await generateConvex(config,
|
|
7112
|
+
await generateConvex(config, path20.join(targetDir, "convex"));
|
|
6789
7113
|
spinner.succeed("Convex configured");
|
|
6790
7114
|
spinner.start("Configuring Better-Auth...");
|
|
6791
7115
|
await generateBetterAuth(config, targetDir);
|
|
@@ -6828,7 +7152,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6828
7152
|
spinner.start("Creating shared UI package...");
|
|
6829
7153
|
await generateUIPackage(config, targetDir);
|
|
6830
7154
|
spinner.succeed("Shared UI package created");
|
|
6831
|
-
const webDir =
|
|
7155
|
+
const webDir = path20.join(targetDir, "apps/web");
|
|
6832
7156
|
spinner.start("Generating web application...");
|
|
6833
7157
|
await generateBaseNextjs(config, webDir);
|
|
6834
7158
|
await generateTailwind(config, webDir);
|
|
@@ -6838,10 +7162,10 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6838
7162
|
await generateEmail(config, webDir);
|
|
6839
7163
|
await updateWebTsConfig(webDir);
|
|
6840
7164
|
spinner.succeed("Web application generated");
|
|
6841
|
-
const backendDir =
|
|
7165
|
+
const backendDir = path20.join(targetDir, "packages/backend");
|
|
6842
7166
|
spinner.start("Setting up Convex backend...");
|
|
6843
7167
|
await ensureDir(backendDir);
|
|
6844
|
-
await generateConvex(config,
|
|
7168
|
+
await generateConvex(config, path20.join(backendDir, "convex"));
|
|
6845
7169
|
spinner.succeed("Convex backend configured");
|
|
6846
7170
|
if (config.integrations.analytics !== "none") {
|
|
6847
7171
|
spinner.start(`Setting up ${config.integrations.analytics} analytics...`);
|
|
@@ -6864,7 +7188,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6864
7188
|
spinner.succeed(`${config.integrations.monitoring} monitoring configured`);
|
|
6865
7189
|
}
|
|
6866
7190
|
if (config.marketingSite !== "none") {
|
|
6867
|
-
const marketingDir =
|
|
7191
|
+
const marketingDir = path20.join(targetDir, "apps/marketing");
|
|
6868
7192
|
spinner.start(`Generating ${config.marketingSite} marketing site...`);
|
|
6869
7193
|
if (config.marketingSite === "payload") {
|
|
6870
7194
|
await generatePayload(config, marketingDir);
|
|
@@ -6876,7 +7200,7 @@ async function generateMonorepoProject(config, spinner) {
|
|
|
6876
7200
|
spinner.succeed(`${config.marketingSite} marketing site generated`);
|
|
6877
7201
|
}
|
|
6878
7202
|
if (config.includeDocs) {
|
|
6879
|
-
const docsDir =
|
|
7203
|
+
const docsDir = path20.join(targetDir, "apps/docs");
|
|
6880
7204
|
spinner.start("Generating Fumadocs documentation site...");
|
|
6881
7205
|
await generateFumadocs(config, docsDir);
|
|
6882
7206
|
spinner.succeed("Fumadocs documentation site generated");
|
|
@@ -6897,7 +7221,7 @@ async function runPostGenerationSteps(config, spinner) {
|
|
|
6897
7221
|
}
|
|
6898
7222
|
spinner.start("Installing shadcn/ui components...");
|
|
6899
7223
|
try {
|
|
6900
|
-
const shadcnDir = config.structure === "monorepo" ?
|
|
7224
|
+
const shadcnDir = config.structure === "monorepo" ? path20.join(targetDir, "packages/ui") : targetDir;
|
|
6901
7225
|
await runShadcnAdd(shadcnDir);
|
|
6902
7226
|
spinner.succeed("shadcn/ui components installed");
|
|
6903
7227
|
} catch (error) {
|
|
@@ -6957,7 +7281,7 @@ async function generateBiomeConfig(targetDir) {
|
|
|
6957
7281
|
}
|
|
6958
7282
|
};
|
|
6959
7283
|
await writeFile(
|
|
6960
|
-
|
|
7284
|
+
path20.join(targetDir, "biome.json"),
|
|
6961
7285
|
JSON.stringify(biomeJson, null, 2)
|
|
6962
7286
|
);
|
|
6963
7287
|
}
|
|
@@ -7008,13 +7332,13 @@ yarn-error.log*
|
|
|
7008
7332
|
*.pem
|
|
7009
7333
|
.cache
|
|
7010
7334
|
`;
|
|
7011
|
-
await writeFile(
|
|
7335
|
+
await writeFile(path20.join(targetDir, ".gitignore"), gitignoreContent);
|
|
7012
7336
|
}
|
|
7013
7337
|
async function generateHuskyHooks(targetDir) {
|
|
7014
7338
|
const preCommitContent = `pnpm lint-staged
|
|
7015
7339
|
`;
|
|
7016
|
-
await ensureDir(
|
|
7017
|
-
await writeFile(
|
|
7340
|
+
await ensureDir(path20.join(targetDir, ".husky"));
|
|
7341
|
+
await writeFile(path20.join(targetDir, ".husky/pre-commit"), preCommitContent);
|
|
7018
7342
|
}
|
|
7019
7343
|
async function updateWebTsConfig(webDir) {
|
|
7020
7344
|
const tsConfig = {
|
|
@@ -7028,14 +7352,14 @@ async function updateWebTsConfig(webDir) {
|
|
|
7028
7352
|
exclude: ["node_modules"]
|
|
7029
7353
|
};
|
|
7030
7354
|
await writeFile(
|
|
7031
|
-
|
|
7355
|
+
path20.join(webDir, "tsconfig.json"),
|
|
7032
7356
|
JSON.stringify(tsConfig, null, 2)
|
|
7033
7357
|
);
|
|
7034
7358
|
}
|
|
7035
7359
|
async function generateNextjsMarketing(config, marketingDir) {
|
|
7036
|
-
await ensureDir(
|
|
7037
|
-
await ensureDir(
|
|
7038
|
-
await ensureDir(
|
|
7360
|
+
await ensureDir(path20.join(marketingDir, "src/app"));
|
|
7361
|
+
await ensureDir(path20.join(marketingDir, "src/components"));
|
|
7362
|
+
await ensureDir(path20.join(marketingDir, "public"));
|
|
7039
7363
|
const packageJson = {
|
|
7040
7364
|
name: "@repo/marketing",
|
|
7041
7365
|
version: "0.1.0",
|
|
@@ -7066,7 +7390,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7066
7390
|
}
|
|
7067
7391
|
};
|
|
7068
7392
|
await writeFile(
|
|
7069
|
-
|
|
7393
|
+
path20.join(marketingDir, "package.json"),
|
|
7070
7394
|
JSON.stringify(packageJson, null, 2)
|
|
7071
7395
|
);
|
|
7072
7396
|
const homePageContent = `export default function HomePage() {
|
|
@@ -7089,7 +7413,7 @@ async function generateNextjsMarketing(config, marketingDir) {
|
|
|
7089
7413
|
}
|
|
7090
7414
|
`;
|
|
7091
7415
|
await writeFile(
|
|
7092
|
-
|
|
7416
|
+
path20.join(marketingDir, "src/app/page.tsx"),
|
|
7093
7417
|
homePageContent
|
|
7094
7418
|
);
|
|
7095
7419
|
const layoutContent = `import type { Metadata } from 'next'
|
|
@@ -7113,7 +7437,7 @@ export default function RootLayout({
|
|
|
7113
7437
|
}
|
|
7114
7438
|
`;
|
|
7115
7439
|
await writeFile(
|
|
7116
|
-
|
|
7440
|
+
path20.join(marketingDir, "src/app/layout.tsx"),
|
|
7117
7441
|
layoutContent
|
|
7118
7442
|
);
|
|
7119
7443
|
const globalsCss = `@import "tailwindcss";
|
|
@@ -7125,7 +7449,7 @@ export default function RootLayout({
|
|
|
7125
7449
|
}
|
|
7126
7450
|
`;
|
|
7127
7451
|
await writeFile(
|
|
7128
|
-
|
|
7452
|
+
path20.join(marketingDir, "src/app/globals.css"),
|
|
7129
7453
|
globalsCss
|
|
7130
7454
|
);
|
|
7131
7455
|
const tsConfig = {
|
|
@@ -7139,7 +7463,7 @@ export default function RootLayout({
|
|
|
7139
7463
|
exclude: ["node_modules"]
|
|
7140
7464
|
};
|
|
7141
7465
|
await writeFile(
|
|
7142
|
-
|
|
7466
|
+
path20.join(marketingDir, "tsconfig.json"),
|
|
7143
7467
|
JSON.stringify(tsConfig, null, 2)
|
|
7144
7468
|
);
|
|
7145
7469
|
const nextConfig = `import type { NextConfig } from 'next'
|
|
@@ -7150,14 +7474,14 @@ const nextConfig: NextConfig = {
|
|
|
7150
7474
|
|
|
7151
7475
|
export default nextConfig
|
|
7152
7476
|
`;
|
|
7153
|
-
await writeFile(
|
|
7477
|
+
await writeFile(path20.join(marketingDir, "next.config.ts"), nextConfig);
|
|
7154
7478
|
const postcssConfig = `export default {
|
|
7155
7479
|
plugins: {
|
|
7156
7480
|
'@tailwindcss/postcss': {},
|
|
7157
7481
|
},
|
|
7158
7482
|
}
|
|
7159
7483
|
`;
|
|
7160
|
-
await writeFile(
|
|
7484
|
+
await writeFile(path20.join(marketingDir, "postcss.config.mjs"), postcssConfig);
|
|
7161
7485
|
}
|
|
7162
7486
|
function displaySuccessMessage(config) {
|
|
7163
7487
|
const apps = ["web"];
|
|
@@ -7171,37 +7495,37 @@ function displaySuccessMessage(config) {
|
|
|
7171
7495
|
apps.push("design-system");
|
|
7172
7496
|
}
|
|
7173
7497
|
console.log();
|
|
7174
|
-
|
|
7498
|
+
p7.outro(pc3.green("Project created successfully!"));
|
|
7175
7499
|
console.log();
|
|
7176
|
-
console.log(
|
|
7500
|
+
console.log(pc3.bold("Next steps:"));
|
|
7177
7501
|
console.log();
|
|
7178
|
-
console.log(` ${
|
|
7502
|
+
console.log(` ${pc3.cyan("cd")} ${config.name}`);
|
|
7179
7503
|
console.log();
|
|
7180
|
-
console.log(` ${
|
|
7181
|
-
console.log(` ${
|
|
7504
|
+
console.log(` ${pc3.dim("# Set up your environment variables")}`);
|
|
7505
|
+
console.log(` ${pc3.cyan("cp")} .env.example .env.local`);
|
|
7182
7506
|
console.log();
|
|
7183
|
-
console.log(` ${
|
|
7184
|
-
console.log(` ${
|
|
7507
|
+
console.log(` ${pc3.dim("# Start Convex development server")}`);
|
|
7508
|
+
console.log(` ${pc3.cyan("pnpm")} convex dev`);
|
|
7185
7509
|
console.log();
|
|
7186
|
-
console.log(` ${
|
|
7187
|
-
console.log(` ${
|
|
7510
|
+
console.log(` ${pc3.dim("# In another terminal, start the app")}`);
|
|
7511
|
+
console.log(` ${pc3.cyan("pnpm")} dev`);
|
|
7188
7512
|
if (config.marketingSite === "payload") {
|
|
7189
7513
|
console.log();
|
|
7190
|
-
console.log(
|
|
7191
|
-
console.log(
|
|
7192
|
-
console.log(
|
|
7193
|
-
console.log(
|
|
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"));
|
|
7194
7518
|
}
|
|
7195
7519
|
console.log();
|
|
7196
|
-
console.log(
|
|
7197
|
-
console.log(` ${
|
|
7198
|
-
console.log(` ${
|
|
7199
|
-
console.log(` ${
|
|
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")}`);
|
|
7200
7524
|
if (config.marketingSite === "payload") {
|
|
7201
|
-
console.log(` ${
|
|
7525
|
+
console.log(` ${pc3.dim("-")} Payload CMS: ${pc3.cyan("https://payloadcms.com/docs")}`);
|
|
7202
7526
|
}
|
|
7203
7527
|
if (config.includeDocs) {
|
|
7204
|
-
console.log(` ${
|
|
7528
|
+
console.log(` ${pc3.dim("-")} Fumadocs: ${pc3.cyan("https://fumadocs.vercel.app")}`);
|
|
7205
7529
|
}
|
|
7206
7530
|
console.log();
|
|
7207
7531
|
}
|
|
@@ -7219,7 +7543,7 @@ var kofiGradient = gradient(["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]);
|
|
|
7219
7543
|
function printBanner() {
|
|
7220
7544
|
console.log(kofiGradient(BANNER));
|
|
7221
7545
|
console.log(
|
|
7222
|
-
|
|
7546
|
+
pc4.dim(" Scaffold opinionated full-stack projects with ease\n")
|
|
7223
7547
|
);
|
|
7224
7548
|
}
|
|
7225
7549
|
var program = new Command();
|
|
@@ -7253,10 +7577,10 @@ program.name("create-kofi-stack").description(
|
|
|
7253
7577
|
await generateProject(config);
|
|
7254
7578
|
} catch (error) {
|
|
7255
7579
|
if (error instanceof Error && error.message.includes("cancelled")) {
|
|
7256
|
-
console.log(
|
|
7580
|
+
console.log(pc4.yellow("\nOperation cancelled."));
|
|
7257
7581
|
process.exit(0);
|
|
7258
7582
|
}
|
|
7259
|
-
console.error(
|
|
7583
|
+
console.error(pc4.red("\nAn error occurred:"), error);
|
|
7260
7584
|
process.exit(1);
|
|
7261
7585
|
}
|
|
7262
7586
|
});
|