kythia-core 0.10.1-beta ā 0.11.1-beta
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 +87 -1
- package/package.json +11 -1
- package/src/Kythia.js +9 -7
- package/src/KythiaClient.js +1 -2
- package/src/cli/Command.js +68 -0
- package/src/cli/commands/CacheClearCommand.js +136 -0
- package/src/cli/commands/LangCheckCommand.js +396 -0
- package/src/cli/commands/LangTranslateCommand.js +336 -0
- package/src/cli/commands/MakeMigrationCommand.js +82 -0
- package/src/cli/commands/MakeModelCommand.js +81 -0
- package/src/cli/commands/MigrateCommand.js +259 -0
- package/src/cli/commands/NamespaceCommand.js +112 -0
- package/src/cli/commands/StructureCommand.js +70 -0
- package/src/cli/commands/UpversionCommand.js +94 -0
- package/src/cli/index.js +69 -0
- package/src/cli/utils/db.js +117 -0
- package/src/database/KythiaMigrator.js +1 -1
- package/src/database/KythiaModel.js +76 -48
- package/src/database/KythiaSequelize.js +1 -1
- package/src/database/KythiaStorage.js +1 -1
- package/src/database/ModelLoader.js +1 -1
- package/src/managers/AddonManager.js +10 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/InteractionManager.js +56 -2
- package/src/managers/ShutdownManager.js +1 -1
- package/.husky/pre-commit +0 -4
- package/biome.json +0 -40
- package/bun.lock +0 -445
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š Database Migration Runner
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/MigrateCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.1-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Manages database schema updates using Umzug. Supports standard migration,
|
|
11
|
+
* fresh resets (nuclear option), and smart batch-based rollbacks.
|
|
12
|
+
*
|
|
13
|
+
* ⨠Core Features:
|
|
14
|
+
* - Fresh Mode: Wipes database and re-runs migrations from scratch.
|
|
15
|
+
* - Smart Rollback: Rolls back only the last batch of migrations.
|
|
16
|
+
* - Auto-Create: Detects missing database and offers to create it.
|
|
17
|
+
* - Safety Net: Prompts for confirmation on destructive actions.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const Command = require('../Command');
|
|
21
|
+
const { umzug, sequelize, storage } = require('../utils/db');
|
|
22
|
+
const pc = require('picocolors');
|
|
23
|
+
const readline = require('node:readline');
|
|
24
|
+
|
|
25
|
+
function promptYN(question) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const rl = readline.createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout,
|
|
30
|
+
});
|
|
31
|
+
rl.question(question, (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve(answer.trim().toLowerCase());
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class MigrateCommand extends Command {
|
|
39
|
+
signature = 'migrate';
|
|
40
|
+
description = 'Run pending database migrations';
|
|
41
|
+
|
|
42
|
+
configure(cmd) {
|
|
43
|
+
cmd
|
|
44
|
+
.option('-f, --fresh', 'Wipe database and re-run all migrations')
|
|
45
|
+
.option('-r, --rollback', 'Rollback the last batch of migrations');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async handle(options) {
|
|
49
|
+
console.log(pc.dim('š Connecting to database...'));
|
|
50
|
+
let needReauth = false;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await sequelize.authenticate();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const dbErrorCodes = ['ER_BAD_DB_ERROR', '3D000'];
|
|
56
|
+
if (
|
|
57
|
+
dbErrorCodes.includes(err.original?.code) ||
|
|
58
|
+
dbErrorCodes.includes(err.original?.sqlState)
|
|
59
|
+
) {
|
|
60
|
+
const dbName = sequelize.config?.database || '(unknown)';
|
|
61
|
+
console.log(pc.red(`ā Database "${dbName}" does not exist.`));
|
|
62
|
+
const answer = await promptYN(
|
|
63
|
+
pc.yellow(`Do you want to create the database "${dbName}"? (y/n): `),
|
|
64
|
+
);
|
|
65
|
+
if (answer === 'y' || answer === 'yes') {
|
|
66
|
+
try {
|
|
67
|
+
const { Sequelize } = require('sequelize');
|
|
68
|
+
const currentDialect = sequelize.getDialect();
|
|
69
|
+
const adminConfig = {
|
|
70
|
+
...sequelize.config,
|
|
71
|
+
dialect: currentDialect,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (currentDialect === 'mysql' || currentDialect === 'mariadb') {
|
|
75
|
+
delete adminConfig.database;
|
|
76
|
+
} else if (currentDialect === 'postgres') {
|
|
77
|
+
adminConfig.database = 'postgres';
|
|
78
|
+
} else if (currentDialect === 'sqlite') {
|
|
79
|
+
console.log(
|
|
80
|
+
pc.green('SQLite database file will be created automatically.'),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const adminSequelize = new Sequelize(adminConfig);
|
|
85
|
+
adminSequelize.options.logging = false;
|
|
86
|
+
|
|
87
|
+
await adminSequelize
|
|
88
|
+
.query(`CREATE DATABASE \`${dbName}\``)
|
|
89
|
+
.catch(async (e) => {
|
|
90
|
+
if (currentDialect === 'postgres') {
|
|
91
|
+
await adminSequelize.query(`CREATE DATABASE "${dbName}"`);
|
|
92
|
+
} else {
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await adminSequelize.close();
|
|
98
|
+
console.log(pc.green(`ā
Database "${dbName}" created.`));
|
|
99
|
+
needReauth = true;
|
|
100
|
+
} catch (createErr) {
|
|
101
|
+
console.error(
|
|
102
|
+
pc.bgRed(' ERROR '),
|
|
103
|
+
pc.red('Failed to create database:'),
|
|
104
|
+
createErr.message,
|
|
105
|
+
);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
console.log(pc.red('Migration cancelled.'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
console.error(pc.bgRed(' ERROR '), pc.red(err.message));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (needReauth) {
|
|
119
|
+
try {
|
|
120
|
+
await sequelize.authenticate();
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(
|
|
123
|
+
pc.bgRed(' ERROR '),
|
|
124
|
+
pc.red('Failed to connect after creating database:'),
|
|
125
|
+
e.message,
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (options.fresh) {
|
|
133
|
+
console.log(pc.red('š§Ø DROPPING ALL TABLES (Fresh)...'));
|
|
134
|
+
|
|
135
|
+
const answer = await promptYN(
|
|
136
|
+
pc.bgRed(pc.white(' DANGER ')) +
|
|
137
|
+
pc.yellow(' This will wipe ALL DATA. Are you sure? (y/n): '),
|
|
138
|
+
);
|
|
139
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
140
|
+
console.log(pc.cyan('Operation cancelled.'));
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const queryInterface = sequelize.getQueryInterface();
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
sequelize.getDialect() === 'mysql' ||
|
|
148
|
+
sequelize.getDialect() === 'mariadb'
|
|
149
|
+
) {
|
|
150
|
+
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { raw: true });
|
|
151
|
+
} else if (sequelize.getDialect() === 'sqlite') {
|
|
152
|
+
await sequelize.query('PRAGMA foreign_keys = OFF', { raw: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await queryInterface.dropAllTables();
|
|
157
|
+
|
|
158
|
+
await queryInterface.dropTable('migrations').catch(() => {});
|
|
159
|
+
await queryInterface.dropTable('SequelizeMeta').catch(() => {});
|
|
160
|
+
|
|
161
|
+
console.log(pc.green('ā
All tables dropped. Database is clean.'));
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(pc.red(`ā Failed to drop tables: ${e.message}`));
|
|
164
|
+
throw e;
|
|
165
|
+
} finally {
|
|
166
|
+
if (
|
|
167
|
+
sequelize.getDialect() === 'mysql' ||
|
|
168
|
+
sequelize.getDialect() === 'mariadb'
|
|
169
|
+
) {
|
|
170
|
+
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { raw: true });
|
|
171
|
+
} else if (sequelize.getDialect() === 'sqlite') {
|
|
172
|
+
await sequelize.query('PRAGMA foreign_keys = ON', { raw: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(pc.dim(' -> Re-running all migrations...'));
|
|
177
|
+
|
|
178
|
+
if (storage && typeof storage.setBatch === 'function') {
|
|
179
|
+
storage.setBatch(1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const executed = await umzug.up();
|
|
183
|
+
|
|
184
|
+
console.log(
|
|
185
|
+
pc.green(
|
|
186
|
+
`ā
Database refreshed! (${executed.length} migrations re-applied in Batch 1)`,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options.rollback) {
|
|
193
|
+
const lastBatchNum = await storage.getLastBatchNumber();
|
|
194
|
+
|
|
195
|
+
if (lastBatchNum === 0) {
|
|
196
|
+
console.log(pc.gray('⨠Nothing to rollback (No batches found).'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const filesInBatch = await storage.getLastBatchMigrations();
|
|
201
|
+
|
|
202
|
+
console.log(
|
|
203
|
+
pc.yellow(
|
|
204
|
+
`āŖ Rolling back Batch #${lastBatchNum} (${filesInBatch.length} files)...`,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (filesInBatch.length > 0) {
|
|
209
|
+
const rolledBack = await umzug.down({
|
|
210
|
+
migrations: filesInBatch,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (rolledBack.length === 0)
|
|
214
|
+
console.log(
|
|
215
|
+
pc.red('ā Rollback logic executed but no files processed.'),
|
|
216
|
+
);
|
|
217
|
+
else
|
|
218
|
+
console.log(
|
|
219
|
+
pc.green(`ā
Batch #${lastBatchNum} rolled back successfully.`),
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(
|
|
223
|
+
pc.gray(
|
|
224
|
+
'⨠Batch record exists but no files found (Manual DB modification?).',
|
|
225
|
+
),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const pending = await umzug.pending();
|
|
232
|
+
if (pending.length === 0) {
|
|
233
|
+
console.log(pc.gray('⨠Nothing to migrate. Database is up to date.'));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const lastBatch = await storage.getLastBatchNumber();
|
|
238
|
+
const newBatch = lastBatch + 1;
|
|
239
|
+
|
|
240
|
+
storage.setBatch(newBatch);
|
|
241
|
+
|
|
242
|
+
console.log(pc.cyan(`š Running migrations (Batch #${newBatch})...`));
|
|
243
|
+
|
|
244
|
+
const executed = await umzug.up();
|
|
245
|
+
|
|
246
|
+
console.log(
|
|
247
|
+
pc.green(`ā
Batch #${newBatch} completed (${executed.length} files).`),
|
|
248
|
+
);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error(pc.bgRed(' ERROR '), pc.red(err.message));
|
|
251
|
+
|
|
252
|
+
process.exit(1);
|
|
253
|
+
} finally {
|
|
254
|
+
await sequelize.close();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = MigrateCommand;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š·ļø JSDoc Namespace Automator
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/NamespaceCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.1-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Scans the entire project structure and automatically adds or updates
|
|
11
|
+
* JSDoc `@namespace` headers to ensure code documentation consistency.
|
|
12
|
+
* Detects file types (Command, Model, Event) based on directory context.
|
|
13
|
+
*
|
|
14
|
+
* ⨠Core Features:
|
|
15
|
+
* - Smart Detection: Infers `@type` from folder names (commands, events, etc).
|
|
16
|
+
* - Recursive Scan: Processes nested directories including addons.
|
|
17
|
+
* - Safe Update: Updates headers without touching code logic.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const Command = require('../Command');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const pc = require('picocolors');
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
|
|
25
|
+
class NamespaceCommand extends Command {
|
|
26
|
+
signature = 'dev:namespace';
|
|
27
|
+
description = 'Add or update JSDoc @namespace headers in command files';
|
|
28
|
+
|
|
29
|
+
async handle() {
|
|
30
|
+
console.log(pc.cyan('š Starting namespace annotation process...'));
|
|
31
|
+
const rootDir = process.cwd();
|
|
32
|
+
|
|
33
|
+
// Helper: Find Files Recursive
|
|
34
|
+
function findJsFilesRecursive(dir) {
|
|
35
|
+
let results = [];
|
|
36
|
+
if (!fs.existsSync(dir)) return results;
|
|
37
|
+
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
for (const file of list) {
|
|
39
|
+
if (file.name === 'node_modules' || file.name === '.git') continue;
|
|
40
|
+
const fullPath = path.join(dir, file.name);
|
|
41
|
+
if (file.isDirectory()) {
|
|
42
|
+
results = results.concat(findJsFilesRecursive(fullPath));
|
|
43
|
+
} else if (file.name.endsWith('.js')) {
|
|
44
|
+
results.push(fullPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getFileType(filePath) {
|
|
51
|
+
const fileName = path.basename(filePath);
|
|
52
|
+
const parentDirName = path.basename(path.dirname(filePath));
|
|
53
|
+
const grandParentDirName = path.basename(
|
|
54
|
+
path.dirname(path.dirname(filePath)),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (fileName === '_command.js') return 'Command Group Definition';
|
|
58
|
+
if (fileName === '_group.js') return 'Subcommand Group Definition';
|
|
59
|
+
if (parentDirName === 'commands' || grandParentDirName === 'commands')
|
|
60
|
+
return 'Command';
|
|
61
|
+
if (parentDirName === 'events') return 'Event Handler';
|
|
62
|
+
if (parentDirName === 'helpers') return 'Helper Script';
|
|
63
|
+
if (parentDirName === 'models') return 'Database Model';
|
|
64
|
+
if (parentDirName === 'migrations') return 'Database Migration';
|
|
65
|
+
if (parentDirName === 'tasks') return 'Scheduled Task';
|
|
66
|
+
return 'Module';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let filesToProcess = [];
|
|
70
|
+
|
|
71
|
+
const addonsPath = path.join(rootDir, 'addons');
|
|
72
|
+
if (fs.existsSync(addonsPath)) {
|
|
73
|
+
console.log(pc.dim('š Scanning addons...'));
|
|
74
|
+
filesToProcess = filesToProcess.concat(findJsFilesRecursive(addonsPath));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pkg = require(path.join(rootDir, 'package.json'));
|
|
78
|
+
const currentYear = new Date().getFullYear();
|
|
79
|
+
|
|
80
|
+
filesToProcess.forEach((filePath) => {
|
|
81
|
+
const relativePath = path.relative(rootDir, filePath).replace(/\\/g, '/');
|
|
82
|
+
const fileType = getFileType(filePath);
|
|
83
|
+
|
|
84
|
+
const newHeader = `/**
|
|
85
|
+
* @namespace: ${relativePath}
|
|
86
|
+
* @type: ${fileType}
|
|
87
|
+
* @copyright Ā© ${currentYear} kenndeclouv
|
|
88
|
+
* @assistant chaa & graa
|
|
89
|
+
* @version ${pkg.version}
|
|
90
|
+
*/`;
|
|
91
|
+
|
|
92
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
93
|
+
const headerRegex = /\/\*\*[\s\S]*?namespace:[\s\S]*?\*\//;
|
|
94
|
+
|
|
95
|
+
let newContent;
|
|
96
|
+
if (headerRegex.test(content)) {
|
|
97
|
+
newContent = content.replace(headerRegex, newHeader.trim());
|
|
98
|
+
} else {
|
|
99
|
+
newContent = `${newHeader}\n\n${content}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (newContent.trim() !== content.trim()) {
|
|
103
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
104
|
+
console.log(pc.green(`š Updated: ${relativePath}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(pc.green('\nā
Namespace annotation complete!'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = NamespaceCommand;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* šŗļø Project Structure Mapper
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/StructureCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.1-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Generates a markdown tree representation of the entire project directory.
|
|
11
|
+
* Useful for documentation and providing context to AI assistants.
|
|
12
|
+
*
|
|
13
|
+
* ⨠Core Features:
|
|
14
|
+
* - Clean Output: Excludes noise (`node_modules`, `.git`).
|
|
15
|
+
* - Format: Outputs standardized tree syntax to `temp/structure.md`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const Command = require('../Command');
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const pc = require('picocolors');
|
|
22
|
+
|
|
23
|
+
class StructureCommand extends Command {
|
|
24
|
+
signature = 'gen:structure';
|
|
25
|
+
description = 'Generate project structure to temp/structure.md';
|
|
26
|
+
|
|
27
|
+
async handle() {
|
|
28
|
+
const rootDir = process.cwd();
|
|
29
|
+
const outputDir = path.join(rootDir, 'temp');
|
|
30
|
+
const outputFile = path.join(outputDir, 'structure.md');
|
|
31
|
+
const exclude = [
|
|
32
|
+
'.git',
|
|
33
|
+
'.vscode',
|
|
34
|
+
'node_modules',
|
|
35
|
+
'dist',
|
|
36
|
+
'logs',
|
|
37
|
+
'.husky',
|
|
38
|
+
'.yalc',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function generateTree(dir, prefix = '') {
|
|
42
|
+
const items = fs
|
|
43
|
+
.readdirSync(dir)
|
|
44
|
+
.filter((item) => !exclude.includes(item));
|
|
45
|
+
let tree = '';
|
|
46
|
+
|
|
47
|
+
items.forEach((item, index) => {
|
|
48
|
+
const fullPath = path.join(dir, item);
|
|
49
|
+
const isLast = index === items.length - 1;
|
|
50
|
+
const connector = isLast ? 'āāā ' : 'āāā ';
|
|
51
|
+
tree += `${prefix}${connector}${item}\n`;
|
|
52
|
+
|
|
53
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
54
|
+
tree += generateTree(fullPath, prefix + (isLast ? ' ' : 'ā '));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return tree;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(outputDir)) {
|
|
61
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tree = generateTree(rootDir);
|
|
65
|
+
fs.writeFileSync(outputFile, tree, 'utf8');
|
|
66
|
+
console.log(pc.green(`ā
Project structure saved to: ${outputFile}`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = StructureCommand;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š Semantic Version Bumper
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/UpversionCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.1-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Synchronizes the `@version` tag in all JSDoc headers across the project
|
|
11
|
+
* to match the version defined in `package.json`.
|
|
12
|
+
*
|
|
13
|
+
* ⨠Core Features:
|
|
14
|
+
* - Mass Update: Updates hundreds of files in seconds.
|
|
15
|
+
* - Regex Powered: Accurately targets version tags without affecting other code.
|
|
16
|
+
* - Safety: Ignores sensitive folders like `node_modules` and `.git`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const Command = require('../Command');
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const pc = require('picocolors');
|
|
23
|
+
|
|
24
|
+
class UpversionCommand extends Command {
|
|
25
|
+
signature = 'version:up';
|
|
26
|
+
description = 'Update @version in JSDoc headers to match package.json';
|
|
27
|
+
|
|
28
|
+
async handle() {
|
|
29
|
+
const rootDir = process.cwd();
|
|
30
|
+
const pkgPath = path.join(rootDir, 'package.json');
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(pkgPath)) {
|
|
33
|
+
console.error(pc.red('ā package.json not found!'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pkg = require(pkgPath);
|
|
38
|
+
const version = pkg.version;
|
|
39
|
+
console.log(pc.cyan(`⨠Using version from package.json: ${version}`));
|
|
40
|
+
|
|
41
|
+
const ignoredPaths = [
|
|
42
|
+
'node_modules',
|
|
43
|
+
'.git',
|
|
44
|
+
'.env',
|
|
45
|
+
'dist',
|
|
46
|
+
'obfuscate.js',
|
|
47
|
+
'.yalc',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function getAllJsFiles(dir, fileList = []) {
|
|
51
|
+
const files = fs.readdirSync(dir);
|
|
52
|
+
files.forEach((file) => {
|
|
53
|
+
const fullPath = path.join(dir, file);
|
|
54
|
+
if (ignoredPaths.includes(path.basename(fullPath))) return;
|
|
55
|
+
|
|
56
|
+
const stat = fs.statSync(fullPath);
|
|
57
|
+
if (stat.isDirectory()) {
|
|
58
|
+
getAllJsFiles(fullPath, fileList);
|
|
59
|
+
} else if (file.endsWith('.js')) {
|
|
60
|
+
fileList.push(fullPath);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return fileList;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const jsFiles = getAllJsFiles(rootDir);
|
|
67
|
+
const versionRegex = /(@version\s+)v?[\d.\-a-zA-Z]+/g;
|
|
68
|
+
let updatedCount = 0;
|
|
69
|
+
|
|
70
|
+
jsFiles.forEach((file) => {
|
|
71
|
+
try {
|
|
72
|
+
const originalContent = fs.readFileSync(file, 'utf8');
|
|
73
|
+
const newContent = originalContent.replace(
|
|
74
|
+
versionRegex,
|
|
75
|
+
`$1${version}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (originalContent !== newContent) {
|
|
79
|
+
fs.writeFileSync(file, newContent, 'utf8');
|
|
80
|
+
console.log(pc.green(`ā
Updated: ${path.relative(rootDir, file)}`));
|
|
81
|
+
updatedCount++;
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(pc.red(`ā Failed to process: ${file}`), err.message);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log(
|
|
89
|
+
pc.green(`\nš Version update complete! (${updatedCount} files changed)`),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = UpversionCommand;
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ā” Kythia CLI Entry Point
|
|
5
|
+
*
|
|
6
|
+
* @file src/cli/index.js
|
|
7
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
8
|
+
* @assistant chaa & graa
|
|
9
|
+
* @version 0.11.1-beta
|
|
10
|
+
*
|
|
11
|
+
* @description
|
|
12
|
+
* The main bootstrap entry point for the Kythia CLI.
|
|
13
|
+
* It dynamically scans, loads, and registers all command classes found in the
|
|
14
|
+
* `commands` directory, orchestrating the `commander` program execution.
|
|
15
|
+
*
|
|
16
|
+
* ⨠Core Features:
|
|
17
|
+
* - Dynamic Loading: Automatically finds new commands without manual import.
|
|
18
|
+
* - Class-Based Architecture: Supports standard `Command` class structure.
|
|
19
|
+
* - Error Handling: Gracefully handles malformed commands during boot.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { version } = require('../../package.json');
|
|
23
|
+
const { Command } = require('commander');
|
|
24
|
+
const BaseCommand = require('./Command');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const pc = require('picocolors');
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
|
|
29
|
+
const program = new Command();
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.name('kythia')
|
|
33
|
+
.description(pc.cyan('šø Kythia Framework CLI'))
|
|
34
|
+
.version(version);
|
|
35
|
+
|
|
36
|
+
const commandsDir = path.join(__dirname, 'commands');
|
|
37
|
+
|
|
38
|
+
if (fs.existsSync(commandsDir)) {
|
|
39
|
+
const commandFiles = fs
|
|
40
|
+
.readdirSync(commandsDir)
|
|
41
|
+
.filter((file) => file.endsWith('.js'));
|
|
42
|
+
|
|
43
|
+
for (const file of commandFiles) {
|
|
44
|
+
const filePath = path.join(commandsDir, file);
|
|
45
|
+
try {
|
|
46
|
+
const CommandClass = require(filePath);
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
typeof CommandClass === 'function' &&
|
|
50
|
+
CommandClass.prototype instanceof BaseCommand
|
|
51
|
+
) {
|
|
52
|
+
const commandInstance = new CommandClass();
|
|
53
|
+
commandInstance.register(program);
|
|
54
|
+
} else if (typeof CommandClass.register === 'function') {
|
|
55
|
+
CommandClass.register(program);
|
|
56
|
+
} else {
|
|
57
|
+
console.warn(
|
|
58
|
+
pc.yellow(
|
|
59
|
+
`ā ļø Skipped ${file}: Not a valid Command class or missing 'register' function.`,
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(pc.red(`ā Failed to load command ${file}:`), err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
program.parse(process.argv);
|