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.
@@ -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.0-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);
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 🔌 CLI Database & Migration Bootstrapper
3
+ *
4
+ * @file src/cli/utils/db.js
5
+ * @copyright © 2025 kenndeclouv
6
+ * @assistant chaa & graa
7
+ * @version 0.11.0-beta
8
+ *
9
+ * @description
10
+ * Initializes the database connection and migration engine (Umzug) specifically
11
+ * for CLI operations. It dynamically loads the project configuration and
12
+ * discovers migration files across all addons.
13
+ *
14
+ * ✨ Core Features:
15
+ * - Dynamic Config: Loads `kythia.config.js` from the user's project root.
16
+ * - Auto-Discovery: Scans `addons` directory for migration files.
17
+ * - Pretty Logging: Custom console output using `picocolors` for migration status.
18
+ * - Singleton-like: Exports shared instances of Sequelize and Umzug.
19
+ */
20
+
21
+ require('@dotenvx/dotenvx/config');
22
+ const fs = require('node:fs');
23
+ const path = require('node:path');
24
+ const { Umzug } = require('umzug');
25
+ const createSequelizeInstance = require('../../database/KythiaSequelize');
26
+ const KythiaStorage = require('../../database/KythiaStorage');
27
+ const pc = require('picocolors');
28
+
29
+ const configPath = path.resolve(process.cwd(), 'kythia.config.js');
30
+
31
+ if (!fs.existsSync(configPath)) {
32
+ console.error('❌ kythia.config.js not found in root directory!');
33
+ process.exit(1);
34
+ }
35
+
36
+ const config = require(configPath);
37
+
38
+ const logger = {
39
+ info: () => {},
40
+ error: console.error,
41
+ debug: () => {},
42
+ };
43
+
44
+ const sequelize = createSequelizeInstance(config, logger);
45
+
46
+ function getMigrationFiles() {
47
+ const rootDir = process.cwd();
48
+ const addonsDir = path.join(rootDir, 'addons');
49
+
50
+ if (!fs.existsSync(addonsDir)) return [];
51
+
52
+ const migrationFiles = [];
53
+ const addonFolders = fs
54
+ .readdirSync(addonsDir)
55
+ .filter((f) => fs.statSync(path.join(addonsDir, f)).isDirectory());
56
+
57
+ for (const addon of addonFolders) {
58
+ const migrationDir = path.join(addonsDir, addon, 'database', 'migrations');
59
+ if (fs.existsSync(migrationDir)) {
60
+ const files = fs
61
+ .readdirSync(migrationDir)
62
+ .filter((f) => f.endsWith('.js'))
63
+ .map((f) => ({
64
+ name: f,
65
+ path: path.join(migrationDir, f),
66
+ }));
67
+ migrationFiles.push(...files);
68
+ }
69
+ }
70
+ return migrationFiles.sort((a, b) => a.name.localeCompare(b.name));
71
+ }
72
+
73
+ const storage = new KythiaStorage({ sequelize });
74
+
75
+ const umzugLogger = {
76
+ info: (event) => {
77
+ if (typeof event === 'object') {
78
+ if (event.event === 'migrated') {
79
+ console.log(
80
+ pc.green(`✅ Migrated: ${event.name} `) +
81
+ pc.gray(`(${event.durationSeconds}s)`),
82
+ );
83
+ } else if (event.event === 'reverting') {
84
+ console.log(pc.yellow(`↩️ Reverting: ${event.name}`));
85
+ } else if (event.event === 'reverted') {
86
+ console.log(
87
+ pc.red(`❌ Reverted: ${event.name} `) +
88
+ pc.gray(`(${event.durationSeconds}s)`),
89
+ );
90
+ }
91
+ } else {
92
+ console.log(pc.dim(event));
93
+ }
94
+ },
95
+ warn: (msg) => console.warn(pc.yellow(`⚠️ ${msg}`)),
96
+ error: (msg) => console.error(pc.red(`🔥 ${msg}`)),
97
+ };
98
+
99
+ const umzug = new Umzug({
100
+ migrations: getMigrationFiles().map((m) => ({
101
+ name: m.name,
102
+ path: m.path,
103
+ up: async ({ context }) => {
104
+ const migration = require(m.path);
105
+ return migration.up(context, require('sequelize').DataTypes);
106
+ },
107
+ down: async ({ context }) => {
108
+ const migration = require(m.path);
109
+ return migration.down(context, require('sequelize').DataTypes);
110
+ },
111
+ })),
112
+ context: sequelize.getQueryInterface(),
113
+ storage: storage,
114
+ logger: umzugLogger,
115
+ });
116
+
117
+ module.exports = { sequelize, umzug, storage };
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaMigrator.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Scans 'addons' folder for migration files and executes them using Umzug.
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaModel.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Caching layer for Sequelize Models, now sharding-aware. When config.db.redis.shard === true,
@@ -32,7 +32,7 @@ const REDIS_ERROR_TOLERANCE_INTERVAL_MS = 10 * 1000;
32
32
 
