@enfyra/create-app 0.1.30
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/.claude/settings.local.json +9 -0
- package/README.md +116 -0
- package/components/env-builder.js +22 -0
- package/components/project-setup.js +297 -0
- package/components/prompts.js +58 -0
- package/components/validators.js +140 -0
- package/index.js +104 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Create Enfyra App
|
|
2
|
+
|
|
3
|
+
š **The fastest way to create new Enfyra frontend applications**
|
|
4
|
+
|
|
5
|
+
Create Enfyra App is a CLI tool that instantly scaffolds new Enfyra frontend projects with all the essentials configured for you.
|
|
6
|
+
|
|
7
|
+
## ā ļø Prerequisites
|
|
8
|
+
|
|
9
|
+
**Important:** You need to have an Enfyra backend running before creating your frontend application. The frontend will connect to your backend API.
|
|
10
|
+
|
|
11
|
+
š **Set up your backend first:**
|
|
12
|
+
```bash
|
|
13
|
+
npx @enfyra/create-server my-backend
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
š **[Backend Setup Guide](https://www.npmjs.com/package/@enfyra/create-server)** - Complete instructions for setting up your Enfyra backend
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Using npx (recommended)
|
|
22
|
+
npx @enfyra/create-app my-app
|
|
23
|
+
|
|
24
|
+
# Using yarn
|
|
25
|
+
yarn create @enfyra/app my-app
|
|
26
|
+
|
|
27
|
+
# Using pnpm
|
|
28
|
+
pnpm create @enfyra/app my-app
|
|
29
|
+
|
|
30
|
+
# Using bun
|
|
31
|
+
bun create @enfyra/app my-app
|
|
32
|
+
|
|
33
|
+
# Or install globally
|
|
34
|
+
npm install -g @enfyra/create-app
|
|
35
|
+
create-app my-app
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Demo
|
|
39
|
+
|
|
40
|
+
š **Live Demo**: [https://demo.enfyra.io](https://demo.enfyra.io)
|
|
41
|
+
|
|
42
|
+
## What You Get
|
|
43
|
+
|
|
44
|
+
ā
**Nuxt 3** application ready to go
|
|
45
|
+
ā
**Enfyra SDK** pre-configured
|
|
46
|
+
ā
**TypeScript** support
|
|
47
|
+
ā
**Tailwind CSS** styling
|
|
48
|
+
ā
**Environment** variables setup
|
|
49
|
+
ā
**Dependencies** installed automatically
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- **Node.js** 20.0.0 or higher
|
|
54
|
+
- **Package manager**: npm (8+), yarn (1.22+), pnpm (7+), or bun (1+)
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Create a new project
|
|
59
|
+
```bash
|
|
60
|
+
npx @enfyra/create-app my-project
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Interactive setup
|
|
64
|
+
The CLI will guide you through:
|
|
65
|
+
- Package manager selection
|
|
66
|
+
- API endpoint configuration
|
|
67
|
+
- Development port setup
|
|
68
|
+
|
|
69
|
+
### Environment Variables
|
|
70
|
+
After project creation, you can modify the `.env` file at any time to configure:
|
|
71
|
+
- API endpoints
|
|
72
|
+
- Port
|
|
73
|
+
|
|
74
|
+
The `.env` file is automatically created and can be customized for your specific needs.
|
|
75
|
+
|
|
76
|
+
### Start developing
|
|
77
|
+
```bash
|
|
78
|
+
cd my-project
|
|
79
|
+
npm run dev
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## After Installation
|
|
83
|
+
|
|
84
|
+
Your new Enfyra app is ready! Here's what to do next:
|
|
85
|
+
|
|
86
|
+
### Development
|
|
87
|
+
```bash
|
|
88
|
+
npm run dev # Start development server
|
|
89
|
+
npm run build # Build for production
|
|
90
|
+
npm run preview # Preview production build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Learn More
|
|
94
|
+
|
|
95
|
+
š **[Complete Documentation](https://github.com/dothinh115/enfyra-app#readme)** - Full guide to building with Enfyra
|
|
96
|
+
š§ **[API Reference](https://github.com/dothinh115/enfyra-app/blob/main/docs/API.md)** - Backend integration
|
|
97
|
+
šØ **[UI Components](https://github.com/dothinh115/enfyra-app/blob/main/docs/COMPONENTS.md)** - Pre-built components
|
|
98
|
+
ā” **[Best Practices](https://github.com/dothinh115/enfyra-app/blob/main/docs/BEST_PRACTICES.md)** - Development guidelines
|
|
99
|
+
|
|
100
|
+
## Support
|
|
101
|
+
|
|
102
|
+
Having issues? We're here to help:
|
|
103
|
+
|
|
104
|
+
- š [Report bugs](https://github.com/dothinh115/create-enfyra-app/issues)
|
|
105
|
+
- š¬ [Ask questions](https://github.com/dothinh115/enfyra-app/discussions)
|
|
106
|
+
- š§ [Email support](mailto:dothinh115@gmail.com)
|
|
107
|
+
|
|
108
|
+
## Related
|
|
109
|
+
|
|
110
|
+
- **[Enfyra App](https://github.com/dothinh115/enfyra-app)** - The frontend template
|
|
111
|
+
- **[Enfyra BE](https://github.com/dothinh115/enfyra_be)** - Backend framework
|
|
112
|
+
- **[Create Enfyra Server](https://github.com/dothinh115/create-enfyra-server)** - Backend CLI
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
Built by [dothinh115](https://github.com/dothinh115) ⢠MIT License
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function generateEnvFile(projectPath, config) {
|
|
5
|
+
const envContent = buildEnvContent(config);
|
|
6
|
+
const envPath = path.join(projectPath, '.env');
|
|
7
|
+
|
|
8
|
+
await fs.writeFile(envPath, envContent);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildEnvContent(config) {
|
|
12
|
+
const lines = [
|
|
13
|
+
`API_URL=${config.apiUrl}`,
|
|
14
|
+
`PORT=${config.port}`
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return lines.join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
generateEnvFile
|
|
22
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
const { spawn, execSync } = require('child_process');
|
|
6
|
+
const { downloadTemplate } = require('giget');
|
|
7
|
+
const { generateEnvFile } = require('./env-builder');
|
|
8
|
+
|
|
9
|
+
// Check available package managers with version validation
|
|
10
|
+
function detectPackageManagers() {
|
|
11
|
+
const managers = [];
|
|
12
|
+
|
|
13
|
+
// Check npm (minimum version 8.0.0)
|
|
14
|
+
try {
|
|
15
|
+
const npmVersion = execSync('npm --version', {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: 'pipe',
|
|
18
|
+
shell: true
|
|
19
|
+
}).trim();
|
|
20
|
+
const majorVersion = parseInt(npmVersion.split('.')[0]);
|
|
21
|
+
if (majorVersion >= 8) {
|
|
22
|
+
managers.push({ name: 'npm', value: 'npm', version: npmVersion });
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
// Check yarn (minimum version 1.22.0)
|
|
27
|
+
try {
|
|
28
|
+
const yarnVersion = execSync('yarn --version', {
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
stdio: 'pipe',
|
|
31
|
+
shell: true
|
|
32
|
+
}).trim();
|
|
33
|
+
const [major, minor] = yarnVersion.split('.').map(Number);
|
|
34
|
+
if (major > 1 || (major === 1 && minor >= 22)) {
|
|
35
|
+
managers.push({ name: 'yarn', value: 'yarn', version: yarnVersion });
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
|
|
39
|
+
// Check pnpm (minimum version 7.0.0)
|
|
40
|
+
try {
|
|
41
|
+
const pnpmVersion = execSync('pnpm --version', {
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
stdio: 'pipe',
|
|
44
|
+
shell: true
|
|
45
|
+
}).trim();
|
|
46
|
+
const majorVersion = parseInt(pnpmVersion.split('.')[0]);
|
|
47
|
+
if (majorVersion >= 7) {
|
|
48
|
+
managers.push({ name: 'pnpm', value: 'pnpm', version: pnpmVersion });
|
|
49
|
+
}
|
|
50
|
+
} catch {}
|
|
51
|
+
|
|
52
|
+
// Note: bun is not supported due to native binding compatibility issues
|
|
53
|
+
|
|
54
|
+
return managers;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function setupProject(config, projectPath) {
|
|
58
|
+
const spinner = ora();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Download Nuxt template using giget (will create directory)
|
|
62
|
+
spinner.start(chalk.blue(`Downloading Nuxt template...`));
|
|
63
|
+
await downloadTemplate('github:enfyra/app', {
|
|
64
|
+
dir: projectPath,
|
|
65
|
+
force: true,
|
|
66
|
+
provider: 'github'
|
|
67
|
+
});
|
|
68
|
+
spinner.succeed(chalk.green('Template downloaded successfully'));
|
|
69
|
+
|
|
70
|
+
// Update package.json
|
|
71
|
+
spinner.start(chalk.blue('Updating package.json...'));
|
|
72
|
+
await updatePackageJson(projectPath, config);
|
|
73
|
+
spinner.succeed(chalk.green('Package.json updated'));
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// Update Nuxt config with API URL
|
|
77
|
+
spinner.start(chalk.blue('Configuring Nuxt with API URL...'));
|
|
78
|
+
await updateNuxtConfig(projectPath, config);
|
|
79
|
+
spinner.succeed(chalk.green('Nuxt configuration updated'));
|
|
80
|
+
|
|
81
|
+
// Generate environment file
|
|
82
|
+
spinner.start(chalk.blue('Generating environment file...'));
|
|
83
|
+
await generateEnvFile(projectPath, config);
|
|
84
|
+
spinner.succeed(chalk.green('Environment file created'));
|
|
85
|
+
|
|
86
|
+
// Clean package manager restriction files
|
|
87
|
+
spinner.start(chalk.blue('Cleaning package manager restrictions...'));
|
|
88
|
+
await cleanPackageManagerRestrictions(projectPath);
|
|
89
|
+
spinner.succeed(chalk.green('Package manager restrictions cleaned'));
|
|
90
|
+
|
|
91
|
+
// Install dependencies
|
|
92
|
+
spinner.start(chalk.blue(`Installing dependencies with ${config.packageManager}...`));
|
|
93
|
+
await installDependencies(projectPath, config);
|
|
94
|
+
spinner.succeed(chalk.green('Dependencies installed successfully'));
|
|
95
|
+
|
|
96
|
+
// Initialize git repository
|
|
97
|
+
spinner.start(chalk.blue('Initializing git repository...'));
|
|
98
|
+
await initializeGit(projectPath);
|
|
99
|
+
spinner.succeed(chalk.green('Git repository initialized'));
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
spinner.fail(chalk.red('Setup failed'));
|
|
103
|
+
|
|
104
|
+
// Cleanup on failure
|
|
105
|
+
if (fs.existsSync(projectPath)) {
|
|
106
|
+
await fs.remove(projectPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async function updatePackageJson(projectPath, config) {
|
|
116
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
117
|
+
|
|
118
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
119
|
+
throw new Error('package.json not found in template');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
123
|
+
|
|
124
|
+
// Update project name
|
|
125
|
+
packageJson.name = config.projectName;
|
|
126
|
+
|
|
127
|
+
// Update version
|
|
128
|
+
packageJson.version = '0.1.0';
|
|
129
|
+
|
|
130
|
+
// Clear repository info
|
|
131
|
+
delete packageJson.repository;
|
|
132
|
+
delete packageJson.bugs;
|
|
133
|
+
delete packageJson.homepage;
|
|
134
|
+
|
|
135
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function cleanPackageManagerRestrictions(projectPath) {
|
|
139
|
+
// List of files that can restrict package manager usage
|
|
140
|
+
const restrictionFiles = [
|
|
141
|
+
'.npmrc',
|
|
142
|
+
'.yarnrc',
|
|
143
|
+
'.yarnrc.yml',
|
|
144
|
+
'pnpm-workspace.yaml',
|
|
145
|
+
'package-lock.json',
|
|
146
|
+
'yarn.lock',
|
|
147
|
+
'pnpm-lock.yaml',
|
|
148
|
+
'bun.lockb'
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
for (const file of restrictionFiles) {
|
|
152
|
+
const filePath = path.join(projectPath, file);
|
|
153
|
+
if (fs.existsSync(filePath)) {
|
|
154
|
+
await fs.remove(filePath);
|
|
155
|
+
console.log(chalk.yellow(`Removed ${file}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Also check package.json for packageManager field and engines restrictions
|
|
160
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
161
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
162
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
163
|
+
|
|
164
|
+
// Remove packageManager field that locks to specific manager
|
|
165
|
+
if (packageJson.packageManager) {
|
|
166
|
+
delete packageJson.packageManager;
|
|
167
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
168
|
+
console.log(chalk.yellow('Removed packageManager restriction from package.json'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async function updateNuxtConfig(projectPath, config) {
|
|
175
|
+
const nuxtConfigPath = path.join(projectPath, 'nuxt.config.ts');
|
|
176
|
+
|
|
177
|
+
if (fs.existsSync(nuxtConfigPath)) {
|
|
178
|
+
let nuxtConfig = await fs.readFile(nuxtConfigPath, 'utf8');
|
|
179
|
+
|
|
180
|
+
// Update enfyraSDK apiUrl
|
|
181
|
+
if (nuxtConfig.includes('enfyraSDK:')) {
|
|
182
|
+
nuxtConfig = nuxtConfig.replace(
|
|
183
|
+
/enfyraSDK:\s*\{[^}]*\}/,
|
|
184
|
+
`enfyraSDK: {\n apiUrl: "${config.apiUrl}"\n }`
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
// Add enfyraSDK config
|
|
188
|
+
nuxtConfig = nuxtConfig.replace(
|
|
189
|
+
/}\);?\s*$/,
|
|
190
|
+
` enfyraSDK: {\n apiUrl: "${config.apiUrl}"\n },\n});\n`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
await fs.writeFile(nuxtConfigPath, nuxtConfig);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function installDependencies(projectPath, config) {
|
|
200
|
+
const commands = {
|
|
201
|
+
npm: ['install', '--legacy-peer-deps'],
|
|
202
|
+
yarn: ['install'],
|
|
203
|
+
pnpm: ['install']
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const args = commands[config.packageManager] || commands.npm;
|
|
207
|
+
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const install = spawn(config.packageManager, args, {
|
|
210
|
+
cwd: projectPath,
|
|
211
|
+
stdio: 'pipe',
|
|
212
|
+
shell: true
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
let stdout = '';
|
|
216
|
+
let stderr = '';
|
|
217
|
+
|
|
218
|
+
install.stdout.on('data', (data) => {
|
|
219
|
+
stdout += data.toString();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
install.stderr.on('data', (data) => {
|
|
223
|
+
stderr += data.toString();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
install.on('close', (code) => {
|
|
227
|
+
if (code === 0) {
|
|
228
|
+
resolve();
|
|
229
|
+
} else {
|
|
230
|
+
const errorMsg = stderr || stdout || 'Unknown error';
|
|
231
|
+
reject(new Error(`Package installation failed with code ${code}: ${errorMsg}`));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
install.on('error', (error) => {
|
|
236
|
+
reject(new Error(`Package manager spawn error: ${error.message}. Make sure ${config.packageManager} is installed and in PATH`));
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async function initializeGit(projectPath) {
|
|
244
|
+
// Check if git is available
|
|
245
|
+
try {
|
|
246
|
+
execSync('git --version', { stdio: 'pipe', shell: true });
|
|
247
|
+
} catch {
|
|
248
|
+
// Git is not available, skip initialization
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
const gitInit = spawn('git', ['init'], {
|
|
254
|
+
cwd: projectPath,
|
|
255
|
+
stdio: 'pipe',
|
|
256
|
+
shell: true
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
gitInit.on('close', (code) => {
|
|
260
|
+
if (code === 0) {
|
|
261
|
+
// Add initial commit
|
|
262
|
+
const gitAdd = spawn('git', ['add', '.'], {
|
|
263
|
+
cwd: projectPath,
|
|
264
|
+
stdio: 'pipe',
|
|
265
|
+
shell: true
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
gitAdd.on('close', (addCode) => {
|
|
269
|
+
if (addCode === 0) {
|
|
270
|
+
const gitCommit = spawn('git', ['commit', '-m', 'Initial commit from create-app'], {
|
|
271
|
+
cwd: projectPath,
|
|
272
|
+
stdio: 'pipe',
|
|
273
|
+
shell: true
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
gitCommit.on('close', () => {
|
|
277
|
+
resolve();
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
resolve();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
resolve();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
gitInit.on('error', () => {
|
|
289
|
+
resolve();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = {
|
|
295
|
+
setupProject,
|
|
296
|
+
detectPackageManagers
|
|
297
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const { validateProjectName, validatePort, validateApiUrl } = require('./validators');
|
|
3
|
+
|
|
4
|
+
function getPrompts(availableManagers) {
|
|
5
|
+
const questions = [
|
|
6
|
+
{
|
|
7
|
+
type: 'list',
|
|
8
|
+
name: 'packageManager',
|
|
9
|
+
message: 'Package manager:',
|
|
10
|
+
choices: availableManagers.map(pm => ({
|
|
11
|
+
name: `${getPackageManagerIcon(pm.name)} ${pm.name} (v${pm.version})`,
|
|
12
|
+
value: pm.value,
|
|
13
|
+
short: pm.name
|
|
14
|
+
})),
|
|
15
|
+
default: availableManagers.find(pm => pm.value === 'npm')?.value || availableManagers[0]?.value,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'apiUrl',
|
|
20
|
+
message: 'API base URL (must include http:// or https://):',
|
|
21
|
+
validate: validateApiUrl
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'input',
|
|
25
|
+
name: 'port',
|
|
26
|
+
message: 'App port:',
|
|
27
|
+
default: '3000',
|
|
28
|
+
validate: validatePort,
|
|
29
|
+
filter: (value) => parseInt(value, 10)
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return questions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPackageManagerIcon(name) {
|
|
37
|
+
const icons = {
|
|
38
|
+
npm: 'š¦',
|
|
39
|
+
yarn: 'š§¶',
|
|
40
|
+
pnpm: 'ā”',
|
|
41
|
+
bun: 'š'
|
|
42
|
+
};
|
|
43
|
+
return icons[name] || 'š¦';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function promptForConfiguration(initialProjectName, availableManagers) {
|
|
47
|
+
const prompts = getPrompts(availableManagers);
|
|
48
|
+
const config = await inquirer.prompt(prompts);
|
|
49
|
+
|
|
50
|
+
// Add project name to config
|
|
51
|
+
config.projectName = initialProjectName;
|
|
52
|
+
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
promptForConfiguration
|
|
58
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function checkNodeVersion() {
|
|
6
|
+
const currentVersion = process.version;
|
|
7
|
+
const requiredVersion = '20.0.0';
|
|
8
|
+
|
|
9
|
+
const current = parseVersion(currentVersion.slice(1)); // Remove 'v' prefix
|
|
10
|
+
const required = parseVersion(requiredVersion);
|
|
11
|
+
|
|
12
|
+
if (compareVersions(current, required) < 0) {
|
|
13
|
+
console.error(chalk.red.bold('ā Node.js version error'));
|
|
14
|
+
console.error(chalk.red(`Current version: ${currentVersion}`));
|
|
15
|
+
console.error(chalk.red(`Required version: >= v${requiredVersion}`));
|
|
16
|
+
console.error(chalk.yellow('Please update Node.js to continue.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseVersion(version) {
|
|
22
|
+
return version.split('.').map(Number);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function compareVersions(a, b) {
|
|
26
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
27
|
+
const aPart = a[i] || 0;
|
|
28
|
+
const bPart = b[i] || 0;
|
|
29
|
+
|
|
30
|
+
if (aPart > bPart) return 1;
|
|
31
|
+
if (aPart < bPart) return -1;
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function validateProjectName(name) {
|
|
37
|
+
if (!name || typeof name !== 'string') {
|
|
38
|
+
return 'Project name is required';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (name.length < 1) {
|
|
42
|
+
return 'Project name cannot be empty';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (name.length > 214) {
|
|
46
|
+
return 'Project name cannot exceed 214 characters';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!/^[a-z0-9-_]+$/i.test(name)) {
|
|
50
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (/^[.-]/.test(name)) {
|
|
54
|
+
return 'Project name cannot start with a dot or hyphen';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (/[.-]$/.test(name)) {
|
|
58
|
+
return 'Project name cannot end with a dot or hyphen';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for reserved names
|
|
62
|
+
const reservedNames = [
|
|
63
|
+
'node_modules', '.git', '.env', 'package.json', 'dist', 'build',
|
|
64
|
+
'src', 'public', 'assets', 'components', 'pages', 'layouts',
|
|
65
|
+
'middleware', 'plugins', 'utils', 'server', 'static'
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
if (reservedNames.includes(name.toLowerCase())) {
|
|
69
|
+
return `"${name}" is a reserved name and cannot be used`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if directory already exists
|
|
73
|
+
const projectPath = path.resolve(name);
|
|
74
|
+
if (fs.existsSync(projectPath)) {
|
|
75
|
+
return `Directory "${name}" already exists`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validatePort(port) {
|
|
82
|
+
const portNum = parseInt(port, 10);
|
|
83
|
+
|
|
84
|
+
if (isNaN(portNum)) {
|
|
85
|
+
return 'Port must be a number';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (portNum < 1 || portNum > 65535) {
|
|
89
|
+
return 'Port must be between 1 and 65535';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateApiUrl(url) {
|
|
96
|
+
if (!url || url.trim().length === 0) {
|
|
97
|
+
return 'API URL is required';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
101
|
+
return 'API URL must start with http:// or https:// (e.g., http://localhost:1105)';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
new URL(url);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return 'Please enter a valid URL (e.g., http://localhost:1105)';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function validateNonEmpty(value, fieldName) {
|
|
113
|
+
if (!value || value.trim().length === 0) {
|
|
114
|
+
return `${fieldName} cannot be empty`;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validatePositiveNumber(value, fieldName) {
|
|
120
|
+
const num = parseInt(value, 10);
|
|
121
|
+
|
|
122
|
+
if (isNaN(num)) {
|
|
123
|
+
return `${fieldName} must be a number`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (num < 1) {
|
|
127
|
+
return `${fieldName} must be a positive number`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
checkNodeVersion,
|
|
135
|
+
validateProjectName,
|
|
136
|
+
validatePort,
|
|
137
|
+
validateApiUrl,
|
|
138
|
+
validateNonEmpty,
|
|
139
|
+
validatePositiveNumber
|
|
140
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const { checkNodeVersion } = require('./components/validators');
|
|
9
|
+
const { promptForConfiguration } = require('./components/prompts');
|
|
10
|
+
const { setupProject, detectPackageManagers } = require('./components/project-setup');
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
const packageJson = require('./package.json');
|
|
14
|
+
|
|
15
|
+
// ASCII Art Banner
|
|
16
|
+
const banner = `
|
|
17
|
+
${chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
18
|
+
${chalk.cyan.bold('ā')} ${chalk.white.bold('š Create Enfyra App')} ${chalk.cyan.bold('ā')}
|
|
19
|
+
${chalk.cyan.bold('ā')} ${chalk.gray('Frontend scaffolding made easy')} ${chalk.cyan.bold('ā')}
|
|
20
|
+
${chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
console.log(banner);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Check Node.js version
|
|
28
|
+
checkNodeVersion();
|
|
29
|
+
|
|
30
|
+
// Detect available package managers
|
|
31
|
+
const availableManagers = detectPackageManagers();
|
|
32
|
+
|
|
33
|
+
if (availableManagers.length === 0) {
|
|
34
|
+
console.log(chalk.red('ā No compatible package managers found!'));
|
|
35
|
+
console.log(chalk.yellow('Please install npm (v8+), yarn (v1.22+), pnpm (v7+), or bun (v1+)'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Show detected package managers with versions
|
|
40
|
+
console.log(chalk.gray('Detected package managers:'));
|
|
41
|
+
availableManagers.forEach(pm => {
|
|
42
|
+
console.log(chalk.gray(` ⢠${pm.name} v${pm.version}`));
|
|
43
|
+
});
|
|
44
|
+
console.log('');
|
|
45
|
+
|
|
46
|
+
// Get project name from command line or prompt
|
|
47
|
+
let projectName = program.args[0];
|
|
48
|
+
|
|
49
|
+
if (!projectName) {
|
|
50
|
+
const { name } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'name',
|
|
54
|
+
message: 'Project name:',
|
|
55
|
+
validate: (input) => {
|
|
56
|
+
if (!input || input.trim().length === 0) {
|
|
57
|
+
return 'Project name is required';
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
projectName = name;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Validate project name and directory
|
|
67
|
+
const projectPath = path.resolve(projectName);
|
|
68
|
+
|
|
69
|
+
if (fs.existsSync(projectPath)) {
|
|
70
|
+
console.log(chalk.red(`ā Directory ${chalk.bold(projectName)} already exists!`));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Prompt for configuration
|
|
75
|
+
const config = await promptForConfiguration(projectName, availableManagers);
|
|
76
|
+
|
|
77
|
+
// Setup project
|
|
78
|
+
await setupProject(config, projectPath);
|
|
79
|
+
|
|
80
|
+
// Success message
|
|
81
|
+
console.log(chalk.green.bold('\nš Project created successfully!'));
|
|
82
|
+
console.log(chalk.blue('\nš Next steps:'));
|
|
83
|
+
console.log(chalk.gray(` cd ${projectName}`));
|
|
84
|
+
console.log(chalk.gray(` ${config.packageManager} run dev`));
|
|
85
|
+
console.log();
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(chalk.red.bold('\nā Error creating project:'));
|
|
89
|
+
console.error(chalk.red(error.message));
|
|
90
|
+
console.error(chalk.gray(error.stack));
|
|
91
|
+
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Program setup
|
|
97
|
+
program
|
|
98
|
+
.name('create-app')
|
|
99
|
+
.description('Create a new Enfyra frontend application')
|
|
100
|
+
.version(packageJson.version)
|
|
101
|
+
.argument('[project-name]', 'Name of the project to create')
|
|
102
|
+
.action(main);
|
|
103
|
+
|
|
104
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@enfyra/create-app",
|
|
3
|
+
"version": "0.1.30",
|
|
4
|
+
"description": "Create Enfyra frontend applications with ease",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-app": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"preferGlobal": true,
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20.0.0"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"enfyra",
|
|
18
|
+
"frontend",
|
|
19
|
+
"nuxt",
|
|
20
|
+
"vue",
|
|
21
|
+
"cli",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"generator"
|
|
24
|
+
],
|
|
25
|
+
"author": "Enfyra Team",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^4.1.2",
|
|
29
|
+
"commander": "^11.1.0",
|
|
30
|
+
"fs-extra": "^11.1.1",
|
|
31
|
+
"giget": "^2.0.0",
|
|
32
|
+
"inquirer": "^8.2.5",
|
|
33
|
+
"ora": "^5.4.1"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/dothinh115/create-enfyra-app.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/dothinh115/create-enfyra-app/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/dothinh115/create-enfyra-app#readme",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|