@codebakers/cli 1.2.0 → 1.3.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.
- package/dist/commands/install-hook.js +42 -68
- package/dist/commands/scaffold.d.ts +4 -0
- package/dist/commands/scaffold.js +205 -0
- package/dist/commands/setup.js +24 -12
- package/dist/index.js +6 -0
- package/dist/mcp/server.js +211 -0
- package/dist/templates/nextjs-supabase.d.ts +81 -0
- package/dist/templates/nextjs-supabase.js +356 -0
- package/package.json +1 -1
- package/src/commands/install-hook.ts +44 -68
- package/src/commands/scaffold.ts +196 -0
- package/src/commands/setup.ts +22 -14
- package/src/index.ts +7 -0
- package/src/mcp/server.ts +244 -0
- package/src/templates/nextjs-supabase.ts +371 -0
package/src/mcp/server.ts
CHANGED
|
@@ -343,6 +343,40 @@ class CodeBakersServer {
|
|
|
343
343
|
required: ['patterns'],
|
|
344
344
|
},
|
|
345
345
|
},
|
|
346
|
+
{
|
|
347
|
+
name: 'search_patterns',
|
|
348
|
+
description:
|
|
349
|
+
'Search CodeBakers patterns by keyword or topic. Returns relevant code snippets without reading entire files. Use this when you need specific guidance like "supabase auth setup", "optimistic updates", "soft delete", "form validation".',
|
|
350
|
+
inputSchema: {
|
|
351
|
+
type: 'object' as const,
|
|
352
|
+
properties: {
|
|
353
|
+
query: {
|
|
354
|
+
type: 'string',
|
|
355
|
+
description: 'Search query (e.g., "supabase auth", "stripe checkout", "zod validation", "loading states")',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
required: ['query'],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'get_pattern_section',
|
|
363
|
+
description:
|
|
364
|
+
'Get a specific section from a pattern file instead of the whole file. Much faster than get_pattern for targeted lookups.',
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: 'object' as const,
|
|
367
|
+
properties: {
|
|
368
|
+
pattern: {
|
|
369
|
+
type: 'string',
|
|
370
|
+
description: 'Pattern name (e.g., "02-auth", "03-api")',
|
|
371
|
+
},
|
|
372
|
+
section: {
|
|
373
|
+
type: 'string',
|
|
374
|
+
description: 'Section name or keyword to find within the pattern (e.g., "OAuth", "rate limiting", "error handling")',
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
required: ['pattern', 'section'],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
346
380
|
],
|
|
347
381
|
}));
|
|
348
382
|
|
|
@@ -370,6 +404,12 @@ class CodeBakersServer {
|
|
|
370
404
|
case 'get_patterns':
|
|
371
405
|
return this.handleGetPatterns(args as { patterns: string[] });
|
|
372
406
|
|
|
407
|
+
case 'search_patterns':
|
|
408
|
+
return this.handleSearchPatterns(args as { query: string });
|
|
409
|
+
|
|
410
|
+
case 'get_pattern_section':
|
|
411
|
+
return this.handleGetPatternSection(args as { pattern: string; section: string });
|
|
412
|
+
|
|
373
413
|
default:
|
|
374
414
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
375
415
|
}
|
|
@@ -592,6 +632,210 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
592
632
|
return response.json();
|
|
593
633
|
}
|
|
594
634
|
|
|
635
|
+
private async handleSearchPatterns(args: { query: string }) {
|
|
636
|
+
const { query } = args;
|
|
637
|
+
|
|
638
|
+
// Call API endpoint for semantic search
|
|
639
|
+
const response = await fetch(`${this.apiUrl}/api/patterns/search`, {
|
|
640
|
+
method: 'POST',
|
|
641
|
+
headers: {
|
|
642
|
+
'Content-Type': 'application/json',
|
|
643
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
644
|
+
},
|
|
645
|
+
body: JSON.stringify({ query }),
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
if (!response.ok) {
|
|
649
|
+
// Fallback: If search endpoint doesn't exist, do client-side search
|
|
650
|
+
return this.fallbackSearch(query);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const data = await response.json();
|
|
654
|
+
|
|
655
|
+
const results = data.results
|
|
656
|
+
.map((r: { pattern: string; section: string; content: string; relevance: number }) =>
|
|
657
|
+
`### ${r.pattern} - ${r.section}\n\n\`\`\`typescript\n${r.content}\n\`\`\`\n\nRelevance: ${Math.round(r.relevance * 100)}%`
|
|
658
|
+
)
|
|
659
|
+
.join('\n\n---\n\n');
|
|
660
|
+
|
|
661
|
+
return {
|
|
662
|
+
content: [
|
|
663
|
+
{
|
|
664
|
+
type: 'text' as const,
|
|
665
|
+
text: `# Search Results for "${query}"\n\n${results || 'No results found. Try a different query.'}`,
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private async fallbackSearch(query: string) {
|
|
672
|
+
// Keyword-based fallback if API search not available
|
|
673
|
+
const keywordPatternMap: Record<string, string[]> = {
|
|
674
|
+
'auth': ['02-auth'],
|
|
675
|
+
'login': ['02-auth'],
|
|
676
|
+
'oauth': ['02-auth'],
|
|
677
|
+
'supabase': ['02-auth', '01-database'],
|
|
678
|
+
'database': ['01-database'],
|
|
679
|
+
'drizzle': ['01-database'],
|
|
680
|
+
'schema': ['01-database'],
|
|
681
|
+
'api': ['03-api'],
|
|
682
|
+
'route': ['03-api'],
|
|
683
|
+
'validation': ['03-api', '04-frontend'],
|
|
684
|
+
'zod': ['03-api', '04-frontend'],
|
|
685
|
+
'frontend': ['04-frontend'],
|
|
686
|
+
'form': ['04-frontend'],
|
|
687
|
+
'react': ['04-frontend'],
|
|
688
|
+
'component': ['04-frontend'],
|
|
689
|
+
'stripe': ['05-payments'],
|
|
690
|
+
'payment': ['05-payments'],
|
|
691
|
+
'checkout': ['05-payments'],
|
|
692
|
+
'subscription': ['05-payments'],
|
|
693
|
+
'email': ['06-integrations'],
|
|
694
|
+
'webhook': ['06-integrations'],
|
|
695
|
+
'cache': ['07-performance'],
|
|
696
|
+
'test': ['08-testing'],
|
|
697
|
+
'playwright': ['08-testing'],
|
|
698
|
+
'design': ['09-design'],
|
|
699
|
+
'ui': ['09-design'],
|
|
700
|
+
'accessibility': ['09-design'],
|
|
701
|
+
'websocket': ['11-realtime'],
|
|
702
|
+
'realtime': ['11-realtime'],
|
|
703
|
+
'notification': ['11-realtime'],
|
|
704
|
+
'saas': ['12-saas'],
|
|
705
|
+
'tenant': ['12-saas'],
|
|
706
|
+
'mobile': ['13-mobile'],
|
|
707
|
+
'expo': ['13-mobile'],
|
|
708
|
+
'ai': ['14-ai'],
|
|
709
|
+
'openai': ['14-ai'],
|
|
710
|
+
'embedding': ['14-ai'],
|
|
711
|
+
'analytics': ['26-analytics'],
|
|
712
|
+
'search': ['27-search'],
|
|
713
|
+
'animation': ['30-motion'],
|
|
714
|
+
'framer': ['30-motion'],
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const lowerQuery = query.toLowerCase();
|
|
718
|
+
const matchedPatterns = new Set<string>();
|
|
719
|
+
|
|
720
|
+
for (const [keyword, patterns] of Object.entries(keywordPatternMap)) {
|
|
721
|
+
if (lowerQuery.includes(keyword)) {
|
|
722
|
+
patterns.forEach(p => matchedPatterns.add(p));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (matchedPatterns.size === 0) {
|
|
727
|
+
return {
|
|
728
|
+
content: [
|
|
729
|
+
{
|
|
730
|
+
type: 'text' as const,
|
|
731
|
+
text: `# No patterns found for "${query}"\n\nTry:\n- "auth" for authentication patterns\n- "api" for API route patterns\n- "form" for frontend form patterns\n- "stripe" for payment patterns\n\nOr use \`list_patterns\` to see all available patterns.`,
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const patterns = Array.from(matchedPatterns).slice(0, 3);
|
|
738
|
+
const result = await this.fetchPatterns(patterns);
|
|
739
|
+
|
|
740
|
+
const content = Object.entries(result.patterns || {})
|
|
741
|
+
.map(([name, text]) => `## ${name}\n\n${text}`)
|
|
742
|
+
.join('\n\n---\n\n');
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
content: [
|
|
746
|
+
{
|
|
747
|
+
type: 'text' as const,
|
|
748
|
+
text: `# Patterns matching "${query}"\n\nFound in: ${patterns.join(', ')}\n\n${content}`,
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private async handleGetPatternSection(args: { pattern: string; section: string }) {
|
|
755
|
+
const { pattern, section } = args;
|
|
756
|
+
|
|
757
|
+
// Fetch the full pattern first
|
|
758
|
+
const result = await this.fetchPatterns([pattern]);
|
|
759
|
+
|
|
760
|
+
if (!result.patterns || !result.patterns[pattern]) {
|
|
761
|
+
throw new McpError(
|
|
762
|
+
ErrorCode.InvalidRequest,
|
|
763
|
+
`Pattern "${pattern}" not found. Use list_patterns to see available patterns.`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const fullContent = result.patterns[pattern];
|
|
768
|
+
|
|
769
|
+
// Find the section (case-insensitive search for headers or content)
|
|
770
|
+
const sectionLower = section.toLowerCase();
|
|
771
|
+
const lines = fullContent.split('\n');
|
|
772
|
+
const sections: string[] = [];
|
|
773
|
+
let currentSection = '';
|
|
774
|
+
let currentContent: string[] = [];
|
|
775
|
+
let capturing = false;
|
|
776
|
+
let relevanceScore = 0;
|
|
777
|
+
|
|
778
|
+
for (let i = 0; i < lines.length; i++) {
|
|
779
|
+
const line = lines[i];
|
|
780
|
+
|
|
781
|
+
// Check if this is a header
|
|
782
|
+
if (line.match(/^#{1,3}\s/)) {
|
|
783
|
+
// Save previous section if we were capturing
|
|
784
|
+
if (capturing && currentContent.length > 0) {
|
|
785
|
+
sections.push(`### ${currentSection}\n\n${currentContent.join('\n')}`);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
currentSection = line.replace(/^#+\s*/, '');
|
|
789
|
+
currentContent = [];
|
|
790
|
+
|
|
791
|
+
// Check if this section matches our query
|
|
792
|
+
if (currentSection.toLowerCase().includes(sectionLower)) {
|
|
793
|
+
capturing = true;
|
|
794
|
+
relevanceScore++;
|
|
795
|
+
} else {
|
|
796
|
+
capturing = false;
|
|
797
|
+
}
|
|
798
|
+
} else if (capturing) {
|
|
799
|
+
currentContent.push(line);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Also check content for keyword matches
|
|
803
|
+
if (!capturing && line.toLowerCase().includes(sectionLower)) {
|
|
804
|
+
// Found keyword in content, capture surrounding context
|
|
805
|
+
const start = Math.max(0, i - 5);
|
|
806
|
+
const end = Math.min(lines.length, i + 20);
|
|
807
|
+
const context = lines.slice(start, end).join('\n');
|
|
808
|
+
sections.push(`### Found at line ${i + 1}\n\n${context}`);
|
|
809
|
+
relevanceScore++;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Capture last section if we were still capturing
|
|
814
|
+
if (capturing && currentContent.length > 0) {
|
|
815
|
+
sections.push(`### ${currentSection}\n\n${currentContent.join('\n')}`);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (sections.length === 0) {
|
|
819
|
+
return {
|
|
820
|
+
content: [
|
|
821
|
+
{
|
|
822
|
+
type: 'text' as const,
|
|
823
|
+
text: `# Section "${section}" not found in ${pattern}\n\nThe pattern exists but doesn't contain a section matching "${section}".\n\nTry:\n- A broader search term\n- \`get_pattern ${pattern}\` to see the full content\n- \`search_patterns ${section}\` to search across all patterns`,
|
|
824
|
+
},
|
|
825
|
+
],
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return {
|
|
830
|
+
content: [
|
|
831
|
+
{
|
|
832
|
+
type: 'text' as const,
|
|
833
|
+
text: `# ${pattern} - "${section}"\n\n${sections.slice(0, 5).join('\n\n---\n\n')}`,
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
595
839
|
async run(): Promise<void> {
|
|
596
840
|
const transport = new StdioServerTransport();
|
|
597
841
|
await this.server.connect(transport);
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// Next.js + Supabase + Drizzle project template
|
|
2
|
+
|
|
3
|
+
export const PACKAGE_JSON = {
|
|
4
|
+
name: "my-project",
|
|
5
|
+
version: "0.1.0",
|
|
6
|
+
private: true,
|
|
7
|
+
scripts: {
|
|
8
|
+
"dev": "next dev",
|
|
9
|
+
"build": "next build",
|
|
10
|
+
"start": "next start",
|
|
11
|
+
"lint": "next lint",
|
|
12
|
+
"db:generate": "drizzle-kit generate",
|
|
13
|
+
"db:push": "drizzle-kit push",
|
|
14
|
+
"db:studio": "drizzle-kit studio"
|
|
15
|
+
},
|
|
16
|
+
dependencies: {
|
|
17
|
+
"next": "15.1.0",
|
|
18
|
+
"react": "^19.0.0",
|
|
19
|
+
"react-dom": "^19.0.0",
|
|
20
|
+
"@supabase/supabase-js": "^2.47.10",
|
|
21
|
+
"@supabase/ssr": "^0.5.2",
|
|
22
|
+
"drizzle-orm": "^0.38.2",
|
|
23
|
+
"postgres": "^3.4.5",
|
|
24
|
+
"zod": "^3.24.1",
|
|
25
|
+
"react-hook-form": "^7.54.1",
|
|
26
|
+
"@hookform/resolvers": "^3.9.1",
|
|
27
|
+
"lucide-react": "^0.468.0",
|
|
28
|
+
"clsx": "^2.1.1",
|
|
29
|
+
"tailwind-merge": "^2.6.0"
|
|
30
|
+
},
|
|
31
|
+
devDependencies: {
|
|
32
|
+
"@types/node": "^22.10.2",
|
|
33
|
+
"@types/react": "^19.0.1",
|
|
34
|
+
"@types/react-dom": "^19.0.1",
|
|
35
|
+
"typescript": "^5.7.2",
|
|
36
|
+
"tailwindcss": "^3.4.17",
|
|
37
|
+
"postcss": "^8.4.49",
|
|
38
|
+
"autoprefixer": "^10.4.20",
|
|
39
|
+
"drizzle-kit": "^0.30.1",
|
|
40
|
+
"eslint": "^9.17.0",
|
|
41
|
+
"eslint-config-next": "15.1.0"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ENV_EXAMPLE = `# Supabase
|
|
46
|
+
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
|
47
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
48
|
+
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
49
|
+
|
|
50
|
+
# Database (from Supabase connection string)
|
|
51
|
+
DATABASE_URL=postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
export const DRIZZLE_CONFIG = `import { defineConfig } from "drizzle-kit";
|
|
55
|
+
|
|
56
|
+
export default defineConfig({
|
|
57
|
+
schema: "./src/db/schema.ts",
|
|
58
|
+
out: "./src/db/migrations",
|
|
59
|
+
dialect: "postgresql",
|
|
60
|
+
dbCredentials: {
|
|
61
|
+
url: process.env.DATABASE_URL!,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
export const DB_SCHEMA = `import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";
|
|
67
|
+
|
|
68
|
+
// Users table (synced with Supabase Auth)
|
|
69
|
+
export const users = pgTable("users", {
|
|
70
|
+
id: uuid("id").primaryKey().notNull(),
|
|
71
|
+
email: text("email").notNull().unique(),
|
|
72
|
+
name: text("name"),
|
|
73
|
+
avatarUrl: text("avatar_url"),
|
|
74
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
75
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Example: Add your tables here following this pattern
|
|
79
|
+
// export const posts = pgTable("posts", {
|
|
80
|
+
// id: uuid("id").primaryKey().defaultRandom(),
|
|
81
|
+
// title: text("title").notNull(),
|
|
82
|
+
// content: text("content"),
|
|
83
|
+
// authorId: uuid("author_id").references(() => users.id),
|
|
84
|
+
// published: boolean("published").default(false),
|
|
85
|
+
// createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
86
|
+
// });
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
export const DB_INDEX = `import { drizzle } from "drizzle-orm/postgres-js";
|
|
90
|
+
import postgres from "postgres";
|
|
91
|
+
import * as schema from "./schema";
|
|
92
|
+
|
|
93
|
+
const connectionString = process.env.DATABASE_URL!;
|
|
94
|
+
|
|
95
|
+
const client = postgres(connectionString, { prepare: false });
|
|
96
|
+
export const db = drizzle(client, { schema });
|
|
97
|
+
|
|
98
|
+
export type DB = typeof db;
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
export const SUPABASE_SERVER = `import { createServerClient } from "@supabase/ssr";
|
|
102
|
+
import { cookies } from "next/headers";
|
|
103
|
+
|
|
104
|
+
export async function createClient() {
|
|
105
|
+
const cookieStore = await cookies();
|
|
106
|
+
|
|
107
|
+
return createServerClient(
|
|
108
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
109
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
110
|
+
{
|
|
111
|
+
cookies: {
|
|
112
|
+
getAll() {
|
|
113
|
+
return cookieStore.getAll();
|
|
114
|
+
},
|
|
115
|
+
setAll(cookiesToSet) {
|
|
116
|
+
try {
|
|
117
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
118
|
+
cookieStore.set(name, value, options)
|
|
119
|
+
);
|
|
120
|
+
} catch {
|
|
121
|
+
// The \`setAll\` method was called from a Server Component.
|
|
122
|
+
// This can be ignored if you have middleware refreshing user sessions.
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
export const SUPABASE_CLIENT = `import { createBrowserClient } from "@supabase/ssr";
|
|
132
|
+
|
|
133
|
+
export function createClient() {
|
|
134
|
+
return createBrowserClient(
|
|
135
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
136
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
export const SUPABASE_MIDDLEWARE = `import { createServerClient } from "@supabase/ssr";
|
|
142
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
143
|
+
|
|
144
|
+
export async function updateSession(request: NextRequest) {
|
|
145
|
+
let supabaseResponse = NextResponse.next({
|
|
146
|
+
request,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const supabase = createServerClient(
|
|
150
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
151
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
152
|
+
{
|
|
153
|
+
cookies: {
|
|
154
|
+
getAll() {
|
|
155
|
+
return request.cookies.getAll();
|
|
156
|
+
},
|
|
157
|
+
setAll(cookiesToSet) {
|
|
158
|
+
cookiesToSet.forEach(({ name, value }) =>
|
|
159
|
+
request.cookies.set(name, value)
|
|
160
|
+
);
|
|
161
|
+
supabaseResponse = NextResponse.next({
|
|
162
|
+
request,
|
|
163
|
+
});
|
|
164
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
165
|
+
supabaseResponse.cookies.set(name, value, options)
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// This will refresh session if expired
|
|
173
|
+
await supabase.auth.getUser();
|
|
174
|
+
|
|
175
|
+
return supabaseResponse;
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
export const MIDDLEWARE = `import { type NextRequest } from "next/server";
|
|
180
|
+
import { updateSession } from "@/lib/supabase/middleware";
|
|
181
|
+
|
|
182
|
+
export async function middleware(request: NextRequest) {
|
|
183
|
+
return await updateSession(request);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const config = {
|
|
187
|
+
matcher: [
|
|
188
|
+
/*
|
|
189
|
+
* Match all request paths except for:
|
|
190
|
+
* - _next/static (static files)
|
|
191
|
+
* - _next/image (image optimization)
|
|
192
|
+
* - favicon.ico
|
|
193
|
+
* - public files
|
|
194
|
+
*/
|
|
195
|
+
"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
export const TAILWIND_CONFIG = `import type { Config } from "tailwindcss";
|
|
201
|
+
|
|
202
|
+
const config: Config = {
|
|
203
|
+
content: [
|
|
204
|
+
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
205
|
+
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
206
|
+
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
207
|
+
],
|
|
208
|
+
theme: {
|
|
209
|
+
extend: {},
|
|
210
|
+
},
|
|
211
|
+
plugins: [],
|
|
212
|
+
};
|
|
213
|
+
export default config;
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
export const POSTCSS_CONFIG = `module.exports = {
|
|
217
|
+
plugins: {
|
|
218
|
+
tailwindcss: {},
|
|
219
|
+
autoprefixer: {},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
export const TSCONFIG = {
|
|
225
|
+
compilerOptions: {
|
|
226
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
227
|
+
allowJs: true,
|
|
228
|
+
skipLibCheck: true,
|
|
229
|
+
strict: true,
|
|
230
|
+
noEmit: true,
|
|
231
|
+
esModuleInterop: true,
|
|
232
|
+
module: "esnext",
|
|
233
|
+
moduleResolution: "bundler",
|
|
234
|
+
resolveJsonModule: true,
|
|
235
|
+
isolatedModules: true,
|
|
236
|
+
jsx: "preserve",
|
|
237
|
+
incremental: true,
|
|
238
|
+
plugins: [{ name: "next" }],
|
|
239
|
+
paths: {
|
|
240
|
+
"@/*": ["./src/*"]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
244
|
+
exclude: ["node_modules"]
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const NEXT_CONFIG = `import type { NextConfig } from "next";
|
|
248
|
+
|
|
249
|
+
const nextConfig: NextConfig = {
|
|
250
|
+
/* config options here */
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default nextConfig;
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
export const GLOBALS_CSS = `@tailwind base;
|
|
257
|
+
@tailwind components;
|
|
258
|
+
@tailwind utilities;
|
|
259
|
+
|
|
260
|
+
:root {
|
|
261
|
+
--background: #ffffff;
|
|
262
|
+
--foreground: #171717;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media (prefers-color-scheme: dark) {
|
|
266
|
+
:root {
|
|
267
|
+
--background: #0a0a0a;
|
|
268
|
+
--foreground: #ededed;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
body {
|
|
273
|
+
color: var(--foreground);
|
|
274
|
+
background: var(--background);
|
|
275
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
export const LAYOUT_TSX = `import type { Metadata } from "next";
|
|
280
|
+
import "./globals.css";
|
|
281
|
+
|
|
282
|
+
export const metadata: Metadata = {
|
|
283
|
+
title: "My App",
|
|
284
|
+
description: "Built with CodeBakers patterns",
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export default function RootLayout({
|
|
288
|
+
children,
|
|
289
|
+
}: Readonly<{
|
|
290
|
+
children: React.ReactNode;
|
|
291
|
+
}>) {
|
|
292
|
+
return (
|
|
293
|
+
<html lang="en">
|
|
294
|
+
<body>{children}</body>
|
|
295
|
+
</html>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
|
|
300
|
+
export const PAGE_TSX = `export default function Home() {
|
|
301
|
+
return (
|
|
302
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-24">
|
|
303
|
+
<h1 className="text-4xl font-bold mb-4">Welcome to Your App</h1>
|
|
304
|
+
<p className="text-gray-600 mb-8">Built with CodeBakers patterns</p>
|
|
305
|
+
<div className="flex gap-4">
|
|
306
|
+
<a
|
|
307
|
+
href="/login"
|
|
308
|
+
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
|
|
309
|
+
>
|
|
310
|
+
Get Started
|
|
311
|
+
</a>
|
|
312
|
+
<a
|
|
313
|
+
href="/docs"
|
|
314
|
+
className="px-6 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
|
315
|
+
>
|
|
316
|
+
Documentation
|
|
317
|
+
</a>
|
|
318
|
+
</div>
|
|
319
|
+
</main>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
`;
|
|
323
|
+
|
|
324
|
+
export const UTILS_CN = `import { clsx, type ClassValue } from "clsx";
|
|
325
|
+
import { twMerge } from "tailwind-merge";
|
|
326
|
+
|
|
327
|
+
export function cn(...inputs: ClassValue[]) {
|
|
328
|
+
return twMerge(clsx(inputs));
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
|
|
332
|
+
export const GITIGNORE = `# Dependencies
|
|
333
|
+
node_modules
|
|
334
|
+
.pnpm-debug.log*
|
|
335
|
+
|
|
336
|
+
# Next.js
|
|
337
|
+
.next/
|
|
338
|
+
out/
|
|
339
|
+
|
|
340
|
+
# Production
|
|
341
|
+
build
|
|
342
|
+
dist
|
|
343
|
+
|
|
344
|
+
# Misc
|
|
345
|
+
.DS_Store
|
|
346
|
+
*.pem
|
|
347
|
+
|
|
348
|
+
# Debug
|
|
349
|
+
npm-debug.log*
|
|
350
|
+
yarn-debug.log*
|
|
351
|
+
yarn-error.log*
|
|
352
|
+
|
|
353
|
+
# Local env files
|
|
354
|
+
.env
|
|
355
|
+
.env.local
|
|
356
|
+
.env.development.local
|
|
357
|
+
.env.test.local
|
|
358
|
+
.env.production.local
|
|
359
|
+
|
|
360
|
+
# Vercel
|
|
361
|
+
.vercel
|
|
362
|
+
|
|
363
|
+
# TypeScript
|
|
364
|
+
*.tsbuildinfo
|
|
365
|
+
next-env.d.ts
|
|
366
|
+
|
|
367
|
+
# IDE
|
|
368
|
+
.idea
|
|
369
|
+
.vscode/*
|
|
370
|
+
!.vscode/settings.json
|
|
371
|
+
`;
|