33
33
  function safeStringify(obj, logger) {
34
34
  try {
35
- return JSON.stringify(obj, (value) =>
35
+ return JSON.stringify(obj, (_key, value) =>
36
36
  typeof value === 'bigint' ? value.toString() : value,
37
37
  );
38
38
  } catch (err) {
@@ -799,23 +799,22 @@ class KythiaModel extends Model {
799
799
  }
800
800
  }
801
801
 
802
- /**
803
- * 🔴 (Private) Retrieves and deserializes an entry specifically from Redis.
804
- */
805
802
  static async _redisGetCachedEntry(cacheKey, includeOptions) {
806
803
  try {
807
804
  const result = await this.redis.get(cacheKey);
805
+
808
806
  if (result === null || result === undefined)
809
807
  return { hit: false, data: undefined };
810
808
 
811
809
  this.cacheStats.redisHits++;
812
- if (result === NEGATIVE_CACHE_PLACEHOLDER)
810
+ if (result === NEGATIVE_CACHE_PLACEHOLDER) {
813
811
  return { hit: true, data: null };
812
+ }
814
813
 
815
814
  const parsedData = safeParse(result, this.logger);
816
815
 
817
- if (typeof parsedData !== 'object' || parsedData === null) {
818
- return { hit: true, data: parsedData };
816
+ if (parsedData === null) {
817
+ return { hit: false, data: undefined };
819
818
  }
820
819
 
821
820
  const includeAsArray = includeOptions
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaSequelize.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Main Sequelize connection factory for the application.
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaStorage.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Custom storage adapter for Umzug that mimics Laravel's migration table structure.
@@ -4,7 +4,7 @@
4
4
  * @file src/loaders/ModelLoader.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Scans the `addons` directory for KythiaModel definitions, requires them,
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/AddonManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all addon loading, command registration, and component management.
@@ -466,6 +466,7 @@ class AddonManager {
466
466
  async _loadTopLevelCommandGroup(
467
467
  commandsPath,
468
468
  addon,
469
+ addonPermissionDefaults,
469
470
  commandNamesSet,
470
471
  commandDataForDeployment,
471
472
  ) {
@@ -476,6 +477,14 @@ class AddonManager {
476
477
  commandDef = this._instantiateBaseCommand(commandDef);
477
478
  }
478
479
 
480
+ const category = addon.name;
481
+ const categoryDefaults = addonPermissionDefaults[category] || {};
482
+
483
+ commandDef = {
484
+ ...categoryDefaults,
485
+ ...commandDef,
486
+ };
487
+
479
488
  const mainBuilder = this._createBuilderFromData(
480
489
  commandDef.data,
481
490
  SlashCommandBuilder,
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/EventManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all Discord event listeners except InteractionCreate.
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/InteractionManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all Discord interaction events including slash commands, buttons, modals,
@@ -763,7 +763,7 @@ class InteractionManager {
763
763
  .setLabel(
764
764
  await this.t(interaction, 'common.error.button.contact.owner'),
765
765
  )
766
- .setURL(`discord://-/users/${ownerFirstId}`),
766
+ .setURL(`https://discord.com/users/${ownerFirstId}`),
767
767
  ),
768
768
  ),
769
769
  ];
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/ShutdownManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.10.0-beta
7
+ * @version 0.11.0-beta
8
8
  *
9
9
  * @description
10
10
  * Handles graceful shutdown procedures including interval tracking,
package/.husky/pre-commit DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
- npx lint-staged
package/biome.json DELETED
@@ -1,40 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
3
- "vcs": {
4
- "enabled": true,
5
- "clientKind": "git",
6
- "useIgnoreFile": true
7
- },
8
- "files": {
9
- "ignoreUnknown": false
10
- },
11
- "formatter": {
12
- "enabled": true,
13
- "indentStyle": "tab"
14
- },
15
- "linter": {
16
- "enabled": true,
17
- "rules": {
18
- "recommended": true,
19
- "correctness": {
20
- "noUnusedVariables": "error"
21
- },
22
- "complexity": {
23
- "noThisInStatic": "off"
24
- }
25
- }
26
- },
27
- "javascript": {
28
- "formatter": {
29
- "quoteStyle": "single"
30
- }
31
- },
32
- "assist": {
33
- "enabled": true,
34
- "actions": {
35
- "source": {
36
- "organizeImports": "off"
37
- }
38
- }
39
- }
40
- }