scaffy-tool 0.2.0 → 1.0.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.
- package/README.md +21 -26
- package/cli.js +18 -41
- package/core/detector.js +5 -31
- package/core/executor.js +5 -27
- package/core/interviewer.js +15 -19
- package/core/plugin-validator.js +1 -1
- package/core/registry.js +47 -28
- package/core/utils.js +2 -19
- package/package.json +11 -11
- package/registry/go/gin/plugin.json +23 -0
- package/registry/go/gin/v1/questions.js +19 -0
- package/registry/go/gin/v1/scaffold.js +196 -0
- package/registry/index.json +4 -4
- package/registry/javascript/expressjs/plugin.json +44 -0
- package/registry/javascript/expressjs/v4/questions.js +43 -0
- package/registry/javascript/expressjs/v4/scaffold.js +236 -0
- package/registry/javascript/nestjs/plugin.json +2 -2
- package/registry/javascript/nestjs/v10/questions.js +6 -4
- package/registry/javascript/nestjs/v10/scaffold.js +1 -1
- package/registry/javascript/nestjs/v11/questions.js +61 -0
- package/registry/javascript/nestjs/v11/scaffold.js +94 -0
- package/registry/javascript/nextjs/plugin.json +44 -0
- package/registry/javascript/nextjs/v14/questions.js +44 -0
- package/registry/javascript/nextjs/v14/scaffold.js +57 -0
- package/registry/javascript/vuejs/v3/questions.js +5 -3
- package/registry/javascript/vuejs/v3/scaffold.js +3 -10
- package/registry/php/laravel/plugin.json +2 -2
- package/registry/php/laravel/v11/questions.js +3 -3
- package/registry/php/laravel/v11/scaffold.js +1 -1
- package/registry/php/laravel/v12/questions.js +45 -0
- package/registry/php/laravel/v12/scaffold.js +46 -0
- package/registry/php/laravel/v13/questions.js +28 -0
- package/registry/php/laravel/v13/scaffold.js +46 -0
- package/registry/php/laravel/v13/test/.gitkeep +0 -0
- package/registry/php/laravel/v13/test/questions.test.js +48 -0
- package/registry/php/laravel/v13/test/scaffold.test.js +105 -0
- package/registry/php/symfony/plugin.json +35 -0
- package/registry/php/symfony/v7/questions.js +19 -0
- package/registry/php/symfony/v7/scaffold.js +74 -0
- package/registry/python/django/plugin.json +35 -0
- package/registry/python/django/v5/questions.js +24 -0
- package/registry/python/django/v5/scaffold.js +107 -0
- package/registry/python/fastapi/plugin.json +35 -0
- package/registry/python/fastapi/v1/questions.js +25 -0
- package/registry/python/fastapi/v1/scaffold.js +180 -0
package/README.md
CHANGED
|
@@ -88,13 +88,13 @@ One command. A few questions. Ready to code.
|
|
|
88
88
|
|
|
89
89
|
Existing scaffolding tools are framework-specific, opinionated, and go stale fast. Scaffy takes a fundamentally different approach.
|
|
90
90
|
|
|
91
|
-
| | Traditional Scaffolding
|
|
92
|
-
|
|
93
|
-
| **Multi-framework support** | ❌ One tool per framework
|
|
94
|
-
| **Always up to date** | ❌ Stores stale templates
|
|
95
|
-
| **Requirement checking** | ❌ Breaks halfway through
|
|
96
|
-
| **Community extensible** | ❌ Closed, maintainer-only
|
|
97
|
-
| **Version management** | ❌ Usually locked to one
|
|
91
|
+
| | Traditional Scaffolding | Scaffy |
|
|
92
|
+
| --------------------------- | -------------------------- | ---------------------------------- |
|
|
93
|
+
| **Multi-framework support** | ❌ One tool per framework | ✅ Every framework, one tool |
|
|
94
|
+
| **Always up to date** | ❌ Stores stale templates | ✅ Uses official CLI commands |
|
|
95
|
+
| **Requirement checking** | ❌ Breaks halfway through | ✅ Validates before starting |
|
|
96
|
+
| **Community extensible** | ❌ Closed, maintainer-only | ✅ Add any framework in 3 files |
|
|
97
|
+
| **Version management** | ❌ Usually locked to one | ✅ Multiple versions per framework |
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
@@ -142,7 +142,7 @@ scaffy --help # Help
|
|
|
142
142
|
<td rowspan="2"><strong>PHP</strong></td>
|
|
143
143
|
<td>Laravel</td>
|
|
144
144
|
<td><code>scaffy laravel</code></td>
|
|
145
|
-
<td>
|
|
145
|
+
<td>v11, v12, v13</td>
|
|
146
146
|
</tr>
|
|
147
147
|
<tr>
|
|
148
148
|
<td>Symfony</td>
|
|
@@ -153,7 +153,7 @@ scaffy --help # Help
|
|
|
153
153
|
<td rowspan="4"><strong>JavaScript</strong></td>
|
|
154
154
|
<td>NestJS</td>
|
|
155
155
|
<td><code>scaffy nestjs</code></td>
|
|
156
|
-
<td>v10</td>
|
|
156
|
+
<td>v10, v11</td>
|
|
157
157
|
</tr>
|
|
158
158
|
<tr>
|
|
159
159
|
<td>VueJS</td>
|
|
@@ -171,7 +171,7 @@ scaffy --help # Help
|
|
|
171
171
|
<td>v4</td>
|
|
172
172
|
</tr>
|
|
173
173
|
<tr>
|
|
174
|
-
<td rowspan="
|
|
174
|
+
<td rowspan="2"><strong>Python</strong></td>
|
|
175
175
|
<td>Django</td>
|
|
176
176
|
<td><code>scaffy django</code></td>
|
|
177
177
|
<td>v5</td>
|
|
@@ -179,12 +179,7 @@ scaffy --help # Help
|
|
|
179
179
|
<tr>
|
|
180
180
|
<td>FastAPI</td>
|
|
181
181
|
<td><code>scaffy fastapi</code></td>
|
|
182
|
-
<td>
|
|
183
|
-
</tr>
|
|
184
|
-
<tr>
|
|
185
|
-
<td>Flask</td>
|
|
186
|
-
<td><code>scaffy flask</code></td>
|
|
187
|
-
<td>v3</td>
|
|
182
|
+
<td>v1</td>
|
|
188
183
|
</tr>
|
|
189
184
|
<tr>
|
|
190
185
|
<td><strong>Go</strong></td>
|
|
@@ -219,20 +214,16 @@ Scaffy does **not** store template files. Instead, `scaffold.js` calls the frame
|
|
|
219
214
|
```javascript
|
|
220
215
|
// registry/php/laravel/v11/scaffold.js
|
|
221
216
|
module.exports = async (answers, utils) => {
|
|
222
|
-
const { projectName, starterKit, database } = answers
|
|
217
|
+
const { projectName, starterKit, database } = answers;
|
|
223
218
|
|
|
224
219
|
// Step 1 — Official Laravel installer
|
|
225
|
-
await utils.run(
|
|
226
|
-
`composer create-project laravel/laravel ${projectName}`
|
|
227
|
-
)
|
|
220
|
+
await utils.run(`composer create-project laravel/laravel ${projectName}`);
|
|
228
221
|
|
|
229
222
|
// Step 2 — Layered on top with your choices
|
|
230
223
|
if (starterKit === 'Breeze') {
|
|
231
|
-
await utils.runInProject(projectName,
|
|
232
|
-
`php artisan breeze:install`
|
|
233
|
-
)
|
|
224
|
+
await utils.runInProject(projectName, `php artisan breeze:install`);
|
|
234
225
|
}
|
|
235
|
-
}
|
|
226
|
+
};
|
|
236
227
|
```
|
|
237
228
|
|
|
238
229
|
When a new framework version releases tomorrow, a community member can add support in minutes — with zero changes to Scaffy's core.
|
|
@@ -306,7 +297,8 @@ Scaffy maintains a minimum **80% test coverage** across all core modules. Every
|
|
|
306
297
|
|
|
307
298
|
## 🛣️ Roadmap
|
|
308
299
|
|
|
309
|
-
**v0.1.0 — Hello World**
|
|
300
|
+
**v0.1.0 — Hello World** _(current)_
|
|
301
|
+
|
|
310
302
|
- [x] Core engine — detector, registry, interviewer, executor
|
|
311
303
|
- [x] Functional programming architecture
|
|
312
304
|
- [x] Requirement checking with OS-specific install guides
|
|
@@ -315,17 +307,20 @@ Scaffy maintains a minimum **80% test coverage** across all core modules. Every
|
|
|
315
307
|
- [x] Published on npm
|
|
316
308
|
|
|
317
309
|
**v0.2.0 — Community Ready**
|
|
310
|
+
|
|
318
311
|
- [ ] ESM migration — modern JavaScript standard
|
|
319
312
|
- [ ] Plugin contribution system with automated CI validation
|
|
320
313
|
- [ ] Full documentation site
|
|
321
314
|
- [ ] 15+ framework plugins
|
|
322
315
|
|
|
323
316
|
**v0.3.0 — Growth**
|
|
317
|
+
|
|
324
318
|
- [ ] AI mode — describe your project in plain English
|
|
325
319
|
- [ ] `scaffy add github:user/plugin` — install external plugins
|
|
326
320
|
- [ ] Discord community
|
|
327
321
|
|
|
328
322
|
**v1.0.0 — Ecosystem**
|
|
323
|
+
|
|
329
324
|
- [ ] VS Code extension
|
|
330
325
|
- [ ] Plugin marketplace website
|
|
331
326
|
- [ ] Enterprise adoption
|
|
@@ -389,4 +384,4 @@ Built with dedication by the Scaffy community
|
|
|
389
384
|
·
|
|
390
385
|
<a href="https://github.com/TanvirHossen112/scaffy/discussions">💬 Join the discussion</a>
|
|
391
386
|
|
|
392
|
-
</div>
|
|
387
|
+
</div>
|
package/cli.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import figlet from 'figlet';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import * as detector from './core/detector.js';
|
|
8
|
+
import * as registry from './core/registry.js';
|
|
9
|
+
import * as interviewer from './core/interviewer.js';
|
|
10
|
+
import * as executor from './core/executor.js';
|
|
11
|
+
import { buildPluginUtils, title, error, divider } from './core/utils.js';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
7
14
|
const { version } = require('./package.json');
|
|
8
|
-
const detector = require('./core/detector');
|
|
9
|
-
const registry = require('./core/registry');
|
|
10
|
-
const interviewer = require('./core/interviewer');
|
|
11
|
-
const executor = require('./core/executor');
|
|
12
|
-
const { buildPluginUtils, title, error, divider } = require('./core/utils');
|
|
13
15
|
|
|
14
|
-
// ─── Banner ───────────────────────────────────────────
|
|
15
16
|
const showBanner = () => {
|
|
16
17
|
console.log(
|
|
17
18
|
chalk.cyan(
|
|
@@ -24,39 +25,29 @@ const showBanner = () => {
|
|
|
24
25
|
console.log(chalk.gray(' One command. Any framework. Ready to code.\n'));
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
// ─── Run Full Scaffold Flow ────────────────────────────
|
|
28
28
|
const runScaffold = async framework => {
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const plugin = registry.loadPlugin(framework, version);
|
|
30
|
+
const selectedVersion = await interviewer.askVersion(framework);
|
|
31
|
+
const plugin = await registry.loadPlugin(framework, selectedVersion);
|
|
33
32
|
|
|
34
|
-
// Step 2 — Check requirements
|
|
35
33
|
const requirementsMet = await detector.report(plugin.meta.requires);
|
|
36
|
-
|
|
37
34
|
if (!requirementsMet) {
|
|
38
35
|
process.exit(1);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
// Step 3 — Ask questions
|
|
42
38
|
const answers = await interviewer.askFrameworkQuestions(framework, plugin);
|
|
43
39
|
|
|
44
|
-
// Step 4 — Build utils for plugin
|
|
45
40
|
const utils = buildPluginUtils(executor);
|
|
46
41
|
|
|
47
|
-
// Step 5 — Run scaffold
|
|
48
42
|
divider();
|
|
49
|
-
|
|
50
|
-
chalk.cyan(`Scaffolding ${framework.name} project...`)
|
|
51
|
-
).start();
|
|
52
|
-
|
|
43
|
+
console.log(chalk.cyan(`\n🚀 Scaffolding ${framework.name} project...\n`));
|
|
53
44
|
try {
|
|
54
45
|
await plugin.scaffold(answers, utils);
|
|
55
|
-
|
|
56
|
-
chalk.green(
|
|
46
|
+
console.log(
|
|
47
|
+
chalk.green(`\n✅ ${framework.name} project created successfully!\n`)
|
|
57
48
|
);
|
|
58
49
|
} catch (err) {
|
|
59
|
-
|
|
50
|
+
console.log(chalk.red('\n❌ Scaffolding failed\n'));
|
|
60
51
|
error(err.message);
|
|
61
52
|
process.exit(1);
|
|
62
53
|
}
|
|
@@ -68,7 +59,6 @@ const runScaffold = async framework => {
|
|
|
68
59
|
}
|
|
69
60
|
};
|
|
70
61
|
|
|
71
|
-
// ─── Program Setup ────────────────────────────────────
|
|
72
62
|
const program = new Command();
|
|
73
63
|
|
|
74
64
|
program
|
|
@@ -76,21 +66,16 @@ program
|
|
|
76
66
|
.description('One command. Any framework. Ready to code.')
|
|
77
67
|
.version(version, '-v, --version', 'Show current version');
|
|
78
68
|
|
|
79
|
-
// ─── Default Command ───────────────────────────────────
|
|
80
69
|
program
|
|
81
70
|
.argument('[framework]', 'Framework name to scaffold directly')
|
|
82
71
|
.action(async frameworkQuery => {
|
|
83
72
|
showBanner();
|
|
84
|
-
|
|
85
|
-
// Direct framework mode
|
|
86
73
|
if (frameworkQuery) {
|
|
87
74
|
const framework = await interviewer.askDirectFramework(frameworkQuery);
|
|
88
75
|
if (!framework) process.exit(1);
|
|
89
76
|
await runScaffold(framework);
|
|
90
77
|
return;
|
|
91
78
|
}
|
|
92
|
-
|
|
93
|
-
// Interactive mode
|
|
94
79
|
try {
|
|
95
80
|
title('Select A Framework');
|
|
96
81
|
const framework = await interviewer.askFramework();
|
|
@@ -101,7 +86,6 @@ program
|
|
|
101
86
|
}
|
|
102
87
|
});
|
|
103
88
|
|
|
104
|
-
// ─── List Command ──────────────────────────────────────
|
|
105
89
|
program
|
|
106
90
|
.command('list')
|
|
107
91
|
.description('List all available frameworks')
|
|
@@ -110,15 +94,12 @@ program
|
|
|
110
94
|
registry.displayFrameworks();
|
|
111
95
|
});
|
|
112
96
|
|
|
113
|
-
// ─── Search Command ────────────────────────────────────
|
|
114
97
|
program
|
|
115
98
|
.command('search <query>')
|
|
116
99
|
.description('Search for a framework by name or language')
|
|
117
100
|
.action(query => {
|
|
118
101
|
showBanner();
|
|
119
|
-
|
|
120
102
|
const results = registry.searchFrameworks(query);
|
|
121
|
-
|
|
122
103
|
if (results.length === 0) {
|
|
123
104
|
console.log(chalk.red(`\n No frameworks found for "${query}"\n`));
|
|
124
105
|
console.log(
|
|
@@ -126,18 +107,14 @@ program
|
|
|
126
107
|
);
|
|
127
108
|
return;
|
|
128
109
|
}
|
|
129
|
-
|
|
130
110
|
console.log(chalk.bold(`\n 🔍 Results for "${query}":\n`));
|
|
131
|
-
|
|
132
111
|
results.forEach(f => {
|
|
133
112
|
console.log(
|
|
134
113
|
chalk.white(` • ${f.name}`) +
|
|
135
114
|
chalk.gray(` (${f.language}) — scaffy ${f.alias[0]}`)
|
|
136
115
|
);
|
|
137
116
|
});
|
|
138
|
-
|
|
139
117
|
console.log('');
|
|
140
118
|
});
|
|
141
119
|
|
|
142
|
-
// ─── Parse ────────────────────────────────────────────
|
|
143
120
|
program.parse(process.argv);
|
package/core/detector.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
5
|
|
|
6
6
|
const getOS = () => {
|
|
7
7
|
const platform = os.platform();
|
|
@@ -49,25 +49,18 @@ const buildToolResult = (tool, installed, versionOk, extra = {}) => ({
|
|
|
49
49
|
|
|
50
50
|
const checkTool = requirement => {
|
|
51
51
|
const { tool, checkCommand, parseVersion, minVersion } = requirement;
|
|
52
|
-
|
|
53
52
|
const { success, output } = runCommand(checkCommand);
|
|
54
|
-
|
|
55
53
|
if (!success) {
|
|
56
54
|
return buildToolResult(tool, false, false);
|
|
57
55
|
}
|
|
58
|
-
|
|
59
56
|
if (!parseVersion || !minVersion) {
|
|
60
57
|
return buildToolResult(tool, true, true);
|
|
61
58
|
}
|
|
62
|
-
|
|
63
59
|
const installedVersion = parseVersionFromOutput(output, parseVersion);
|
|
64
|
-
|
|
65
60
|
if (!installedVersion) {
|
|
66
61
|
return buildToolResult(tool, true, true);
|
|
67
62
|
}
|
|
68
|
-
|
|
69
63
|
const versionOk = satisfiesMinVersion(installedVersion, minVersion);
|
|
70
|
-
|
|
71
64
|
return buildToolResult(tool, true, versionOk, {
|
|
72
65
|
installedVersion,
|
|
73
66
|
...(!versionOk && { requiredVersion: minVersion }),
|
|
@@ -79,9 +72,7 @@ const checkAll = requirements => {
|
|
|
79
72
|
...checkTool(req),
|
|
80
73
|
installGuide: req.installGuide,
|
|
81
74
|
}));
|
|
82
|
-
|
|
83
75
|
const allPassed = results.every(r => r.installed && r.versionOk);
|
|
84
|
-
|
|
85
76
|
return { allPassed, results };
|
|
86
77
|
};
|
|
87
78
|
|
|
@@ -89,7 +80,6 @@ const formatToolStatus = result => {
|
|
|
89
80
|
if (result.installed && result.versionOk) {
|
|
90
81
|
return chalk.green(` ✅ ${result.tool}`) + chalk.gray(' — found');
|
|
91
82
|
}
|
|
92
|
-
|
|
93
83
|
if (result.installed && !result.versionOk) {
|
|
94
84
|
return (
|
|
95
85
|
chalk.yellow(` ⚠️ ${result.tool}`) +
|
|
@@ -99,35 +89,27 @@ const formatToolStatus = result => {
|
|
|
99
89
|
)
|
|
100
90
|
);
|
|
101
91
|
}
|
|
102
|
-
|
|
103
92
|
return chalk.red(` ❌ ${result.tool}`) + chalk.gray(' — not found');
|
|
104
93
|
};
|
|
105
94
|
|
|
106
95
|
const formatInstallGuide = (guide, currentOS) => {
|
|
107
96
|
if (!guide) return [];
|
|
108
|
-
|
|
109
97
|
const lines = [];
|
|
110
|
-
|
|
111
98
|
if (guide[currentOS]) {
|
|
112
99
|
lines.push(chalk.cyan(` 💻 Install: `) + chalk.white(guide[currentOS]));
|
|
113
100
|
}
|
|
114
|
-
|
|
115
101
|
if (guide.docs) {
|
|
116
102
|
lines.push(chalk.cyan(` 📖 Docs: `) + chalk.underline(guide.docs));
|
|
117
103
|
}
|
|
118
|
-
|
|
119
104
|
return lines;
|
|
120
105
|
};
|
|
121
106
|
|
|
122
107
|
const formatResult = currentOS => result => {
|
|
123
108
|
const lines = [formatToolStatus(result)];
|
|
124
|
-
|
|
125
109
|
const needsGuide = !result.installed || !result.versionOk;
|
|
126
|
-
|
|
127
110
|
if (needsGuide) {
|
|
128
111
|
lines.push(...formatInstallGuide(result.installGuide, currentOS));
|
|
129
112
|
}
|
|
130
|
-
|
|
131
113
|
return lines.join('\n');
|
|
132
114
|
};
|
|
133
115
|
|
|
@@ -144,43 +126,35 @@ const formatSuccessMessage = () =>
|
|
|
144
126
|
|
|
145
127
|
const report = requirements => {
|
|
146
128
|
console.log(chalk.bold('\n🔍 Checking requirements...\n'));
|
|
147
|
-
|
|
148
129
|
const { allPassed, results } = checkAll(requirements);
|
|
149
130
|
const currentOS = getOS();
|
|
150
|
-
|
|
151
131
|
results.map(formatResult(currentOS)).forEach(line => console.log(line));
|
|
152
|
-
|
|
153
132
|
console.log(allPassed ? formatSuccessMessage() : formatFailureMessage());
|
|
154
|
-
|
|
155
133
|
return allPassed;
|
|
156
134
|
};
|
|
157
135
|
|
|
158
136
|
const detectAvailableChoices = async choices => {
|
|
159
137
|
if (!choices || choices.length === 0) return [];
|
|
160
|
-
|
|
161
138
|
const results = await Promise.all(
|
|
162
139
|
choices.map(async choice => {
|
|
163
140
|
try {
|
|
164
141
|
const { success, output } = runCommand(choice.checkCommand);
|
|
165
|
-
|
|
166
142
|
if (!success || !output || output.toString().trim() === '') {
|
|
167
143
|
return { ...choice, installed: false };
|
|
168
144
|
}
|
|
169
|
-
|
|
170
145
|
return { ...choice, installed: true };
|
|
171
146
|
} catch {
|
|
172
147
|
return { ...choice, installed: false };
|
|
173
148
|
}
|
|
174
149
|
})
|
|
175
150
|
);
|
|
176
|
-
|
|
177
151
|
const available = results.filter(c => c.installed);
|
|
178
152
|
return available.length > 0
|
|
179
153
|
? available
|
|
180
154
|
: [{ ...choices[0], installed: false }];
|
|
181
155
|
};
|
|
182
156
|
|
|
183
|
-
|
|
157
|
+
export {
|
|
184
158
|
getOS,
|
|
185
159
|
runCommand,
|
|
186
160
|
parseVersionFromOutput,
|
package/core/executor.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
5
|
|
|
6
6
|
const run = command =>
|
|
7
7
|
new Promise((resolve, reject) => {
|
|
8
8
|
console.log(chalk.gray(`\n $ ${command}\n`));
|
|
9
|
-
|
|
10
9
|
const [cmd, ...args] = command.split(' ');
|
|
11
|
-
|
|
12
10
|
const child = spawn(cmd, args, {
|
|
13
11
|
stdio: 'inherit',
|
|
14
12
|
shell: true,
|
|
15
13
|
});
|
|
16
|
-
|
|
17
14
|
child.on('close', code => {
|
|
18
15
|
if (code !== 0) {
|
|
19
16
|
reject(new Error(`Command failed with exit code ${code}: ${command}`));
|
|
@@ -21,7 +18,6 @@ const run = command =>
|
|
|
21
18
|
resolve();
|
|
22
19
|
}
|
|
23
20
|
});
|
|
24
|
-
|
|
25
21
|
child.on('error', err => {
|
|
26
22
|
reject(new Error(`Failed to run command: ${command}\n${err.message}`));
|
|
27
23
|
});
|
|
@@ -29,16 +25,13 @@ const run = command =>
|
|
|
29
25
|
|
|
30
26
|
const runInProject = (projectName, command) => {
|
|
31
27
|
console.log(chalk.gray(`\n $ cd ${projectName} && ${command}\n`));
|
|
32
|
-
|
|
33
28
|
return new Promise((resolve, reject) => {
|
|
34
29
|
const [cmd, ...args] = command.split(' ');
|
|
35
|
-
|
|
36
30
|
const child = spawn(cmd, args, {
|
|
37
31
|
stdio: 'inherit',
|
|
38
32
|
shell: true,
|
|
39
33
|
cwd: path.join(process.cwd(), projectName),
|
|
40
34
|
});
|
|
41
|
-
|
|
42
35
|
child.on('close', code => {
|
|
43
36
|
if (code !== 0) {
|
|
44
37
|
reject(new Error(`Command failed with exit code ${code}: ${command}`));
|
|
@@ -46,7 +39,6 @@ const runInProject = (projectName, command) => {
|
|
|
46
39
|
resolve();
|
|
47
40
|
}
|
|
48
41
|
});
|
|
49
|
-
|
|
50
42
|
child.on('error', err => {
|
|
51
43
|
reject(new Error(`Failed to run command: ${command}\n${err.message}`));
|
|
52
44
|
});
|
|
@@ -55,13 +47,10 @@ const runInProject = (projectName, command) => {
|
|
|
55
47
|
|
|
56
48
|
const setEnv = (projectName, vars) => {
|
|
57
49
|
const envPath = path.join(process.cwd(), projectName, '.env');
|
|
58
|
-
|
|
59
50
|
if (!fs.existsSync(envPath)) {
|
|
60
51
|
throw new Error(`.env file not found in ${projectName}`);
|
|
61
52
|
}
|
|
62
|
-
|
|
63
53
|
let content = fs.readFileSync(envPath, 'utf8');
|
|
64
|
-
|
|
65
54
|
Object.entries(vars).forEach(([key, value]) => {
|
|
66
55
|
const regex = new RegExp(`^${key}=.*`, 'm');
|
|
67
56
|
if (regex.test(content)) {
|
|
@@ -70,36 +59,25 @@ const setEnv = (projectName, vars) => {
|
|
|
70
59
|
content += `\n${key}=${value}`;
|
|
71
60
|
}
|
|
72
61
|
});
|
|
73
|
-
|
|
74
62
|
fs.writeFileSync(envPath, content);
|
|
75
63
|
};
|
|
76
64
|
|
|
77
65
|
const appendToFile = (filePath, content) => {
|
|
78
66
|
const fullPath = path.join(process.cwd(), filePath);
|
|
79
67
|
const dir = path.dirname(fullPath);
|
|
80
|
-
|
|
81
68
|
if (!fs.existsSync(dir)) {
|
|
82
69
|
fs.mkdirSync(dir, { recursive: true });
|
|
83
70
|
}
|
|
84
|
-
|
|
85
71
|
fs.appendFileSync(fullPath, content);
|
|
86
72
|
};
|
|
87
73
|
|
|
88
74
|
const createFile = (filePath, content = '') => {
|
|
89
75
|
const fullPath = path.join(process.cwd(), filePath);
|
|
90
76
|
const dir = path.dirname(fullPath);
|
|
91
|
-
|
|
92
77
|
if (!fs.existsSync(dir)) {
|
|
93
78
|
fs.mkdirSync(dir, { recursive: true });
|
|
94
79
|
}
|
|
95
|
-
|
|
96
80
|
fs.writeFileSync(fullPath, content);
|
|
97
81
|
};
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
run,
|
|
101
|
-
runInProject,
|
|
102
|
-
setEnv,
|
|
103
|
-
appendToFile,
|
|
104
|
-
createFile,
|
|
105
|
-
};
|
|
83
|
+
export { run, runInProject, setEnv, appendToFile, createFile };
|
package/core/interviewer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { getAvailableFrameworks, findFramework } from './registry.js';
|
|
4
4
|
|
|
5
5
|
const baseQuestions = framework => [
|
|
6
6
|
{
|
|
@@ -26,12 +26,15 @@ const buildQuestions = (framework, pluginQuestions) => [
|
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
const buildFrameworkChoices = () =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
chalk.
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
getAvailableFrameworks().map(f => {
|
|
30
|
+
const versionsStr = f.versions
|
|
31
|
+
.map(v => (v === f.latest ? chalk.green(`${v} (latest)`) : chalk.gray(v)))
|
|
32
|
+
.join(chalk.gray(', '));
|
|
33
|
+
return {
|
|
34
|
+
name: `${f.name} ${chalk.gray(`(${f.language})`)} — ` + versionsStr,
|
|
35
|
+
value: f,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
35
38
|
|
|
36
39
|
const buildVersionChoices = framework =>
|
|
37
40
|
framework.versions.map(v => ({
|
|
@@ -42,7 +45,7 @@ const buildVersionChoices = framework =>
|
|
|
42
45
|
const askFramework = async () => {
|
|
43
46
|
const { framework } = await inquirer.prompt([
|
|
44
47
|
{
|
|
45
|
-
type: '
|
|
48
|
+
type: 'select',
|
|
46
49
|
name: 'framework',
|
|
47
50
|
message: 'Which framework?',
|
|
48
51
|
choices: buildFrameworkChoices(),
|
|
@@ -55,22 +58,19 @@ const askVersion = async framework => {
|
|
|
55
58
|
if (framework.versions.length === 1) {
|
|
56
59
|
return framework.latest;
|
|
57
60
|
}
|
|
58
|
-
|
|
59
61
|
const { version } = await inquirer.prompt([
|
|
60
62
|
{
|
|
61
|
-
type: '
|
|
63
|
+
type: 'select',
|
|
62
64
|
name: 'version',
|
|
63
65
|
message: 'Which version?',
|
|
64
66
|
choices: buildVersionChoices(framework),
|
|
65
67
|
},
|
|
66
68
|
]);
|
|
67
|
-
|
|
68
69
|
return version;
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
const askDirectFramework = async query => {
|
|
72
73
|
const framework = findFramework(query);
|
|
73
|
-
|
|
74
74
|
if (!framework) {
|
|
75
75
|
console.log(chalk.red(`\n❌ Framework "${query}" not found.\n`));
|
|
76
76
|
console.log(
|
|
@@ -78,20 +78,16 @@ const askDirectFramework = async query => {
|
|
|
78
78
|
);
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
81
|
return framework;
|
|
83
82
|
};
|
|
84
83
|
|
|
85
84
|
const askFrameworkQuestions = async (framework, plugin) => {
|
|
86
85
|
console.log(chalk.bold(`\n🚀 Setting up ${framework.name}\n`));
|
|
87
|
-
|
|
88
86
|
const pluginQuestions =
|
|
89
87
|
typeof plugin.questions === 'function'
|
|
90
88
|
? await plugin.questions()
|
|
91
89
|
: plugin.questions;
|
|
92
|
-
|
|
93
90
|
const questions = buildQuestions(framework, pluginQuestions);
|
|
94
|
-
|
|
95
91
|
const answers = await inquirer.prompt(questions);
|
|
96
92
|
return answers;
|
|
97
93
|
};
|
|
@@ -125,7 +121,7 @@ const runInteractiveMode = async () => {
|
|
|
125
121
|
}
|
|
126
122
|
};
|
|
127
123
|
|
|
128
|
-
|
|
124
|
+
export {
|
|
129
125
|
baseQuestions,
|
|
130
126
|
buildQuestions,
|
|
131
127
|
buildFrameworkChoices,
|
package/core/plugin-validator.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// TODO: Coming in Sprint 1
|
|
2
|
-
|
|
2
|
+
export default {};
|