wiggum-cli 0.1.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/LICENSE +21 -0
- package/README.md +341 -0
- package/bin/ralph.js +8 -0
- package/dist/ai/enhancer.d.ts +100 -0
- package/dist/ai/enhancer.d.ts.map +1 -0
- package/dist/ai/enhancer.js +233 -0
- package/dist/ai/enhancer.js.map +1 -0
- package/dist/ai/index.d.ts +8 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +11 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/prompts.d.ts +26 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +201 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/providers.d.ts +35 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +104 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/monitor.d.ts +17 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +342 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/new.d.ts +19 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +272 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/run.d.ts +16 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +175 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/generator/config.d.ts +59 -0
- package/dist/generator/config.d.ts.map +1 -0
- package/dist/generator/config.js +68 -0
- package/dist/generator/config.js.map +1 -0
- package/dist/generator/index.d.ts +64 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +147 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/templates.d.ts +70 -0
- package/dist/generator/templates.d.ts.map +1 -0
- package/dist/generator/templates.js +296 -0
- package/dist/generator/templates.js.map +1 -0
- package/dist/generator/writer.d.ts +93 -0
- package/dist/generator/writer.d.ts.map +1 -0
- package/dist/generator/writer.js +213 -0
- package/dist/generator/writer.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/detectors/core/framework.d.ts +11 -0
- package/dist/scanner/detectors/core/framework.d.ts.map +1 -0
- package/dist/scanner/detectors/core/framework.js +275 -0
- package/dist/scanner/detectors/core/framework.js.map +1 -0
- package/dist/scanner/detectors/core/packageManager.d.ts +11 -0
- package/dist/scanner/detectors/core/packageManager.d.ts.map +1 -0
- package/dist/scanner/detectors/core/packageManager.js +74 -0
- package/dist/scanner/detectors/core/packageManager.js.map +1 -0
- package/dist/scanner/detectors/core/styling.d.ts +12 -0
- package/dist/scanner/detectors/core/styling.d.ts.map +1 -0
- package/dist/scanner/detectors/core/styling.js +230 -0
- package/dist/scanner/detectors/core/styling.js.map +1 -0
- package/dist/scanner/detectors/core/testing.d.ts +12 -0
- package/dist/scanner/detectors/core/testing.d.ts.map +1 -0
- package/dist/scanner/detectors/core/testing.js +190 -0
- package/dist/scanner/detectors/core/testing.js.map +1 -0
- package/dist/scanner/detectors/data/api.d.ts +12 -0
- package/dist/scanner/detectors/data/api.d.ts.map +1 -0
- package/dist/scanner/detectors/data/api.js +261 -0
- package/dist/scanner/detectors/data/api.js.map +1 -0
- package/dist/scanner/detectors/data/database.d.ts +12 -0
- package/dist/scanner/detectors/data/database.d.ts.map +1 -0
- package/dist/scanner/detectors/data/database.js +213 -0
- package/dist/scanner/detectors/data/database.js.map +1 -0
- package/dist/scanner/detectors/data/orm.d.ts +12 -0
- package/dist/scanner/detectors/data/orm.d.ts.map +1 -0
- package/dist/scanner/detectors/data/orm.js +160 -0
- package/dist/scanner/detectors/data/orm.js.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts +12 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.js +211 -0
- package/dist/scanner/detectors/frontend/formHandling.js.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts +12 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.js +221 -0
- package/dist/scanner/detectors/frontend/stateManagement.js.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts +12 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.js +285 -0
- package/dist/scanner/detectors/frontend/uiComponents.js.map +1 -0
- package/dist/scanner/detectors/infra/deployment.d.ts +12 -0
- package/dist/scanner/detectors/infra/deployment.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/deployment.js +301 -0
- package/dist/scanner/detectors/infra/deployment.js.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts +12 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.js +219 -0
- package/dist/scanner/detectors/infra/monorepo.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts +12 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.js +154 -0
- package/dist/scanner/detectors/mcp/mcpProject.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts +17 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.js +193 -0
- package/dist/scanner/detectors/mcp/mcpServers.js.map +1 -0
- package/dist/scanner/detectors/services/analytics.d.ts +12 -0
- package/dist/scanner/detectors/services/analytics.d.ts.map +1 -0
- package/dist/scanner/detectors/services/analytics.js +236 -0
- package/dist/scanner/detectors/services/analytics.js.map +1 -0
- package/dist/scanner/detectors/services/auth.d.ts +12 -0
- package/dist/scanner/detectors/services/auth.d.ts.map +1 -0
- package/dist/scanner/detectors/services/auth.js +217 -0
- package/dist/scanner/detectors/services/auth.js.map +1 -0
- package/dist/scanner/detectors/services/email.d.ts +12 -0
- package/dist/scanner/detectors/services/email.d.ts.map +1 -0
- package/dist/scanner/detectors/services/email.js +211 -0
- package/dist/scanner/detectors/services/email.js.map +1 -0
- package/dist/scanner/detectors/services/payments.d.ts +12 -0
- package/dist/scanner/detectors/services/payments.d.ts.map +1 -0
- package/dist/scanner/detectors/services/payments.js +185 -0
- package/dist/scanner/detectors/services/payments.js.map +1 -0
- package/dist/scanner/detectors/utils.d.ts +160 -0
- package/dist/scanner/detectors/utils.d.ts.map +1 -0
- package/dist/scanner/detectors/utils.js +222 -0
- package/dist/scanner/detectors/utils.js.map +1 -0
- package/dist/scanner/index.d.ts +42 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +282 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/registry.d.ts +43 -0
- package/dist/scanner/registry.d.ts.map +1 -0
- package/dist/scanner/registry.js +243 -0
- package/dist/scanner/registry.js.map +1 -0
- package/dist/scanner/types.d.ts +112 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +6 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/templates/config/ralph.config.js.tmpl +38 -0
- package/dist/templates/guides/AGENTS.md.tmpl +100 -0
- package/dist/templates/guides/FRONTEND.md.tmpl +523 -0
- package/dist/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/dist/templates/guides/SECURITY.md.tmpl +100 -0
- package/dist/templates/prompts/PROMPT.md.tmpl +77 -0
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/dist/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/dist/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/dist/templates/root/.gitignore.tmpl +5 -0
- package/dist/templates/root/LEARNINGS.md.tmpl +24 -0
- package/dist/templates/root/README.md.tmpl +61 -0
- package/dist/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/dist/templates/scripts/loop.sh.tmpl +59 -0
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/dist/templates/specs/README.md.tmpl +57 -0
- package/dist/templates/specs/_example.md.tmpl +71 -0
- package/dist/utils/config.d.ts +95 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +148 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/header.d.ts +5 -0
- package/dist/utils/header.d.ts.map +1 -0
- package/dist/utils/header.js +15 -0
- package/dist/utils/header.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +44 -0
- package/src/ai/enhancer.ts +350 -0
- package/src/ai/index.ts +38 -0
- package/src/ai/prompts.ts +217 -0
- package/src/ai/providers.ts +136 -0
- package/src/cli.ts +255 -0
- package/src/commands/init.ts +149 -0
- package/src/commands/monitor.ts +412 -0
- package/src/commands/new.ts +312 -0
- package/src/commands/run.ts +214 -0
- package/src/generator/config.ts +116 -0
- package/src/generator/index.ts +227 -0
- package/src/generator/templates.ts +412 -0
- package/src/generator/writer.ts +293 -0
- package/src/index.ts +41 -0
- package/src/scanner/detectors/core/framework.ts +332 -0
- package/src/scanner/detectors/core/packageManager.ts +91 -0
- package/src/scanner/detectors/core/styling.ts +261 -0
- package/src/scanner/detectors/core/testing.ts +221 -0
- package/src/scanner/detectors/data/api.ts +303 -0
- package/src/scanner/detectors/data/database.ts +245 -0
- package/src/scanner/detectors/data/orm.ts +180 -0
- package/src/scanner/detectors/frontend/formHandling.ts +244 -0
- package/src/scanner/detectors/frontend/stateManagement.ts +261 -0
- package/src/scanner/detectors/frontend/uiComponents.ts +328 -0
- package/src/scanner/detectors/infra/deployment.ts +343 -0
- package/src/scanner/detectors/infra/monorepo.ts +251 -0
- package/src/scanner/detectors/mcp/mcpProject.ts +176 -0
- package/src/scanner/detectors/mcp/mcpServers.ts +237 -0
- package/src/scanner/detectors/services/analytics.ts +273 -0
- package/src/scanner/detectors/services/auth.ts +254 -0
- package/src/scanner/detectors/services/email.ts +244 -0
- package/src/scanner/detectors/services/payments.ts +213 -0
- package/src/scanner/detectors/utils.ts +251 -0
- package/src/scanner/index.ts +354 -0
- package/src/scanner/registry.ts +301 -0
- package/src/scanner/types.ts +152 -0
- package/src/templates/config/ralph.config.js.tmpl +38 -0
- package/src/templates/guides/AGENTS.md.tmpl +100 -0
- package/src/templates/guides/FRONTEND.md.tmpl +523 -0
- package/src/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/src/templates/guides/SECURITY.md.tmpl +100 -0
- package/src/templates/prompts/PROMPT.md.tmpl +77 -0
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/src/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/src/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/src/templates/root/.gitignore.tmpl +5 -0
- package/src/templates/root/LEARNINGS.md.tmpl +24 -0
- package/src/templates/root/README.md.tmpl +61 -0
- package/src/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/src/templates/scripts/loop.sh.tmpl +59 -0
- package/src/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/src/templates/specs/README.md.tmpl +57 -0
- package/src/templates/specs/_example.md.tmpl +71 -0
- package/src/utils/config.ts +221 -0
- package/src/utils/header.ts +15 -0
- package/src/utils/logger.ts +28 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator Orchestrator
|
|
3
|
+
* Main entry point for generating ralph configuration files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import type { ScanResult } from '../scanner/types.js';
|
|
8
|
+
import {
|
|
9
|
+
extractVariables,
|
|
10
|
+
processAllTemplates,
|
|
11
|
+
getTemplatesDir,
|
|
12
|
+
type TemplateVariables,
|
|
13
|
+
} from './templates.js';
|
|
14
|
+
import { generateConfig, generateConfigFile, type RalphConfig } from './config.js';
|
|
15
|
+
import {
|
|
16
|
+
writeFiles,
|
|
17
|
+
createDirectoryStructure,
|
|
18
|
+
mapTemplateOutputPaths,
|
|
19
|
+
formatWriteSummary,
|
|
20
|
+
type WriteOptions,
|
|
21
|
+
type WriteSummary,
|
|
22
|
+
DEFAULT_WRITE_OPTIONS,
|
|
23
|
+
} from './writer.js';
|
|
24
|
+
|
|
25
|
+
// Re-export types and utilities
|
|
26
|
+
export type { TemplateVariables } from './templates.js';
|
|
27
|
+
export type { RalphConfig } from './config.js';
|
|
28
|
+
export type { WriteOptions, WriteSummary, WriteResult } from './writer.js';
|
|
29
|
+
export {
|
|
30
|
+
extractVariables,
|
|
31
|
+
processTemplate,
|
|
32
|
+
processTemplateFile,
|
|
33
|
+
getTemplatesDir,
|
|
34
|
+
} from './templates.js';
|
|
35
|
+
export { generateConfig, generateConfigFile } from './config.js';
|
|
36
|
+
export { writeFileWithOptions, formatWriteSummary, DEFAULT_WRITE_OPTIONS } from './writer.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Options for the generator
|
|
40
|
+
*/
|
|
41
|
+
export interface GeneratorOptions {
|
|
42
|
+
/** Custom variables to add/override */
|
|
43
|
+
customVariables?: Record<string, string>;
|
|
44
|
+
/** How to handle existing files */
|
|
45
|
+
existingFiles?: 'backup' | 'skip' | 'overwrite';
|
|
46
|
+
/** Whether to generate config file */
|
|
47
|
+
generateConfig?: boolean;
|
|
48
|
+
/** Verbose output */
|
|
49
|
+
verbose?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default generator options
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_GENERATOR_OPTIONS: GeneratorOptions = {
|
|
56
|
+
customVariables: {},
|
|
57
|
+
existingFiles: 'backup',
|
|
58
|
+
generateConfig: true,
|
|
59
|
+
verbose: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Result of generation
|
|
64
|
+
*/
|
|
65
|
+
export interface GenerationResult {
|
|
66
|
+
/** Whether generation was successful */
|
|
67
|
+
success: boolean;
|
|
68
|
+
/** Template variables used */
|
|
69
|
+
variables: TemplateVariables;
|
|
70
|
+
/** Generated config */
|
|
71
|
+
config?: RalphConfig;
|
|
72
|
+
/** Write summary */
|
|
73
|
+
writeSummary: WriteSummary;
|
|
74
|
+
/** Errors encountered */
|
|
75
|
+
errors: string[];
|
|
76
|
+
/** Time taken in milliseconds */
|
|
77
|
+
generationTime: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Main Generator class
|
|
82
|
+
*/
|
|
83
|
+
export class Generator {
|
|
84
|
+
private options: GeneratorOptions;
|
|
85
|
+
|
|
86
|
+
constructor(options: GeneratorOptions = {}) {
|
|
87
|
+
this.options = { ...DEFAULT_GENERATOR_OPTIONS, ...options };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate all ralph files from scan result
|
|
92
|
+
*/
|
|
93
|
+
async generate(scanResult: ScanResult): Promise<GenerationResult> {
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
const errors: string[] = [];
|
|
96
|
+
|
|
97
|
+
// Extract variables from scan result
|
|
98
|
+
const variables = extractVariables(scanResult, this.options.customVariables || {});
|
|
99
|
+
|
|
100
|
+
// Get templates directory
|
|
101
|
+
const templatesDir = getTemplatesDir();
|
|
102
|
+
|
|
103
|
+
// Process all templates
|
|
104
|
+
let processedTemplates: Map<string, string>;
|
|
105
|
+
try {
|
|
106
|
+
processedTemplates = await processAllTemplates(templatesDir, variables);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
errors.push(`Failed to process templates: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
variables,
|
|
112
|
+
writeSummary: {
|
|
113
|
+
total: 0,
|
|
114
|
+
created: 0,
|
|
115
|
+
backedUp: 0,
|
|
116
|
+
skipped: 0,
|
|
117
|
+
overwritten: 0,
|
|
118
|
+
errors: 1,
|
|
119
|
+
results: [],
|
|
120
|
+
},
|
|
121
|
+
errors,
|
|
122
|
+
generationTime: Date.now() - startTime,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Generate config if requested
|
|
127
|
+
let config: RalphConfig | undefined;
|
|
128
|
+
if (this.options.generateConfig) {
|
|
129
|
+
config = generateConfig(scanResult, this.options.customVariables || {});
|
|
130
|
+
const configContent = generateConfigFile(config);
|
|
131
|
+
processedTemplates.set('config/ralph.config.js', configContent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Map template outputs to final paths
|
|
135
|
+
const mappedOutputs = mapTemplateOutputPaths(processedTemplates);
|
|
136
|
+
|
|
137
|
+
// Create directory structure
|
|
138
|
+
try {
|
|
139
|
+
await createDirectoryStructure(scanResult.projectRoot);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
errors.push(`Failed to create directory structure: ${error instanceof Error ? error.message : String(error)}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Write all files
|
|
145
|
+
const writeOptions: WriteOptions = {
|
|
146
|
+
existingFiles: this.options.existingFiles || 'backup',
|
|
147
|
+
createBackups: this.options.existingFiles === 'backup',
|
|
148
|
+
verbose: this.options.verbose || false,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const writeSummary = await writeFiles(mappedOutputs, scanResult.projectRoot, writeOptions);
|
|
152
|
+
|
|
153
|
+
// Add any write errors to errors list
|
|
154
|
+
for (const result of writeSummary.results) {
|
|
155
|
+
if (result.action === 'error' && result.error) {
|
|
156
|
+
errors.push(`Failed to write ${result.path}: ${result.error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const generationTime = Date.now() - startTime;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: errors.length === 0,
|
|
164
|
+
variables,
|
|
165
|
+
config,
|
|
166
|
+
writeSummary,
|
|
167
|
+
errors,
|
|
168
|
+
generationTime,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Convenience function to generate ralph files
|
|
175
|
+
*/
|
|
176
|
+
export async function generateRalph(
|
|
177
|
+
scanResult: ScanResult,
|
|
178
|
+
options: GeneratorOptions = {}
|
|
179
|
+
): Promise<GenerationResult> {
|
|
180
|
+
const generator = new Generator(options);
|
|
181
|
+
return generator.generate(scanResult);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Format generation result for display
|
|
186
|
+
*/
|
|
187
|
+
export function formatGenerationResult(result: GenerationResult): string {
|
|
188
|
+
const lines: string[] = [];
|
|
189
|
+
|
|
190
|
+
if (result.success) {
|
|
191
|
+
lines.push('Generation completed successfully!');
|
|
192
|
+
} else {
|
|
193
|
+
lines.push('Generation completed with errors.');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
lines.push(`Time: ${result.generationTime}ms`);
|
|
197
|
+
lines.push('');
|
|
198
|
+
|
|
199
|
+
// Show stack info
|
|
200
|
+
lines.push('Detected Stack:');
|
|
201
|
+
lines.push(` Framework: ${result.variables.framework}${result.variables.frameworkVersion ? ' ' + result.variables.frameworkVersion : ''}`);
|
|
202
|
+
lines.push(` Package Manager: ${result.variables.packageManager}`);
|
|
203
|
+
if (result.variables.unitTest !== 'none') {
|
|
204
|
+
lines.push(` Unit Testing: ${result.variables.unitTest}`);
|
|
205
|
+
}
|
|
206
|
+
if (result.variables.e2eTest !== 'none') {
|
|
207
|
+
lines.push(` E2E Testing: ${result.variables.e2eTest}`);
|
|
208
|
+
}
|
|
209
|
+
if (result.variables.styling !== 'css') {
|
|
210
|
+
lines.push(` Styling: ${result.variables.styling}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push('');
|
|
213
|
+
|
|
214
|
+
// Show write summary
|
|
215
|
+
lines.push(formatWriteSummary(result.writeSummary));
|
|
216
|
+
|
|
217
|
+
// Show errors
|
|
218
|
+
if (result.errors.length > 0) {
|
|
219
|
+
lines.push('');
|
|
220
|
+
lines.push('Errors:');
|
|
221
|
+
for (const error of result.errors) {
|
|
222
|
+
lines.push(` - ${error}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Processing System
|
|
3
|
+
* Reads template files with {{variable}} placeholders and substitutes values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import type { ScanResult, DetectedStack } from '../scanner/types.js';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Template variables available for substitution
|
|
16
|
+
*/
|
|
17
|
+
export interface TemplateVariables {
|
|
18
|
+
// Project info
|
|
19
|
+
projectName: string;
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
|
|
22
|
+
// Framework info
|
|
23
|
+
framework: string;
|
|
24
|
+
frameworkVersion: string;
|
|
25
|
+
frameworkVariant: string;
|
|
26
|
+
|
|
27
|
+
// Package manager
|
|
28
|
+
packageManager: string;
|
|
29
|
+
packageManagerVersion: string;
|
|
30
|
+
|
|
31
|
+
// Testing
|
|
32
|
+
unitTest: string;
|
|
33
|
+
unitTestVersion: string;
|
|
34
|
+
e2eTest: string;
|
|
35
|
+
e2eTestVersion: string;
|
|
36
|
+
|
|
37
|
+
// Styling
|
|
38
|
+
styling: string;
|
|
39
|
+
stylingVersion: string;
|
|
40
|
+
stylingVariant: string;
|
|
41
|
+
|
|
42
|
+
// Commands (derived from package manager)
|
|
43
|
+
devCommand: string;
|
|
44
|
+
buildCommand: string;
|
|
45
|
+
testCommand: string;
|
|
46
|
+
lintCommand: string;
|
|
47
|
+
typecheckCommand: string;
|
|
48
|
+
formatCommand: string;
|
|
49
|
+
|
|
50
|
+
// Paths
|
|
51
|
+
appDir: string;
|
|
52
|
+
|
|
53
|
+
// Custom variables
|
|
54
|
+
[key: string]: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default template variables
|
|
59
|
+
*/
|
|
60
|
+
const DEFAULT_VARIABLES: Partial<TemplateVariables> = {
|
|
61
|
+
framework: 'unknown',
|
|
62
|
+
frameworkVersion: '',
|
|
63
|
+
frameworkVariant: '',
|
|
64
|
+
packageManager: 'npm',
|
|
65
|
+
packageManagerVersion: '',
|
|
66
|
+
unitTest: 'none',
|
|
67
|
+
unitTestVersion: '',
|
|
68
|
+
e2eTest: 'none',
|
|
69
|
+
e2eTestVersion: '',
|
|
70
|
+
styling: 'css',
|
|
71
|
+
stylingVersion: '',
|
|
72
|
+
stylingVariant: '',
|
|
73
|
+
appDir: 'app',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Derive commands from package manager
|
|
78
|
+
*/
|
|
79
|
+
function deriveCommands(packageManager: string): Pick<
|
|
80
|
+
TemplateVariables,
|
|
81
|
+
'devCommand' | 'buildCommand' | 'testCommand' | 'lintCommand' | 'typecheckCommand' | 'formatCommand'
|
|
82
|
+
> {
|
|
83
|
+
const pm = packageManager.toLowerCase();
|
|
84
|
+
const run = pm === 'npm' ? 'npm run' : pm;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
devCommand: `${run} dev`,
|
|
88
|
+
buildCommand: `${run} build`,
|
|
89
|
+
testCommand: pm === 'npm' ? 'npm test' : `${pm} test`,
|
|
90
|
+
lintCommand: `${run} lint`,
|
|
91
|
+
typecheckCommand: pm === 'npm' ? 'npx tsc --noEmit' : `${pm} tsc --noEmit`,
|
|
92
|
+
formatCommand: pm === 'npm' ? 'npx prettier --write .' : `${pm} prettier --write .`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extract template variables from scan result
|
|
98
|
+
*/
|
|
99
|
+
export function extractVariables(scanResult: ScanResult, customVars: Record<string, string> = {}): TemplateVariables {
|
|
100
|
+
const { stack, projectRoot } = scanResult;
|
|
101
|
+
|
|
102
|
+
// Extract project name from path
|
|
103
|
+
const projectName = projectRoot.split('/').pop() || 'project';
|
|
104
|
+
|
|
105
|
+
// Extract framework info
|
|
106
|
+
const framework = stack.framework?.name || DEFAULT_VARIABLES.framework!;
|
|
107
|
+
const frameworkVersion = stack.framework?.version || DEFAULT_VARIABLES.frameworkVersion!;
|
|
108
|
+
const frameworkVariant = stack.framework?.variant || DEFAULT_VARIABLES.frameworkVariant!;
|
|
109
|
+
|
|
110
|
+
// Extract package manager info
|
|
111
|
+
const packageManager = stack.packageManager?.name || DEFAULT_VARIABLES.packageManager!;
|
|
112
|
+
const packageManagerVersion = stack.packageManager?.version || DEFAULT_VARIABLES.packageManagerVersion!;
|
|
113
|
+
|
|
114
|
+
// Extract testing info
|
|
115
|
+
const unitTest = stack.testing?.unit?.name || DEFAULT_VARIABLES.unitTest!;
|
|
116
|
+
const unitTestVersion = stack.testing?.unit?.version || DEFAULT_VARIABLES.unitTestVersion!;
|
|
117
|
+
const e2eTest = stack.testing?.e2e?.name || DEFAULT_VARIABLES.e2eTest!;
|
|
118
|
+
const e2eTestVersion = stack.testing?.e2e?.version || DEFAULT_VARIABLES.e2eTestVersion!;
|
|
119
|
+
|
|
120
|
+
// Extract styling info
|
|
121
|
+
const styling = stack.styling?.name || DEFAULT_VARIABLES.styling!;
|
|
122
|
+
const stylingVersion = stack.styling?.version || DEFAULT_VARIABLES.stylingVersion!;
|
|
123
|
+
const stylingVariant = stack.styling?.variant || DEFAULT_VARIABLES.stylingVariant!;
|
|
124
|
+
|
|
125
|
+
// Derive commands
|
|
126
|
+
const commands = deriveCommands(packageManager);
|
|
127
|
+
|
|
128
|
+
// Determine app directory
|
|
129
|
+
const appDir = frameworkVariant === 'app-router' || framework.toLowerCase().includes('next') ? 'app' : 'src';
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
projectName,
|
|
133
|
+
projectRoot,
|
|
134
|
+
framework,
|
|
135
|
+
frameworkVersion,
|
|
136
|
+
frameworkVariant,
|
|
137
|
+
packageManager,
|
|
138
|
+
packageManagerVersion,
|
|
139
|
+
unitTest,
|
|
140
|
+
unitTestVersion,
|
|
141
|
+
e2eTest,
|
|
142
|
+
e2eTestVersion,
|
|
143
|
+
styling,
|
|
144
|
+
stylingVersion,
|
|
145
|
+
stylingVariant,
|
|
146
|
+
appDir,
|
|
147
|
+
...commands,
|
|
148
|
+
...customVars,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if a value is considered "truthy" for template conditionals
|
|
154
|
+
*/
|
|
155
|
+
function isTruthyValue(value: string | undefined): boolean {
|
|
156
|
+
return Boolean(value && value !== 'none' && value !== 'unknown' && value !== '');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Find matching closing tag for nested blocks
|
|
161
|
+
* Returns the index of the matching {{/tag}} or -1 if not found
|
|
162
|
+
*/
|
|
163
|
+
function findMatchingClose(content: string, openTag: string, closeTag: string, startIndex: number = 0): number {
|
|
164
|
+
let depth = 1;
|
|
165
|
+
let i = startIndex;
|
|
166
|
+
|
|
167
|
+
while (i < content.length && depth > 0) {
|
|
168
|
+
const openMatch = content.indexOf(openTag, i);
|
|
169
|
+
const closeMatch = content.indexOf(closeTag, i);
|
|
170
|
+
|
|
171
|
+
if (closeMatch === -1) {
|
|
172
|
+
return -1; // No more closing tags
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (openMatch !== -1 && openMatch < closeMatch) {
|
|
176
|
+
// Found another opening tag before the closing tag
|
|
177
|
+
depth++;
|
|
178
|
+
i = openMatch + openTag.length;
|
|
179
|
+
} else {
|
|
180
|
+
// Found a closing tag
|
|
181
|
+
depth--;
|
|
182
|
+
if (depth === 0) {
|
|
183
|
+
return closeMatch;
|
|
184
|
+
}
|
|
185
|
+
i = closeMatch + closeTag.length;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return -1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Process a single if block (handles nesting via recursion)
|
|
194
|
+
*/
|
|
195
|
+
function processIfBlock(
|
|
196
|
+
template: string,
|
|
197
|
+
variables: TemplateVariables,
|
|
198
|
+
startIndex: number,
|
|
199
|
+
varName: string
|
|
200
|
+
): { result: string; endIndex: number } {
|
|
201
|
+
const openTagEnd = template.indexOf('}}', startIndex) + 2;
|
|
202
|
+
const closeTag = '{{/if}}';
|
|
203
|
+
|
|
204
|
+
// Find the matching close tag (handling nesting)
|
|
205
|
+
let searchStart = openTagEnd;
|
|
206
|
+
let closeIndex = findMatchingClose(template, '{{#if', closeTag, searchStart);
|
|
207
|
+
|
|
208
|
+
if (closeIndex === -1) {
|
|
209
|
+
// No matching close, return as-is
|
|
210
|
+
return { result: template.slice(startIndex, openTagEnd), endIndex: openTagEnd };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const innerContent = template.slice(openTagEnd, closeIndex);
|
|
214
|
+
|
|
215
|
+
// Check for else clause (not inside nested if)
|
|
216
|
+
let elseIndex = -1;
|
|
217
|
+
let depth = 0;
|
|
218
|
+
for (let i = 0; i < innerContent.length; i++) {
|
|
219
|
+
if (innerContent.slice(i, i + 5) === '{{#if') {
|
|
220
|
+
depth++;
|
|
221
|
+
} else if (innerContent.slice(i, i + 7) === '{{/if}}') {
|
|
222
|
+
depth--;
|
|
223
|
+
} else if (depth === 0 && innerContent.slice(i, i + 8) === '{{else}}') {
|
|
224
|
+
elseIndex = i;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const value = variables[varName as keyof TemplateVariables];
|
|
230
|
+
const isTruthy = isTruthyValue(value);
|
|
231
|
+
|
|
232
|
+
let selectedContent: string;
|
|
233
|
+
if (elseIndex !== -1) {
|
|
234
|
+
const ifPart = innerContent.slice(0, elseIndex);
|
|
235
|
+
const elsePart = innerContent.slice(elseIndex + 8);
|
|
236
|
+
selectedContent = isTruthy ? ifPart : elsePart;
|
|
237
|
+
} else {
|
|
238
|
+
selectedContent = isTruthy ? innerContent : '';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { result: selectedContent, endIndex: closeIndex + closeTag.length };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Process conditional blocks in template
|
|
246
|
+
* Supports: {{#if variable}}...{{/if}} and {{#if variable}}...{{else}}...{{/if}}
|
|
247
|
+
* Handles nested conditionals correctly
|
|
248
|
+
*/
|
|
249
|
+
function processConditionals(template: string, variables: TemplateVariables): string {
|
|
250
|
+
let result = '';
|
|
251
|
+
let i = 0;
|
|
252
|
+
|
|
253
|
+
while (i < template.length) {
|
|
254
|
+
// Look for {{#if
|
|
255
|
+
const ifMatch = template.slice(i).match(/^\{\{#if\s+(\w+)\}\}/);
|
|
256
|
+
if (ifMatch) {
|
|
257
|
+
const varName = ifMatch[1];
|
|
258
|
+
const { result: blockResult, endIndex } = processIfBlock(template, variables, i, varName);
|
|
259
|
+
// Recursively process the content for nested conditionals
|
|
260
|
+
result += processConditionals(blockResult, variables);
|
|
261
|
+
i += endIndex - i;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Look for {{#unless
|
|
266
|
+
const unlessMatch = template.slice(i).match(/^\{\{#unless\s+(\w+)\}\}/);
|
|
267
|
+
if (unlessMatch) {
|
|
268
|
+
const varName = unlessMatch[1];
|
|
269
|
+
const openTagEnd = i + unlessMatch[0].length;
|
|
270
|
+
const closeTag = '{{/unless}}';
|
|
271
|
+
const closeIndex = template.indexOf(closeTag, openTagEnd);
|
|
272
|
+
|
|
273
|
+
if (closeIndex !== -1) {
|
|
274
|
+
const innerContent = template.slice(openTagEnd, closeIndex);
|
|
275
|
+
const value = variables[varName as keyof TemplateVariables];
|
|
276
|
+
const isTruthy = isTruthyValue(value);
|
|
277
|
+
|
|
278
|
+
if (!isTruthy) {
|
|
279
|
+
result += processConditionals(innerContent, variables);
|
|
280
|
+
}
|
|
281
|
+
i = closeIndex + closeTag.length;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Regular character, copy it
|
|
287
|
+
result += template[i];
|
|
288
|
+
i++;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Process variable substitution in template
|
|
296
|
+
* Supports: {{variable}} and {{variable || default}}
|
|
297
|
+
*/
|
|
298
|
+
function processVariables(template: string, variables: TemplateVariables): string {
|
|
299
|
+
// Process variables with defaults
|
|
300
|
+
const defaultRegex = /\{\{(\w+)\s*\|\|\s*([^}]+)\}\}/g;
|
|
301
|
+
let result = template.replace(defaultRegex, (_, varName, defaultValue) => {
|
|
302
|
+
const value = variables[varName as keyof TemplateVariables];
|
|
303
|
+
return value && value !== '' ? value : defaultValue.trim();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Process simple variables
|
|
307
|
+
const varRegex = /\{\{(\w+)\}\}/g;
|
|
308
|
+
result = result.replace(varRegex, (_, varName) => {
|
|
309
|
+
const value = variables[varName as keyof TemplateVariables];
|
|
310
|
+
return value !== undefined ? value : '';
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Process a template string with variable substitution
|
|
318
|
+
*/
|
|
319
|
+
export function processTemplate(template: string, variables: TemplateVariables): string {
|
|
320
|
+
// First process conditionals
|
|
321
|
+
let result = processConditionals(template, variables);
|
|
322
|
+
|
|
323
|
+
// Then substitute variables
|
|
324
|
+
result = processVariables(result, variables);
|
|
325
|
+
|
|
326
|
+
// Clean up multiple blank lines
|
|
327
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
328
|
+
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Read and process a template file
|
|
334
|
+
*/
|
|
335
|
+
export async function processTemplateFile(templatePath: string, variables: TemplateVariables): Promise<string> {
|
|
336
|
+
const template = await readFile(templatePath, 'utf-8');
|
|
337
|
+
return processTemplate(template, variables);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get the templates directory path
|
|
342
|
+
*/
|
|
343
|
+
export function getTemplatesDir(): string {
|
|
344
|
+
// In development (src), templates are at src/templates
|
|
345
|
+
// In production (dist), templates are at dist/templates
|
|
346
|
+
const srcTemplates = join(__dirname, '..', 'templates');
|
|
347
|
+
return srcTemplates;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Template file info
|
|
352
|
+
*/
|
|
353
|
+
export interface TemplateFile {
|
|
354
|
+
/** Source template path */
|
|
355
|
+
sourcePath: string;
|
|
356
|
+
/** Relative path from templates directory */
|
|
357
|
+
relativePath: string;
|
|
358
|
+
/** Output path (without .tmpl extension) */
|
|
359
|
+
outputPath: string;
|
|
360
|
+
/** Category (prompts, guides, specs, config) */
|
|
361
|
+
category: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Discover all template files
|
|
366
|
+
*/
|
|
367
|
+
export async function discoverTemplates(templatesDir: string): Promise<TemplateFile[]> {
|
|
368
|
+
const templates: TemplateFile[] = [];
|
|
369
|
+
|
|
370
|
+
async function scanDir(dir: string, category: string = ''): Promise<void> {
|
|
371
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
372
|
+
|
|
373
|
+
for (const entry of entries) {
|
|
374
|
+
const fullPath = join(dir, entry.name);
|
|
375
|
+
|
|
376
|
+
if (entry.isDirectory()) {
|
|
377
|
+
await scanDir(fullPath, entry.name);
|
|
378
|
+
} else if (entry.name.endsWith('.tmpl')) {
|
|
379
|
+
const relativePath = fullPath.slice(templatesDir.length + 1);
|
|
380
|
+
const outputPath = relativePath.replace(/\.tmpl$/, '');
|
|
381
|
+
|
|
382
|
+
templates.push({
|
|
383
|
+
sourcePath: fullPath,
|
|
384
|
+
relativePath,
|
|
385
|
+
outputPath,
|
|
386
|
+
category,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await scanDir(templatesDir);
|
|
393
|
+
return templates;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Process all templates and return their contents
|
|
398
|
+
*/
|
|
399
|
+
export async function processAllTemplates(
|
|
400
|
+
templatesDir: string,
|
|
401
|
+
variables: TemplateVariables
|
|
402
|
+
): Promise<Map<string, string>> {
|
|
403
|
+
const templates = await discoverTemplates(templatesDir);
|
|
404
|
+
const processed = new Map<string, string>();
|
|
405
|
+
|
|
406
|
+
for (const template of templates) {
|
|
407
|
+
const content = await processTemplateFile(template.sourcePath, variables);
|
|
408
|
+
processed.set(template.outputPath, content);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return processed;
|
|
412
|
+
}
|