@flutry/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Flutry-HQ/cli/main/assets/logo.png" alt="Flurty CLI logo" width="180">
3
+ </p>
4
+
5
+ # Flurty CLI
6
+
7
+ A small, reliable command-line tool for creating and extending Flutry projects.
8
+
9
+ ## Features
10
+
11
+ - Interactive Flutry project creation
12
+ - Secure `.env` secret generation
13
+ - Optional database configuration with MySQL or MariaDB
14
+ - Route and service generation
15
+ - Sequelize model generation
16
+ - npm, Yarn, and pnpm support
17
+ - Animated and colored terminal feedback
18
+ - Safe protection against overwriting existing files
19
+
20
+ ## Installation
21
+
22
+ Install the package globally:
23
+
24
+ ```bash
25
+ npm install -g @flutry/cli
26
+ ```
27
+
28
+ Or run it directly with `npx`:
29
+
30
+ ```bash
31
+ npx @flutry/cli new
32
+ ```
33
+
34
+ ## Create A Project
35
+
36
+ Start the interactive project generator:
37
+
38
+ ```bash
39
+ flurty new
40
+ ```
41
+
42
+ The generator asks for:
43
+
44
+ - Project folder and package name
45
+ - Database activation and connection settings
46
+ - Package manager
47
+ - Dependency installation
48
+
49
+ After creation, the project includes `.env` and `.env.example` files.
50
+
51
+ ## Generate A Route
52
+
53
+ Run this command from the root of an existing Flutry project:
54
+
55
+ ```bash
56
+ flurty generate route user
57
+ ```
58
+
59
+ This creates:
60
+
61
+ ```text
62
+ src/routes/user/user.route.ts
63
+ src/routes/user/user.service.ts
64
+ ```
65
+
66
+ The generated route includes a basic `GET /user` endpoint that returns an `Ok` response.
67
+
68
+ ## Generate A Model
69
+
70
+ Create a Sequelize model with:
71
+
72
+ ```bash
73
+ flurty generate model user
74
+ ```
75
+
76
+ This creates:
77
+
78
+ ```text
79
+ src/models/user.model.ts
80
+ ```
81
+
82
+ The generated model contains an `id` field, a required unique `message` field, and a Sequelize initializer.
83
+
84
+ ## Short Alias
85
+
86
+ The `generate` command also has a short `g` alias:
87
+
88
+ ```bash
89
+ flurty g route user
90
+ flurty g model user
91
+ ```
92
+
93
+ Generators only run inside a Flutry project. They check for `package.json` and the Flutry router before creating files.
94
+
95
+ ## Environment Configuration
96
+
97
+ The generator creates a complete environment template, including database values even when the database is initially disabled:
98
+
99
+ ```env
100
+ PORT=1337
101
+ HOST=0.0.0.0
102
+ PREFIX_API=
103
+ DB=false
104
+ DB_NAME=
105
+ DB_USER=
106
+ DB_PASS=
107
+ DB_HOST=
108
+ DB_PORT=3306
109
+ DB_TYPE=mariadb
110
+ SECRET_KEY=
111
+ SECRET_SALT=
112
+ JWT_SECRET_KEY=
113
+ ```
114
+
115
+ The real `.env` file receives generated secrets. The `.env.example` file never receives database credentials or generated secrets.
116
+
117
+ ## Development
118
+
119
+ Install dependencies:
120
+
121
+ ```bash
122
+ yarn install
123
+ ```
124
+
125
+ Build the CLI:
126
+
127
+ ```bash
128
+ yarn build
129
+ ```
130
+
131
+ Run the compiled CLI:
132
+
133
+ ```bash
134
+ yarn start
135
+ ```
136
+
137
+ Watch TypeScript files during development:
138
+
139
+ ```bash
140
+ yarn dev
141
+ ```
142
+
143
+ ## Project Structure
144
+
145
+ ```text
146
+ src/
147
+ commands/
148
+ generate.ts
149
+ new.ts
150
+ types/
151
+ degit.d.ts
152
+ index.ts
153
+ ```
154
+
155
+ ## License
156
+
157
+ MIT
Binary file
@@ -0,0 +1,2 @@
1
+ export declare function generateRoute(name: string): Promise<void>;
2
+ export declare function generateModel(name: string): Promise<void>;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateRoute = generateRoute;
7
+ exports.generateModel = generateModel;
8
+ const fs_1 = require("fs");
9
+ const path_1 = __importDefault(require("path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const ora_1 = __importDefault(require("ora"));
12
+ function normalizeName(name) {
13
+ const fileName = name.trim().toLowerCase();
14
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(fileName)) {
15
+ throw new Error('Name must contain lowercase letters, numbers, or hyphens and start with a letter.');
16
+ }
17
+ const className = fileName
18
+ .split('-')
19
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
20
+ .join('');
21
+ return { fileName, className };
22
+ }
23
+ function routeTemplate(className, fileName) {
24
+ return [
25
+ "import Router from '../../package/router/router';",
26
+ '',
27
+ `export default class ${className}Route extends Router {`,
28
+ ' constructor() {',
29
+ ' super();',
30
+ ` //! [GET] /${fileName} || Responsed OK`,
31
+ " this.get('/', async (ctx) => {",
32
+ " return ctx.send({ message: 'Ok' });",
33
+ ' });',
34
+ ' }',
35
+ '}',
36
+ '',
37
+ ].join('\n');
38
+ }
39
+ function serviceTemplate(className) {
40
+ return `export default class ${className}Service {}\n`;
41
+ }
42
+ function modelTemplate(className, fileName) {
43
+ return [
44
+ "import { Model, DataTypes, Sequelize } from 'sequelize';",
45
+ '',
46
+ `export type ${className}ModelType = {`,
47
+ ' id: string;',
48
+ ' message: string;',
49
+ '};',
50
+ '',
51
+ `export default class ${className} extends Model {`,
52
+ ' static initialize(sequelize: Sequelize) {',
53
+ ` ${className}.init(`,
54
+ ' {',
55
+ ' id: {',
56
+ ' type: DataTypes.STRING,',
57
+ ' primaryKey: true,',
58
+ ' },',
59
+ ' message: {',
60
+ ' type: DataTypes.STRING,',
61
+ ' allowNull: false,',
62
+ ' },',
63
+ ' },',
64
+ ' {',
65
+ ' sequelize,',
66
+ ` tableName: '${fileName}',`,
67
+ ' timestamps: false,',
68
+ ' indexes: [',
69
+ ' {',
70
+ ' unique: true,',
71
+ " fields: ['message'],",
72
+ ' },',
73
+ ' ],',
74
+ ' },',
75
+ ' );',
76
+ ' }',
77
+ '}',
78
+ '',
79
+ ].join('\n');
80
+ }
81
+ async function writeNewFile(filePath, content) {
82
+ try {
83
+ await fs_1.promises.access(filePath);
84
+ throw new Error(`File already exists: ${path_1.default.relative(process.cwd(), filePath)}`);
85
+ }
86
+ catch (error) {
87
+ if (error instanceof Error && error.message.startsWith('File already exists:'))
88
+ throw error;
89
+ }
90
+ await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
91
+ await fs_1.promises.writeFile(filePath, content, 'utf8');
92
+ }
93
+ async function isFlutryProject(projectRoot) {
94
+ try {
95
+ await Promise.all([
96
+ fs_1.promises.access(path_1.default.join(projectRoot, 'package.json')),
97
+ fs_1.promises.access(path_1.default.join(projectRoot, 'src', 'package', 'router', 'router.ts')),
98
+ ]);
99
+ return true;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ async function generate(type, name) {
106
+ const projectRoot = process.cwd();
107
+ if (!(await isFlutryProject(projectRoot))) {
108
+ console.error('This command can only be used inside a Flutry project.');
109
+ return;
110
+ }
111
+ const { fileName, className } = normalizeName(name);
112
+ const sourceRoot = path_1.default.join(projectRoot, 'src');
113
+ const spinner = (0, ora_1.default)({
114
+ text: type === 'route' ? chalk_1.default.cyan(`Generating route ${fileName}`) : chalk_1.default.magenta(`Generating model ${fileName}`),
115
+ spinner: 'dots12',
116
+ color: type === 'route' ? 'cyan' : 'magenta',
117
+ }).start();
118
+ try {
119
+ if (type === 'route') {
120
+ const routeDirectory = path_1.default.join(sourceRoot, 'routes', fileName);
121
+ spinner.text = chalk_1.default.cyan(`Creating ${fileName}.route.ts`);
122
+ await writeNewFile(path_1.default.join(routeDirectory, `${fileName}.route.ts`), routeTemplate(className, fileName));
123
+ spinner.text = chalk_1.default.blue(`Creating ${fileName}.service.ts`);
124
+ await writeNewFile(path_1.default.join(routeDirectory, `${fileName}.service.ts`), serviceTemplate(className));
125
+ spinner.succeed(chalk_1.default.greenBright(`✦ Route generated: src/routes/${fileName}/`));
126
+ return;
127
+ }
128
+ spinner.text = chalk_1.default.magenta(`Creating ${fileName}.model.ts`);
129
+ await writeNewFile(path_1.default.join(sourceRoot, 'models', `${fileName}.model.ts`), modelTemplate(className, fileName));
130
+ spinner.succeed(chalk_1.default.greenBright(`✦ Model generated: src/models/${fileName}.model.ts`));
131
+ }
132
+ catch (error) {
133
+ spinner.fail(chalk_1.default.red(`Could not generate ${type} ${fileName}`));
134
+ throw error;
135
+ }
136
+ }
137
+ async function generateRoute(name) {
138
+ try {
139
+ await generate('route', name);
140
+ }
141
+ catch (error) {
142
+ console.error(`${chalk_1.default.redBright('✖')} ${error instanceof Error ? error.message : 'Route generation failed.'}`);
143
+ process.exitCode = 1;
144
+ }
145
+ }
146
+ async function generateModel(name) {
147
+ try {
148
+ await generate('model', name);
149
+ }
150
+ catch (error) {
151
+ console.error(`${chalk_1.default.redBright('✖')} ${error instanceof Error ? error.message : 'Model generation failed.'}`);
152
+ process.exitCode = 1;
153
+ }
154
+ }
@@ -0,0 +1,17 @@
1
+ type PackageManager = 'npm' | 'yarn' | 'pnpm';
2
+ type Answers = {
3
+ folderName: string;
4
+ projectName: string;
5
+ database: boolean;
6
+ dbName: string;
7
+ dbUser: string;
8
+ dbPass: string;
9
+ dbHost: string;
10
+ dbPort: string;
11
+ dbType: 'mysql' | 'mariadb';
12
+ packageManager: PackageManager;
13
+ installNow: boolean;
14
+ };
15
+ export declare function promptQuestions(): Promise<Answers>;
16
+ export default function newCommand(): Promise<void>;
17
+ export {};
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.promptQuestions = promptQuestions;
7
+ exports.default = newCommand;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const fs_1 = require("fs");
10
+ const path_1 = __importDefault(require("path"));
11
+ const child_process_1 = require("child_process");
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const degit_1 = __importDefault(require("degit"));
14
+ const ora_1 = __importDefault(require("ora"));
15
+ const prompts_1 = require("@inquirer/prompts");
16
+ const DEFAULTS = {
17
+ port: '1337',
18
+ host: '0.0.0.0',
19
+ dbPort: '3306',
20
+ dbType: 'mariadb',
21
+ };
22
+ async function promptQuestions() {
23
+ console.log(`\n${chalk_1.default.cyanBright('╭────────────────────────────────────╮')}`);
24
+ console.log(`${chalk_1.default.cyanBright('│')} ${chalk_1.default.bold.white('✦ FLUTRY')} ${chalk_1.default.gray('project generator')} ${chalk_1.default.cyanBright('│')}`);
25
+ console.log(`${chalk_1.default.cyanBright('╰────────────────────────────────────╯')}`);
26
+ console.log(chalk_1.default.gray(' Create a clean project with a few focused choices.\n'));
27
+ const folderName = await (0, prompts_1.input)({
28
+ message: `${chalk_1.default.cyan('📁')} Project folder:`,
29
+ validate: (value) => validateFolderName(value) || 'Enter a folder name without / or \\.',
30
+ });
31
+ const projectName = await (0, prompts_1.input)({
32
+ message: `${chalk_1.default.blue('📦')} Package name:`,
33
+ default: folderName.toLowerCase().replace(/\s+/g, '-'),
34
+ validate: validatePackageName,
35
+ });
36
+ const database = await (0, prompts_1.confirm)({ message: `${chalk_1.default.yellow('🗄️')} Enable database?`, default: false });
37
+ const databaseAnswers = database
38
+ ? {
39
+ dbName: await (0, prompts_1.input)({ message: `${chalk_1.default.yellow('◈')} Database name:`, default: 'flutrydb' }),
40
+ dbUser: await (0, prompts_1.input)({ message: `${chalk_1.default.yellow('◈')} Database user:`, default: 'root' }),
41
+ dbPass: await (0, prompts_1.password)({ message: `${chalk_1.default.yellow('◈')} Database password:`, mask: '*' }),
42
+ dbHost: await (0, prompts_1.input)({ message: `${chalk_1.default.yellow('◈')} Database host:`, default: 'localhost' }),
43
+ dbPort: await (0, prompts_1.input)({ message: `${chalk_1.default.yellow('◈')} Database port:`, default: DEFAULTS.dbPort, validate: validatePort }),
44
+ dbType: await (0, prompts_1.select)({
45
+ message: `${chalk_1.default.yellow('◈')} Database type:`,
46
+ choices: [
47
+ { name: 'MariaDB', value: 'mariadb' },
48
+ { name: 'MySQL', value: 'mysql' },
49
+ ],
50
+ default: DEFAULTS.dbType,
51
+ }),
52
+ }
53
+ : {
54
+ dbName: '',
55
+ dbUser: '',
56
+ dbPass: '',
57
+ dbHost: '',
58
+ dbPort: '',
59
+ dbType: DEFAULTS.dbType,
60
+ };
61
+ const packageManager = await (0, prompts_1.select)({
62
+ message: `${chalk_1.default.magenta('⚙')} Package manager:`,
63
+ choices: [
64
+ { name: 'npm', value: 'npm' },
65
+ { name: 'Yarn', value: 'yarn' },
66
+ { name: 'pnpm', value: 'pnpm' },
67
+ ],
68
+ default: 'npm',
69
+ });
70
+ return {
71
+ folderName,
72
+ projectName,
73
+ database,
74
+ ...databaseAnswers,
75
+ packageManager,
76
+ installNow: await (0, prompts_1.confirm)({ message: `${chalk_1.default.green('⚡')} Install dependencies now?`, default: true }),
77
+ };
78
+ }
79
+ function buildEnv(answers, example) {
80
+ const secret = (name) => (example ? `your_${name.toLowerCase()}` : crypto_1.default.randomBytes(64).toString('hex'));
81
+ return [
82
+ '# Server',
83
+ `PORT=${DEFAULTS.port}`,
84
+ `HOST=${DEFAULTS.host}`,
85
+ 'PREFIX_API=',
86
+ '# Database',
87
+ `DB=${answers.database ? 'true' : 'false'}`,
88
+ `DB_NAME=${example ? '' : answers.dbName}`,
89
+ `DB_USER=${example ? '' : answers.dbUser}`,
90
+ `DB_PASS=${example ? '' : answers.dbPass}`,
91
+ `DB_HOST=${example ? '' : answers.dbHost}`,
92
+ `DB_PORT=${example ? '' : answers.dbPort}`,
93
+ `DB_TYPE=${answers.dbType}`,
94
+ '# Secrets',
95
+ `SECRET_KEY=${secret('SECRET_KEY')}`,
96
+ `SECRET_SALT=${secret('SECRET_SALT')}`,
97
+ `JWT_SECRET_KEY=${secret('JWT_SECRET_KEY')}`,
98
+ ,
99
+ ].join('\n');
100
+ }
101
+ async function removeIfPresent(filePath) {
102
+ await fs_1.promises.rm(filePath, { recursive: true, force: true });
103
+ }
104
+ async function createProject(answers) {
105
+ const targetDir = path_1.default.resolve(process.cwd(), answers.folderName);
106
+ const spinner = (0, ora_1.default)({ text: chalk_1.default.cyan('Creating project'), spinner: 'dots12', color: 'cyan' }).start();
107
+ try {
108
+ if (await exists(targetDir))
109
+ throw new Error(`Folder "${answers.folderName}" already exists.`);
110
+ spinner.text = chalk_1.default.blue('Downloading project template');
111
+ await (0, degit_1.default)('https://github.com/Flutry-HQ/Flutry.git', { cache: false, force: true }).clone(targetDir);
112
+ spinner.text = chalk_1.default.magenta('Applying project settings');
113
+ const packageJsonPath = path_1.default.join(targetDir, 'package.json');
114
+ const packageJson = JSON.parse(await fs_1.promises.readFile(packageJsonPath, 'utf8'));
115
+ packageJson.name = answers.projectName;
116
+ await fs_1.promises.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
117
+ await Promise.all([
118
+ fs_1.promises.writeFile(path_1.default.join(targetDir, '.env'), buildEnv(answers, false), 'utf8'),
119
+ fs_1.promises.writeFile(path_1.default.join(targetDir, '.env.example'), buildEnv(answers, true), 'utf8'),
120
+ removeIfPresent(path_1.default.join(targetDir, '.gitattributes')),
121
+ ]);
122
+ await fs_1.promises.writeFile(path_1.default.join(targetDir, 'README.md'), `# ${answers.folderName}\n\nGenerated by Flutry CLI.\n`, 'utf8');
123
+ spinner.succeed(chalk_1.default.greenBright('Project created successfully'));
124
+ return targetDir;
125
+ }
126
+ catch (error) {
127
+ spinner.fail(chalk_1.default.red('Project creation failed'));
128
+ await removeIfPresent(targetDir);
129
+ throw error;
130
+ }
131
+ }
132
+ async function installPackages(targetDir, packageManager) {
133
+ const spinner = (0, ora_1.default)({ text: chalk_1.default.yellow(`Installing dependencies with ${packageManager}`), spinner: 'arc', color: 'yellow' }).start();
134
+ await new Promise((resolve, reject) => {
135
+ const executable = process.platform === 'win32' ? `${packageManager}.cmd` : packageManager;
136
+ const command = process.platform === 'win32' ? (process.env.ComSpec ?? 'cmd.exe') : executable;
137
+ const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', executable, 'install'] : ['install'];
138
+ const child = (0, child_process_1.spawn)(command, commandArgs, { cwd: targetDir, stdio: ['ignore', 'pipe', 'pipe'] });
139
+ let errorOutput = '';
140
+ child.stderr.on('data', (data) => {
141
+ errorOutput += data.toString();
142
+ });
143
+ child.on('error', reject);
144
+ child.on('close', (code) => {
145
+ if (code === 0)
146
+ resolve();
147
+ else
148
+ reject(new Error(`${packageManager} install failed${errorOutput ? `: ${errorOutput.trim()}` : ''}`));
149
+ });
150
+ });
151
+ spinner.succeed(chalk_1.default.greenBright('Dependencies installed'));
152
+ }
153
+ async function exists(filePath) {
154
+ try {
155
+ await fs_1.promises.access(filePath);
156
+ return true;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ function validateFolderName(value) {
163
+ return value.trim().length > 0 && value !== '.' && value !== '..' && !/[\\/]/.test(value);
164
+ }
165
+ function validatePort(value) {
166
+ return /^\d{1,5}$/.test(value) && Number(value) > 0 && Number(value) <= 65535 ? true : 'Enter a valid port (1-65535).';
167
+ }
168
+ function validatePackageName(name) {
169
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) ? true : 'Use lowercase letters, numbers and hyphens only.';
170
+ }
171
+ async function newCommand() {
172
+ try {
173
+ const answers = await promptQuestions();
174
+ const targetDir = await createProject(answers);
175
+ if (answers.installNow)
176
+ await installPackages(targetDir, answers.packageManager);
177
+ console.log(`\n${chalk_1.default.greenBright('╭─')} ${chalk_1.default.bold.green('✓ Project ready')} ${chalk_1.default.greenBright('─'.repeat(19))}`);
178
+ console.log(`${chalk_1.default.greenBright('│')} ${chalk_1.default.gray('Location:')} ${chalk_1.default.cyan(`./${answers.folderName}`)}`);
179
+ console.log(`${chalk_1.default.greenBright('│')} ${chalk_1.default.gray('Next:')}`);
180
+ console.log(`${chalk_1.default.greenBright('│')} ${chalk_1.default.cyan(`cd ${answers.folderName}`)}`);
181
+ if (!answers.installNow)
182
+ console.log(`${chalk_1.default.greenBright('│')} ${chalk_1.default.yellow(`${answers.packageManager} install`)}`);
183
+ console.log(`${chalk_1.default.greenBright('│')} ${chalk_1.default.green(`${answers.packageManager} dev`)}`);
184
+ console.log(`${chalk_1.default.greenBright('╰────────────────────────────────────╯')}\n`);
185
+ }
186
+ catch (error) {
187
+ console.error(`\n${chalk_1.default.redBright('✖')} ${error instanceof Error ? error.message : 'Command failed.'}`);
188
+ process.exitCode = 1;
189
+ }
190
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const new_1 = __importDefault(require("./commands/new"));
9
+ const generate_1 = require("./commands/generate");
10
+ commander_1.program.command('new').description('Create a new Flutry project').action(new_1.default);
11
+ const generate = commander_1.program.command('generate').alias('g').description('Generate a route or model');
12
+ generate.command('route <name>').description('Generate a route and service').action(generate_1.generateRoute);
13
+ generate.command('model <name>').description('Generate a Sequelize model').action(generate_1.generateModel);
14
+ commander_1.program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@flutry/cli",
3
+ "version": "1.0.0",
4
+ "description": "Flurty project scaffolding CLI",
5
+ "bin": {
6
+ "flurty": "dist/index.js"
7
+ },
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "assets",
13
+ "README.md"
14
+ ],
15
+ "repository": "https://github.com/Flutry-HQ/cli.git",
16
+ "author": "HEDI <admin@otamoon.hu>",
17
+ "license": "MIT",
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "start": "node dist/index.js",
21
+ "dev": "tsc --watch"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^26.2.0",
25
+ "typescript": "^7.0.2"
26
+ },
27
+ "dependencies": {
28
+ "@inquirer/prompts": "^7.8.4",
29
+ "chalk": "^4.1.2",
30
+ "commander": "^15.0.0",
31
+ "degit": "^2.8.4",
32
+ "ora": "^9.4.1"
33
+ }
34
+ }