@flowdevcli/flowdev 1.0.2 → 1.0.4
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/package.json +1 -1
- package/src/commands/scaffold/generate.js +38 -62
- package/src/commands/system/update.js +26 -13
- package/src/core/cli.js +6 -0
package/package.json
CHANGED
|
@@ -10,7 +10,6 @@ const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
|
10
10
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
11
11
|
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
const PYTHON_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
15
14
|
const GENERAL_REGEX = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
|
|
16
15
|
|
|
@@ -36,44 +35,25 @@ export async function generateCommand() {
|
|
|
36
35
|
validate: (input, currentAnswers) => {
|
|
37
36
|
const name = input.trim();
|
|
38
37
|
if (!name) return 'A name is required.';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return ' Name cannot start with a number. Please use a letter.';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
if (/^\d/.test(name)) return 'Name cannot start with a number.';
|
|
39
|
+
|
|
45
40
|
if (currentAnswers.type === 'django') {
|
|
46
|
-
if (!PYTHON_REGEX.test(name))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
else {
|
|
52
|
-
if (!GENERAL_REGEX.test(name)) {
|
|
53
|
-
return ' Invalid name. Use letters, numbers, dashes (-) or underscores (_).';
|
|
54
|
-
}
|
|
41
|
+
if (!PYTHON_REGEX.test(name)) return 'Use underscores (_) for Django projects.';
|
|
42
|
+
} else {
|
|
43
|
+
if (!GENERAL_REGEX.test(name)) return 'Invalid characters used.';
|
|
55
44
|
}
|
|
56
|
-
|
|
57
45
|
return true;
|
|
58
46
|
}
|
|
59
47
|
}
|
|
60
48
|
]);
|
|
61
49
|
|
|
62
|
-
|
|
63
50
|
if (answers.type === 'django') {
|
|
64
51
|
const djangoSub = await inquirer.prompt([{
|
|
65
52
|
type: 'input',
|
|
66
53
|
name: 'appName',
|
|
67
54
|
message: 'Initial app name:',
|
|
68
55
|
default: 'core',
|
|
69
|
-
validate: (input) =>
|
|
70
|
-
const name = input.trim();
|
|
71
|
-
|
|
72
|
-
if (!PYTHON_REGEX.test(name)) {
|
|
73
|
-
return ' Invalid Python App name. Must start with a letter and contain no dashes.';
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
56
|
+
validate: (input) => PYTHON_REGEX.test(input) || 'Invalid Python App name.'
|
|
77
57
|
}]);
|
|
78
58
|
answers.appName = djangoSub.appName;
|
|
79
59
|
}
|
|
@@ -97,18 +77,15 @@ export async function generateCommand() {
|
|
|
97
77
|
}
|
|
98
78
|
|
|
99
79
|
await initGit(projectDir, spinner);
|
|
100
|
-
|
|
101
|
-
spinner.succeed(chalk.green(`Project "${answers.projectName}" is ready and configured!`));
|
|
80
|
+
spinner.succeed(chalk.green(`Project "${answers.projectName}" is ready!`));
|
|
102
81
|
showSuccessTips(answers);
|
|
103
82
|
|
|
104
83
|
} catch (error) {
|
|
105
84
|
spinner.fail(chalk.red('Generation failed.'));
|
|
106
|
-
console.error(chalk.red(`\
|
|
85
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
107
86
|
}
|
|
108
87
|
}
|
|
109
88
|
|
|
110
|
-
|
|
111
|
-
|
|
112
89
|
async function setupVite(dir, name, framework, withTailwind, spinner) {
|
|
113
90
|
spinner.text = `Scaffolding ${framework} with Vite...`;
|
|
114
91
|
execSync(`${npmCmd} create vite@latest "${name}" -- --template ${framework}`, { stdio: 'ignore' });
|
|
@@ -119,13 +96,17 @@ async function setupVite(dir, name, framework, withTailwind, spinner) {
|
|
|
119
96
|
spinner.text = 'Installing core dependencies...';
|
|
120
97
|
execSync(`${npmCmd} install`, { stdio: 'ignore' });
|
|
121
98
|
|
|
99
|
+
|
|
122
100
|
const folders = ['components', 'services', 'utils', 'hooks', 'assets'];
|
|
123
101
|
for (const f of folders) await fs.ensureDir(path.join(dir, 'src', f));
|
|
124
102
|
|
|
125
103
|
if (withTailwind) {
|
|
126
|
-
spinner.text = '
|
|
127
|
-
|
|
104
|
+
spinner.text = 'Configuring Tailwind CSS v3...';
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
execSync(`${npmCmd} install -D tailwindcss@3 postcss@8 autoprefixer@10`, { stdio: 'ignore' });
|
|
128
108
|
|
|
109
|
+
|
|
129
110
|
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
130
111
|
export default {
|
|
131
112
|
content: [
|
|
@@ -139,6 +120,7 @@ export default {
|
|
|
139
120
|
}`;
|
|
140
121
|
await fs.writeFile(path.join(dir, 'tailwind.config.js'), tailwindConfig);
|
|
141
122
|
|
|
123
|
+
|
|
142
124
|
const postcssConfig = `export default {
|
|
143
125
|
plugins: {
|
|
144
126
|
tailwindcss: {},
|
|
@@ -147,7 +129,18 @@ export default {
|
|
|
147
129
|
}`;
|
|
148
130
|
await fs.writeFile(path.join(dir, 'postcss.config.js'), postcssConfig);
|
|
149
131
|
|
|
150
|
-
|
|
132
|
+
|
|
133
|
+
const possibleCssFiles = ['index.css', 'style.css', 'App.css'];
|
|
134
|
+
let targetCssFile = 'index.css';
|
|
135
|
+
|
|
136
|
+
for (const file of possibleCssFiles) {
|
|
137
|
+
if (await fs.pathExists(path.join(dir, 'src', file))) {
|
|
138
|
+
targetCssFile = file;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const cssPath = path.join(dir, 'src', targetCssFile);
|
|
151
144
|
const tailwindDirectives = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n`;
|
|
152
145
|
|
|
153
146
|
let currentCss = "";
|
|
@@ -163,35 +156,18 @@ export default {
|
|
|
163
156
|
async function setupDjango(dir, data, spinner) {
|
|
164
157
|
const { projectName, appName } = data;
|
|
165
158
|
const isWin = process.platform === 'win32';
|
|
166
|
-
|
|
167
159
|
await fs.ensureDir(dir);
|
|
168
160
|
const originalDir = process.cwd();
|
|
169
161
|
process.chdir(dir);
|
|
170
|
-
|
|
171
162
|
try {
|
|
172
|
-
spinner.text = '
|
|
163
|
+
spinner.text = 'Setting up Django environment...';
|
|
173
164
|
execSync(`${pythonCmd} -m venv venv`, { stdio: 'ignore' });
|
|
174
|
-
|
|
175
|
-
const venvPython = isWin
|
|
176
|
-
? path.join(dir, 'venv', 'Scripts', 'python.exe')
|
|
177
|
-
: path.join(dir, 'venv', 'bin', 'python');
|
|
178
|
-
|
|
179
|
-
spinner.text = 'Installing Django...';
|
|
165
|
+
const venvPython = isWin ? path.join(dir, 'venv', 'Scripts', 'python.exe') : path.join(dir, 'venv', 'bin', 'python');
|
|
180
166
|
execSync(`"${venvPython}" -m pip install django`, { stdio: 'ignore' });
|
|
181
|
-
|
|
182
|
-
spinner.text = 'Initializing Django project...';
|
|
183
|
-
|
|
184
167
|
execSync(`"${venvPython}" -m django startproject config .`, { stdio: 'ignore' });
|
|
185
|
-
|
|
186
|
-
spinner.text = `Creating app: ${appName}...`;
|
|
187
168
|
execSync(`"${venvPython}" manage.py startapp ${appName}`, { stdio: 'ignore' });
|
|
188
|
-
|
|
189
|
-
await fs.writeFile(path.join(dir, appName, '
|
|
190
|
-
`from django.urls import path\nfrom . import views\n\nurlpatterns = [ path('', views.index, name='index'), ]`);
|
|
191
|
-
|
|
192
|
-
await fs.writeFile(path.join(dir, appName, 'views.py'),
|
|
193
|
-
`from django.http import HttpResponse\n\ndef index(request):\n return HttpResponse("<h1>${projectName} is live!</h1>")`);
|
|
194
|
-
|
|
169
|
+
await fs.writeFile(path.join(dir, appName, 'urls.py'), `from django.urls import path\nfrom . import views\n\nurlpatterns = [ path('', views.index, name='index'), ]`);
|
|
170
|
+
await fs.writeFile(path.join(dir, appName, 'views.py'), `from django.http import HttpResponse\n\ndef index(request):\n return HttpResponse("<h1>${projectName} is live!</h1>")`);
|
|
195
171
|
} finally {
|
|
196
172
|
process.chdir(originalDir);
|
|
197
173
|
}
|
|
@@ -202,14 +178,13 @@ async function initGit(dir, spinner) {
|
|
|
202
178
|
spinner.text = 'Initializing Git...';
|
|
203
179
|
const ignorePath = path.join(dir, '.gitignore');
|
|
204
180
|
if (!(await fs.pathExists(ignorePath))) {
|
|
205
|
-
|
|
206
|
-
await fs.writeFile(ignorePath, defaultIgnore);
|
|
181
|
+
await fs.writeFile(ignorePath, 'node_modules\n.env\ndist\nbuild\n__pycache__\n*.log\nvenv\n');
|
|
207
182
|
}
|
|
208
183
|
const originalDir = process.cwd();
|
|
209
184
|
process.chdir(dir);
|
|
210
185
|
execSync('git init', { stdio: 'ignore' });
|
|
211
186
|
execSync('git add .', { stdio: 'ignore' });
|
|
212
|
-
execSync('git commit -m "Initial commit by FlowDev
|
|
187
|
+
execSync('git commit -m "Initial commit by FlowDev "', { stdio: 'ignore' });
|
|
213
188
|
process.chdir(originalDir);
|
|
214
189
|
} catch (err) {
|
|
215
190
|
spinner.warn(chalk.yellow('Git initialization skipped.'));
|
|
@@ -218,26 +193,27 @@ async function initGit(dir, spinner) {
|
|
|
218
193
|
|
|
219
194
|
async function setupAngular(dir, name, spinner) {
|
|
220
195
|
spinner.text = 'Generating Angular Workspace...';
|
|
221
|
-
|
|
222
196
|
execSync(`${npxCmd} --yes -p @angular/cli ng new "${name}" --defaults --skip-git`, { stdio: 'ignore' });
|
|
223
197
|
}
|
|
224
198
|
|
|
225
199
|
async function setupExpress(dir, name, spinner) {
|
|
226
200
|
spinner.text = 'Setting up Express...';
|
|
227
201
|
await fs.ensureDir(dir);
|
|
228
|
-
const pkg = { name, version: '1.0.0', scripts: { start: 'node src/index.js' }, dependencies: { express: '^4.18.2', cors: '^2.8.5' }};
|
|
202
|
+
const pkg = { name, version: '1.0.0', type: 'module', scripts: { start: 'node src/index.js' }, dependencies: { express: '^4.18.2', cors: '^2.8.5' }};
|
|
229
203
|
await fs.writeJson(path.join(dir, 'package.json'), pkg, { spaces: 2 });
|
|
230
204
|
await fs.ensureDir(path.join(dir, 'src'));
|
|
231
|
-
await fs.writeFile(path.join(dir, 'src', 'index.js'), `
|
|
205
|
+
await fs.writeFile(path.join(dir, 'src', 'index.js'), `import express from 'express';\nconst app = express();\napp.get('/', (req, res) => res.send('API OK'));\napp.listen(3000, () => console.log('Server on 3000'));`);
|
|
206
|
+
const originalDir = process.cwd();
|
|
232
207
|
process.chdir(dir);
|
|
233
208
|
execSync(`${npmCmd} install`, { stdio: 'ignore' });
|
|
209
|
+
process.chdir(originalDir);
|
|
234
210
|
}
|
|
235
211
|
|
|
236
212
|
function showSuccessTips(data) {
|
|
237
213
|
console.log(chalk.yellow('\n Tips :'));
|
|
238
214
|
console.log(`${chalk.white('*')} cd ${chalk.bold(data.projectName)}`);
|
|
239
215
|
if (data.type.includes('tailwind')) {
|
|
240
|
-
console.log(`${chalk.green('*')} Tailwind
|
|
216
|
+
console.log(`${chalk.green('*')} Tailwind CSS v3 successfully configured.`);
|
|
241
217
|
console.log(`${chalk.white('*')} Run: ${chalk.bold('npm run dev')}`);
|
|
242
218
|
} else if (data.type === 'django') {
|
|
243
219
|
console.log(`${chalk.white('*')} Run: ${chalk.bold('python manage.py runserver')}`);
|
|
@@ -2,34 +2,47 @@ import chalk from "chalk";
|
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import ora from "ora";
|
|
5
7
|
|
|
6
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
9
|
const __dirname = path.dirname(__filename);
|
|
8
10
|
|
|
9
11
|
export async function updateCommand() {
|
|
10
|
-
const spinner = ora(chalk.cyan('Checking for
|
|
12
|
+
const spinner = ora(chalk.cyan('Checking for updates...')).start();
|
|
11
13
|
|
|
12
14
|
try {
|
|
15
|
+
|
|
13
16
|
const packageJsonPath = path.resolve(__dirname, '../../../package.json');
|
|
14
|
-
const pkg = await
|
|
17
|
+
const pkg = await fs.readJson(packageJsonPath);
|
|
15
18
|
|
|
16
19
|
const currentVersion = pkg.version;
|
|
17
20
|
const packageName = pkg.name;
|
|
21
|
+
|
|
18
22
|
let latestVersion;
|
|
19
23
|
try {
|
|
20
|
-
latestVersion = execSync(`npm
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
latestVersion = execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
|
|
25
|
+
} catch (e) {
|
|
26
|
+
spinner.warn(chalk.yellow('Could not reach the npm registry.'));
|
|
27
|
+
return;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
if (latestVersion
|
|
27
|
-
|
|
30
|
+
if (latestVersion === currentVersion) {
|
|
31
|
+
spinner.succeed(chalk.green(`FlowDev is up to date! (v${currentVersion})`));
|
|
32
|
+
} else {
|
|
33
|
+
spinner.info(chalk.yellow(`A new version is available: ${latestVersion} (Current: ${currentVersion})`));
|
|
34
|
+
|
|
35
|
+
const updateSpinner = ora(chalk.magenta('Updating FlowDev...')).start();
|
|
36
|
+
try {
|
|
37
|
+
|
|
38
|
+
execSync(`npm install -g ${packageName}@latest`, { stdio: 'ignore' });
|
|
39
|
+
updateSpinner.succeed(chalk.green(`FlowDev has been updated to v${latestVersion}! ✨`));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
updateSpinner.fail(chalk.red('Update failed. Try running with sudo: sudo npm install -g @flowdevcli/flowdev'));
|
|
42
|
+
}
|
|
28
43
|
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
spinner.fail(chalk.red('An error occurred while checking for updates.'));
|
|
46
|
+
console.error(error);
|
|
29
47
|
}
|
|
30
|
-
|
|
31
|
-
catch(error){
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
48
|
}
|
package/src/core/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { kubeCommand } from '../commands/devops/kube.js';
|
|
|
12
12
|
import { generateCommand } from '../commands/scaffold/generate.js';
|
|
13
13
|
import { auditCommand } from '../commands/ai/audit.js';
|
|
14
14
|
import { testCommand } from '../commands/ai/test.js';
|
|
15
|
+
import { updateCommand } from '../commands/system/update.js';
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
const require = createRequire(import.meta.url);
|
|
@@ -109,5 +110,10 @@ export function setupCLI() {
|
|
|
109
110
|
await testCommand(file);
|
|
110
111
|
});
|
|
111
112
|
|
|
113
|
+
program
|
|
114
|
+
.command('update')
|
|
115
|
+
.description('Update FlowDev to the latest version')
|
|
116
|
+
.action(updateCommand);
|
|
117
|
+
|
|
112
118
|
return program;
|
|
113
119
|
}
|