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,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Writer
|
|
3
|
+
* Creates .ralph directory structure and writes processed templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdir, writeFile, readFile, copyFile, stat, rename, readdir } from 'node:fs/promises';
|
|
7
|
+
import { join, dirname, basename } from 'node:path';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for file writing
|
|
12
|
+
*/
|
|
13
|
+
export interface WriteOptions {
|
|
14
|
+
/** How to handle existing files: 'backup', 'skip', 'overwrite' */
|
|
15
|
+
existingFiles: 'backup' | 'skip' | 'overwrite';
|
|
16
|
+
/** Whether to create backup files */
|
|
17
|
+
createBackups: boolean;
|
|
18
|
+
/** Verbose output */
|
|
19
|
+
verbose: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default write options
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_WRITE_OPTIONS: WriteOptions = {
|
|
26
|
+
existingFiles: 'backup',
|
|
27
|
+
createBackups: true,
|
|
28
|
+
verbose: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Result of a write operation
|
|
33
|
+
*/
|
|
34
|
+
export interface WriteResult {
|
|
35
|
+
/** Path that was written */
|
|
36
|
+
path: string;
|
|
37
|
+
/** Whether the write was successful */
|
|
38
|
+
success: boolean;
|
|
39
|
+
/** Action taken: created, backed_up, skipped, overwritten */
|
|
40
|
+
action: 'created' | 'backed_up' | 'skipped' | 'overwritten' | 'error';
|
|
41
|
+
/** Error message if any */
|
|
42
|
+
error?: string;
|
|
43
|
+
/** Backup path if created */
|
|
44
|
+
backupPath?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Summary of all write operations
|
|
49
|
+
*/
|
|
50
|
+
export interface WriteSummary {
|
|
51
|
+
/** Total files processed */
|
|
52
|
+
total: number;
|
|
53
|
+
/** Files created (new) */
|
|
54
|
+
created: number;
|
|
55
|
+
/** Files backed up and replaced */
|
|
56
|
+
backedUp: number;
|
|
57
|
+
/** Files skipped (already existed) */
|
|
58
|
+
skipped: number;
|
|
59
|
+
/** Files overwritten */
|
|
60
|
+
overwritten: number;
|
|
61
|
+
/** Files that failed */
|
|
62
|
+
errors: number;
|
|
63
|
+
/** Individual results */
|
|
64
|
+
results: WriteResult[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a directory and all parent directories
|
|
69
|
+
*/
|
|
70
|
+
export async function ensureDir(dirPath: string): Promise<void> {
|
|
71
|
+
await mkdir(dirPath, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a file exists
|
|
76
|
+
*/
|
|
77
|
+
export async function fileExists(filePath: string): Promise<boolean> {
|
|
78
|
+
try {
|
|
79
|
+
await stat(filePath);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create a backup of an existing file
|
|
88
|
+
*/
|
|
89
|
+
export async function backupFile(filePath: string): Promise<string> {
|
|
90
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
91
|
+
const dir = dirname(filePath);
|
|
92
|
+
const name = basename(filePath);
|
|
93
|
+
const backupPath = join(dir, `.${name}.backup-${timestamp}`);
|
|
94
|
+
|
|
95
|
+
await copyFile(filePath, backupPath);
|
|
96
|
+
return backupPath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Write a single file with options
|
|
101
|
+
*/
|
|
102
|
+
export async function writeFileWithOptions(
|
|
103
|
+
filePath: string,
|
|
104
|
+
content: string,
|
|
105
|
+
options: WriteOptions = DEFAULT_WRITE_OPTIONS
|
|
106
|
+
): Promise<WriteResult> {
|
|
107
|
+
const result: WriteResult = {
|
|
108
|
+
path: filePath,
|
|
109
|
+
success: false,
|
|
110
|
+
action: 'created',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Ensure directory exists
|
|
115
|
+
await ensureDir(dirname(filePath));
|
|
116
|
+
|
|
117
|
+
// Check if file exists
|
|
118
|
+
const exists = await fileExists(filePath);
|
|
119
|
+
|
|
120
|
+
if (exists) {
|
|
121
|
+
switch (options.existingFiles) {
|
|
122
|
+
case 'skip':
|
|
123
|
+
result.action = 'skipped';
|
|
124
|
+
result.success = true;
|
|
125
|
+
return result;
|
|
126
|
+
|
|
127
|
+
case 'backup':
|
|
128
|
+
if (options.createBackups) {
|
|
129
|
+
result.backupPath = await backupFile(filePath);
|
|
130
|
+
result.action = 'backed_up';
|
|
131
|
+
} else {
|
|
132
|
+
result.action = 'overwritten';
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'overwrite':
|
|
137
|
+
result.action = 'overwritten';
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Write the file
|
|
143
|
+
await writeFile(filePath, content, 'utf-8');
|
|
144
|
+
result.success = true;
|
|
145
|
+
|
|
146
|
+
if (options.verbose) {
|
|
147
|
+
console.log(` ${result.action}: ${filePath}`);
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
result.action = 'error';
|
|
151
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
152
|
+
result.success = false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write multiple files from a Map
|
|
160
|
+
*/
|
|
161
|
+
export async function writeFiles(
|
|
162
|
+
files: Map<string, string>,
|
|
163
|
+
baseDir: string,
|
|
164
|
+
options: WriteOptions = DEFAULT_WRITE_OPTIONS
|
|
165
|
+
): Promise<WriteSummary> {
|
|
166
|
+
const summary: WriteSummary = {
|
|
167
|
+
total: files.size,
|
|
168
|
+
created: 0,
|
|
169
|
+
backedUp: 0,
|
|
170
|
+
skipped: 0,
|
|
171
|
+
overwritten: 0,
|
|
172
|
+
errors: 0,
|
|
173
|
+
results: [],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
for (const [relativePath, content] of files) {
|
|
177
|
+
const fullPath = join(baseDir, relativePath);
|
|
178
|
+
const result = await writeFileWithOptions(fullPath, content, options);
|
|
179
|
+
summary.results.push(result);
|
|
180
|
+
|
|
181
|
+
switch (result.action) {
|
|
182
|
+
case 'created':
|
|
183
|
+
summary.created++;
|
|
184
|
+
break;
|
|
185
|
+
case 'backed_up':
|
|
186
|
+
summary.backedUp++;
|
|
187
|
+
break;
|
|
188
|
+
case 'skipped':
|
|
189
|
+
summary.skipped++;
|
|
190
|
+
break;
|
|
191
|
+
case 'overwritten':
|
|
192
|
+
summary.overwritten++;
|
|
193
|
+
break;
|
|
194
|
+
case 'error':
|
|
195
|
+
summary.errors++;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return summary;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Directory structure for .ralph
|
|
205
|
+
*/
|
|
206
|
+
export const RALPH_DIRECTORY_STRUCTURE = {
|
|
207
|
+
root: '.ralph',
|
|
208
|
+
directories: [
|
|
209
|
+
'.ralph/prompts',
|
|
210
|
+
'.ralph/guides',
|
|
211
|
+
'.ralph/specs',
|
|
212
|
+
'.ralph/scripts',
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create the .ralph directory structure
|
|
218
|
+
*/
|
|
219
|
+
export async function createDirectoryStructure(projectRoot: string): Promise<void> {
|
|
220
|
+
for (const dir of RALPH_DIRECTORY_STRUCTURE.directories) {
|
|
221
|
+
await ensureDir(join(projectRoot, dir));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Map template output paths to their final locations in .ralph
|
|
227
|
+
*/
|
|
228
|
+
export function mapTemplateOutputPaths(templateOutputs: Map<string, string>): Map<string, string> {
|
|
229
|
+
const mapped = new Map<string, string>();
|
|
230
|
+
|
|
231
|
+
for (const [outputPath, content] of templateOutputs) {
|
|
232
|
+
// Map template categories to .ralph structure
|
|
233
|
+
let finalPath: string;
|
|
234
|
+
|
|
235
|
+
if (outputPath.startsWith('prompts/')) {
|
|
236
|
+
finalPath = `.ralph/${outputPath}`;
|
|
237
|
+
} else if (outputPath.startsWith('guides/')) {
|
|
238
|
+
finalPath = `.ralph/${outputPath}`;
|
|
239
|
+
} else if (outputPath.startsWith('specs/')) {
|
|
240
|
+
finalPath = `.ralph/${outputPath}`;
|
|
241
|
+
} else if (outputPath.startsWith('scripts/')) {
|
|
242
|
+
// Scripts go to .ralph/scripts/
|
|
243
|
+
finalPath = `.ralph/${outputPath}`;
|
|
244
|
+
} else if (outputPath.startsWith('config/')) {
|
|
245
|
+
// ralph.config.js goes to project root
|
|
246
|
+
finalPath = outputPath.replace('config/', '');
|
|
247
|
+
} else if (outputPath.startsWith('root/')) {
|
|
248
|
+
// Root files go to .ralph/
|
|
249
|
+
finalPath = `.ralph/${outputPath.replace('root/', '')}`;
|
|
250
|
+
} else {
|
|
251
|
+
// Default: put in .ralph/
|
|
252
|
+
finalPath = `.ralph/${outputPath}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
mapped.set(finalPath, content);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return mapped;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Format write summary for display
|
|
263
|
+
*/
|
|
264
|
+
export function formatWriteSummary(summary: WriteSummary): string {
|
|
265
|
+
const lines: string[] = [];
|
|
266
|
+
|
|
267
|
+
lines.push('Write Summary:');
|
|
268
|
+
lines.push(` Total files: ${summary.total}`);
|
|
269
|
+
lines.push(` Created: ${summary.created}`);
|
|
270
|
+
|
|
271
|
+
if (summary.backedUp > 0) {
|
|
272
|
+
lines.push(` Backed up & updated: ${summary.backedUp}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (summary.skipped > 0) {
|
|
276
|
+
lines.push(` Skipped (existing): ${summary.skipped}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (summary.overwritten > 0) {
|
|
280
|
+
lines.push(` Overwritten: ${summary.overwritten}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (summary.errors > 0) {
|
|
284
|
+
lines.push(` Errors: ${summary.errors}`);
|
|
285
|
+
for (const result of summary.results) {
|
|
286
|
+
if (result.action === 'error') {
|
|
287
|
+
lines.push(` - ${result.path}: ${result.error}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return lines.join('\n');
|
|
293
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createCli } from './cli.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Main entry point for the Ralph CLI
|
|
5
|
+
*/
|
|
6
|
+
export async function main(): Promise<void> {
|
|
7
|
+
const program = createCli();
|
|
8
|
+
await program.parseAsync(process.argv);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Export for programmatic use
|
|
12
|
+
export { createCli } from './cli.js';
|
|
13
|
+
export { displayHeader } from './utils/header.js';
|
|
14
|
+
export { logger } from './utils/logger.js';
|
|
15
|
+
|
|
16
|
+
// Export scanner
|
|
17
|
+
export { Scanner, scanProject, formatScanResult } from './scanner/index.js';
|
|
18
|
+
export type {
|
|
19
|
+
DetectionResult,
|
|
20
|
+
DetectedStack,
|
|
21
|
+
ScanResult,
|
|
22
|
+
ScannerOptions,
|
|
23
|
+
} from './scanner/index.js';
|
|
24
|
+
|
|
25
|
+
// Export generator
|
|
26
|
+
export {
|
|
27
|
+
Generator,
|
|
28
|
+
generateRalph,
|
|
29
|
+
formatGenerationResult,
|
|
30
|
+
extractVariables,
|
|
31
|
+
generateConfig,
|
|
32
|
+
generateConfigFile,
|
|
33
|
+
} from './generator/index.js';
|
|
34
|
+
export type {
|
|
35
|
+
GeneratorOptions,
|
|
36
|
+
GenerationResult,
|
|
37
|
+
TemplateVariables,
|
|
38
|
+
RalphConfig,
|
|
39
|
+
WriteOptions,
|
|
40
|
+
WriteSummary,
|
|
41
|
+
} from './generator/index.js';
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Detector
|
|
3
|
+
* Detects web frameworks: Next.js, React, Vue, Svelte, Remix, Astro
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import type { Detector, DetectionResult } from '../../types.js';
|
|
9
|
+
import {
|
|
10
|
+
readPackageJson,
|
|
11
|
+
getDependencies,
|
|
12
|
+
findConfigFile,
|
|
13
|
+
type DependencyMap,
|
|
14
|
+
} from '../utils.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect Next.js variant (app router vs pages router)
|
|
18
|
+
*/
|
|
19
|
+
function detectNextJsVariant(projectRoot: string): string | undefined {
|
|
20
|
+
const appDir = join(projectRoot, 'app');
|
|
21
|
+
const srcAppDir = join(projectRoot, 'src', 'app');
|
|
22
|
+
|
|
23
|
+
if (existsSync(appDir) || existsSync(srcAppDir)) {
|
|
24
|
+
return 'app-router';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pagesDir = join(projectRoot, 'pages');
|
|
28
|
+
const srcPagesDir = join(projectRoot, 'src', 'pages');
|
|
29
|
+
|
|
30
|
+
if (existsSync(pagesDir) || existsSync(srcPagesDir)) {
|
|
31
|
+
return 'pages-router';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detect Next.js
|
|
39
|
+
*/
|
|
40
|
+
function detectNextJs(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
41
|
+
const evidence: string[] = [];
|
|
42
|
+
let confidence = 0;
|
|
43
|
+
|
|
44
|
+
// Check for next dependency
|
|
45
|
+
if (deps.next) {
|
|
46
|
+
evidence.push(`next@${deps.next} in dependencies`);
|
|
47
|
+
confidence += 60;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for next.config.* files
|
|
51
|
+
const configExtensions = ['.js', '.mjs', '.ts'];
|
|
52
|
+
const configFile = findConfigFile(projectRoot, 'next.config', configExtensions);
|
|
53
|
+
if (configFile) {
|
|
54
|
+
evidence.push(`${configFile} found`);
|
|
55
|
+
confidence += 30;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (confidence === 0) return null;
|
|
59
|
+
|
|
60
|
+
const variant = detectNextJsVariant(projectRoot);
|
|
61
|
+
if (variant) {
|
|
62
|
+
evidence.push(`${variant} detected`);
|
|
63
|
+
confidence += 10;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
name: 'Next.js',
|
|
68
|
+
version: deps.next,
|
|
69
|
+
variant,
|
|
70
|
+
confidence: Math.min(confidence, 100),
|
|
71
|
+
evidence,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect Vue/Nuxt
|
|
77
|
+
*/
|
|
78
|
+
function detectVue(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
79
|
+
const evidence: string[] = [];
|
|
80
|
+
let confidence = 0;
|
|
81
|
+
|
|
82
|
+
// Check for nuxt (takes precedence as it includes Vue)
|
|
83
|
+
if (deps.nuxt) {
|
|
84
|
+
evidence.push(`nuxt@${deps.nuxt} in dependencies`);
|
|
85
|
+
confidence += 70;
|
|
86
|
+
|
|
87
|
+
const nuxtConfig = findConfigFile(projectRoot, 'nuxt.config', ['.js', '.ts']);
|
|
88
|
+
if (nuxtConfig) {
|
|
89
|
+
evidence.push(`${nuxtConfig} found`);
|
|
90
|
+
confidence += 30;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
name: 'Nuxt',
|
|
95
|
+
version: deps.nuxt,
|
|
96
|
+
confidence: Math.min(confidence, 100),
|
|
97
|
+
evidence,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for vue
|
|
102
|
+
if (deps.vue) {
|
|
103
|
+
evidence.push(`vue@${deps.vue} in dependencies`);
|
|
104
|
+
confidence += 50;
|
|
105
|
+
|
|
106
|
+
// Check for Vue-specific configs
|
|
107
|
+
const vueConfig = findConfigFile(projectRoot, 'vue.config', ['.js', '.ts']);
|
|
108
|
+
if (vueConfig) {
|
|
109
|
+
evidence.push(`${vueConfig} found`);
|
|
110
|
+
confidence += 20;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const viteConfig = findConfigFile(projectRoot, 'vite.config', ['.js', '.ts', '.mjs']);
|
|
114
|
+
if (viteConfig && deps['@vitejs/plugin-vue']) {
|
|
115
|
+
evidence.push('Vite + Vue plugin detected');
|
|
116
|
+
confidence += 20;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (confidence === 0) return null;
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
name: 'Vue',
|
|
123
|
+
version: deps.vue,
|
|
124
|
+
confidence: Math.min(confidence, 100),
|
|
125
|
+
evidence,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Detect Svelte/SvelteKit
|
|
134
|
+
*/
|
|
135
|
+
function detectSvelte(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
136
|
+
const evidence: string[] = [];
|
|
137
|
+
let confidence = 0;
|
|
138
|
+
|
|
139
|
+
// Check for SvelteKit (takes precedence)
|
|
140
|
+
if (deps['@sveltejs/kit']) {
|
|
141
|
+
evidence.push(`@sveltejs/kit@${deps['@sveltejs/kit']} in dependencies`);
|
|
142
|
+
confidence += 70;
|
|
143
|
+
|
|
144
|
+
const svelteConfig = findConfigFile(projectRoot, 'svelte.config', ['.js', '.ts']);
|
|
145
|
+
if (svelteConfig) {
|
|
146
|
+
evidence.push(`${svelteConfig} found`);
|
|
147
|
+
confidence += 30;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
name: 'SvelteKit',
|
|
152
|
+
version: deps['@sveltejs/kit'],
|
|
153
|
+
confidence: Math.min(confidence, 100),
|
|
154
|
+
evidence,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check for Svelte
|
|
159
|
+
if (deps.svelte) {
|
|
160
|
+
evidence.push(`svelte@${deps.svelte} in dependencies`);
|
|
161
|
+
confidence += 50;
|
|
162
|
+
|
|
163
|
+
const svelteConfig = findConfigFile(projectRoot, 'svelte.config', ['.js', '.ts']);
|
|
164
|
+
if (svelteConfig) {
|
|
165
|
+
evidence.push(`${svelteConfig} found`);
|
|
166
|
+
confidence += 30;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (confidence === 0) return null;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
name: 'Svelte',
|
|
173
|
+
version: deps.svelte,
|
|
174
|
+
confidence: Math.min(confidence, 100),
|
|
175
|
+
evidence,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Detect Remix
|
|
184
|
+
*/
|
|
185
|
+
function detectRemix(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
186
|
+
const evidence: string[] = [];
|
|
187
|
+
let confidence = 0;
|
|
188
|
+
|
|
189
|
+
if (deps['@remix-run/react'] || deps['@remix-run/node']) {
|
|
190
|
+
const version = deps['@remix-run/react'] || deps['@remix-run/node'];
|
|
191
|
+
evidence.push(`@remix-run packages@${version} in dependencies`);
|
|
192
|
+
confidence += 70;
|
|
193
|
+
|
|
194
|
+
// Check for remix config
|
|
195
|
+
const remixConfig = findConfigFile(projectRoot, 'remix.config', ['.js', '.ts']);
|
|
196
|
+
if (remixConfig) {
|
|
197
|
+
evidence.push(`${remixConfig} found`);
|
|
198
|
+
confidence += 20;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Remix v2+ uses vite
|
|
202
|
+
const viteConfig = findConfigFile(projectRoot, 'vite.config', ['.js', '.ts', '.mjs']);
|
|
203
|
+
if (viteConfig && deps['@remix-run/dev']) {
|
|
204
|
+
evidence.push('Vite-based Remix setup detected');
|
|
205
|
+
confidence += 10;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
name: 'Remix',
|
|
210
|
+
version,
|
|
211
|
+
confidence: Math.min(confidence, 100),
|
|
212
|
+
evidence,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect Astro
|
|
221
|
+
*/
|
|
222
|
+
function detectAstro(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
223
|
+
const evidence: string[] = [];
|
|
224
|
+
let confidence = 0;
|
|
225
|
+
|
|
226
|
+
if (deps.astro) {
|
|
227
|
+
evidence.push(`astro@${deps.astro} in dependencies`);
|
|
228
|
+
confidence += 60;
|
|
229
|
+
|
|
230
|
+
const astroConfig = findConfigFile(projectRoot, 'astro.config', ['.mjs', '.js', '.ts']);
|
|
231
|
+
if (astroConfig) {
|
|
232
|
+
evidence.push(`${astroConfig} found`);
|
|
233
|
+
confidence += 30;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
name: 'Astro',
|
|
238
|
+
version: deps.astro,
|
|
239
|
+
confidence: Math.min(confidence, 100),
|
|
240
|
+
evidence,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Detect plain React (no framework)
|
|
249
|
+
*/
|
|
250
|
+
function detectReact(projectRoot: string, deps: DependencyMap): DetectionResult | null {
|
|
251
|
+
const evidence: string[] = [];
|
|
252
|
+
let confidence = 0;
|
|
253
|
+
|
|
254
|
+
if (deps.react) {
|
|
255
|
+
evidence.push(`react@${deps.react} in dependencies`);
|
|
256
|
+
confidence += 40;
|
|
257
|
+
|
|
258
|
+
// Check for common React setups
|
|
259
|
+
if (deps['react-scripts']) {
|
|
260
|
+
evidence.push('Create React App detected (react-scripts)');
|
|
261
|
+
confidence += 40;
|
|
262
|
+
return {
|
|
263
|
+
name: 'React',
|
|
264
|
+
version: deps.react,
|
|
265
|
+
variant: 'create-react-app',
|
|
266
|
+
confidence: Math.min(confidence, 100),
|
|
267
|
+
evidence,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Vite + React
|
|
272
|
+
if (deps['@vitejs/plugin-react'] || deps['@vitejs/plugin-react-swc']) {
|
|
273
|
+
evidence.push('Vite + React setup detected');
|
|
274
|
+
confidence += 30;
|
|
275
|
+
return {
|
|
276
|
+
name: 'React',
|
|
277
|
+
version: deps.react,
|
|
278
|
+
variant: 'vite',
|
|
279
|
+
confidence: Math.min(confidence, 100),
|
|
280
|
+
evidence,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Plain React
|
|
285
|
+
return {
|
|
286
|
+
name: 'React',
|
|
287
|
+
version: deps.react,
|
|
288
|
+
confidence: Math.min(confidence, 100),
|
|
289
|
+
evidence,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Framework detector
|
|
298
|
+
*/
|
|
299
|
+
export const frameworkDetector: Detector = {
|
|
300
|
+
category: 'framework',
|
|
301
|
+
name: 'Framework Detector',
|
|
302
|
+
|
|
303
|
+
async detect(projectRoot: string): Promise<DetectionResult | null> {
|
|
304
|
+
const pkg = readPackageJson(projectRoot);
|
|
305
|
+
if (!pkg) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const deps = getDependencies(pkg);
|
|
310
|
+
|
|
311
|
+
// Order matters: check meta-frameworks first, then base frameworks
|
|
312
|
+
const detectors = [
|
|
313
|
+
detectNextJs,
|
|
314
|
+
detectRemix,
|
|
315
|
+
detectAstro,
|
|
316
|
+
detectVue, // Also handles Nuxt
|
|
317
|
+
detectSvelte, // Also handles SvelteKit
|
|
318
|
+
detectReact, // Check React last as it's often a dependency of frameworks
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
for (const detector of detectors) {
|
|
322
|
+
const result = detector(projectRoot, deps);
|
|
323
|
+
if (result && result.confidence >= 40) {
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return null;
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export default frameworkDetector;
|