meocord 1.6.0 → 1.7.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 +334 -498
- package/dist/cjs/testing/index.cjs +144 -0
- package/dist/esm/testing/index.js +1 -0
- package/dist/esm/testing/meocord-testing-module.js +13 -0
- package/dist/esm/testing/mock-interaction.js +132 -0
- package/dist/types/testing/index.d.ts +74 -2
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# MeoCord Framework
|
|
2
2
|
|
|
3
|
-
**MeoCord** is a
|
|
4
|
-
|
|
5
|
-
While still growing, MeoCord provides a solid foundation for developers to create bots tailored for communities, automation, or entertainment.
|
|
3
|
+
**MeoCord** is a decorator-based Discord bot framework built on top of discord.js. It brings a NestJS-style architecture — controllers, services, guards, and dependency injection — to bot development, with a full CLI, TypeScript-first design, and testing utilities included out of the box.
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
@@ -11,15 +9,17 @@ While still growing, MeoCord provides a solid foundation for developers to creat
|
|
|
11
9
|
- [Features](#features)
|
|
12
10
|
- [Getting Started](#getting-started)
|
|
13
11
|
- [Prerequisites](#prerequisites)
|
|
14
|
-
- [Create
|
|
12
|
+
- [Create a New App](#create-a-new-app)
|
|
13
|
+
- [Quick Example](#quick-example)
|
|
15
14
|
- [Project Structure](#project-structure)
|
|
16
|
-
- [Example Structure](#example-structure)
|
|
17
|
-
- [Key Components](#key-components)
|
|
18
15
|
- [Configuration](#configuration)
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
16
|
+
- [meocord.config.ts](#meocordconfigts)
|
|
17
|
+
- [ESLint](#eslint)
|
|
18
|
+
- [CLI Reference](#cli-reference)
|
|
19
|
+
- [Guards](#guards)
|
|
21
20
|
- [Custom Decorators](#custom-decorators)
|
|
22
|
-
- [
|
|
21
|
+
- [Testing](#testing)
|
|
22
|
+
- [Deployment](#deployment)
|
|
23
23
|
- [Contributing](#contributing)
|
|
24
24
|
- [License](#license)
|
|
25
25
|
|
|
@@ -27,31 +27,13 @@ While still growing, MeoCord provides a solid foundation for developers to creat
|
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
29
|
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- **
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **
|
|
37
|
-
Simplify and extend bot behavior through a robust decorator system. Leverage built-in decorators for streamlined
|
|
38
|
-
functionality.
|
|
39
|
-
- **Specialized Services**
|
|
40
|
-
Register specialized services using the `@MeoCord` decorator. For example, add a RabbitMQ service to listen for events
|
|
41
|
-
and trigger actions, such as sending Discord messages. This promotes modularity, flexibility, and seamless integration
|
|
42
|
-
of custom services.
|
|
43
|
-
- **Seamless Discord.js Integration**
|
|
44
|
-
Built on top of Discord.js to provide full support for the Discord API, with added features like activity management,
|
|
45
|
-
intents, partials, and custom client options.
|
|
46
|
-
- **TypeScript-First Approach**
|
|
47
|
-
Designed with TypeScript in mind, offering strict type safety, interfaces, and decorators to empower modern
|
|
48
|
-
development workflows.
|
|
49
|
-
- **Extensible Webpack Integration**
|
|
50
|
-
Easily customize your build process using an exposed Webpack configuration hook. Add rules, plugins, or modify setups
|
|
51
|
-
to match your project's requirements.
|
|
52
|
-
- **Dynamic Activity Support**
|
|
53
|
-
Manage bot presence dynamically, such as setting activities (e.g., "Playing X with Y") or linking bot status to
|
|
54
|
-
real-time events.
|
|
30
|
+
- **Decorator-based controllers** — Handle slash commands, buttons, modals, select menus, context menus, messages, and reactions with `@Command`, `@Controller`, and `@UseGuard` decorators. No routing boilerplate.
|
|
31
|
+
- **Dependency injection** — Built on Inversify. Services are wired into controllers automatically; no manual instantiation or service locators.
|
|
32
|
+
- **Guard system** — Pre-execution hooks for auth, rate limiting, metrics, and anything else. Apply per-method or per-class with `@UseGuard`. Guards receive the full interaction context.
|
|
33
|
+
- **Full CLI** — `meocord create`, `build`, `start`, `generate`. Scaffolds controllers, services, and guards; handles Webpack builds for both development and production.
|
|
34
|
+
- **Testing utilities** — `MeoCordTestingModule`, `createMockInteraction`, `createChatInputOptions`, and `overrideGuard` let you test controllers against real guard logic without a Discord connection.
|
|
35
|
+
- **TypeScript-first** — Strict types throughout. Decorator metadata, `DeepMocked<T>` for test mocks, and typed config interfaces included.
|
|
36
|
+
- **Extensible build** — Expose a Webpack config hook in `meocord.config.ts` to add rules, plugins, or loaders without ejecting.
|
|
55
37
|
|
|
56
38
|
---
|
|
57
39
|
|
|
@@ -59,571 +41,460 @@ While still growing, MeoCord provides a solid foundation for developers to creat
|
|
|
59
41
|
|
|
60
42
|
### Prerequisites
|
|
61
43
|
|
|
62
|
-
- **Runtime**:
|
|
63
|
-
- **TypeScript**:
|
|
64
|
-
- **Package
|
|
65
|
-
|
|
66
|
-
### Module Support
|
|
67
|
-
|
|
68
|
-
**MeoCord** supports both **ESM** (`import`) and **CommonJS** (`require`) consumption:
|
|
69
|
-
|
|
70
|
-
- The package exports dual module formats via `dist/esm/` (ESM) and `dist/cjs/` (CommonJS), with type declarations in `dist/types/`.
|
|
71
|
-
- New projects generated with the `meocord create` CLI are already configured for ESM, so no additional setup is required.
|
|
72
|
-
- **If you are migrating an existing CommonJS-based project**, you can still use MeoCord with your existing setup. If you prefer ESM:
|
|
73
|
-
1. Add `"type": "module"` to your `package.json`:
|
|
74
|
-
|
|
75
|
-
```json
|
|
76
|
-
{
|
|
77
|
-
"name": "your-project",
|
|
78
|
-
"version": "1.0.0",
|
|
79
|
-
"type": "module",
|
|
80
|
-
"dependencies": {
|
|
81
|
-
"meocord": "^x.x.x"
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
2. If your codebase uses `require()` statements, replace them with `import` statements to ensure compatibility with
|
|
86
|
-
ESM:
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
// Before (CommonJS)
|
|
90
|
-
const Package = require('any-package');
|
|
91
|
-
|
|
92
|
-
// After (ESM)
|
|
93
|
-
import Package from 'any-package';
|
|
94
|
-
```
|
|
95
|
-
3. Update your `tsconfig.json` file to ensure compatibility with ESM:
|
|
96
|
-
|
|
97
|
-
```json
|
|
98
|
-
{
|
|
99
|
-
"compilerOptions": {
|
|
100
|
-
"module": "ESNext",
|
|
101
|
-
"target": "ESNext",
|
|
102
|
-
"moduleResolution": "Bundler",
|
|
103
|
-
"strict": true,
|
|
104
|
-
"strictNullChecks": true,
|
|
105
|
-
"strictBindCallApply": true,
|
|
106
|
-
"strictPropertyInitialization": false,
|
|
107
|
-
"forceConsistentCasingInFileNames": true,
|
|
108
|
-
"noFallthroughCasesInSwitch": true,
|
|
109
|
-
"emitDecoratorMetadata": true,
|
|
110
|
-
"experimentalDecorators": true,
|
|
111
|
-
"resolveJsonModule": true,
|
|
112
|
-
"verbatimModuleSyntax": true,
|
|
113
|
-
"noUnusedLocals": true,
|
|
114
|
-
"noUnusedParameters": true,
|
|
115
|
-
"skipLibCheck": true,
|
|
116
|
-
"noImplicitAny": false,
|
|
117
|
-
"noEmit": true,
|
|
118
|
-
"outDir": "./dist",
|
|
119
|
-
"rootDir": ".",
|
|
120
|
-
"paths": {
|
|
121
|
-
"@src/*": ["./src/*"]
|
|
122
|
-
},
|
|
123
|
-
"types": ["node"]
|
|
124
|
-
},
|
|
125
|
-
"include": ["src/**/*.ts"],
|
|
126
|
-
"exclude": [
|
|
127
|
-
"meocord.config.ts",
|
|
128
|
-
"dist",
|
|
129
|
-
"jest.config.ts",
|
|
130
|
-
"node_modules"
|
|
131
|
-
]
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
4. Rename files to use `.mjs` if necessary, or adapt them to ESM-compatible `.js` while ensuring that `"type": "module"` is set in your `package.json`.
|
|
135
|
-
|
|
136
|
-
For more migration details, refer to the [Node.js ESM documentation](https://nodejs.org/api/esm.html).
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
### Create Fresh App
|
|
141
|
-
|
|
142
|
-
Follow these steps to create and run a **MeoCord** application:
|
|
44
|
+
- **Runtime**: Node.js (latest LTS) or Bun 1.x+
|
|
45
|
+
- **TypeScript**: 5.0+
|
|
46
|
+
- **Package manager**: npm, yarn, pnpm, or bun
|
|
143
47
|
|
|
144
|
-
|
|
48
|
+
MeoCord ships dual ESM/CJS builds. New projects generated by the CLI are preconfigured for ESM.
|
|
145
49
|
|
|
146
|
-
###
|
|
147
|
-
|
|
148
|
-
Use the CLI to generate your application. You'll be prompted to choose a package manager from those installed on your system.
|
|
50
|
+
### Create a New App
|
|
149
51
|
|
|
150
52
|
```shell
|
|
151
53
|
npx meocord create <your-app-name>
|
|
152
54
|
```
|
|
153
55
|
|
|
154
|
-
|
|
56
|
+
The CLI detects installed package managers and prompts you to choose, or you can pass a flag directly:
|
|
155
57
|
|
|
156
58
|
```shell
|
|
157
59
|
npx meocord create <your-app-name> --use-bun
|
|
158
60
|
npx meocord create <your-app-name> --use-npm
|
|
159
|
-
npx meocord create <your-app-name> --use-yarn
|
|
160
61
|
npx meocord create <your-app-name> --use-pnpm
|
|
62
|
+
npx meocord create <your-app-name> --use-yarn
|
|
161
63
|
```
|
|
162
64
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
### 2. Configure `meocord.config.ts`
|
|
65
|
+
Set your Discord bot token in `meocord.config.ts`, then start the bot:
|
|
166
66
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
import { type MeoCordConfig } from 'meocord/interface'
|
|
171
|
-
|
|
172
|
-
export default {
|
|
173
|
-
appName: 'DJS ChuTao',
|
|
174
|
-
discordToken: '<add-your-token-here>',
|
|
175
|
-
} satisfies MeoCordConfig
|
|
67
|
+
```shell
|
|
68
|
+
npx meocord start --dev # development with live-reload
|
|
69
|
+
npx meocord start --build --prod # production build + start
|
|
176
70
|
```
|
|
177
71
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
### 3. Run the Application
|
|
72
|
+
### Quick Example
|
|
181
73
|
|
|
182
|
-
|
|
74
|
+
A minimal slash command controller:
|
|
183
75
|
|
|
184
|
-
|
|
185
|
-
|
|
76
|
+
```typescript
|
|
77
|
+
import { Controller, Command, UseGuard } from 'meocord/decorator'
|
|
78
|
+
import { CommandType } from 'meocord/enum'
|
|
79
|
+
import { type ChatInputCommandInteraction } from 'discord.js'
|
|
80
|
+
import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
|
|
81
|
+
import { GreetingService } from '@src/services/greeting.service.js'
|
|
186
82
|
|
|
187
|
-
|
|
188
|
-
|
|
83
|
+
@Controller()
|
|
84
|
+
export class GreetingSlashController {
|
|
85
|
+
constructor(private readonly greetingService: GreetingService) {}
|
|
86
|
+
|
|
87
|
+
@Command('greet', CommandType.SLASH)
|
|
88
|
+
@UseGuard({ provide: RateLimiterGuard, params: { limit: 3, window: 10_000 } })
|
|
89
|
+
async greet(interaction: ChatInputCommandInteraction) {
|
|
90
|
+
const name = interaction.options.getString('name', true)
|
|
91
|
+
const message = await this.greetingService.buildGreeting(name)
|
|
92
|
+
await interaction.reply({ content: message })
|
|
93
|
+
}
|
|
94
|
+
}
|
|
189
95
|
```
|
|
190
96
|
|
|
191
|
-
|
|
192
|
-
Run in production mode with fresh production build:
|
|
97
|
+
Register it in `src/app.ts`:
|
|
193
98
|
|
|
194
|
-
```
|
|
195
|
-
|
|
99
|
+
```typescript
|
|
100
|
+
import { MeoCord } from 'meocord/decorator'
|
|
101
|
+
import { GatewayIntentBits, Partials } from 'discord.js'
|
|
102
|
+
import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
|
|
103
|
+
import { GreetingService } from '@src/services/greeting.service.js'
|
|
104
|
+
|
|
105
|
+
@MeoCord({
|
|
106
|
+
controllers: [GreetingSlashController],
|
|
107
|
+
// `services` is for specialized, event-driven services (e.g. RabbitMQ consumers,
|
|
108
|
+
// schedulers). Regular business-logic services are injected via controller
|
|
109
|
+
// constructors — they don't belong here.
|
|
110
|
+
services: [RabbitMQService],
|
|
111
|
+
clientOptions: {
|
|
112
|
+
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
113
|
+
partials: [Partials.Message, Partials.Channel],
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
export class App {}
|
|
196
117
|
```
|
|
197
118
|
|
|
198
119
|
---
|
|
199
120
|
|
|
200
121
|
## Project Structure
|
|
201
122
|
|
|
202
|
-
When using MeoCord, the expected project structure is as follows:
|
|
203
|
-
|
|
204
|
-
### Example Structure
|
|
205
|
-
|
|
206
123
|
```
|
|
207
124
|
.
|
|
208
|
-
.
|
|
209
|
-
├── .gitignore
|
|
210
|
-
├── .prettierrc.mjs
|
|
125
|
+
├── meocord.config.ts
|
|
211
126
|
├── eslint.config.ts
|
|
212
127
|
├── jest.config.ts
|
|
213
|
-
├── meocord.config.ts
|
|
214
|
-
├── package.json
|
|
215
|
-
├── src
|
|
216
|
-
│ ├── app.ts
|
|
217
|
-
│ ├── controllers
|
|
218
|
-
│ │ ├── button
|
|
219
|
-
│ │ ├── context-menu
|
|
220
|
-
│ │ ├── message
|
|
221
|
-
│ │ ├── modal-submit
|
|
222
|
-
│ │ ├── reaction
|
|
223
|
-
│ │ ├── select-menu
|
|
224
|
-
│ │ └── slash
|
|
225
|
-
│ ├── guards
|
|
226
|
-
│ │ └── rate-limit.guard.ts
|
|
227
|
-
│ ├── main.ts
|
|
228
|
-
│ └── services
|
|
229
|
-
│ └── sample.service.ts
|
|
230
128
|
├── tsconfig.json
|
|
231
129
|
├── tsconfig.eslint.json
|
|
232
130
|
├── tsconfig.test.json
|
|
233
|
-
|
|
131
|
+
├── package.json
|
|
132
|
+
└── src
|
|
133
|
+
├── main.ts # Entry point — bootstraps the app
|
|
134
|
+
├── app.ts # Root module — registers controllers and services
|
|
135
|
+
├── controllers
|
|
136
|
+
│ ├── slash
|
|
137
|
+
│ │ ├── builders/ # Slash command option/subcommand builders
|
|
138
|
+
│ │ ├── sample.slash.controller.ts
|
|
139
|
+
│ │ └── sample.slash.controller.spec.ts
|
|
140
|
+
│ ├── button
|
|
141
|
+
│ │ ├── sample.button.controller.ts
|
|
142
|
+
│ │ └── sample.button.controller.spec.ts
|
|
143
|
+
│ ├── select-menu
|
|
144
|
+
│ │ ├── sample.select-menu.controller.ts
|
|
145
|
+
│ │ └── sample.select-menu.controller.spec.ts
|
|
146
|
+
│ ├── modal-submit
|
|
147
|
+
│ │ ├── sample.modal-submit.controller.ts
|
|
148
|
+
│ │ └── sample.modal-submit.controller.spec.ts
|
|
149
|
+
│ ├── context-menu
|
|
150
|
+
│ │ ├── builders/ # Context menu command builders
|
|
151
|
+
│ │ ├── sample.context-menu.controller.ts
|
|
152
|
+
│ │ └── sample.context-menu.controller.spec.ts
|
|
153
|
+
│ ├── message
|
|
154
|
+
│ │ ├── sample.message.controller.ts
|
|
155
|
+
│ │ └── sample.message.controller.spec.ts
|
|
156
|
+
│ └── reaction
|
|
157
|
+
│ ├── sample.reaction.controller.ts
|
|
158
|
+
│ └── sample.reaction.controller.spec.ts
|
|
159
|
+
├── guards
|
|
160
|
+
│ ├── rate-limit.guard.ts
|
|
161
|
+
│ └── rate-limit.guard.spec.ts
|
|
162
|
+
└── services
|
|
163
|
+
├── sample.service.ts
|
|
164
|
+
└── sample.service.spec.ts
|
|
234
165
|
```
|
|
235
166
|
|
|
236
|
-
This structure ensures clear separation of concerns and scalable project architecture.
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
### Key Components
|
|
241
|
-
|
|
242
|
-
1. **Entry Point** (`src/main.ts`):
|
|
243
|
-
|
|
244
|
-
- The main application logic, where you initialize and run the app.
|
|
245
|
-
2. **Application Configuration** (`src/app.ts`):
|
|
246
|
-
|
|
247
|
-
- Acts as the central configuration point, where controllers, services, client options, and other metadata are defined.
|
|
248
|
-
- Ties all modular components (controllers, specialized services, activities, etc.) together to define the core structure of the app.
|
|
249
|
-
3. **Controllers** (`src/controllers`):
|
|
250
|
-
|
|
251
|
-
- Build feature-specific logic (e.g., context menus, message handling in bots).
|
|
252
|
-
4. **Services** (`src/services`):
|
|
253
|
-
|
|
254
|
-
- Core business logic and reusable service definitions.
|
|
255
|
-
5. **Guards** (`src/guards`):
|
|
256
|
-
|
|
257
|
-
- Middleware-like services for pre-execution logic (e.g., rate-limiting, authorization).
|
|
258
|
-
6. **Assets** (`src/assets`):
|
|
259
|
-
|
|
260
|
-
- Fonts, images, and other static files for your application.
|
|
261
|
-
|
|
262
167
|
---
|
|
263
168
|
|
|
264
169
|
## Configuration
|
|
265
170
|
|
|
266
|
-
###
|
|
267
|
-
|
|
268
|
-
If needed, you can extend or modify the default configuration to fit your project's requirements:
|
|
269
|
-
|
|
270
|
-
```javascript
|
|
271
|
-
import unusedImports from 'eslint-plugin-unused-imports';
|
|
272
|
-
import meocordEslint, { typescriptConfig } from 'meocord/eslint';
|
|
273
|
-
|
|
274
|
-
const customConfig = {
|
|
275
|
-
...typescriptConfig,
|
|
276
|
-
plugins: {
|
|
277
|
-
...typescriptConfig.plugins,
|
|
278
|
-
'unused-imports': unusedImports,
|
|
279
|
-
},
|
|
280
|
-
rules: {
|
|
281
|
-
...typescriptConfig.rules,
|
|
282
|
-
'unused-imports/no-unused-imports': 'error', // Ensure no unused imports
|
|
283
|
-
'unused-imports/no-unused-vars': [
|
|
284
|
-
'error',
|
|
285
|
-
{
|
|
286
|
-
vars: 'all',
|
|
287
|
-
varsIgnorePattern: '^_', // Allow variables starting with `_`
|
|
288
|
-
args: 'after-used',
|
|
289
|
-
argsIgnorePattern: '^_',
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
// Add or override additional rules here
|
|
293
|
-
},
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
export default [...meocordEslint, customConfig];
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
### Customize the `meocord.config.ts`
|
|
302
|
-
|
|
303
|
-
The **configuration file** (`meocord.config.ts`) is responsible for defining the application-wide settings. It provides essential configurations such as the app name and Discord token.
|
|
171
|
+
### `meocord.config.ts`
|
|
304
172
|
|
|
305
|
-
|
|
173
|
+
The top-level config file. At minimum it needs `discordToken`. The `webpack` hook lets you extend the build without ejecting.
|
|
306
174
|
|
|
307
175
|
```typescript
|
|
308
|
-
import '
|
|
309
|
-
import { MeoCordConfig } from 'meocord/interface'
|
|
176
|
+
import { type MeoCordConfig } from 'meocord/interface'
|
|
310
177
|
|
|
311
178
|
export default {
|
|
312
|
-
appName: '
|
|
179
|
+
appName: 'MyBot',
|
|
313
180
|
discordToken: process.env.TOKEN!,
|
|
314
181
|
webpack: config => {
|
|
315
182
|
config.module.rules?.push({
|
|
316
|
-
//
|
|
183
|
+
// add custom rules here
|
|
317
184
|
})
|
|
318
|
-
|
|
319
185
|
return config
|
|
320
|
-
}
|
|
186
|
+
},
|
|
321
187
|
} satisfies MeoCordConfig
|
|
322
188
|
```
|
|
323
189
|
|
|
324
|
-
|
|
325
|
-
1. **Environment Variable Loading**:
|
|
326
|
-
The `load-env.util` ensures that environment variables from `.env` files are loaded before executing the app. *(This utility internally uses the `dotenv` package to load variables from `.env` files. For example, a `.env`
|
|
327
|
-
file containing `TOKEN=your-discord-bot-token` makes `process.env.TOKEN` accessible in the application).*
|
|
328
|
-
**Example of `load-env.util`:**
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
import { config } from 'dotenv';
|
|
332
|
-
import path from 'path';
|
|
333
|
-
|
|
334
|
-
const getEnvFilePath = (): string => {
|
|
335
|
-
switch (process.env.NODE_ENV) {
|
|
336
|
-
case 'production':
|
|
337
|
-
return '.env.prod';
|
|
338
|
-
default:
|
|
339
|
-
return '.env.dev';
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const envFilePath = path.resolve(process.cwd(), getEnvFilePath());
|
|
344
|
-
|
|
345
|
-
config({
|
|
346
|
-
path: envFilePath,
|
|
347
|
-
encoding: 'utf8',
|
|
348
|
-
});
|
|
349
|
-
```
|
|
350
|
-
2. **Import Configuration Interface**:
|
|
351
|
-
Imports the `MeoCordConfig` interface to enforce type safety on the configuration object.
|
|
352
|
-
3. **Application Name**:
|
|
353
|
-
The `appName` defines the name of the bot or application.
|
|
354
|
-
4. **Discord Token**:
|
|
355
|
-
The `discordToken` property retrieves the bot's token from the environment variables, ensuring security and
|
|
356
|
-
flexibility.
|
|
357
|
-
5. **Custom Webpack Configuration**:
|
|
358
|
-
Adds an optional function to modify and extend the default Webpack configuration, including custom module rules
|
|
359
|
-
or plugins.
|
|
360
|
-
6. **Return Configuration**:
|
|
361
|
-
Ensures the final configuration satisfies the `MeoCordConfig` type, guaranteeing that all required properties are
|
|
362
|
-
correctly defined.
|
|
190
|
+
### ESLint
|
|
363
191
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
## CLI Usage
|
|
367
|
-
|
|
368
|
-
### Overview
|
|
192
|
+
MeoCord exports a base ESLint config from `meocord/eslint`. Extend it as needed:
|
|
369
193
|
|
|
370
|
-
|
|
194
|
+
```javascript
|
|
195
|
+
import meocordEslint, { typescriptConfig } from 'meocord/eslint'
|
|
196
|
+
import unusedImports from 'eslint-plugin-unused-imports'
|
|
197
|
+
|
|
198
|
+
export default [
|
|
199
|
+
...meocordEslint,
|
|
200
|
+
{
|
|
201
|
+
...typescriptConfig,
|
|
202
|
+
plugins: {
|
|
203
|
+
...typescriptConfig.plugins,
|
|
204
|
+
'unused-imports': unusedImports,
|
|
205
|
+
},
|
|
206
|
+
rules: {
|
|
207
|
+
...typescriptConfig.rules,
|
|
208
|
+
'unused-imports/no-unused-imports': 'error',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
```
|
|
371
213
|
|
|
372
|
-
|
|
214
|
+
---
|
|
373
215
|
|
|
374
|
-
|
|
216
|
+
## CLI Reference
|
|
375
217
|
|
|
376
218
|
```shell
|
|
377
219
|
npx meocord --help
|
|
378
220
|
```
|
|
379
221
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
CLI for managing the MeoCord application
|
|
389
|
-
|
|
390
|
-
Available Options:
|
|
391
|
-
┌───────────────┬───────────────────────────┐
|
|
392
|
-
│ Option │ Description │
|
|
393
|
-
├───────────────┼───────────────────────────┤
|
|
394
|
-
│ -V, --version │ output the version number │
|
|
395
|
-
└───────────────┴───────────────────────────┘
|
|
396
|
-
|
|
397
|
-
Available Commands:
|
|
398
|
-
┌──────────┬───────┬──────────────────────────────────┐
|
|
399
|
-
│ Command │ Alias │ Description │
|
|
400
|
-
├──────────┼───────┼──────────────────────────────────┤
|
|
401
|
-
│ show │ — │ Display information │
|
|
402
|
-
├──────────┼───────┼──────────────────────────────────┤
|
|
403
|
-
│ create │ — │ Create a new MeoCord application │
|
|
404
|
-
├──────────┼───────┼──────────────────────────────────┤
|
|
405
|
-
│ build │ — │ Build the application │
|
|
406
|
-
├──────────┼───────┼──────────────────────────────────┤
|
|
407
|
-
│ start │ — │ Start the application │
|
|
408
|
-
├──────────┼───────┼──────────────────────────────────┤
|
|
409
|
-
│ generate │ g │ Generate components │
|
|
410
|
-
└──────────┴───────┴──────────────────────────────────┘
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### Key Commands Overview
|
|
414
|
-
|
|
415
|
-
The following section provides details for frequently used commands, but note that **additional commands** may be available by running `meocord --help`.
|
|
416
|
-
|
|
417
|
-
#### `meocord build`
|
|
222
|
+
| Command | Alias | Description |
|
|
223
|
+
|------------|-------|--------------------------------------|
|
|
224
|
+
| `create` | — | Scaffold a new MeoCord application |
|
|
225
|
+
| `build` | — | Compile the application via Webpack |
|
|
226
|
+
| `start` | — | Start the application |
|
|
227
|
+
| `generate` | `g` | Scaffold controllers, services, guards |
|
|
228
|
+
| `show` | — | Display framework info |
|
|
418
229
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
**Usage:**
|
|
230
|
+
**Common flags:**
|
|
422
231
|
|
|
423
232
|
```shell
|
|
424
|
-
npx meocord build --prod
|
|
425
|
-
npx meocord build --dev
|
|
233
|
+
npx meocord build --prod # production build
|
|
234
|
+
npx meocord build --dev # development build
|
|
235
|
+
npx meocord start --dev # dev mode with live-reload
|
|
236
|
+
npx meocord start --build --prod # production build + start
|
|
237
|
+
npx meocord g co slash "profile" # generate a slash controller
|
|
238
|
+
npx meocord g --help # list all generator sub-commands
|
|
426
239
|
```
|
|
427
240
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
Starts the application with options for either a **production** or **development** environment. The application can also build automatically before starting.
|
|
241
|
+
---
|
|
431
242
|
|
|
432
|
-
|
|
243
|
+
## Guards
|
|
433
244
|
|
|
434
|
-
|
|
435
|
-
npx meocord start --build --prod # Start in production mode with fresh production build
|
|
436
|
-
npx meocord start --dev # Start in development mode (will always fresh build)
|
|
437
|
-
```
|
|
245
|
+
Guards run before the handler method. Each guard implements `canActivate` — return `true` to allow, `false` to block.
|
|
438
246
|
|
|
439
|
-
|
|
247
|
+
```typescript
|
|
248
|
+
import { Guard } from 'meocord/decorator'
|
|
249
|
+
import { type GuardInterface } from 'meocord/interface'
|
|
250
|
+
import { type ChatInputCommandInteraction } from 'discord.js'
|
|
251
|
+
import { RedisService } from '@src/services/redis.service.js'
|
|
440
252
|
|
|
441
|
-
|
|
253
|
+
@Guard()
|
|
254
|
+
export class RateLimiterGuard implements GuardInterface {
|
|
255
|
+
constructor(private readonly redis: RedisService) {}
|
|
442
256
|
|
|
443
|
-
|
|
257
|
+
// limit and window are injected via @UseGuard params
|
|
258
|
+
limit = 5
|
|
259
|
+
window = 60_000
|
|
444
260
|
|
|
445
|
-
|
|
446
|
-
|
|
261
|
+
async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
|
|
262
|
+
const key = `ratelimit:${interaction.user.id}`
|
|
263
|
+
const count = await this.redis.increment(key, this.window)
|
|
264
|
+
return count <= this.limit
|
|
265
|
+
}
|
|
266
|
+
}
|
|
447
267
|
```
|
|
448
268
|
|
|
449
|
-
|
|
269
|
+
Apply to a single method or an entire controller:
|
|
450
270
|
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
|
|
271
|
+
```typescript
|
|
272
|
+
// Per-method, with params
|
|
273
|
+
@Command('search', CommandType.SLASH)
|
|
274
|
+
@UseGuard({ provide: RateLimiterGuard, params: { limit: 5, window: 60_000 } })
|
|
275
|
+
async search(interaction: ChatInputCommandInteraction) { ... }
|
|
454
276
|
|
|
455
|
-
|
|
277
|
+
// Per-class (applies to every command in the controller)
|
|
278
|
+
@Controller()
|
|
279
|
+
@UseGuard(MetricsGuard, DefaultGuard)
|
|
280
|
+
export class ProfileController { ... }
|
|
281
|
+
```
|
|
456
282
|
|
|
457
283
|
---
|
|
458
284
|
|
|
459
|
-
|
|
285
|
+
## Custom Decorators
|
|
460
286
|
|
|
461
|
-
|
|
462
|
-
npx meocord g --help
|
|
463
|
-
```
|
|
287
|
+
MeoCord exports `applyDecorators` and `SetMetadata` from `meocord/common` for composing reusable decorators.
|
|
464
288
|
|
|
465
|
-
|
|
289
|
+
### Composing guards into a reusable decorator
|
|
466
290
|
|
|
467
|
-
|
|
291
|
+
```typescript
|
|
292
|
+
import { applyDecorators } from 'meocord/common'
|
|
293
|
+
import { UseGuard } from 'meocord/decorator'
|
|
294
|
+
import { DefaultGuard, RateLimiterGuard } from '@src/guards/index.js'
|
|
468
295
|
|
|
469
|
-
|
|
296
|
+
export const Protected = (limit = 5) =>
|
|
297
|
+
applyDecorators(
|
|
298
|
+
UseGuard(DefaultGuard, { provide: RateLimiterGuard, params: { limit } }),
|
|
299
|
+
)
|
|
300
|
+
```
|
|
470
301
|
|
|
471
|
-
|
|
302
|
+
```typescript
|
|
303
|
+
@Command('profile', CommandType.SLASH)
|
|
304
|
+
@Protected(3)
|
|
305
|
+
async profile(interaction: ChatInputCommandInteraction) { ... }
|
|
306
|
+
```
|
|
472
307
|
|
|
473
|
-
|
|
308
|
+
### Attaching metadata for guards to read
|
|
474
309
|
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
|
|
310
|
+
```typescript
|
|
311
|
+
// Define the metadata decorator
|
|
312
|
+
import { SetMetadata } from 'meocord/common'
|
|
313
|
+
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
|
|
478
314
|
|
|
479
|
-
|
|
315
|
+
// Read it inside a guard
|
|
316
|
+
@Guard()
|
|
317
|
+
export class RolesGuard implements GuardInterface {
|
|
318
|
+
async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
|
|
319
|
+
const required: string[] = Reflect.getMetadata('roles', interaction.constructor) ?? []
|
|
320
|
+
if (!required.length) return true
|
|
321
|
+
// ... validate member roles
|
|
322
|
+
return true
|
|
323
|
+
}
|
|
324
|
+
}
|
|
480
325
|
|
|
481
|
-
|
|
326
|
+
// Compose into a single decorator
|
|
327
|
+
export const RequireRoles = (...roles: string[]) =>
|
|
328
|
+
applyDecorators(Roles(...roles), UseGuard(RolesGuard))
|
|
482
329
|
|
|
483
|
-
|
|
484
|
-
|
|
330
|
+
// Apply
|
|
331
|
+
@Command('ban', CommandType.SLASH)
|
|
332
|
+
@RequireRoles('admin', 'moderator')
|
|
333
|
+
async ban(interaction: ChatInputCommandInteraction) { ... }
|
|
485
334
|
```
|
|
486
335
|
|
|
487
|
-
Once built, you can deploy or run the application efficiently.
|
|
488
|
-
|
|
489
336
|
---
|
|
490
337
|
|
|
491
|
-
##
|
|
338
|
+
## Testing
|
|
492
339
|
|
|
493
|
-
MeoCord
|
|
340
|
+
MeoCord ships a `meocord/testing` entry point with utilities for testing controllers in isolation — no real Discord connection required.
|
|
494
341
|
|
|
495
|
-
### `
|
|
342
|
+
### `MeoCordTestingModule`
|
|
496
343
|
|
|
497
|
-
|
|
344
|
+
Builds an isolated DI container from your controllers and providers.
|
|
498
345
|
|
|
499
346
|
```typescript
|
|
500
|
-
import {
|
|
501
|
-
import {
|
|
502
|
-
import {
|
|
347
|
+
import { MeoCordTestingModule } from 'meocord/testing'
|
|
348
|
+
import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
|
|
349
|
+
import { GreetingService } from '@src/services/greeting.service.js'
|
|
503
350
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
)
|
|
351
|
+
const module = MeoCordTestingModule.create({
|
|
352
|
+
controllers: [GreetingSlashController],
|
|
353
|
+
providers: [{ provide: GreetingService, useValue: mockGreetingService }],
|
|
354
|
+
}).compile()
|
|
509
355
|
|
|
510
|
-
|
|
511
|
-
export const RateLimited = (limit: number) =>
|
|
512
|
-
applyDecorators(
|
|
513
|
-
UseGuard(DefaultGuard, { provide: RateLimiterGuard, params: { limit } }),
|
|
514
|
-
)
|
|
356
|
+
const controller = module.get(GreetingSlashController)
|
|
515
357
|
```
|
|
516
358
|
|
|
517
|
-
|
|
359
|
+
### `createMockInteraction`
|
|
360
|
+
|
|
361
|
+
Creates a mock instance of any discord.js class. The full prototype chain is preserved so `instanceof` checks at every level pass. Every method is auto-stubbed as a `jest.fn()`.
|
|
518
362
|
|
|
519
363
|
```typescript
|
|
520
|
-
import {
|
|
521
|
-
import {
|
|
364
|
+
import { createMockInteraction } from 'meocord/testing'
|
|
365
|
+
import { ChatInputCommandInteraction, BaseInteraction } from 'discord.js'
|
|
522
366
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
367
|
+
const interaction = createMockInteraction(ChatInputCommandInteraction)
|
|
368
|
+
|
|
369
|
+
expect(interaction).toBeInstanceOf(ChatInputCommandInteraction) // true
|
|
370
|
+
expect(interaction).toBeInstanceOf(BaseInteraction) // true
|
|
371
|
+
|
|
372
|
+
interaction.reply.mockResolvedValue(undefined)
|
|
373
|
+
await interaction.reply({ content: 'hi' })
|
|
374
|
+
expect(interaction.reply).toHaveBeenCalledWith({ content: 'hi' })
|
|
375
|
+
|
|
376
|
+
// direct property writes work normally
|
|
377
|
+
interaction.guildId = 'guild-123'
|
|
533
378
|
```
|
|
534
379
|
|
|
535
|
-
|
|
380
|
+
Works for any discord.js class — `ButtonInteraction`, `ModalSubmitInteraction`, `StringSelectMenuInteraction`, `Message`, `MessageReaction`, and any future class. No per-type maintenance.
|
|
536
381
|
|
|
537
|
-
|
|
382
|
+
### `createChatInputOptions`
|
|
538
383
|
|
|
539
|
-
|
|
384
|
+
Builds a typed options resolver from a plain record. Type routing mirrors the real `CommandInteractionOptionResolver`: wrong-type access returns `null`, `required=true` throws if the option is absent.
|
|
540
385
|
|
|
541
386
|
```typescript
|
|
542
|
-
import {
|
|
543
|
-
|
|
544
|
-
|
|
387
|
+
import { createMockInteraction, createChatInputOptions } from 'meocord/testing'
|
|
388
|
+
import { ChatInputCommandInteraction } from 'discord.js'
|
|
389
|
+
|
|
390
|
+
const interaction = createMockInteraction(ChatInputCommandInteraction)
|
|
391
|
+
interaction.options = createChatInputOptions({
|
|
392
|
+
subcommandGroup: 'admin',
|
|
393
|
+
subcommand: 'ban',
|
|
394
|
+
user: { id: '123456789' },
|
|
395
|
+
reason: 'spam',
|
|
396
|
+
duration: 7,
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
interaction.options.getSubcommandGroup() // → 'admin'
|
|
400
|
+
interaction.options.getSubcommand() // → 'ban'
|
|
401
|
+
interaction.options.getUser('user') // → { id: '123456789' }
|
|
402
|
+
interaction.options.getString('reason') // → 'spam'
|
|
403
|
+
interaction.options.getNumber('duration') // → 7
|
|
404
|
+
interaction.options.getString('duration') // → null (wrong type)
|
|
405
|
+
interaction.options.getNumber('x', true) // → throws (absent + required)
|
|
545
406
|
```
|
|
546
407
|
|
|
547
|
-
|
|
408
|
+
All methods are `jest.fn()` — override any per test with `.mockReturnValue()`.
|
|
548
409
|
|
|
549
|
-
|
|
550
|
-
import { Guard } from 'meocord/decorator'
|
|
551
|
-
import { type GuardInterface } from 'meocord/interface'
|
|
552
|
-
import type { ChatInputCommandInteraction } from 'discord.js'
|
|
410
|
+
### `overrideGuard`
|
|
553
411
|
|
|
554
|
-
|
|
555
|
-
export class RolesGuard implements GuardInterface {
|
|
556
|
-
async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
|
|
557
|
-
const required: string[] = Reflect.getMetadata('roles', interaction.constructor) ?? []
|
|
558
|
-
if (!required.length) return true
|
|
412
|
+
Replaces a guard class in the DI container with a stub. No guard dependencies need to be provided.
|
|
559
413
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
}
|
|
414
|
+
```typescript
|
|
415
|
+
const module = MeoCordTestingModule.create({
|
|
416
|
+
controllers: [GreetingSlashController],
|
|
417
|
+
providers: [{ provide: GreetingService, useValue: mockGreetingService }],
|
|
418
|
+
})
|
|
419
|
+
.overrideGuard(MetricsGuard).useValue({ canActivate: () => true })
|
|
420
|
+
.overrideGuard(RateLimiterGuard).useValue({ canActivate: () => true })
|
|
421
|
+
.compile()
|
|
565
422
|
```
|
|
566
423
|
|
|
567
|
-
|
|
424
|
+
`canActivate: () => true` allows the method to run. `() => false` blocks it. Multiple guards chain fluently.
|
|
425
|
+
|
|
426
|
+
### Full example
|
|
568
427
|
|
|
569
428
|
```typescript
|
|
570
|
-
import {
|
|
571
|
-
import {
|
|
429
|
+
import { jest } from '@jest/globals'
|
|
430
|
+
import { MeoCordTestingModule, createMockInteraction, createChatInputOptions } from 'meocord/testing'
|
|
431
|
+
import { ChatInputCommandInteraction } from 'discord.js'
|
|
432
|
+
import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
|
|
433
|
+
import { GreetingService } from '@src/services/greeting.service.js'
|
|
434
|
+
import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
|
|
435
|
+
|
|
436
|
+
describe('GreetingSlashController', () => {
|
|
437
|
+
let controller: GreetingSlashController
|
|
438
|
+
let greetingService: { buildGreeting: jest.MockedFunction<GreetingService['buildGreeting']> }
|
|
439
|
+
|
|
440
|
+
beforeEach(() => {
|
|
441
|
+
greetingService = { buildGreeting: jest.fn() }
|
|
442
|
+
|
|
443
|
+
const module = MeoCordTestingModule.create({
|
|
444
|
+
controllers: [GreetingSlashController],
|
|
445
|
+
providers: [{ provide: GreetingService, useValue: greetingService }],
|
|
446
|
+
})
|
|
447
|
+
.overrideGuard(RateLimiterGuard).useValue({ canActivate: () => true })
|
|
448
|
+
.compile()
|
|
572
449
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
Roles(...roles),
|
|
576
|
-
UseGuard(RolesGuard),
|
|
577
|
-
)
|
|
450
|
+
controller = module.get(GreetingSlashController)
|
|
451
|
+
})
|
|
578
452
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
453
|
+
it('replies with a greeting for the provided name', async () => {
|
|
454
|
+
jest.mocked(greetingService.buildGreeting).mockResolvedValue('Hello, Alice!')
|
|
455
|
+
|
|
456
|
+
const interaction = createMockInteraction(ChatInputCommandInteraction)
|
|
457
|
+
interaction.options = createChatInputOptions({ name: 'Alice' })
|
|
458
|
+
interaction.reply.mockResolvedValue(undefined as any)
|
|
459
|
+
|
|
460
|
+
await controller.greet(interaction)
|
|
461
|
+
|
|
462
|
+
expect(greetingService.buildGreeting).toHaveBeenCalledWith('Alice')
|
|
463
|
+
expect(interaction.reply).toHaveBeenCalledWith({ content: 'Hello, Alice!' })
|
|
464
|
+
})
|
|
465
|
+
})
|
|
585
466
|
```
|
|
586
467
|
|
|
587
468
|
---
|
|
588
469
|
|
|
589
|
-
## Deployment
|
|
470
|
+
## Deployment
|
|
590
471
|
|
|
591
|
-
Install all
|
|
472
|
+
Install all dependencies and build for production:
|
|
592
473
|
|
|
593
474
|
```shell
|
|
594
|
-
npm ci
|
|
595
|
-
yarn install --frozen-lockfile # yarn
|
|
596
|
-
pnpm install --frozen-lockfile # pnpm
|
|
597
|
-
bun install --frozen-lockfile # bun
|
|
475
|
+
npm ci && npx meocord build --prod
|
|
598
476
|
```
|
|
599
477
|
|
|
600
|
-
|
|
478
|
+
Strip dev dependencies:
|
|
601
479
|
|
|
602
480
|
```shell
|
|
603
|
-
|
|
481
|
+
npm ci --omit=dev # npm
|
|
482
|
+
yarn install --production # yarn
|
|
483
|
+
pnpm install --prod # pnpm
|
|
484
|
+
bun install --production # bun
|
|
604
485
|
```
|
|
605
486
|
|
|
606
|
-
|
|
487
|
+
Required files on the server:
|
|
607
488
|
|
|
608
|
-
```shell
|
|
609
|
-
npm ci --omit=dev # npm
|
|
610
|
-
yarn install --production # yarn
|
|
611
|
-
pnpm install --prod # pnpm
|
|
612
|
-
bun install --production # bun
|
|
613
489
|
```
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
├── dist
|
|
620
|
-
├── node_modules (production dependencies only)
|
|
621
|
-
├── .env (if applicable, ensure it contains necessary variables)
|
|
622
|
-
├── package.json
|
|
623
|
-
└── <lockfile> # bun.lock / package-lock.json / yarn.lock / pnpm-lock.yaml
|
|
490
|
+
dist/
|
|
491
|
+
node_modules/ (production only)
|
|
492
|
+
package.json
|
|
493
|
+
.env (if used)
|
|
494
|
+
<lockfile>
|
|
624
495
|
```
|
|
625
496
|
|
|
626
|
-
Start
|
|
497
|
+
Start in production:
|
|
627
498
|
|
|
628
499
|
```shell
|
|
629
500
|
npx meocord start --prod
|
|
@@ -633,56 +504,21 @@ npx meocord start --prod
|
|
|
633
504
|
|
|
634
505
|
## Contributing
|
|
635
506
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
2. **Create a Feature Branch**:
|
|
641
|
-
Use the following command to create a branch for your changes:
|
|
642
|
-
|
|
643
|
-
```textmate
|
|
644
|
-
git checkout -b feature/your-feature-name
|
|
645
|
-
```
|
|
646
|
-
3. **Make Meaningful Commits**:
|
|
647
|
-
Commit your changes with clear, descriptive, and concise messages that explain what your changes do:
|
|
648
|
-
|
|
649
|
-
```textmate
|
|
650
|
-
git commit -m "feat: add [brief description of your feature or fix]"
|
|
651
|
-
```
|
|
652
|
-
4. **Push Your Changes**:
|
|
653
|
-
Push your branch to your forked repository with this command:
|
|
654
|
-
|
|
655
|
-
```textmate
|
|
656
|
-
git push origin feature/your-feature-name
|
|
657
|
-
```
|
|
658
|
-
5. **Open a Pull Request (PR)**:
|
|
507
|
+
1. Fork the repository
|
|
508
|
+
2. Create a feature branch: `git checkout -b feat/your-feature`
|
|
509
|
+
3. Commit with conventional commits: `git commit -m "feat: add X"`
|
|
510
|
+
4. Push and open a pull request against `main`
|
|
659
511
|
|
|
660
|
-
|
|
661
|
-
- Click "Compare & Pull Request."
|
|
662
|
-
- Provide a descriptive title and a detailed description of your changes.
|
|
663
|
-
|
|
664
|
-
Be sure to include:
|
|
665
|
-
|
|
666
|
-
- The purpose of your changes.
|
|
667
|
-
- Any relevant details or links.
|
|
668
|
-
- Steps to reproduce/test the changes, if applicable.
|
|
669
|
-
6. **Engage in Reviews**:
|
|
670
|
-
Work with maintainers to address any feedback or changes they request.
|
|
671
|
-
|
|
672
|
-
Thank you for helping make **MeoCord** better!
|
|
512
|
+
Include a description of what changed and why, and add tests for any new behaviour.
|
|
673
513
|
|
|
674
514
|
---
|
|
675
515
|
|
|
676
516
|
## Release Notes
|
|
677
517
|
|
|
678
|
-
Full
|
|
518
|
+
Full changelog is available on the [GitHub Releases](https://github.com/l7aromeo/meocord/releases) page.
|
|
679
519
|
|
|
680
520
|
---
|
|
681
521
|
|
|
682
522
|
## License
|
|
683
523
|
|
|
684
524
|
**MeoCord Framework** is licensed under the [MIT License](./LICENSE).
|
|
685
|
-
|
|
686
|
-
You are free to use, modify, and distribute this software in any project — personal, academic, or commercial — with no restrictions other than preserving the copyright notice.
|
|
687
|
-
|
|
688
|
-
For full details, consult the [LICENSE](./LICENSE) file included in the repository.
|