Skip to content

Commit 79aefcd

Browse files
committed
feat: refactor file generation logic by introducing renderTemplate function and removing redundant code
1 parent b2f781c commit 79aefcd

File tree

3 files changed

+150
-122
lines changed

3 files changed

+150
-122
lines changed

app/api/generate/[framework]/[fileName]/route.ts

Lines changed: 8 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { readFile } from 'fs/promises'
3-
import path from 'path'
4-
52
import type { WizardResponses } from '@/types/wizard'
6-
import { getTemplateConfig, type TemplateKey } from '@/lib/template-config'
7-
import { getStackGuidance } from '@/lib/stack-guidance'
8-
9-
function mapOutputFileToTemplateType(outputFile: string): string {
10-
const mapping: Record<string, string> = {
11-
'instructions-md': 'copilot-instructions',
12-
'agents-md': 'agents',
13-
'cursor-rules': 'cursor-rules',
14-
'json-rules': 'json-rules',
15-
}
16-
17-
return mapping[outputFile] ?? outputFile
18-
}
3+
import { renderTemplate } from '@/lib/template-render'
194

205
export async function POST(
216
request: NextRequest,
@@ -25,113 +10,15 @@ export async function POST(
2510
const { framework, fileName } = await context.params
2611
const responses = (await request.json()) as WizardResponses
2712

28-
const frameworkFromPath =
29-
framework && !['general', 'none', 'undefined'].includes(framework)
30-
? framework
31-
: undefined
32-
33-
const templateKeyFromParams: TemplateKey = {
34-
templateType: mapOutputFileToTemplateType(fileName),
35-
stack: frameworkFromPath,
36-
}
37-
38-
let templateConfig = getTemplateConfig(templateKeyFromParams)
39-
40-
if (!templateConfig && responses.outputFile) {
41-
const templateKeyFromBody: TemplateKey = {
42-
templateType: mapOutputFileToTemplateType(responses.outputFile),
43-
stack: responses.stackSelection || undefined,
44-
}
45-
46-
templateConfig = getTemplateConfig(templateKeyFromBody)
47-
}
48-
49-
if (!templateConfig) {
50-
templateConfig = getTemplateConfig(fileName)
51-
}
52-
53-
if (!templateConfig) {
54-
return NextResponse.json(
55-
{ error: `Template not found for fileName: ${fileName}` },
56-
{ status: 404 },
57-
)
58-
}
59-
60-
const templatePath = path.join(process.cwd(), 'file-templates', templateConfig.template)
61-
const template = await readFile(templatePath, 'utf-8')
62-
63-
let generatedContent = template
64-
const isJsonTemplate = templateConfig.template.toLowerCase().endsWith('.json')
65-
66-
const escapeForJson = (value: string) => {
67-
const escaped = JSON.stringify(value)
68-
return escaped.slice(1, -1)
69-
}
70-
71-
const replaceVariable = (key: keyof WizardResponses, fallback = 'Not specified') => {
72-
const placeholder = `{{${key}}}`
73-
74-
if (!generatedContent.includes(placeholder)) {
75-
return
76-
}
77-
78-
const value = responses[key]
79-
80-
if (value === null || value === undefined || value === '') {
81-
const replacement = isJsonTemplate ? escapeForJson(fallback) : fallback
82-
generatedContent = generatedContent.replace(placeholder, replacement)
83-
} else {
84-
const replacementValue = String(value)
85-
const replacement = isJsonTemplate ? escapeForJson(replacementValue) : replacementValue
86-
generatedContent = generatedContent.replace(placeholder, replacement)
87-
}
88-
}
89-
90-
replaceVariable('stackSelection')
91-
replaceVariable('tooling')
92-
replaceVariable('language')
93-
replaceVariable('projectPriority')
94-
replaceVariable('codeStyle')
95-
replaceVariable('variableNaming')
96-
replaceVariable('fileNaming')
97-
replaceVariable('componentNaming')
98-
replaceVariable('exports')
99-
replaceVariable('comments')
100-
replaceVariable('collaboration')
101-
replaceVariable('fileStructure')
102-
replaceVariable('styling')
103-
replaceVariable('stateManagement')
104-
replaceVariable('apiLayer')
105-
replaceVariable('folders')
106-
replaceVariable('testingUT')
107-
replaceVariable('testingE2E')
108-
replaceVariable('dataFetching')
109-
replaceVariable('reactPerf')
110-
replaceVariable('auth')
111-
replaceVariable('validation')
112-
replaceVariable('logging')
113-
replaceVariable('commitStyle')
114-
replaceVariable('prRules')
115-
const replaceStaticPlaceholder = (placeholderKey: string, value: string) => {
116-
const placeholder = `{{${placeholderKey}}}`
117-
118-
if (!generatedContent.includes(placeholder)) {
119-
return
120-
}
121-
122-
const replacement = isJsonTemplate ? escapeForJson(value) : value
123-
generatedContent = generatedContent.replace(placeholder, replacement)
124-
}
125-
126-
replaceVariable('outputFile')
127-
128-
const stackGuidanceSlug = responses.stackSelection || frameworkFromPath
129-
const stackGuidance = getStackGuidance(stackGuidanceSlug)
130-
replaceStaticPlaceholder('stackGuidance', stackGuidance)
13+
const rendered = await renderTemplate({
14+
responses,
15+
frameworkFromPath: framework,
16+
fileNameFromPath: fileName,
17+
})
13118

13219
return NextResponse.json({
133-
content: generatedContent,
134-
fileName: templateConfig.outputFileName,
20+
content: rendered.content,
21+
fileName: rendered.fileName,
13522
})
13623
} catch (error) {
13724
console.error('Error generating file:', error)

lib/scan-generate.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,3 @@ export async function generateFromRepoScan(
2828

2929
return result
3030
}
31-

lib/template-render.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { readFile } from 'fs/promises'
2+
import path from 'path'
3+
4+
import type { WizardResponses } from '@/types/wizard'
5+
import { getTemplateConfig, type TemplateKey } from '@/lib/template-config'
6+
import { getStackGuidance } from '@/lib/stack-guidance'
7+
8+
function mapOutputFileToTemplateType(outputFile: string): string {
9+
const mapping: Record<string, string> = {
10+
'instructions-md': 'copilot-instructions',
11+
'agents-md': 'agents',
12+
'cursor-rules': 'cursor-rules',
13+
'json-rules': 'json-rules',
14+
}
15+
16+
return mapping[outputFile] ?? outputFile
17+
}
18+
19+
export type RenderTemplateParams = {
20+
responses: WizardResponses
21+
frameworkFromPath?: string | undefined
22+
fileNameFromPath?: string | undefined
23+
}
24+
25+
export type RenderTemplateResult = {
26+
fileName: string
27+
content: string
28+
isJson: boolean
29+
}
30+
31+
export async function renderTemplate({
32+
responses,
33+
frameworkFromPath,
34+
fileNameFromPath,
35+
}: RenderTemplateParams): Promise<RenderTemplateResult> {
36+
const framework = frameworkFromPath && !['general', 'none', 'undefined'].includes(frameworkFromPath)
37+
? frameworkFromPath
38+
: undefined
39+
40+
// Resolve template config from either route params or body fields
41+
const templateKeyFromParams: TemplateKey | null = fileNameFromPath
42+
? {
43+
templateType: mapOutputFileToTemplateType(fileNameFromPath),
44+
stack: framework,
45+
}
46+
: null
47+
48+
let templateConfig = templateKeyFromParams ? getTemplateConfig(templateKeyFromParams) : null
49+
50+
if (!templateConfig && responses.outputFile) {
51+
const templateKeyFromBody: TemplateKey = {
52+
templateType: mapOutputFileToTemplateType(responses.outputFile),
53+
stack: responses.stackSelection || undefined,
54+
}
55+
templateConfig = getTemplateConfig(templateKeyFromBody)
56+
}
57+
58+
if (!templateConfig && fileNameFromPath) {
59+
templateConfig = getTemplateConfig(fileNameFromPath)
60+
}
61+
62+
if (!templateConfig) {
63+
throw new Error(`Template not found for fileName: ${fileNameFromPath || responses.outputFile || 'unknown'}`)
64+
}
65+
66+
const templatePath = path.join(process.cwd(), 'file-templates', templateConfig.template)
67+
const template = await readFile(templatePath, 'utf-8')
68+
69+
let generatedContent = template
70+
const isJsonTemplate = templateConfig.template.toLowerCase().endsWith('.json')
71+
72+
const escapeForJson = (value: string) => {
73+
const escaped = JSON.stringify(value)
74+
return escaped.slice(1, -1)
75+
}
76+
77+
const replaceVariable = (key: keyof WizardResponses, fallback = 'Not specified') => {
78+
const placeholder = `{{${String(key)}}}`
79+
80+
if (!generatedContent.includes(placeholder)) {
81+
return
82+
}
83+
84+
const value = responses[key]
85+
86+
if (value === null || value === undefined || value === '') {
87+
const replacement = isJsonTemplate ? escapeForJson(fallback) : fallback
88+
generatedContent = generatedContent.replace(placeholder, replacement)
89+
} else {
90+
const replacementValue = String(value)
91+
const replacement = isJsonTemplate ? escapeForJson(replacementValue) : replacementValue
92+
generatedContent = generatedContent.replace(placeholder, replacement)
93+
}
94+
}
95+
96+
replaceVariable('stackSelection')
97+
replaceVariable('tooling')
98+
replaceVariable('language')
99+
replaceVariable('projectPriority')
100+
replaceVariable('codeStyle')
101+
replaceVariable('variableNaming')
102+
replaceVariable('fileNaming')
103+
replaceVariable('componentNaming')
104+
replaceVariable('exports')
105+
replaceVariable('comments')
106+
replaceVariable('collaboration')
107+
replaceVariable('fileStructure')
108+
replaceVariable('styling')
109+
replaceVariable('stateManagement')
110+
replaceVariable('apiLayer')
111+
replaceVariable('folders')
112+
replaceVariable('testingUT')
113+
replaceVariable('testingE2E')
114+
replaceVariable('dataFetching')
115+
replaceVariable('reactPerf')
116+
replaceVariable('auth')
117+
replaceVariable('validation')
118+
replaceVariable('logging')
119+
replaceVariable('commitStyle')
120+
replaceVariable('prRules')
121+
replaceVariable('outputFile')
122+
123+
const replaceStaticPlaceholder = (placeholderKey: string, value: string) => {
124+
const placeholder = `{{${placeholderKey}}}`
125+
if (!generatedContent.includes(placeholder)) {
126+
return
127+
}
128+
const replacement = isJsonTemplate ? escapeForJson(value) : value
129+
generatedContent = generatedContent.replace(placeholder, replacement)
130+
}
131+
132+
const stackGuidanceSlug = responses.stackSelection || framework
133+
const stackGuidance = getStackGuidance(stackGuidanceSlug)
134+
replaceStaticPlaceholder('stackGuidance', stackGuidance)
135+
136+
return {
137+
content: generatedContent,
138+
fileName: templateConfig.outputFileName,
139+
isJson: isJsonTemplate,
140+
}
141+
}
142+

0 commit comments

Comments
 (0)