scaffy-tool 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.
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/cli.js +143 -0
- package/core/.gitkeep +0 -0
- package/core/detector.js +198 -0
- package/core/executor.js +105 -0
- package/core/interviewer.js +140 -0
- package/core/plugin-validator.js +2 -0
- package/core/registry.js +128 -0
- package/core/utils.js +63 -0
- package/package.json +82 -0
- package/registry/index.json +85 -0
- package/registry/javascript/nestjs/plugin.json +44 -0
- package/registry/javascript/nestjs/v10/questions.js +59 -0
- package/registry/javascript/nestjs/v10/scaffold.js +94 -0
- package/registry/javascript/vuejs/plugin.json +44 -0
- package/registry/javascript/vuejs/v3/questions.js +64 -0
- package/registry/javascript/vuejs/v3/scaffold.js +53 -0
- package/registry/php/laravel/plugin.json +34 -0
- package/registry/php/laravel/v11/questions.js +44 -0
- package/registry/php/laravel/v11/scaffold.js +74 -0
package/core/detector.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const semver = require('semver');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
const getOS = () => {
|
|
7
|
+
const platform = os.platform();
|
|
8
|
+
if (platform === 'darwin') return 'mac';
|
|
9
|
+
if (platform === 'win32') return 'windows';
|
|
10
|
+
return 'linux';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const runCommand = command => {
|
|
14
|
+
try {
|
|
15
|
+
return {
|
|
16
|
+
success: true,
|
|
17
|
+
output: execSync(command, {
|
|
18
|
+
stdio: 'pipe',
|
|
19
|
+
timeout: 5000,
|
|
20
|
+
}).toString(),
|
|
21
|
+
};
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
output: '',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const parseVersionFromOutput = (output, parseVersion) => {
|
|
31
|
+
if (!parseVersion || !output) return null;
|
|
32
|
+
const match = output.match(new RegExp(parseVersion));
|
|
33
|
+
return match ? match[1] : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const satisfiesMinVersion = (installedVersion, minVersion) => {
|
|
37
|
+
if (!installedVersion || !minVersion) return true;
|
|
38
|
+
const clean = semver.coerce(installedVersion);
|
|
39
|
+
if (!clean) return true;
|
|
40
|
+
return semver.gte(clean, minVersion);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const buildToolResult = (tool, installed, versionOk, extra = {}) => ({
|
|
44
|
+
tool,
|
|
45
|
+
installed,
|
|
46
|
+
versionOk,
|
|
47
|
+
...extra,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const checkTool = requirement => {
|
|
51
|
+
const { tool, checkCommand, parseVersion, minVersion } = requirement;
|
|
52
|
+
|
|
53
|
+
const { success, output } = runCommand(checkCommand);
|
|
54
|
+
|
|
55
|
+
if (!success) {
|
|
56
|
+
return buildToolResult(tool, false, false);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!parseVersion || !minVersion) {
|
|
60
|
+
return buildToolResult(tool, true, true);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const installedVersion = parseVersionFromOutput(output, parseVersion);
|
|
64
|
+
|
|
65
|
+
if (!installedVersion) {
|
|
66
|
+
return buildToolResult(tool, true, true);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const versionOk = satisfiesMinVersion(installedVersion, minVersion);
|
|
70
|
+
|
|
71
|
+
return buildToolResult(tool, true, versionOk, {
|
|
72
|
+
installedVersion,
|
|
73
|
+
...(!versionOk && { requiredVersion: minVersion }),
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const checkAll = requirements => {
|
|
78
|
+
const results = requirements.map(req => ({
|
|
79
|
+
...checkTool(req),
|
|
80
|
+
installGuide: req.installGuide,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const allPassed = results.every(r => r.installed && r.versionOk);
|
|
84
|
+
|
|
85
|
+
return { allPassed, results };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const formatToolStatus = result => {
|
|
89
|
+
if (result.installed && result.versionOk) {
|
|
90
|
+
return chalk.green(` ✅ ${result.tool}`) + chalk.gray(' — found');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result.installed && !result.versionOk) {
|
|
94
|
+
return (
|
|
95
|
+
chalk.yellow(` ⚠️ ${result.tool}`) +
|
|
96
|
+
chalk.gray(
|
|
97
|
+
` — v${result.installedVersion} found,` +
|
|
98
|
+
` v${result.requiredVersion}+ required`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return chalk.red(` ❌ ${result.tool}`) + chalk.gray(' — not found');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const formatInstallGuide = (guide, currentOS) => {
|
|
107
|
+
if (!guide) return [];
|
|
108
|
+
|
|
109
|
+
const lines = [];
|
|
110
|
+
|
|
111
|
+
if (guide[currentOS]) {
|
|
112
|
+
lines.push(chalk.cyan(` 💻 Install: `) + chalk.white(guide[currentOS]));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (guide.docs) {
|
|
116
|
+
lines.push(chalk.cyan(` 📖 Docs: `) + chalk.underline(guide.docs));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return lines;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const formatResult = currentOS => result => {
|
|
123
|
+
const lines = [formatToolStatus(result)];
|
|
124
|
+
|
|
125
|
+
const needsGuide = !result.installed || !result.versionOk;
|
|
126
|
+
|
|
127
|
+
if (needsGuide) {
|
|
128
|
+
lines.push(...formatInstallGuide(result.installGuide, currentOS));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return lines.join('\n');
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const formatFailureMessage = () =>
|
|
135
|
+
chalk.red(`
|
|
136
|
+
──────────────────────────────────────────
|
|
137
|
+
❌ Requirements not met.
|
|
138
|
+
Please install missing tools and retry.
|
|
139
|
+
Nothing was created. Your system is clean.
|
|
140
|
+
──────────────────────────────────────────\n`);
|
|
141
|
+
|
|
142
|
+
const formatSuccessMessage = () =>
|
|
143
|
+
chalk.green('\n ✅ All requirements met!\n');
|
|
144
|
+
|
|
145
|
+
const report = requirements => {
|
|
146
|
+
console.log(chalk.bold('\n🔍 Checking requirements...\n'));
|
|
147
|
+
|
|
148
|
+
const { allPassed, results } = checkAll(requirements);
|
|
149
|
+
const currentOS = getOS();
|
|
150
|
+
|
|
151
|
+
results.map(formatResult(currentOS)).forEach(line => console.log(line));
|
|
152
|
+
|
|
153
|
+
console.log(allPassed ? formatSuccessMessage() : formatFailureMessage());
|
|
154
|
+
|
|
155
|
+
return allPassed;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const detectAvailableChoices = async choices => {
|
|
159
|
+
if (!choices || choices.length === 0) return [];
|
|
160
|
+
|
|
161
|
+
const results = await Promise.all(
|
|
162
|
+
choices.map(async choice => {
|
|
163
|
+
try {
|
|
164
|
+
const { success, output } = runCommand(choice.checkCommand);
|
|
165
|
+
|
|
166
|
+
if (!success || !output || output.toString().trim() === '') {
|
|
167
|
+
return { ...choice, installed: false };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { ...choice, installed: true };
|
|
171
|
+
} catch {
|
|
172
|
+
return { ...choice, installed: false };
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const available = results.filter(c => c.installed);
|
|
178
|
+
return available.length > 0
|
|
179
|
+
? available
|
|
180
|
+
: [{ ...choices[0], installed: false }];
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
getOS,
|
|
185
|
+
runCommand,
|
|
186
|
+
parseVersionFromOutput,
|
|
187
|
+
satisfiesMinVersion,
|
|
188
|
+
buildToolResult,
|
|
189
|
+
checkTool,
|
|
190
|
+
checkAll,
|
|
191
|
+
formatToolStatus,
|
|
192
|
+
formatInstallGuide,
|
|
193
|
+
formatResult,
|
|
194
|
+
formatFailureMessage,
|
|
195
|
+
formatSuccessMessage,
|
|
196
|
+
report,
|
|
197
|
+
detectAvailableChoices,
|
|
198
|
+
};
|
package/core/executor.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
const run = command =>
|
|
7
|
+
new Promise((resolve, reject) => {
|
|
8
|
+
console.log(chalk.gray(`\n $ ${command}\n`));
|
|
9
|
+
|
|
10
|
+
const [cmd, ...args] = command.split(' ');
|
|
11
|
+
|
|
12
|
+
const child = spawn(cmd, args, {
|
|
13
|
+
stdio: 'inherit',
|
|
14
|
+
shell: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
child.on('close', code => {
|
|
18
|
+
if (code !== 0) {
|
|
19
|
+
reject(new Error(`Command failed with exit code ${code}: ${command}`));
|
|
20
|
+
} else {
|
|
21
|
+
resolve();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
child.on('error', err => {
|
|
26
|
+
reject(new Error(`Failed to run command: ${command}\n${err.message}`));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const runInProject = (projectName, command) => {
|
|
31
|
+
console.log(chalk.gray(`\n $ cd ${projectName} && ${command}\n`));
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const [cmd, ...args] = command.split(' ');
|
|
35
|
+
|
|
36
|
+
const child = spawn(cmd, args, {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
shell: true,
|
|
39
|
+
cwd: path.join(process.cwd(), projectName),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
child.on('close', code => {
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
reject(new Error(`Command failed with exit code ${code}: ${command}`));
|
|
45
|
+
} else {
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
child.on('error', err => {
|
|
51
|
+
reject(new Error(`Failed to run command: ${command}\n${err.message}`));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const setEnv = (projectName, vars) => {
|
|
57
|
+
const envPath = path.join(process.cwd(), projectName, '.env');
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(envPath)) {
|
|
60
|
+
throw new Error(`.env file not found in ${projectName}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let content = fs.readFileSync(envPath, 'utf8');
|
|
64
|
+
|
|
65
|
+
Object.entries(vars).forEach(([key, value]) => {
|
|
66
|
+
const regex = new RegExp(`^${key}=.*`, 'm');
|
|
67
|
+
if (regex.test(content)) {
|
|
68
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
69
|
+
} else {
|
|
70
|
+
content += `\n${key}=${value}`;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(envPath, content);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const appendToFile = (filePath, content) => {
|
|
78
|
+
const fullPath = path.join(process.cwd(), filePath);
|
|
79
|
+
const dir = path.dirname(fullPath);
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(dir)) {
|
|
82
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fs.appendFileSync(fullPath, content);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const createFile = (filePath, content = '') => {
|
|
89
|
+
const fullPath = path.join(process.cwd(), filePath);
|
|
90
|
+
const dir = path.dirname(fullPath);
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(dir)) {
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync(fullPath, content);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
run,
|
|
101
|
+
runInProject,
|
|
102
|
+
setEnv,
|
|
103
|
+
appendToFile,
|
|
104
|
+
createFile,
|
|
105
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { getFrameworks, findFramework } = require('./registry');
|
|
4
|
+
|
|
5
|
+
const baseQuestions = framework => [
|
|
6
|
+
{
|
|
7
|
+
type: 'input',
|
|
8
|
+
name: 'projectName',
|
|
9
|
+
message: 'Project name?',
|
|
10
|
+
default: `my-${framework.name.toLowerCase()}-app`,
|
|
11
|
+
validate: input => {
|
|
12
|
+
if (!input.trim()) {
|
|
13
|
+
return 'Project name is required';
|
|
14
|
+
}
|
|
15
|
+
if (!/^[a-z0-9-_]+$/.test(input)) {
|
|
16
|
+
return 'Use only lowercase letters, numbers, - and _';
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const buildQuestions = (framework, pluginQuestions) => [
|
|
24
|
+
...baseQuestions(framework),
|
|
25
|
+
...pluginQuestions,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const buildFrameworkChoices = () =>
|
|
29
|
+
getFrameworks().map(f => ({
|
|
30
|
+
name:
|
|
31
|
+
`${f.name} ${chalk.gray(`(${f.language})`)} ` +
|
|
32
|
+
chalk.cyan(`— latest: ${f.latest}`),
|
|
33
|
+
value: f,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const buildVersionChoices = framework =>
|
|
37
|
+
framework.versions.map(v => ({
|
|
38
|
+
name: v === framework.latest ? `${v} ${chalk.cyan('(latest ⭐)')}` : v,
|
|
39
|
+
value: v,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const askFramework = async () => {
|
|
43
|
+
const { framework } = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'list',
|
|
46
|
+
name: 'framework',
|
|
47
|
+
message: 'Which framework?',
|
|
48
|
+
choices: buildFrameworkChoices(),
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
return framework;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const askVersion = async framework => {
|
|
55
|
+
if (framework.versions.length === 1) {
|
|
56
|
+
return framework.latest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { version } = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'list',
|
|
62
|
+
name: 'version',
|
|
63
|
+
message: 'Which version?',
|
|
64
|
+
choices: buildVersionChoices(framework),
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
return version;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const askDirectFramework = async query => {
|
|
72
|
+
const framework = findFramework(query);
|
|
73
|
+
|
|
74
|
+
if (!framework) {
|
|
75
|
+
console.log(chalk.red(`\n❌ Framework "${query}" not found.\n`));
|
|
76
|
+
console.log(
|
|
77
|
+
chalk.gray(' Run scaffy list to see all available frameworks.\n')
|
|
78
|
+
);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return framework;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const askFrameworkQuestions = async (framework, plugin) => {
|
|
86
|
+
console.log(chalk.bold(`\n🚀 Setting up ${framework.name}\n`));
|
|
87
|
+
|
|
88
|
+
const pluginQuestions =
|
|
89
|
+
typeof plugin.questions === 'function'
|
|
90
|
+
? await plugin.questions()
|
|
91
|
+
: plugin.questions;
|
|
92
|
+
|
|
93
|
+
const questions = buildQuestions(framework, pluginQuestions);
|
|
94
|
+
|
|
95
|
+
const answers = await inquirer.prompt(questions);
|
|
96
|
+
return answers;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handlePromptError = err => {
|
|
100
|
+
if (err.isTtyError) {
|
|
101
|
+
console.log(chalk.red('\n❌ Terminal not supported\n'));
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.yellow('\n👋 Scaffolding cancelled\n'));
|
|
104
|
+
}
|
|
105
|
+
process.exit(0);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const runInterview = async (framework, plugin) => {
|
|
109
|
+
try {
|
|
110
|
+
const version = await askVersion(framework);
|
|
111
|
+
const answers = await askFrameworkQuestions(framework, plugin);
|
|
112
|
+
return { version, answers };
|
|
113
|
+
} catch (err) {
|
|
114
|
+
handlePromptError(err);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const runInteractiveMode = async () => {
|
|
119
|
+
try {
|
|
120
|
+
const framework = await askFramework();
|
|
121
|
+
const version = await askVersion(framework);
|
|
122
|
+
return { framework, version };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
handlePromptError(err);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
baseQuestions,
|
|
130
|
+
buildQuestions,
|
|
131
|
+
buildFrameworkChoices,
|
|
132
|
+
buildVersionChoices,
|
|
133
|
+
askFramework,
|
|
134
|
+
askVersion,
|
|
135
|
+
askDirectFramework,
|
|
136
|
+
askFrameworkQuestions,
|
|
137
|
+
handlePromptError,
|
|
138
|
+
runInterview,
|
|
139
|
+
runInteractiveMode,
|
|
140
|
+
};
|
package/core/registry.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const REGISTRY_PATH = path.join(__dirname, '..', 'registry');
|
|
6
|
+
const INDEX_PATH = path.join(REGISTRY_PATH, 'index.json');
|
|
7
|
+
|
|
8
|
+
const loadIndex = () => {
|
|
9
|
+
try {
|
|
10
|
+
const raw = fs.readFileSync(INDEX_PATH, 'utf8');
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getFrameworks = () => {
|
|
18
|
+
const index = loadIndex();
|
|
19
|
+
if (!index) {
|
|
20
|
+
console.log(chalk.red('❌ Failed to load registry/index.json'));
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return index.frameworks;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const findFramework = query => {
|
|
27
|
+
const q = query.toLowerCase().trim();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
getFrameworks().find(
|
|
31
|
+
f =>
|
|
32
|
+
f.name.toLowerCase() === q || f.alias.some(a => a.toLowerCase() === q)
|
|
33
|
+
) || null
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const searchFrameworks = query => {
|
|
38
|
+
const q = query.toLowerCase().trim();
|
|
39
|
+
|
|
40
|
+
return getFrameworks().filter(
|
|
41
|
+
f =>
|
|
42
|
+
f.name.toLowerCase().includes(q) ||
|
|
43
|
+
f.language.toLowerCase().includes(q) ||
|
|
44
|
+
f.alias.some(a => a.toLowerCase().includes(q))
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const groupByLanguage = frameworks =>
|
|
49
|
+
frameworks.reduce(
|
|
50
|
+
(acc, f) => ({
|
|
51
|
+
...acc,
|
|
52
|
+
[f.language]: [...(acc[f.language] || []), f],
|
|
53
|
+
}),
|
|
54
|
+
{}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const validatePluginFiles = (pluginPath, version) => {
|
|
58
|
+
const versionPath = path.join(pluginPath, version);
|
|
59
|
+
|
|
60
|
+
const requiredFiles = [
|
|
61
|
+
{ path: path.join(pluginPath, 'plugin.json'), name: 'plugin.json' },
|
|
62
|
+
{ path: path.join(versionPath, 'questions.js'), name: 'questions.js' },
|
|
63
|
+
{ path: path.join(versionPath, 'scaffold.js'), name: 'scaffold.js' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const missing = requiredFiles
|
|
67
|
+
.filter(f => !fs.existsSync(f.path))
|
|
68
|
+
.map(f => f.name);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
valid: missing.length === 0,
|
|
72
|
+
missing,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const loadPlugin = (framework, version) => {
|
|
77
|
+
const pluginPath = path.join(REGISTRY_PATH, framework.path);
|
|
78
|
+
const versionPath = path.join(pluginPath, version);
|
|
79
|
+
|
|
80
|
+
const { valid, missing } = validatePluginFiles(pluginPath, version);
|
|
81
|
+
|
|
82
|
+
if (!valid) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Missing files for ${framework.name} ${version}: ` + missing.join(', ')
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
meta: require(path.join(pluginPath, 'plugin.json')),
|
|
90
|
+
questions: require(path.join(versionPath, 'questions.js')),
|
|
91
|
+
scaffold: require(path.join(versionPath, 'scaffold.js')),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const formatFrameworkLine = f =>
|
|
96
|
+
chalk.white(` • ${f.name}`) +
|
|
97
|
+
chalk.gray(` (${f.alias.join(', ')}) — latest: ${f.latest}`);
|
|
98
|
+
|
|
99
|
+
const displayFrameworks = () => {
|
|
100
|
+
const frameworks = getFrameworks();
|
|
101
|
+
|
|
102
|
+
if (frameworks.length === 0) {
|
|
103
|
+
console.log(chalk.red('\n❌ No frameworks found\n'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const grouped = groupByLanguage(frameworks);
|
|
108
|
+
|
|
109
|
+
console.log(chalk.bold('\n📦 Available Frameworks:\n'));
|
|
110
|
+
|
|
111
|
+
Object.entries(grouped).forEach(([lang, list]) => {
|
|
112
|
+
console.log(chalk.cyan(` ${lang.toUpperCase()}`));
|
|
113
|
+
list.forEach(f => console.log(formatFrameworkLine(f)));
|
|
114
|
+
console.log('');
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
loadIndex,
|
|
120
|
+
getFrameworks,
|
|
121
|
+
findFramework,
|
|
122
|
+
searchFrameworks,
|
|
123
|
+
groupByLanguage,
|
|
124
|
+
validatePluginFiles,
|
|
125
|
+
loadPlugin,
|
|
126
|
+
formatFrameworkLine,
|
|
127
|
+
displayFrameworks,
|
|
128
|
+
};
|
package/core/utils.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const log = message => {
|
|
4
|
+
console.log(chalk.white(` ${message}`));
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// ─── Success Message ───────────────────────────────────
|
|
8
|
+
const success = message => {
|
|
9
|
+
console.log(chalk.green(`\n ${message}\n`));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ─── Warning Message ───────────────────────────────────
|
|
13
|
+
const warn = message => {
|
|
14
|
+
console.log(chalk.yellow(` ⚠️ ${message}`));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ─── Error Message ─────────────────────────────────────
|
|
18
|
+
const error = message => {
|
|
19
|
+
console.log(chalk.red(` ❌ ${message}`));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ─── Section Title ─────────────────────────────────────
|
|
23
|
+
const title = message => {
|
|
24
|
+
console.log(chalk.cyan.bold(`\n ── ${message} ──\n`));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// ─── Divider Line ──────────────────────────────────────
|
|
28
|
+
const divider = () => {
|
|
29
|
+
console.log(chalk.gray(' ──────────────────────────────────────────'));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ─── Step Indicator ────────────────────────────────────
|
|
33
|
+
const step = (number, message) => {
|
|
34
|
+
console.log(chalk.cyan(` [${number}]`) + chalk.white(` ${message}`));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ─── Build Utils Object For Plugins ───────────────────
|
|
38
|
+
const buildPluginUtils = executor => ({
|
|
39
|
+
run: executor.run,
|
|
40
|
+
runInProject: executor.runInProject,
|
|
41
|
+
setEnv: executor.setEnv,
|
|
42
|
+
appendToFile: executor.appendToFile,
|
|
43
|
+
createFile: executor.createFile,
|
|
44
|
+
log,
|
|
45
|
+
success,
|
|
46
|
+
warn,
|
|
47
|
+
error,
|
|
48
|
+
title,
|
|
49
|
+
step,
|
|
50
|
+
divider,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── Exports ───────────────────────────────────────────
|
|
54
|
+
module.exports = {
|
|
55
|
+
log,
|
|
56
|
+
success,
|
|
57
|
+
warn,
|
|
58
|
+
error,
|
|
59
|
+
title,
|
|
60
|
+
divider,
|
|
61
|
+
step,
|
|
62
|
+
buildPluginUtils,
|
|
63
|
+
};
|