@flowdevcli/flowdev 1.0.3 → 1.0.5
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/.env.example +1 -0
- package/Dockerfile +3 -3
- package/README.md +157 -0
- package/package.json +4 -3
- package/src/commands/ai/ask.js +35 -149
- package/src/commands/ai/audit.js +29 -19
- package/src/commands/ai/explain.js +31 -14
- package/src/commands/ai/readme.js +70 -0
- package/src/commands/ai/test.js +41 -19
- package/src/commands/config.js +64 -0
- package/src/commands/devops/dockerize.js +19 -0
- package/src/commands/devops/env.js +19 -0
- package/src/commands/devops/kube.js +19 -0
- package/src/commands/scaffold/find.js +19 -0
- package/src/commands/scaffold/generate.js +105 -76
- package/src/commands/system/update.js +45 -13
- package/src/commands/utils/stats.js +19 -0
- package/src/commands/utils/tree.js +19 -0
- package/src/core/cli.js +39 -0
- package/src/services/analyzer.js +19 -0
- package/src/services/file-system.js +19 -0
- package/src/templates/docker/templates.js +18 -0
- package/src/utils/engine-check.js +125 -17
- package/src/utils/logger.js +19 -0
- package/src/commands/devops/ci.js +0 -0
- package/src/commands/scaffold/readme.js +0 -54
package/src/commands/ai/test.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
2
20
|
import chalk from 'chalk';
|
|
3
21
|
import ora from 'ora';
|
|
4
22
|
import fs from 'fs-extra';
|
|
5
23
|
import path from 'path';
|
|
6
24
|
import { logger } from '../../utils/logger.js';
|
|
7
|
-
import {
|
|
25
|
+
import { getAIResponse } from '../../utils/engine-check.js'; // IMPORT UPDATED
|
|
8
26
|
|
|
9
27
|
export async function testCommand(fileRelativePath) {
|
|
10
28
|
const spinner = ora(chalk.cyan(`Preparing test generation...`)).start();
|
|
@@ -12,15 +30,11 @@ export async function testCommand(fileRelativePath) {
|
|
|
12
30
|
try {
|
|
13
31
|
const filePath = path.resolve(process.cwd(), fileRelativePath);
|
|
14
32
|
|
|
15
|
-
|
|
16
33
|
if (!(await fs.pathExists(filePath))) {
|
|
17
34
|
spinner.fail(chalk.red(`File not found: ${fileRelativePath}`));
|
|
18
35
|
return;
|
|
19
36
|
}
|
|
20
37
|
|
|
21
|
-
|
|
22
|
-
await ensureEngineReady(spinner, 'llama3');
|
|
23
|
-
|
|
24
38
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
25
39
|
const ext = path.extname(filePath);
|
|
26
40
|
const fileName = path.basename(filePath, ext);
|
|
@@ -34,27 +48,35 @@ export async function testCommand(fileRelativePath) {
|
|
|
34
48
|
testFileName = `test_${fileName}${ext}`;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
spinner.text = chalk.magenta(`Writing ${framework} tests for ${fileName}...`);
|
|
38
|
-
|
|
39
51
|
const prompt = `
|
|
40
52
|
Analyze the following code and generate a complete test file using ${framework}.
|
|
41
53
|
Requirements:
|
|
42
54
|
- Include all necessary imports.
|
|
43
|
-
- Cover
|
|
44
|
-
- Use descriptive test names.
|
|
55
|
+
- Cover main functions with happy paths and edge cases.
|
|
45
56
|
- Return ONLY the code block starting with \`\`\` and ending with \`\`\`.
|
|
46
57
|
|
|
47
58
|
Code to test:
|
|
48
59
|
${content}
|
|
49
60
|
`;
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
// APPEL UNIFIÉ
|
|
63
|
+
const responseStream = await getAIResponse(
|
|
64
|
+
[{ role: 'user', content: prompt }],
|
|
65
|
+
spinner
|
|
66
|
+
);
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
// On consomme le stream pour reconstruire la réponse complète
|
|
69
|
+
let fullResponse = "";
|
|
70
|
+
spinner.text = chalk.yellow("Writing tests (Streaming)...");
|
|
57
71
|
|
|
72
|
+
for await (const part of responseStream) {
|
|
73
|
+
fullResponse += part.message.content;
|
|
74
|
+
// Petit effet visuel optionnel pour montrer que ça bosse
|
|
75
|
+
spinner.text = chalk.yellow(`Generating tests... (${fullResponse.length} chars)`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
spinner.stop();
|
|
79
|
+
|
|
58
80
|
const codeBlockRegex = /```[\w]*\n([\s\S]*?)```/;
|
|
59
81
|
const match = fullResponse.match(codeBlockRegex);
|
|
60
82
|
const testCode = match ? match[1] : fullResponse;
|
|
@@ -62,12 +84,12 @@ export async function testCommand(fileRelativePath) {
|
|
|
62
84
|
const outputPath = path.join(dir, testFileName);
|
|
63
85
|
await fs.writeFile(outputPath, testCode);
|
|
64
86
|
|
|
65
|
-
(chalk.green(
|
|
66
|
-
console.log(chalk.gray(
|
|
87
|
+
console.log(chalk.green(`\n✅ Tests generated successfully!`));
|
|
88
|
+
console.log(chalk.gray(` Location: `) + chalk.white(outputPath));
|
|
67
89
|
console.log(chalk.yellow(` Tip: Run your tests with '${ext === '.py' ? 'pytest' : 'npm test'}'`));
|
|
68
90
|
|
|
69
91
|
} catch (error) {
|
|
70
|
-
|
|
71
|
-
logger.error(error.message);
|
|
92
|
+
spinner.stop();
|
|
93
|
+
logger.error('Test generation failed: ' + error.message);
|
|
72
94
|
}
|
|
73
95
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import inquirer from 'inquirer';
|
|
21
|
+
import chalk from 'chalk';
|
|
22
|
+
import { setDeepSeekKey, getDeepSeekKey, clearDeepSeekKey } from '../utils/config-manager.js';
|
|
23
|
+
|
|
24
|
+
export async function configCommand() {
|
|
25
|
+
const currentKey = getDeepSeekKey();
|
|
26
|
+
|
|
27
|
+
if (currentKey) {
|
|
28
|
+
console.log(`DeepSeek API Key: ${chalk.green('Configured' + '•'.repeat(10))}`);
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`DeepSeek API Key: ${chalk.red('Not configured')}`);
|
|
31
|
+
}
|
|
32
|
+
console.log('');
|
|
33
|
+
|
|
34
|
+
const { action } = await inquirer.prompt([
|
|
35
|
+
{
|
|
36
|
+
type: 'list',
|
|
37
|
+
name: 'action',
|
|
38
|
+
message: 'What do you want to do?',
|
|
39
|
+
choices: [
|
|
40
|
+
{ name: 'Configure/Update the DeepSeek key', value: 'set' },
|
|
41
|
+
{ name: 'Delete the existing key', value: 'delete' },
|
|
42
|
+
{ name: 'Leave', value: 'exit' }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
if (action === 'set') {
|
|
48
|
+
const { key } = await inquirer.prompt([
|
|
49
|
+
{
|
|
50
|
+
type: 'password',
|
|
51
|
+
name: 'key',
|
|
52
|
+
message: 'Enter your DeepSeek API key (sk-...) :',
|
|
53
|
+
mask: '*'
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
if (key) {
|
|
57
|
+
setDeepSeekKey(key.trim());
|
|
58
|
+
console.log(chalk.green('\nKey saved successfully!'));
|
|
59
|
+
}
|
|
60
|
+
} else if (action === 'delete') {
|
|
61
|
+
clearDeepSeekKey();
|
|
62
|
+
console.log(chalk.yellow('\nKey deleted.'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import fs from 'fs-extra';
|
|
2
21
|
import path from 'node:path';
|
|
3
22
|
import inquirer from 'inquirer';
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import fs from 'fs-extra';
|
|
2
21
|
import path from 'path';
|
|
3
22
|
import chalk from 'chalk';
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import fs from 'fs-extra';
|
|
2
21
|
import path from 'path';
|
|
3
22
|
import chalk from 'chalk';
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import fs from 'fs-extra';
|
|
2
21
|
import path from 'node:path';
|
|
3
22
|
import chalk from 'chalk';
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import fs from 'fs-extra';
|
|
2
21
|
import path from 'path';
|
|
3
22
|
import chalk from 'chalk';
|
|
@@ -13,6 +32,25 @@ const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
|
13
32
|
const PYTHON_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
14
33
|
const GENERAL_REGEX = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
|
|
15
34
|
|
|
35
|
+
|
|
36
|
+
function execute(command, spinner, progressText) {
|
|
37
|
+
if (progressText) spinner.text = progressText;
|
|
38
|
+
try {
|
|
39
|
+
|
|
40
|
+
execSync(command, { stdio: 'pipe', encoding: 'utf-8' });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const logFile = path.join(process.cwd(), 'flowdev-debug.log');
|
|
43
|
+
const errorLog = `
|
|
44
|
+
COMMAND: ${command}
|
|
45
|
+
ERROR: ${error.message}
|
|
46
|
+
STDOUT: ${error.stdout}
|
|
47
|
+
STDERR: ${error.stderr}
|
|
48
|
+
`;
|
|
49
|
+
fs.writeFileSync(logFile, errorLog);
|
|
50
|
+
throw new Error(`Command failed. Check the log file for details: ${chalk.bold(logFile)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
16
54
|
export async function generateCommand() {
|
|
17
55
|
const answers = await inquirer.prompt([
|
|
18
56
|
{
|
|
@@ -40,7 +78,7 @@ export async function generateCommand() {
|
|
|
40
78
|
if (currentAnswers.type === 'django') {
|
|
41
79
|
if (!PYTHON_REGEX.test(name)) return 'Use underscores (_) for Django projects.';
|
|
42
80
|
} else {
|
|
43
|
-
if (!GENERAL_REGEX.test(name)) return 'Invalid characters used.';
|
|
81
|
+
if (!GENERAL_REGEX.test(name)) return 'Invalid characters used (use alphanumeric, - or _).';
|
|
44
82
|
}
|
|
45
83
|
return true;
|
|
46
84
|
}
|
|
@@ -53,7 +91,11 @@ export async function generateCommand() {
|
|
|
53
91
|
name: 'appName',
|
|
54
92
|
message: 'Initial app name:',
|
|
55
93
|
default: 'core',
|
|
56
|
-
validate: (input) =>
|
|
94
|
+
validate: (input) => {
|
|
95
|
+
if (!PYTHON_REGEX.test(input)) return 'Invalid Python App name.';
|
|
96
|
+
if (input === answers.projectName) return 'App name cannot be the same as project name.';
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
57
99
|
}]);
|
|
58
100
|
answers.appName = djangoSub.appName;
|
|
59
101
|
}
|
|
@@ -77,97 +119,79 @@ export async function generateCommand() {
|
|
|
77
119
|
}
|
|
78
120
|
|
|
79
121
|
await initGit(projectDir, spinner);
|
|
80
|
-
spinner.succeed(chalk.green(`Project "${answers.projectName}"
|
|
122
|
+
spinner.succeed(chalk.green(`Project "${answers.projectName}" successfully generated.`));
|
|
81
123
|
showSuccessTips(answers);
|
|
82
124
|
|
|
83
125
|
} catch (error) {
|
|
84
|
-
spinner.fail(chalk.red('
|
|
85
|
-
console.error(chalk.
|
|
126
|
+
spinner.fail(chalk.red('Installation encountered an error.'));
|
|
127
|
+
console.error(`\n${chalk.bgRed(' DEBUG ')} ${error.message}`);
|
|
86
128
|
}
|
|
87
129
|
}
|
|
88
130
|
|
|
131
|
+
|
|
132
|
+
|
|
89
133
|
async function setupVite(dir, name, framework, withTailwind, spinner) {
|
|
90
|
-
spinner
|
|
91
|
-
execSync(`${npmCmd} create vite@latest "${name}" -- --template ${framework}`, { stdio: 'ignore' });
|
|
134
|
+
execute(`${npmCmd} create vite@latest "${name}" -- --template ${framework}`, spinner, `Scaffolding ${framework} with Vite...`);
|
|
92
135
|
|
|
93
136
|
const originalDir = process.cwd();
|
|
94
137
|
process.chdir(dir);
|
|
95
138
|
|
|
96
|
-
spinner
|
|
97
|
-
execSync(`${npmCmd} install`, { stdio: 'ignore' });
|
|
98
|
-
|
|
139
|
+
execute(`${npmCmd} install`, spinner, 'Installing project dependencies...');
|
|
99
140
|
|
|
100
141
|
const folders = ['components', 'services', 'utils', 'hooks', 'assets'];
|
|
101
142
|
for (const f of folders) await fs.ensureDir(path.join(dir, 'src', f));
|
|
102
143
|
|
|
103
144
|
if (withTailwind) {
|
|
104
|
-
spinner
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
execSync(`${npmCmd} install -D tailwindcss@3 postcss@8 autoprefixer@10`, { stdio: 'ignore' });
|
|
145
|
+
execute(`${npmCmd} install -D tailwindcss@3 postcss@8 autoprefixer@10`, spinner, 'Installing Tailwind CSS...');
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
content: [
|
|
113
|
-
"./index.html",
|
|
114
|
-
"./src/**/*.{js,ts,jsx,tsx,vue}",
|
|
115
|
-
],
|
|
116
|
-
theme: {
|
|
117
|
-
extend: {},
|
|
118
|
-
},
|
|
147
|
+
const tailwindConfig = `export default {
|
|
148
|
+
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}"],
|
|
149
|
+
theme: { extend: {} },
|
|
119
150
|
plugins: [],
|
|
120
151
|
}`;
|
|
121
152
|
await fs.writeFile(path.join(dir, 'tailwind.config.js'), tailwindConfig);
|
|
153
|
+
await fs.writeFile(path.join(dir, 'postcss.config.js'), `export default { plugins: { tailwindcss: {}, autoprefixer: {} } }`);
|
|
122
154
|
|
|
123
|
-
|
|
124
|
-
const postcssConfig = `export default {
|
|
125
|
-
plugins: {
|
|
126
|
-
tailwindcss: {},
|
|
127
|
-
autoprefixer: {},
|
|
128
|
-
},
|
|
129
|
-
}`;
|
|
130
|
-
await fs.writeFile(path.join(dir, 'postcss.config.js'), postcssConfig);
|
|
131
|
-
|
|
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);
|
|
155
|
+
const cssPath = path.join(dir, 'src', 'index.css');
|
|
144
156
|
const tailwindDirectives = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n`;
|
|
145
|
-
|
|
146
|
-
let currentCss = "";
|
|
147
|
-
if (await fs.pathExists(cssPath)) {
|
|
148
|
-
currentCss = await fs.readFile(cssPath, 'utf-8');
|
|
149
|
-
}
|
|
157
|
+
const currentCss = (await fs.pathExists(cssPath)) ? await fs.readFile(cssPath, 'utf-8') : "";
|
|
150
158
|
await fs.writeFile(cssPath, tailwindDirectives + currentCss);
|
|
151
159
|
}
|
|
152
|
-
|
|
153
160
|
process.chdir(originalDir);
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
async function setupDjango(dir, data, spinner) {
|
|
157
164
|
const { projectName, appName } = data;
|
|
158
165
|
const isWin = process.platform === 'win32';
|
|
166
|
+
|
|
159
167
|
await fs.ensureDir(dir);
|
|
160
168
|
const originalDir = process.cwd();
|
|
161
169
|
process.chdir(dir);
|
|
170
|
+
|
|
162
171
|
try {
|
|
163
|
-
spinner
|
|
164
|
-
|
|
172
|
+
execute(`${pythonCmd} -m venv venv`, spinner, 'Creating Virtual Environment...');
|
|
173
|
+
|
|
165
174
|
const venvPython = isWin ? path.join(dir, 'venv', 'Scripts', 'python.exe') : path.join(dir, 'venv', 'bin', 'python');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
|
|
176
|
+
execute(`"${venvPython}" -m pip install django`, spinner, 'Installing Django via pip...');
|
|
177
|
+
execute(`"${venvPython}" -m django startproject config .`, spinner, 'Initializing Django core...');
|
|
178
|
+
execute(`"${venvPython}" manage.py startapp ${appName}`, spinner, `Creating app: ${appName}...`);
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
const settingsPath = path.join(dir, 'config', 'settings.py');
|
|
182
|
+
let settings = await fs.readFile(settingsPath, 'utf-8');
|
|
183
|
+
settings = settings.replace("INSTALLED_APPS = [", `INSTALLED_APPS = [\n '${appName}',`);
|
|
184
|
+
await fs.writeFile(settingsPath, settings);
|
|
185
|
+
|
|
186
|
+
// Basic app configuration
|
|
169
187
|
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
188
|
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>")`);
|
|
189
|
+
|
|
190
|
+
// Global URL routing
|
|
191
|
+
const projectUrlsPath = path.join(dir, 'config', 'urls.py');
|
|
192
|
+
const projectUrls = `from django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n path('admin/', admin.site.urls),\n path('', include('${appName}.urls')),\n]`;
|
|
193
|
+
await fs.writeFile(projectUrlsPath, projectUrls);
|
|
194
|
+
|
|
171
195
|
} finally {
|
|
172
196
|
process.chdir(originalDir);
|
|
173
197
|
}
|
|
@@ -175,49 +199,54 @@ async function setupDjango(dir, data, spinner) {
|
|
|
175
199
|
|
|
176
200
|
async function initGit(dir, spinner) {
|
|
177
201
|
try {
|
|
178
|
-
spinner.text = 'Initializing Git...';
|
|
202
|
+
spinner.text = 'Initializing Git repository...';
|
|
179
203
|
const ignorePath = path.join(dir, '.gitignore');
|
|
180
204
|
if (!(await fs.pathExists(ignorePath))) {
|
|
181
|
-
await fs.writeFile(ignorePath, 'node_modules\n.env\ndist\nbuild\n__pycache__\n*.log\nvenv\n');
|
|
205
|
+
await fs.writeFile(ignorePath, 'node_modules\n.env\ndist\nbuild\n__pycache__\n*.log\nvenv\n*.pyc\n');
|
|
182
206
|
}
|
|
183
207
|
const originalDir = process.cwd();
|
|
184
208
|
process.chdir(dir);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
209
|
+
execute('git init', spinner);
|
|
210
|
+
execute('git add .', spinner);
|
|
211
|
+
execute('git commit -m "Initial commit by FlowDev"', spinner);
|
|
188
212
|
process.chdir(originalDir);
|
|
189
213
|
} catch (err) {
|
|
190
|
-
spinner.warn(chalk.yellow('Git initialization skipped.'));
|
|
214
|
+
spinner.warn(chalk.yellow('Git initialization skipped. Please ensure Git is installed.'));
|
|
191
215
|
}
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
async function setupAngular(dir, name, spinner) {
|
|
195
|
-
spinner
|
|
196
|
-
execSync(`${npxCmd} --yes -p @angular/cli ng new "${name}" --defaults --skip-git`, { stdio: 'ignore' });
|
|
219
|
+
execute(`${npxCmd} --yes -p @angular/cli ng new "${name}" --defaults --skip-git`, spinner, 'Generating Angular Workspace...');
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
async function setupExpress(dir, name, spinner) {
|
|
200
|
-
spinner.text = 'Setting up Express...';
|
|
201
223
|
await fs.ensureDir(dir);
|
|
202
|
-
const pkg = {
|
|
224
|
+
const pkg = {
|
|
225
|
+
name,
|
|
226
|
+
version: '1.0.0',
|
|
227
|
+
type: 'module',
|
|
228
|
+
scripts: { start: 'node src/index.js' },
|
|
229
|
+
dependencies: { express: '^4.18.2', cors: '^2.8.5' }
|
|
230
|
+
};
|
|
203
231
|
await fs.writeJson(path.join(dir, 'package.json'), pkg, { spaces: 2 });
|
|
204
232
|
await fs.ensureDir(path.join(dir, 'src'));
|
|
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'));`);
|
|
233
|
+
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 running on http://localhost:3000'));`);
|
|
234
|
+
|
|
206
235
|
const originalDir = process.cwd();
|
|
207
236
|
process.chdir(dir);
|
|
208
|
-
|
|
237
|
+
execute(`${npmCmd} install`, spinner, 'Installing Express dependencies...');
|
|
209
238
|
process.chdir(originalDir);
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
function showSuccessTips(data) {
|
|
213
|
-
console.log(chalk.
|
|
214
|
-
console.log(`${chalk.white('
|
|
215
|
-
if (data.type
|
|
216
|
-
console.log(`${chalk.
|
|
217
|
-
console.log(`${chalk.white('
|
|
218
|
-
} else if (data.type === '
|
|
219
|
-
console.log(`${chalk.white('
|
|
242
|
+
console.log(chalk.blue('\n Next Steps :'));
|
|
243
|
+
console.log(`${chalk.white('1.')} cd ${chalk.bold(data.projectName)}`);
|
|
244
|
+
if (data.type === 'django') {
|
|
245
|
+
console.log(`${chalk.white('2.')} ${process.platform === 'win32' ? 'venv\\Scripts\\activate' : 'source venv/bin/activate'}`);
|
|
246
|
+
console.log(`${chalk.white('3.')} python manage.py runserver`);
|
|
247
|
+
} else if (data.type === 'angular') {
|
|
248
|
+
console.log(`${chalk.white('2.')} ng serve`);
|
|
220
249
|
} else {
|
|
221
|
-
console.log(`${chalk.white('
|
|
250
|
+
console.log(`${chalk.white('2.')} npm ${data.type.includes('tailwind') ? 'run dev' : 'start'}`);
|
|
222
251
|
}
|
|
223
252
|
}
|
|
@@ -1,35 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import chalk from "chalk";
|
|
2
21
|
import { execSync } from "child_process";
|
|
3
22
|
import path from "path";
|
|
4
23
|
import { fileURLToPath } from "url";
|
|
24
|
+
import fs from "fs-extra";
|
|
25
|
+
import ora from "ora";
|
|
5
26
|
|
|
6
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
28
|
const __dirname = path.dirname(__filename);
|
|
8
29
|
|
|
9
30
|
export async function updateCommand() {
|
|
10
|
-
const spinner = ora(chalk.cyan('Checking for
|
|
31
|
+
const spinner = ora(chalk.cyan('Checking for updates...')).start();
|
|
11
32
|
|
|
12
33
|
try {
|
|
34
|
+
|
|
13
35
|
const packageJsonPath = path.resolve(__dirname, '../../../package.json');
|
|
14
|
-
const pkg = await
|
|
36
|
+
const pkg = await fs.readJson(packageJsonPath);
|
|
15
37
|
|
|
16
38
|
const currentVersion = pkg.version;
|
|
17
39
|
const packageName = pkg.name;
|
|
40
|
+
|
|
18
41
|
let latestVersion;
|
|
19
42
|
try {
|
|
20
|
-
latestVersion = execSync(`npm
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
latestVersion = execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
spinner.warn(chalk.yellow('Could not reach the npm registry.'));
|
|
46
|
+
return;
|
|
24
47
|
}
|
|
25
48
|
|
|
26
|
-
if (latestVersion
|
|
27
|
-
|
|
49
|
+
if (latestVersion === currentVersion) {
|
|
50
|
+
spinner.succeed(chalk.green(`FlowDev is up to date! (v${currentVersion})`));
|
|
51
|
+
} else {
|
|
52
|
+
spinner.info(chalk.yellow(`A new version is available: ${latestVersion} (Current: ${currentVersion})`));
|
|
53
|
+
|
|
54
|
+
const updateSpinner = ora(chalk.magenta('Updating FlowDev...')).start();
|
|
55
|
+
try {
|
|
56
|
+
|
|
57
|
+
execSync(`npm install -g ${packageName}@latest`, { stdio: 'ignore' });
|
|
58
|
+
updateSpinner.succeed(chalk.green(`FlowDev has been updated to v${latestVersion}! ✨`));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
updateSpinner.fail(chalk.red('Update failed. Try running with sudo: sudo npm install -g @flowdevcli/flowdev'));
|
|
61
|
+
}
|
|
28
62
|
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
spinner.fail(chalk.red('An error occurred while checking for updates.'));
|
|
65
|
+
console.error(error);
|
|
29
66
|
}
|
|
30
|
-
|
|
31
|
-
catch(error){
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
67
|
}
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FlowDev - Intelligent CLI tool
|
|
3
|
+
* @module flowdev
|
|
4
|
+
* @version 1.0.5
|
|
5
|
+
* * @license MIT
|
|
6
|
+
* Copyright (c) 2026 FlowDev Technologies.
|
|
7
|
+
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
* * The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import ora from 'ora';
|
|
2
21
|
import chalk from 'chalk';
|
|
3
22
|
import { analyzeProject } from '../../services/analyzer.js';
|