kythia-core 0.10.1-beta ā 0.11.0-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 -1
- package/src/cli/Command.js +68 -0
- package/src/cli/commands/CacheClearCommand.js +136 -0
- package/src/cli/commands/LangCheckCommand.js +367 -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 +7 -8
- 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 +2 -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,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š Migration File Generator
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/MakeMigrationCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.0-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Scaffolds a new database migration file with a precise YYYYMMDD_HHMMSS timestamp prefix.
|
|
11
|
+
* Places the file in the specified addon's migration directory.
|
|
12
|
+
*
|
|
13
|
+
* ⨠Core Features:
|
|
14
|
+
* - Timestamped Ordering: Ensures migrations run in creation order.
|
|
15
|
+
* - Addon Aware: Targets the correct module folder automatically.
|
|
16
|
+
* - Template Injection: Provides standard up/down methods via `queryInterface`.
|
|
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 MakeMigrationCommand extends Command {
|
|
25
|
+
signature = 'make:migration <name> <addon>';
|
|
26
|
+
description = 'Create a new migration file for an addon';
|
|
27
|
+
|
|
28
|
+
async handle(_options, name, addon) {
|
|
29
|
+
const rootDir = process.cwd();
|
|
30
|
+
const targetDir = path.join(
|
|
31
|
+
rootDir,
|
|
32
|
+
'addons',
|
|
33
|
+
addon,
|
|
34
|
+
'database',
|
|
35
|
+
'migrations',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(path.join(rootDir, 'addons', addon))) {
|
|
39
|
+
console.error(pc.red(`ā Addon '${addon}' not found!`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(targetDir)) {
|
|
44
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const timestamp = now
|
|
49
|
+
.toISOString()
|
|
50
|
+
.replace(/T/, '_')
|
|
51
|
+
.replace(/[-:]/g, '')
|
|
52
|
+
.split('.')[0];
|
|
53
|
+
const fileName = `${timestamp}_${name}.js`;
|
|
54
|
+
const filePath = path.join(targetDir, fileName);
|
|
55
|
+
|
|
56
|
+
const template = `/**
|
|
57
|
+
* @namespace: addons/${addon}/database/migrations/${fileName}
|
|
58
|
+
* @type: Database Migration
|
|
59
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
60
|
+
* @assistant chaa & graa
|
|
61
|
+
* @version 0.9.12-beta
|
|
62
|
+
*/
|
|
63
|
+
module.exports = {
|
|
64
|
+
async up(queryInterface, DataTypes) {
|
|
65
|
+
// await queryInterface.createTable('table_name', {
|
|
66
|
+
// id: DataTypes.INTEGER
|
|
67
|
+
// });
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async down(queryInterface, DataTypes) {
|
|
71
|
+
// await queryInterface.dropTable('table_name');
|
|
72
|
+
}
|
|
73
|
+
};`;
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(filePath, template);
|
|
76
|
+
|
|
77
|
+
console.log(pc.green('ā
Created migration:'));
|
|
78
|
+
console.log(pc.dim(` addons/${addon}/database/migrations/${fileName}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = MakeMigrationCommand;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š¦ Model Scaffolding Tool
|
|
3
|
+
*
|
|
4
|
+
* @file src/cli/commands/MakeModelCommand.js
|
|
5
|
+
* @copyright Ā© 2025 kenndeclouv
|
|
6
|
+
* @assistant chaa & graa
|
|
7
|
+
* @version 0.11.0-beta
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Generates new Sequelize model files extending `KythiaModel`.
|
|
11
|
+
* Automatically creates the directory structure and populates standard boilerplate.
|
|
12
|
+
*
|
|
13
|
+
* ⨠Core Features:
|
|
14
|
+
* - Smart Scaffolding: Creates models in `addons/{addon}/database/models`.
|
|
15
|
+
* - Duplicate Protection: Prevents overwriting existing models.
|
|
16
|
+
* - Standard Boilerplate: Includes `guarded` and `structure` properties by default.
|
|
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 MakeModelCommand extends Command {
|
|
25
|
+
signature = 'make:model <name> <addon>';
|
|
26
|
+
description = 'Create a new KythiaModel file';
|
|
27
|
+
|
|
28
|
+
async handle(_options, name, addon) {
|
|
29
|
+
//
|
|
30
|
+
const rootDir = process.cwd();
|
|
31
|
+
const targetDir = path.join(rootDir, 'addons', addon, 'database', 'models');
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(path.join(rootDir, 'addons', addon))) {
|
|
34
|
+
console.error(pc.red(`ā Addon '${addon}' not found!`));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(targetDir)) {
|
|
39
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fileName = `${name}.js`;
|
|
43
|
+
const filePath = path.join(targetDir, fileName);
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(filePath)) {
|
|
46
|
+
console.error(
|
|
47
|
+
pc.red(`ā Model '${fileName}' already exists in ${addon}!`),
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const template = `/**
|
|
53
|
+
* @namespace: addons/${addon}/database/models/${fileName}
|
|
54
|
+
* @type: Database Model
|
|
55
|
+
* @copyright Ā© ${new Date().getFullYear()} kenndeclouv
|
|
56
|
+
* @assistant chaa & graa
|
|
57
|
+
* @version 0.9.12-beta
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
const { KythiaModel } = require("kythia-core");
|
|
61
|
+
|
|
62
|
+
class ${name} extends KythiaModel {
|
|
63
|
+
static guarded = ["id"];
|
|
64
|
+
|
|
65
|
+
static get structure() {
|
|
66
|
+
return {
|
|
67
|
+
options: { timestamps: true },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = ${name};`;
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(filePath, template);
|
|
75
|
+
|
|
76
|
+
console.log(pc.green('ā
Created Model:'));
|
|
77
|
+
console.log(pc.dim(` addons/${addon}/database/models/${fileName}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = MakeModelCommand;
|
|
@@ -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.0-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.0-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.0-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.0-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;
|