wmx-os-generators 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/deployGenerator.d.ts +18 -0
  2. package/dist/deployGenerator.js +106 -0
  3. package/dist/docsGenerator.d.ts +5 -0
  4. package/dist/docsGenerator.js +283 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +3 -0
  7. package/dist/scaffold.d.ts +15 -0
  8. package/dist/scaffold.js +89 -0
  9. package/package.json +10 -5
  10. package/src/deployGenerator.d.ts +18 -0
  11. package/src/deployGenerator.js +106 -0
  12. package/src/docsGenerator.d.ts +5 -0
  13. package/src/docsGenerator.js +283 -0
  14. package/src/index.d.ts +3 -0
  15. package/src/index.js +3 -0
  16. package/src/scaffold.d.ts +15 -0
  17. package/src/scaffold.js +89 -0
  18. package/templates/nextjs-postgres/.env.example +0 -1
  19. package/templates/nextjs-postgres/README.md +0 -13
  20. package/templates/nextjs-postgres/drizzle.config.ts +0 -10
  21. package/templates/nextjs-postgres/package.json +0 -26
  22. package/templates/nextjs-postgres/src/app/layout.tsx +0 -14
  23. package/templates/nextjs-postgres/src/app/page.tsx +0 -8
  24. package/templates/react-express-mongo/README.md +0 -23
  25. package/templates/react-express-mongo/backend/.env.example +0 -3
  26. package/templates/react-express-mongo/backend/package.json +0 -23
  27. package/templates/react-express-mongo/backend/src/db.ts +0 -16
  28. package/templates/react-express-mongo/backend/src/index.ts +0 -22
  29. package/templates/react-express-mongo/frontend/index.html +0 -12
  30. package/templates/react-express-mongo/frontend/package.json +0 -21
  31. package/templates/react-express-mongo/frontend/src/App.tsx +0 -12
  32. package/templates/react-express-mongo/frontend/src/main.tsx +0 -9
  33. package/templates/react-express-mongo/frontend/vite.config.ts +0 -12
  34. package/tsconfig.json +0 -8
@@ -0,0 +1,18 @@
1
+ interface WmxConfig {
2
+ projectName?: string;
3
+ framework?: string;
4
+ packageManager?: string;
5
+ frontend?: {
6
+ framework?: string;
7
+ dir?: string;
8
+ };
9
+ backend?: {
10
+ framework?: string;
11
+ dir?: string;
12
+ };
13
+ database?: string;
14
+ }
15
+ export declare function generateVercelConfig(config: WmxConfig): object;
16
+ export declare function generateRenderConfig(config: WmxConfig): string;
17
+ export declare function generateNetlifyConfig(config: WmxConfig): string;
18
+ export {};
@@ -0,0 +1,106 @@
1
+ import fse from 'fs-extra';
2
+ import path from 'path';
3
+ const BACKEND_FRAMEWORKS = ['express', 'nestjs', 'fastify', 'koa'];
4
+ function hasBackend(config) {
5
+ return !!(config.backend && BACKEND_FRAMEWORKS.includes(config.backend.framework ?? ''));
6
+ }
7
+ export function generateVercelConfig(config) {
8
+ const frontendDir = config.frontend?.dir ?? 'frontend';
9
+ const backendDir = config.backend?.dir ?? 'backend';
10
+ if (hasBackend(config)) {
11
+ return {
12
+ version: 2,
13
+ builds: [
14
+ {
15
+ src: `${frontendDir}/package.json`,
16
+ use: '@vercel/static-build',
17
+ config: { distDir: 'dist' },
18
+ },
19
+ { src: `${backendDir}/package.json`, use: '@vercel/node' },
20
+ ],
21
+ routes: [
22
+ { src: '/api/(.*)', dest: `/${backendDir}` },
23
+ { src: '/(.*)', dest: `/${frontendDir}` },
24
+ ],
25
+ };
26
+ }
27
+ return {
28
+ version: 2,
29
+ builds: [
30
+ {
31
+ src: `${frontendDir}/package.json`,
32
+ use: '@vercel/static-build',
33
+ config: { distDir: 'dist' },
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ export function generateRenderConfig(config) {
39
+ const frontendDir = config.frontend?.dir ?? 'frontend';
40
+ const backendDir = config.backend?.dir ?? 'backend';
41
+ const projectName = config.projectName ?? 'my-app';
42
+ const envExamplePath = path.join(process.cwd(), '.env.example');
43
+ let envKeys = [];
44
+ if (fse.pathExistsSync(envExamplePath)) {
45
+ const content = fse.readFileSync(envExamplePath, 'utf-8');
46
+ envKeys = content
47
+ .split('\n')
48
+ .filter(line => line.includes('=') && !line.trimStart().startsWith('#'))
49
+ .map(line => line.split('=')[0].trim())
50
+ .filter(Boolean);
51
+ }
52
+ if (hasBackend(config)) {
53
+ const envSection = envKeys.length > 0
54
+ ? `\n envVars:\n${envKeys.map(k => ` - key: ${k}\n value: placeholder`).join('\n')}`
55
+ : '';
56
+ return `services:
57
+ - type: web
58
+ name: ${projectName}-backend
59
+ runtime: node
60
+ rootDir: ${backendDir}
61
+ buildCommand: npm install && npm run build
62
+ startCommand: npm start${envSection}
63
+
64
+ - type: static
65
+ name: ${projectName}-frontend
66
+ rootDir: ${frontendDir}
67
+ buildCommand: npm install && npm run build
68
+ staticPublishPath: dist
69
+ `;
70
+ }
71
+ return `services:
72
+ - type: static
73
+ name: ${projectName}-frontend
74
+ rootDir: ${frontendDir}
75
+ buildCommand: npm install && npm run build
76
+ staticPublishPath: dist
77
+ `;
78
+ }
79
+ export function generateNetlifyConfig(config) {
80
+ const frontendDir = config.frontend?.dir ?? 'frontend';
81
+ const framework = config.frontend?.framework ?? '';
82
+ let buildCommand;
83
+ let publish;
84
+ if (framework === 'next') {
85
+ buildCommand = 'npm run build';
86
+ publish = '.next';
87
+ }
88
+ else if (framework === 'nuxt') {
89
+ buildCommand = 'npm run generate';
90
+ publish = 'dist';
91
+ }
92
+ else {
93
+ buildCommand = 'npm run build';
94
+ publish = 'dist';
95
+ }
96
+ return `[build]
97
+ base = "${frontendDir}"
98
+ command = "${buildCommand}"
99
+ publish = "${publish}"
100
+
101
+ [[redirects]]
102
+ from = "/*"
103
+ to = "/index.html"
104
+ status = 200
105
+ `;
106
+ }
@@ -0,0 +1,5 @@
1
+ import type { AnalysisResult, RouteEntry } from 'wmx-os-scanners';
2
+ export declare function generateAnalysis(result: AnalysisResult, outputDir: string): Promise<void>;
3
+ export declare function generateReadme(cwd: string): Promise<string>;
4
+ export declare function generateArchitectureDocs(analysis: AnalysisResult): string;
5
+ export declare function generateApiDocs(routes: RouteEntry[]): string;
@@ -0,0 +1,283 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ // ── Existing helpers ──────────────────────────────────────────────────────────
4
+ function topImports(imports, n = 10) {
5
+ const counts = new Map();
6
+ for (const imp of imports) {
7
+ counts.set(imp.to, (counts.get(imp.to) ?? 0) + 1);
8
+ }
9
+ return [...counts.entries()]
10
+ .sort((a, b) => b[1] - a[1])
11
+ .slice(0, n)
12
+ .map(([module, count]) => ({ module, count }));
13
+ }
14
+ export async function generateAnalysis(result, outputDir) {
15
+ await fs.ensureDir(outputDir);
16
+ const top = topImports(result.imports);
17
+ const routeTable = result.routes.length > 0
18
+ ? [
19
+ '| Method | Path | File | Line |',
20
+ '|--------|------|------|------|',
21
+ ...result.routes.map(r => `| \`${r.method}\` | \`${r.path}\` | ${r.file} | ${r.line} |`)
22
+ ].join('\n')
23
+ : '_No Express or Next.js routes detected._';
24
+ const importList = top.length > 0
25
+ ? top.map((t, i) => `${i + 1}. \`${t.module}\` — imported **${t.count}** time${t.count === 1 ? '' : 's'}`).join('\n')
26
+ : '_No imports detected._';
27
+ const md = `# Project Architecture
28
+
29
+ _Generated by wmx analyze on ${new Date().toISOString()}_
30
+
31
+ ## Folder Structure
32
+
33
+ \`\`\`
34
+ ${result.folderTree}
35
+ \`\`\`
36
+
37
+ ## API Routes
38
+
39
+ ${routeTable}
40
+
41
+ ## Top Imports
42
+
43
+ ${importList}
44
+
45
+ ## Exports Summary
46
+
47
+ Total exported symbols: **${result.exports.length}**
48
+
49
+ | Type | Count |
50
+ |------|-------|
51
+ | Functions | ${result.exports.filter(e => e.type === 'function').length} |
52
+ | Classes | ${result.exports.filter(e => e.type === 'class').length} |
53
+ | Constants | ${result.exports.filter(e => e.type === 'const').length} |
54
+ | Types | ${result.exports.filter(e => e.type === 'type').length} |
55
+ `;
56
+ await fs.writeFile(path.join(outputDir, 'analysis.md'), md, 'utf8');
57
+ await fs.writeFile(path.join(outputDir, 'analysis.json'), JSON.stringify(result, null, 2), 'utf8');
58
+ }
59
+ // ── Project structure tree helper (for generateReadme) ────────────────────────
60
+ const PROJ_IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', '.wmx']);
61
+ const PROJ_IGNORE_FILES = new Set(['.env', '.env.example']);
62
+ async function buildProjectStructure(dir, maxDepth, depth = 0) {
63
+ if (depth >= maxDepth)
64
+ return '';
65
+ let entries;
66
+ try {
67
+ entries = await fs.readdir(dir);
68
+ }
69
+ catch {
70
+ return '';
71
+ }
72
+ const filtered = entries.filter(e => {
73
+ if (PROJ_IGNORE_DIRS.has(e))
74
+ return false;
75
+ if (PROJ_IGNORE_FILES.has(e))
76
+ return false;
77
+ if (/\.lock$/.test(e))
78
+ return false;
79
+ return true;
80
+ });
81
+ filtered.sort();
82
+ const indent = ' '.repeat(depth);
83
+ const lines = [];
84
+ for (const entry of filtered) {
85
+ const fullPath = path.join(dir, entry);
86
+ let isDir = false;
87
+ try {
88
+ const stat = await fs.stat(fullPath);
89
+ isDir = stat.isDirectory();
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ if (isDir) {
95
+ lines.push(`${indent}${entry}/`);
96
+ const sub = await buildProjectStructure(fullPath, maxDepth, depth + 1);
97
+ if (sub)
98
+ lines.push(sub);
99
+ }
100
+ else {
101
+ lines.push(`${indent}${entry}`);
102
+ }
103
+ }
104
+ return lines.join('\n');
105
+ }
106
+ // ── generateReadme ────────────────────────────────────────────────────────────
107
+ export async function generateReadme(cwd) {
108
+ // Read package.json
109
+ let pkg = {};
110
+ try {
111
+ const raw = await fs.readFile(path.join(cwd, 'package.json'), 'utf8');
112
+ pkg = JSON.parse(raw);
113
+ }
114
+ catch { /* missing or invalid — use defaults */ }
115
+ // Read .wmxrc.json
116
+ let wmxrc = {};
117
+ try {
118
+ const raw = await fs.readFile(path.join(cwd, '.wmxrc.json'), 'utf8');
119
+ wmxrc = JSON.parse(raw);
120
+ }
121
+ catch { /* missing — use defaults */ }
122
+ // Read .env.example
123
+ let envExampleRaw = '';
124
+ try {
125
+ envExampleRaw = await fs.readFile(path.join(cwd, '.env.example'), 'utf8');
126
+ }
127
+ catch { /* missing — handled below */ }
128
+ // 1. Title
129
+ const title = pkg.name ?? 'My Project';
130
+ // 2. Description
131
+ const description = pkg.description ?? 'A project built with wmx';
132
+ // 3. Tech Stack
133
+ const deps = pkg.dependencies ?? {};
134
+ const devDeps = pkg.devDependencies ?? {};
135
+ const allDeps = { ...deps, ...devDeps };
136
+ const techMap = [
137
+ ['react', 'React'],
138
+ ['express', 'Express'],
139
+ ['mongoose', 'MongoDB'],
140
+ ['next', 'Next.js'],
141
+ ['pg', 'PostgreSQL'],
142
+ ['vue', 'Vue'],
143
+ ];
144
+ const detectedTech = techMap
145
+ .filter(([key]) => key in allDeps)
146
+ .map(([, label]) => `- ${label}`);
147
+ const techStackSection = detectedTech.length > 0
148
+ ? detectedTech.join('\n')
149
+ : '_No tech stack detected._';
150
+ // 4. Prerequisites
151
+ const packageManager = wmxrc.packageManager;
152
+ const prereqItems = ['- Node.js >= 18', ...(packageManager ? [`- ${packageManager}`] : [])];
153
+ const prerequisitesSection = prereqItems.join('\n');
154
+ // 5. Installation
155
+ const repoObj = pkg.repository;
156
+ const repoUrl = typeof repoObj === 'string'
157
+ ? repoObj
158
+ : (repoObj?.url ?? 'https://github.com/your-org/your-repo');
159
+ const projectName = pkg.name ?? 'your-project';
160
+ const installCmd = packageManager === 'pnpm' ? 'pnpm install'
161
+ : packageManager === 'bun' ? 'bun install'
162
+ : packageManager === 'yarn' ? 'yarn install'
163
+ : 'npm install';
164
+ // 6. Environment Variables
165
+ const envKeys = envExampleRaw
166
+ .split('\n')
167
+ .filter(line => line.includes('=') && !line.trimStart().startsWith('#'))
168
+ .map(line => line.split('=')[0].trim())
169
+ .filter(Boolean);
170
+ const envSection = envKeys.length > 0
171
+ ? '```bash\n' + envKeys.map(k => `${k}=`).join('\n') + '\n```'
172
+ : '_No environment variables required._';
173
+ // 7. Available Scripts
174
+ const scripts = pkg.scripts ?? {};
175
+ const scriptEntries = Object.entries(scripts);
176
+ const scriptsSection = scriptEntries.length > 0
177
+ ? scriptEntries.map(([key, val]) => `- \`npm run ${key}\` — ${val}`).join('\n')
178
+ : '_No scripts defined._';
179
+ // 8. Project Structure (2 levels deep)
180
+ const structure = await buildProjectStructure(cwd, 2);
181
+ // 9. License
182
+ const license = pkg.license ?? 'MIT';
183
+ return `# ${title}
184
+
185
+ ${description}
186
+
187
+ ## Tech Stack
188
+
189
+ ${techStackSection}
190
+
191
+ ## Prerequisites
192
+
193
+ ${prerequisitesSection}
194
+
195
+ ## Installation
196
+
197
+ \`\`\`bash
198
+ 1. git clone ${repoUrl}
199
+ 2. cd ${projectName}
200
+ 3. ${installCmd}
201
+ \`\`\`
202
+
203
+ ## Environment Variables
204
+
205
+ ${envSection}
206
+
207
+ ## Available Scripts
208
+
209
+ ${scriptsSection}
210
+
211
+ ## Project Structure
212
+
213
+ \`\`\`
214
+ ${structure}
215
+ \`\`\`
216
+
217
+ ## License
218
+
219
+ This project is licensed under the ${license} license.
220
+ `;
221
+ }
222
+ // ── generateArchitectureDocs ──────────────────────────────────────────────────
223
+ export function generateArchitectureDocs(analysis) {
224
+ // Derive a unique source-file count from the data available in AnalysisResult
225
+ const uniqueFiles = new Set([
226
+ ...analysis.imports.map(i => i.from),
227
+ ...analysis.exports.map(e => e.file),
228
+ ...analysis.routes.map(r => r.file),
229
+ ]).size;
230
+ // API Routes table
231
+ const routeTable = analysis.routes.length > 0
232
+ ? [
233
+ '| Method | Endpoint | File | Description |',
234
+ '|--------|----------|------|-------------|',
235
+ ...analysis.routes.map(r => `| \`${r.method}\` | \`${r.path}\` | ${r.file} | |`)
236
+ ].join('\n')
237
+ : '_No API routes detected._';
238
+ // Key Dependencies — external (non-relative) import targets, deduped
239
+ const externalDeps = [...new Set(analysis.imports
240
+ .map(i => i.to)
241
+ .filter(m => !m.startsWith('.') && !m.startsWith('/')))].sort();
242
+ const depsSection = externalDeps.length > 0
243
+ ? externalDeps.map(d => `- \`${d}\``).join('\n')
244
+ : '_No external dependencies detected._';
245
+ return `# Project Architecture
246
+
247
+ ## Overview
248
+
249
+ This document describes the architecture of the project.
250
+
251
+ Analyzed **${uniqueFiles}** source files.
252
+
253
+ ## Folder Structure
254
+
255
+ \`\`\`
256
+ ${analysis.folderTree}
257
+ \`\`\`
258
+
259
+ ## API Routes
260
+
261
+ ${routeTable}
262
+
263
+ ## Key Dependencies
264
+
265
+ ${depsSection}
266
+ `;
267
+ }
268
+ // ── generateApiDocs ───────────────────────────────────────────────────────────
269
+ export function generateApiDocs(routes) {
270
+ const routeTable = routes.length > 0
271
+ ? [
272
+ '| Method | Endpoint | File | Description |',
273
+ '|--------|----------|------|-------------|',
274
+ ...routes.map(r => `| \`${r.method}\` | \`${r.path}\` | ${r.file} | |`)
275
+ ].join('\n')
276
+ : '_No routes detected._';
277
+ return `# API Reference
278
+
279
+ ${routeTable}
280
+
281
+ _Update the Description column with endpoint-specific documentation._
282
+ `;
283
+ }
@@ -0,0 +1,3 @@
1
+ export { ScaffoldGenerator } from "./scaffold.js";
2
+ export { generateAnalysis, generateReadme, generateArchitectureDocs, generateApiDocs } from "./docsGenerator.js";
3
+ export { generateVercelConfig, generateRenderConfig, generateNetlifyConfig } from "./deployGenerator.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { ScaffoldGenerator } from "./scaffold.js";
2
+ export { generateAnalysis, generateReadme, generateArchitectureDocs, generateApiDocs } from "./docsGenerator.js";
3
+ export { generateVercelConfig, generateRenderConfig, generateNetlifyConfig } from "./deployGenerator.js";
@@ -0,0 +1,15 @@
1
+ interface ScaffoldAnswers {
2
+ projectName: string;
3
+ framework: string;
4
+ backend: string;
5
+ database: string;
6
+ packageManager: string;
7
+ features?: string[];
8
+ [key: string]: unknown;
9
+ }
10
+ export declare class ScaffoldGenerator {
11
+ static generate(answers: ScaffoldAnswers): Promise<void>;
12
+ private static resolveTemplate;
13
+ private static replaceInAllFiles;
14
+ }
15
+ export {};
@@ -0,0 +1,89 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import fs from 'fs-extra';
4
+ import { execa } from 'execa';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ export class ScaffoldGenerator {
8
+ static async generate(answers) {
9
+ const templateName = ScaffoldGenerator.resolveTemplate(answers);
10
+ const templatesDir = path.join(__dirname, '..', 'templates');
11
+ const templatePath = path.join(templatesDir, templateName);
12
+ const targetPath = path.join(process.cwd(), answers.projectName);
13
+ if (!(await fs.pathExists(templatePath))) {
14
+ throw new Error(`Template not found: ${templateName} (looked in ${templatePath})`);
15
+ }
16
+ if (await fs.pathExists(targetPath)) {
17
+ throw new Error(`Directory already exists: ${targetPath}`);
18
+ }
19
+ console.log(`\nScaffolding "${answers.projectName}" from template "${templateName}"...`);
20
+ await fs.copy(templatePath, targetPath);
21
+ await ScaffoldGenerator.replaceInAllFiles(targetPath, '__PROJECT_NAME__', answers.projectName);
22
+ const wmxrc = {
23
+ projectName: answers.projectName,
24
+ framework: answers.framework,
25
+ backend: answers.backend,
26
+ database: answers.database,
27
+ packageManager: answers.packageManager,
28
+ features: answers.features ?? [],
29
+ scaffoldedAt: new Date().toISOString(),
30
+ template: templateName
31
+ };
32
+ await fs.writeJson(path.join(targetPath, '.wmxrc.json'), wmxrc, { spaces: 2 });
33
+ console.log(`\nRunning ${answers.packageManager} install...`);
34
+ try {
35
+ if (templateName === 'react-express-mongo') {
36
+ await execa(answers.packageManager, ['install'], {
37
+ cwd: path.join(targetPath, 'frontend'),
38
+ stdio: 'inherit'
39
+ });
40
+ await execa(answers.packageManager, ['install'], {
41
+ cwd: path.join(targetPath, 'backend'),
42
+ stdio: 'inherit'
43
+ });
44
+ }
45
+ else {
46
+ await execa(answers.packageManager, ['install'], {
47
+ cwd: targetPath,
48
+ stdio: 'inherit'
49
+ });
50
+ }
51
+ }
52
+ catch (err) {
53
+ console.warn(`\nPackage install failed (you can run it manually): ${err.message}`);
54
+ }
55
+ console.log(`\n✅ Project "${answers.projectName}" created at ${targetPath}`);
56
+ console.log(` cd ${answers.projectName} and follow the README to get started.\n`);
57
+ }
58
+ static resolveTemplate(answers) {
59
+ const fw = answers.framework?.toLowerCase() ?? '';
60
+ const be = answers.backend?.toLowerCase() ?? '';
61
+ const db = answers.database?.toLowerCase() ?? '';
62
+ if (fw.includes('next'))
63
+ return 'nextjs-postgres';
64
+ if (fw.includes('react') && be.includes('express') && db.includes('mongo'))
65
+ return 'react-express-mongo';
66
+ return 'react-express-mongo';
67
+ }
68
+ static async replaceInAllFiles(dir, search, replacement) {
69
+ const entries = await fs.readdir(dir, { withFileTypes: true });
70
+ for (const entry of entries) {
71
+ const fullPath = path.join(dir, entry.name);
72
+ if (entry.isDirectory()) {
73
+ await ScaffoldGenerator.replaceInAllFiles(fullPath, search, replacement);
74
+ }
75
+ else {
76
+ try {
77
+ const content = await fs.readFile(fullPath, 'utf8');
78
+ if (content.includes(search)) {
79
+ const updated = content.split(search).join(replacement);
80
+ await fs.writeFile(fullPath, updated, 'utf8');
81
+ }
82
+ }
83
+ catch {
84
+ // Skip binary files silently
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wmx-os-generators",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -9,14 +9,19 @@
9
9
  "dev": "tsc --project tsconfig.json --watch"
10
10
  },
11
11
  "dependencies": {
12
- "wmx-os-core": "workspace:*",
13
- "wmx-os-scanners": "workspace:*",
14
12
  "execa": "^8.0.0",
15
- "fs-extra": "^11.0.0"
13
+ "fs-extra": "^11.0.0",
14
+ "wmx-os-core": "^0.1.3",
15
+ "wmx-os-scanners": "^0.1.1"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/fs-extra": "^11.0.0",
19
19
  "@types/node": "^20.0.0",
20
20
  "typescript": "^5.4.0"
21
- }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "package.json"
26
+ ]
22
27
  }
@@ -0,0 +1,18 @@
1
+ interface WmxConfig {
2
+ projectName?: string;
3
+ framework?: string;
4
+ packageManager?: string;
5
+ frontend?: {
6
+ framework?: string;
7
+ dir?: string;
8
+ };
9
+ backend?: {
10
+ framework?: string;
11
+ dir?: string;
12
+ };
13
+ database?: string;
14
+ }
15
+ export declare function generateVercelConfig(config: WmxConfig): object;
16
+ export declare function generateRenderConfig(config: WmxConfig): string;
17
+ export declare function generateNetlifyConfig(config: WmxConfig): string;
18
+ export {};
@@ -0,0 +1,106 @@
1
+ import fse from 'fs-extra';
2
+ import path from 'path';
3
+ const BACKEND_FRAMEWORKS = ['express', 'nestjs', 'fastify', 'koa'];
4
+ function hasBackend(config) {
5
+ return !!(config.backend && BACKEND_FRAMEWORKS.includes(config.backend.framework ?? ''));
6
+ }
7
+ export function generateVercelConfig(config) {
8
+ const frontendDir = config.frontend?.dir ?? 'frontend';
9
+ const backendDir = config.backend?.dir ?? 'backend';
10
+ if (hasBackend(config)) {
11
+ return {
12
+ version: 2,
13
+ builds: [
14
+ {
15
+ src: `${frontendDir}/package.json`,
16
+ use: '@vercel/static-build',
17
+ config: { distDir: 'dist' },
18
+ },
19
+ { src: `${backendDir}/package.json`, use: '@vercel/node' },
20
+ ],
21
+ routes: [
22
+ { src: '/api/(.*)', dest: `/${backendDir}` },
23
+ { src: '/(.*)', dest: `/${frontendDir}` },
24
+ ],
25
+ };
26
+ }
27
+ return {
28
+ version: 2,
29
+ builds: [
30
+ {
31
+ src: `${frontendDir}/package.json`,
32
+ use: '@vercel/static-build',
33
+ config: { distDir: 'dist' },
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ export function generateRenderConfig(config) {
39
+ const frontendDir = config.frontend?.dir ?? 'frontend';
40
+ const backendDir = config.backend?.dir ?? 'backend';
41
+ const projectName = config.projectName ?? 'my-app';
42
+ const envExamplePath = path.join(process.cwd(), '.env.example');
43
+ let envKeys = [];
44
+ if (fse.pathExistsSync(envExamplePath)) {
45
+ const content = fse.readFileSync(envExamplePath, 'utf-8');
46
+ envKeys = content
47
+ .split('\n')
48
+ .filter(line => line.includes('=') && !line.trimStart().startsWith('#'))
49
+ .map(line => line.split('=')[0].trim())
50
+ .filter(Boolean);
51
+ }
52
+ if (hasBackend(config)) {
53
+ const envSection = envKeys.length > 0
54
+ ? `\n envVars:\n${envKeys.map(k => ` - key: ${k}\n value: placeholder`).join('\n')}`
55
+ : '';
56
+ return `services:
57
+ - type: web
58
+ name: ${projectName}-backend
59
+ runtime: node
60
+ rootDir: ${backendDir}
61
+ buildCommand: npm install && npm run build
62
+ startCommand: npm start${envSection}
63
+
64
+ - type: static
65
+ name: ${projectName}-frontend
66
+ rootDir: ${frontendDir}
67
+ buildCommand: npm install && npm run build
68
+ staticPublishPath: dist
69
+ `;
70
+ }
71
+ return `services:
72
+ - type: static
73
+ name: ${projectName}-frontend
74
+ rootDir: ${frontendDir}
75
+ buildCommand: npm install && npm run build
76
+ staticPublishPath: dist
77
+ `;
78
+ }
79
+ export function generateNetlifyConfig(config) {
80
+ const frontendDir = config.frontend?.dir ?? 'frontend';
81
+ const framework = config.frontend?.framework ?? '';
82
+ let buildCommand;
83
+ let publish;
84
+ if (framework === 'next') {
85
+ buildCommand = 'npm run build';
86
+ publish = '.next';
87
+ }
88
+ else if (framework === 'nuxt') {
89
+ buildCommand = 'npm run generate';
90
+ publish = 'dist';
91
+ }
92
+ else {
93
+ buildCommand = 'npm run build';
94
+ publish = 'dist';
95
+ }
96
+ return `[build]
97
+ base = "${frontendDir}"
98
+ command = "${buildCommand}"
99
+ publish = "${publish}"
100
+
101
+ [[redirects]]
102
+ from = "/*"
103
+ to = "/index.html"
104
+ status = 200
105
+ `;
106
+ }