meocord 3.1.0 → 3.2.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.
Files changed (48) hide show
  1. package/README.md +138 -15
  2. package/dist/cjs/decorator/index.cjs +1 -1
  3. package/dist/esm/bin/app-template/README.md.template +61 -0
  4. package/dist/esm/bin/app-template/_env.example.template +2 -0
  5. package/dist/esm/bin/app-template/_gitignore.template +22 -0
  6. package/dist/esm/bin/app-template/_prettierrc.mjs.template +10 -0
  7. package/dist/esm/bin/app-template/eslint.config.ts.template +25 -0
  8. package/dist/esm/bin/app-template/meocord.config.ts.template +25 -0
  9. package/dist/esm/bin/app-template/package.json.template +38 -0
  10. package/dist/esm/bin/app-template/src/app.ts.template +44 -0
  11. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +17 -0
  12. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +19 -0
  13. package/dist/esm/bin/app-template/src/controllers/context-menu/builders/sample.builder.ts.template +10 -0
  14. package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.spec.ts.template +17 -0
  15. package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.ts.template +13 -0
  16. package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.spec.ts.template +17 -0
  17. package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.ts.template +28 -0
  18. package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.spec.ts.template +17 -0
  19. package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.ts.template +13 -0
  20. package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.spec.ts.template +17 -0
  21. package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.ts.template +29 -0
  22. package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.spec.ts.template +17 -0
  23. package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.ts.template +11 -0
  24. package/dist/esm/bin/app-template/src/controllers/slash/builders/sample.builder.ts.template +10 -0
  25. package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.spec.ts.template +17 -0
  26. package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.ts.template +19 -0
  27. package/dist/esm/bin/app-template/src/guards/rate-limit.guard.spec.ts.template +13 -0
  28. package/dist/esm/bin/app-template/src/guards/rate-limit.guard.ts.template +52 -0
  29. package/dist/esm/bin/app-template/src/main.ts.template +14 -0
  30. package/dist/esm/bin/app-template/src/services/sample.service.spec.ts.template +17 -0
  31. package/dist/esm/bin/app-template/src/services/sample.service.ts.template +9 -0
  32. package/dist/esm/bin/app-template/tsconfig.eslint.json.template +5 -0
  33. package/dist/esm/bin/app-template/tsconfig.json.template +30 -0
  34. package/dist/esm/bin/app-template/tsconfig.test.json.template +8 -0
  35. package/dist/esm/bin/app-template/vitest.config.ts.template +35 -0
  36. package/dist/esm/bin/builder-template/controller/button.controller.template +1 -1
  37. package/dist/esm/bin/helper/app-generator.helper.js +78 -0
  38. package/dist/esm/bin/meocord.js +156 -75
  39. package/dist/esm/decorator/guard.decorator.js +1 -1
  40. package/dist/esm/util/common.util.js +11 -3
  41. package/dist/esm/util/generator-cli.util.js +21 -4
  42. package/dist/esm/util/package-manager.util.js +9 -2
  43. package/dist/esm/util/package-version.util.js +32 -0
  44. package/dist/esm/util/runtime.util.js +72 -0
  45. package/dist/types/core/index.d.ts +2 -2
  46. package/dist/types/decorator/index.d.ts +1 -1
  47. package/dist/types/interface/index.d.ts +5 -3
  48. package/package.json +11 -12
package/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
  - [Testing](#testing)
26
26
  - [Deployment](#deployment)
27
27
  - [Contributing](#contributing)
28
+ - [Release Notes](#release-notes)
28
29
  - [License](#license)
29
30
 
30
31
  ---
@@ -66,30 +67,57 @@ npx meocord create <your-app-name> --use-pnpm
66
67
  npx meocord create <your-app-name> --use-yarn
67
68
  ```
68
69
 
69
- Set your Discord bot token in `meocord.config.ts`, then start the bot:
70
+ The generated project is named after what you passed, pins the framework version that
71
+ created it, and comes with a working slash command, button, select menu, modal, context
72
+ menu, message and reaction controller, plus a guard, a service and a spec for each.
73
+
74
+ Add your bot token and start:
70
75
 
71
76
  ```shell
72
- npx meocord start --dev # development with live-reload
77
+ cd <your-app-name>
78
+ cp .env.example .env # then put your token in DISCORD_TOKEN
79
+ npx meocord start --dev # development with live-reload
73
80
  npx meocord start --build --prod # production build + start
74
81
  ```
75
82
 
83
+ The token is read from the environment rather than written into `meocord.config.ts`,
84
+ which is committed — `.env` is gitignored so a token cannot be pushed by accident.
85
+ Building needs no token; only starting does.
86
+
76
87
  ### Quick Example
77
88
 
78
- A minimal slash command controller:
89
+ A minimal slash command. A command Discord knows about needs a builder — that is what gets registered. The
90
+ builder receives the name from `@Command`, so the two cannot drift apart:
79
91
 
80
92
  ```typescript
81
- import { Controller, Command, UseGuard } from 'meocord/decorator'
93
+ import { SlashCommandBuilder } from 'discord.js'
94
+ import { CommandBuilder } from 'meocord/decorator'
82
95
  import { CommandType } from 'meocord/enum'
96
+
97
+ @CommandBuilder(CommandType.SLASH)
98
+ export class GreetingCommandBuilder {
99
+ build(commandName: string) {
100
+ return new SlashCommandBuilder()
101
+ .setName(commandName)
102
+ .setDescription('Greets someone')
103
+ .addStringOption(option => option.setName('name').setDescription('Who to greet').setRequired(true))
104
+ }
105
+ }
106
+ ```
107
+
108
+ ```typescript
109
+ import { Controller, Command, UseGuard } from 'meocord/decorator'
83
110
  import { type ChatInputCommandInteraction } from 'discord.js'
84
- import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
111
+ import { GreetingCommandBuilder } from '@src/controllers/slash/builders/greeting.builder.js'
112
+ import { RateLimitGuard } from '@src/guards/rate-limit.guard.js'
85
113
  import { GreetingService } from '@src/services/greeting.service.js'
86
114
 
87
115
  @Controller()
88
116
  export class GreetingSlashController {
89
117
  constructor(private readonly greetingService: GreetingService) {}
90
118
 
91
- @Command('greet', CommandType.SLASH)
92
- @UseGuard({ provide: RateLimiterGuard, params: { limit: 3, window: 10_000 } })
119
+ @Command('greet', GreetingCommandBuilder)
120
+ @UseGuard({ provide: RateLimitGuard, params: { limit: 3, windowInSeconds: 10 } })
93
121
  async greet(interaction: ChatInputCommandInteraction) {
94
122
  const name = interaction.options.getString('name', true)
95
123
  const message = await this.greetingService.buildGreeting(name)
@@ -126,6 +154,10 @@ export class App {}
126
154
 
127
155
  ```
128
156
  .
157
+ ├── README.md
158
+ ├── .env.example
159
+ ├── .gitignore
160
+ ├── .prettierrc.mjs
129
161
  ├── meocord.config.ts
130
162
  ├── eslint.config.ts
131
163
  ├── vitest.config.ts
@@ -231,18 +263,35 @@ npx meocord --help
231
263
  | `generate` | `g` | Scaffold controllers, services, guards |
232
264
  | `show` | — | Display framework info |
233
265
 
234
- **Common flags:**
266
+ Every command's own flags:
267
+
268
+ | Command | Flags |
269
+ | ---------- | ------------------------------------------------------- |
270
+ | `create` | `--use-npm` · `--use-yarn` · `--use-pnpm` · `--use-bun` |
271
+ | `build` | `-d, --dev` · `-p, --prod` |
272
+ | `start` | `-b, --build` · `-d, --dev` · `-p, --prod` |
273
+ | `show` | `-w, --warranty` · `-c, --license` |
274
+ | `generate` | see the sub-commands below |
275
+
276
+ `meocord -V` / `--version` prints the installed version.
277
+
278
+ `start` accepts one environment variable, `MEOCORD_RUNTIME`, which pins the binary the application is run with — see [Which runtime the bot runs on](#which-runtime-the-bot-runs-on).
235
279
 
236
280
  ```shell
237
281
  npx meocord build --prod # production build
238
- npx meocord build --dev # development build
239
282
  npx meocord start --dev # dev mode with live-reload
240
283
  npx meocord start --build --prod # production build + start
241
- npx meocord g co slash "profile" # generate a slash controller
242
- npx meocord g --help # list all generator sub-commands
243
284
  ```
244
285
 
245
- ### Generating a controller
286
+ ### Generators
287
+
288
+ | Sub-command | Alias | Generates |
289
+ | ------------ | ----- | ----------------------------------- |
290
+ | `controller` | `co` | a controller, its spec, its builder |
291
+ | `service` | `s` | a service and its spec |
292
+ | `guard` | `gu` | a guard and its spec |
293
+
294
+ #### Controllers
246
295
 
247
296
  ```shell
248
297
  npx meocord g co <type> <name>
@@ -825,7 +874,7 @@ import {
825
874
  import { ChatInputCommandInteraction } from 'discord.js'
826
875
  import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
827
876
  import { GreetingService } from '@src/services/greeting.service.js'
828
- import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
877
+ import { RateLimitGuard } from '@src/guards/rate-limit.guard.js'
829
878
 
830
879
  describe('GreetingSlashController', () => {
831
880
  let controller: GreetingSlashController
@@ -894,6 +943,62 @@ Start in production:
894
943
  npx meocord start --prod
895
944
  ```
896
945
 
946
+ ### Which runtime the bot runs on
947
+
948
+ `start` runs the bot on **the runtime you launched it with**. There is nothing to configure and no config key to set — if you typed `bun`, you get a bun process:
949
+
950
+ ```shell
951
+ bun run start # dist/main.js runs under bun
952
+ npm run start # dist/main.js runs under node
953
+ ```
954
+
955
+ Two signals decide it, most explicit first: the runtime executing the CLI, and — when the CLI itself was handed to node — the runner that launched it. `bun run` honours the bin's `#!/usr/bin/env node` shebang, so bun sets `npm_execpath` to its own binary and that is what the bot is spawned with. npm, pnpm and yarn point it at a `.js` file instead, which cannot run the bundle, so those fall through to node as expected.
956
+
957
+ That matters for more than tidiness. Pinning `node` would oblige a bun-only image to install a second runtime purely to launch, or to carry `--bun` on every command. It also decides the allocator: for a bot doing heavy native work — canvas rendering through a napi module, say — glibc's malloc and bun's mimalloc produce very different resident-memory curves on the same workload, because they differ in how eagerly freed pages go back to the OS.
958
+
959
+ Development works the same way. The watcher runs the bundle through the same command production does, so a runtime that works in `--dev` cannot quietly differ from the one that ships.
960
+
961
+ #### Running the CLI itself on bun
962
+
963
+ The resolution above decides what the _bot_ runs on. The CLI process is decided earlier,
964
+ by the interpreter line `#!/usr/bin/env node`, which nothing in the package can influence
965
+ — it is read before any of the program exists. On a machine with no node at all,
966
+ invoking the CLI directly fails before it starts:
967
+
968
+ ```
969
+ $ meocord start --prod
970
+ env: node: No such file or directory
971
+ ```
972
+
973
+ That line stays as it is because Windows depends on it: npm there never runs the file
974
+ through its shebang, it parses the line and writes a `.cmd` invoking the program named in
975
+ it. `#!/usr/bin/env node` yields `node`; anything else yields a program Windows cannot
976
+ resolve.
977
+
978
+ So on a bun-only image, tell bun to ignore the line. Either per command:
979
+
980
+ ```shell
981
+ bun --bun meocord start --prod
982
+ ```
983
+
984
+ or once for the project, which is what a bun-only Dockerfile wants:
985
+
986
+ ```toml
987
+ # bunfig.toml
988
+ [run]
989
+ bun = true
990
+ ```
991
+
992
+ Then plain `bun run start` runs the CLI and the bot on bun, and node need not exist.
993
+
994
+ #### Pinning a specific binary
995
+
996
+ To override both signals — a particular install, or a different runtime for comparison — set `MEOCORD_RUNTIME`:
997
+
998
+ ```shell
999
+ MEOCORD_RUNTIME=/usr/local/bin/bun npm run start
1000
+ ```
1001
+
897
1002
  ---
898
1003
 
899
1004
  ## Contributing
@@ -902,9 +1007,27 @@ npx meocord start --prod
902
1007
  2. Create a feature branch: `git checkout -b feat/your-feature`
903
1008
  3. Commit with conventional commits: `git commit -m "feat: add X"`
904
1009
  4. Run `bun run lint` and `bun run test` before pushing
905
- 5. Push and open a pull request against `main`
1010
+ 5. If you touched anything under `src/bin/`, also run `bun run build && bun run verify:generated`
1011
+ 6. Push and open a pull request against `main`
1012
+
1013
+ Include a description of what changed and why, and add tests for any new behaviour.
1014
+
1015
+ `verify:generated` generates one controller of every type through the built CLI — flat and nested, in separate throwaway projects — and typechecks the result against the published package. Rendering a template says nothing about whether the code it produces compiles, and two bugs lived behind exactly that gap. It runs in CI as part of the Build job, so you do not have to remember it; running it locally is just faster than waiting.
1016
+
1017
+ ### Commit messages and releases
1018
+
1019
+ Commit messages drive versioning through [semantic-release](https://semantic-release.gitbook.io/). Only these publish:
1020
+
1021
+ | Prefix | Release |
1022
+ | ------------------------------ | ------- |
1023
+ | `feat:` | minor |
1024
+ | `fix:` | patch |
1025
+ | `perf:` | patch |
1026
+ | `BREAKING CHANGE:` in the body | major |
1027
+
1028
+ Everything else — `docs:`, `test:`, `ci:`, `chore:`, `refactor:`, `style:` — lands on `main` without publishing and ships with whatever releasable commit comes next.
906
1029
 
907
- Include a description of what changed and why, and add tests for any new behaviour. Use `fix:` / `feat:` / `docs:` prefixes so [semantic-release](https://semantic-release.gitbook.io/) can version correctly — `test:` commits do not trigger a release.
1030
+ Pick the prefix by what reaches the installed package, not by which file you edited. JSDoc is compiled into the published `.d.ts` and is what a user reads in their editor, so correcting a wrong `@example` is a `fix:` even though you only touched a comment. A README-only change is `docs:`.
908
1031
 
909
1032
  ---
910
1033
 
@@ -154,7 +154,7 @@ export class ButtonInteractionGuard implements GuardInterface {
154
154
  * @example
155
155
  * ```typescript
156
156
  * // Method-level usage
157
- * @Command('profile-{id}', CommandType.BUTTON)
157
+ * @Command('profile/{id}', CommandType.BUTTON)
158
158
  * @UseGuard(
159
159
  * { provide: RateLimiterGuard, params: { limit: 2, window: 3000 } },
160
160
  * ButtonInteractionGuard
@@ -0,0 +1,61 @@
1
+ # {{displayName}}
2
+
3
+ A Discord bot built with [MeoCord](https://github.com/l7aromeo/meocord).
4
+
5
+ ## Setup
6
+
7
+ Copy the example environment file and add your bot token:
8
+
9
+ ```shell
10
+ cp .env.example .env
11
+ ```
12
+
13
+ `.env` is gitignored. Never commit a real token — if one is pushed, reset it in the
14
+ [Discord developer portal](https://discord.com/developers/applications) rather than
15
+ rewriting history.
16
+
17
+ ## Running
18
+
19
+ ```shell
20
+ {{packageManager}} run start:dev # watch mode, rebuilds and restarts on change
21
+ {{packageManager}} run start:prod # production
22
+ ```
23
+
24
+ The bot runs on whichever runtime you launch it with — launch with bun and it is a bun
25
+ process, launch with node and it is a node process.
26
+
27
+ ## Building
28
+
29
+ ```shell
30
+ {{packageManager}} run build:dev
31
+ {{packageManager}} run build:prod
32
+ ```
33
+
34
+ ## Generating components
35
+
36
+ ```shell
37
+ npx meocord g co slash "profile" # a slash controller, its spec and its builder
38
+ npx meocord g s "profile" # a service
39
+ npx meocord g gu "rate-limit" # a guard
40
+ npx meocord g --help # every generator
41
+ ```
42
+
43
+ Register whatever you generate in the `controllers` array in `src/app.ts` — controllers
44
+ are wired up by that list, not by where they sit on disk.
45
+
46
+ ## Testing
47
+
48
+ ```shell
49
+ {{packageManager}} run test
50
+ {{packageManager}} run test:coverage
51
+ ```
52
+
53
+ `meocord/testing` provides `MeoCordTestingModule` for building a controller with its
54
+ dependencies, plus `createMockInteraction` and friends for driving it without a Discord
55
+ connection.
56
+
57
+ ## Linting
58
+
59
+ ```shell
60
+ {{packageManager}} run lint
61
+ ```
@@ -0,0 +1,2 @@
1
+ # Copy to .env and fill in. Never commit the real token.
2
+ DISCORD_TOKEN=
@@ -0,0 +1,22 @@
1
+ dist
2
+ coverage
3
+
4
+ /node_modules
5
+ npm-debug.log*
6
+ yarn-debug.log*
7
+ yarn-error.log*
8
+
9
+ .pnp.*
10
+ .yarn/*
11
+ !.yarn/patches
12
+ !.yarn/plugins
13
+ !.yarn/releases
14
+ !.yarn/sdks
15
+ !.yarn/versions
16
+
17
+ .idea
18
+ .vscode/*
19
+
20
+ .env*
21
+ !.env.example
22
+ .DS_Store
@@ -0,0 +1,10 @@
1
+ export default {
2
+ bracketSpacing: true,
3
+ bracketSameLine: true,
4
+ singleQuote: true,
5
+ trailingComma: 'all',
6
+ arrowParens: 'avoid',
7
+ semi: false,
8
+ endOfLine: 'lf',
9
+ printWidth: 120,
10
+ }
@@ -0,0 +1,25 @@
1
+ import unusedImports from 'eslint-plugin-unused-imports'
2
+ import meocordESLint, { typescriptConfig } from 'meocord/eslint'
3
+
4
+ const customConfig = {
5
+ ...typescriptConfig,
6
+ plugins: {
7
+ ...typescriptConfig.plugins,
8
+ 'unused-imports': unusedImports,
9
+ },
10
+ rules: {
11
+ ...typescriptConfig.rules,
12
+ 'unused-imports/no-unused-imports': 'error',
13
+ 'unused-imports/no-unused-vars': [
14
+ 'error',
15
+ {
16
+ vars: 'all',
17
+ varsIgnorePattern: '^_',
18
+ args: 'after-used',
19
+ argsIgnorePattern: '^_',
20
+ },
21
+ ],
22
+ },
23
+ }
24
+
25
+ export default [...meocordESLint, customConfig]
@@ -0,0 +1,25 @@
1
+ import 'dotenv/config'
2
+ import { type MeoCordConfig } from 'meocord/interface'
3
+
4
+ export default {
5
+ appName: '{{displayName}}',
6
+ // Read from the environment rather than written here: this file is committed, and a
7
+ // token pasted into it is a token pushed to the remote. Copy .env.example to .env.
8
+ discordToken: process.env.DISCORD_TOKEN!,
9
+ webpack: config => {
10
+ config.module.rules?.push({
11
+ test: /\.(md|html)$/i,
12
+ type: 'asset/source',
13
+ })
14
+ config.module.rules?.push({
15
+ test: /\.(gif|jpg|jpeg|png|svg|woff|woff2|eot|ttf|otf)$/i,
16
+ type: 'asset/resource',
17
+ exclude: /node_modules/,
18
+ generator: {
19
+ filename: 'assets/[name][ext]',
20
+ },
21
+ })
22
+
23
+ return config
24
+ },
25
+ } satisfies MeoCordConfig
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "{{appName}}",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "private": true,
7
+ "scripts": {
8
+ "build:dev": "{{runtimePrefix}}meocord build --dev",
9
+ "build:prod": "{{runtimePrefix}}meocord build --prod",
10
+ "start:dev": "{{runtimePrefix}}meocord start --dev",
11
+ "start:prod": "{{runtimePrefix}}meocord start --prod",
12
+ "lint": "eslint --fix && tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "test:coverage": "vitest run --coverage"
16
+ },
17
+ "dependencies": {
18
+ "discord.js": "^14.27.0",
19
+ "dotenv": "^17.2.4",
20
+ "meocord": "^{{version}}"
21
+ },
22
+ "devDependencies": {
23
+ "@eslint/js": "^10.0.1",
24
+ "@typescript-eslint/parser": "^8.58.1",
25
+ "@vitest/coverage-istanbul": "^4.1.10",
26
+ "eslint": "^10.2.0",
27
+ "eslint-config-prettier": "^10.1.8",
28
+ "eslint-plugin-import-x": "^4.16.2",
29
+ "eslint-plugin-prettier": "^5.5.5",
30
+ "eslint-plugin-unused-imports": "^4.4.1",
31
+ "globals": "^17.5.0",
32
+ "prettier": "^3.8.2",
33
+ "typescript": "^6.0.2",
34
+ "typescript-eslint": "^8.58.1",
35
+ "unplugin-swc": "^1.5.7",
36
+ "vitest": "^4.1.10"
37
+ }
38
+ }
@@ -0,0 +1,44 @@
1
+ import { GatewayIntentBits, Partials, ActivityType } from 'discord.js'
2
+ import { MeoCord } from 'meocord/decorator'
3
+ import { SampleSlashController } from '@src/controllers/slash/sample.slash.controller'
4
+ import { SampleSelectMenuController } from '@src/controllers/select-menu/sample.select-menu.controller'
5
+ import { SampleButtonController } from '@src/controllers/button/sample.button.controller'
6
+ import { SampleMessageController } from '@src/controllers/message/sample.message.controller'
7
+ import { SampleReactionController } from '@src/controllers/reaction/sample.reaction.controller'
8
+ import { SampleContextMenuController } from '@src/controllers/context-menu/sample.context-menu.controller'
9
+ import { SampleModalSubmitController } from '@src/controllers/modal-submit/sample.modal-submit.controller'
10
+
11
+ @MeoCord({
12
+ controllers: [
13
+ // Slash Commands
14
+ SampleSlashController,
15
+ // Select Menu
16
+ SampleSelectMenuController,
17
+ // Buttons
18
+ SampleButtonController,
19
+ // Message
20
+ SampleMessageController,
21
+ // Reactions
22
+ SampleReactionController,
23
+ // Context Menu
24
+ SampleContextMenuController,
25
+ // Modal Submit
26
+ SampleModalSubmitController,
27
+ ],
28
+ clientOptions: {
29
+ intents: [
30
+ GatewayIntentBits.Guilds,
31
+ GatewayIntentBits.GuildMessages,
32
+ GatewayIntentBits.GuildMessageReactions,
33
+ GatewayIntentBits.MessageContent,
34
+ ],
35
+ partials: [Partials.Message, Partials.Reaction],
36
+ },
37
+ activities: [
38
+ {
39
+ name: ``,
40
+ type: ActivityType.Custom,
41
+ },
42
+ ],
43
+ })
44
+ export default class App {}
@@ -0,0 +1,17 @@
1
+ import { MeoCordTestingModule } from 'meocord/testing'
2
+ import { SampleButtonController } from '@src/controllers/button/sample.button.controller.js'
3
+
4
+ describe('SampleButtonController', () => {
5
+ let controller: SampleButtonController
6
+
7
+ beforeEach(() => {
8
+ const module = MeoCordTestingModule.create({
9
+ controllers: [SampleButtonController],
10
+ }).compile()
11
+ controller = module.get(SampleButtonController)
12
+ })
13
+
14
+ it('should be defined', () => {
15
+ expect(controller).toBeDefined()
16
+ })
17
+ })
@@ -0,0 +1,19 @@
1
+ import { ButtonInteraction } from 'discord.js'
2
+ import { Controller, Command, UseGuard } from 'meocord/decorator'
3
+ import { CommandType } from 'meocord/enum'
4
+ import { RateLimitGuard } from '@src/guards/rate-limit.guard'
5
+
6
+ @Controller()
7
+ export class SampleButtonController {
8
+ @Command('button-click', CommandType.BUTTON)
9
+ @UseGuard(RateLimitGuard)
10
+ async handleButton(interaction: ButtonInteraction) {
11
+ await interaction.reply('Button clicked!')
12
+ }
13
+
14
+ @Command('button-with/{id}', CommandType.BUTTON)
15
+ @UseGuard({ provide: RateLimitGuard, params: { limit: 2, windowInSeconds: 60 } })
16
+ async handleButtonWithId(interaction: ButtonInteraction, { id }: { id: string }) {
17
+ await interaction.reply(`Button with id: ${id} clicked!`)
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import { ContextMenuCommandBuilder, ApplicationCommandType } from 'discord.js'
2
+ import { CommandBuilder } from 'meocord/decorator'
3
+ import { CommandType } from 'meocord/enum'
4
+
5
+ @CommandBuilder(CommandType.CONTEXT_MENU)
6
+ export class SampleCommandBuilder {
7
+ build() {
8
+ return new ContextMenuCommandBuilder().setName('sample-context-menu').setType(ApplicationCommandType.User)
9
+ }
10
+ }
@@ -0,0 +1,17 @@
1
+ import { MeoCordTestingModule } from 'meocord/testing'
2
+ import { SampleContextMenuController } from '@src/controllers/context-menu/sample.context-menu.controller.js'
3
+
4
+ describe('SampleContextMenuController', () => {
5
+ let controller: SampleContextMenuController
6
+
7
+ beforeEach(() => {
8
+ const module = MeoCordTestingModule.create({
9
+ controllers: [SampleContextMenuController],
10
+ }).compile()
11
+ controller = module.get(SampleContextMenuController)
12
+ })
13
+
14
+ it('should be defined', () => {
15
+ expect(controller).toBeDefined()
16
+ })
17
+ })
@@ -0,0 +1,13 @@
1
+ import { UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction } from 'discord.js'
2
+ import { Controller, Command, UseGuard } from 'meocord/decorator'
3
+ import { SampleCommandBuilder } from '@src/controllers/context-menu/builders/sample.builder'
4
+ import { RateLimitGuard } from '@src/guards/rate-limit.guard'
5
+
6
+ @Controller()
7
+ export class SampleContextMenuController {
8
+ @Command('sample-context-menu', SampleCommandBuilder)
9
+ @UseGuard(RateLimitGuard)
10
+ async handleContextMenu(interaction: UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction) {
11
+ await interaction.reply('This is sample reply of context menu command.')
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import { MeoCordTestingModule } from 'meocord/testing'
2
+ import { SampleMessageController } from '@src/controllers/message/sample.message.controller.js'
3
+
4
+ describe('SampleMessageController', () => {
5
+ let controller: SampleMessageController
6
+
7
+ beforeEach(() => {
8
+ const module = MeoCordTestingModule.create({
9
+ controllers: [SampleMessageController],
10
+ }).compile()
11
+ controller = module.get(SampleMessageController)
12
+ })
13
+
14
+ it('should be defined', () => {
15
+ expect(controller).toBeDefined()
16
+ })
17
+ })
@@ -0,0 +1,28 @@
1
+ import { MessageHandler, Controller } from 'meocord/decorator'
2
+ import { Message } from 'discord.js'
3
+ import { Logger } from 'meocord/common'
4
+
5
+ @Controller()
6
+ export class SampleMessageController {
7
+ private readonly logger = new Logger(SampleMessageController.name)
8
+
9
+ @MessageHandler('baka')
10
+ @MessageHandler('Baka')
11
+ async baka(message: Message): Promise<void> {
12
+ try {
13
+ await message.reply(`You Baka!`)
14
+ } catch (error) {
15
+ this.logger.error('Error replying to command', error)
16
+ }
17
+ }
18
+
19
+ @MessageHandler()
20
+ async handleGeneralMessage(message: Message): Promise<void> {
21
+ try {
22
+ this.logger.info(`Received message: ${message.content}`)
23
+ // TODO: Implements your message handler logic here
24
+ } catch (error) {
25
+ this.logger.error('Error handling the message', error)
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,17 @@
1
+ import { MeoCordTestingModule } from 'meocord/testing'
2
+ import { SampleModalSubmitController } from '@src/controllers/modal-submit/sample.modal-submit.controller.js'
3
+
4
+ describe('SampleModalSubmitController', () => {
5
+ let controller: SampleModalSubmitController
6
+
7
+ beforeEach(() => {
8
+ const module = MeoCordTestingModule.create({
9
+ controllers: [SampleModalSubmitController],
10
+ }).compile()
11
+ controller = module.get(SampleModalSubmitController)
12
+ })
13
+
14
+ it('should be defined', () => {
15
+ expect(controller).toBeDefined()
16
+ })
17
+ })
@@ -0,0 +1,13 @@
1
+ import { ModalSubmitInteraction } from 'discord.js'
2
+ import { Controller, Command, UseGuard } from 'meocord/decorator'
3
+ import { CommandType } from 'meocord/enum'
4
+ import { RateLimitGuard } from '@src/guards/rate-limit.guard'
5
+
6
+ @Controller()
7
+ export class SampleModalSubmitController {
8
+ @Command('submit-modal', CommandType.MODAL_SUBMIT)
9
+ @UseGuard(RateLimitGuard)
10
+ async handleModal(interaction: ModalSubmitInteraction) {
11
+ await interaction.reply('Modal submitted!')
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import { MeoCordTestingModule } from 'meocord/testing'
2
+ import { SampleReactionController } from '@src/controllers/reaction/sample.reaction.controller.js'
3
+
4
+ describe('SampleReactionController', () => {
5
+ let controller: SampleReactionController
6
+
7
+ beforeEach(() => {
8
+ const module = MeoCordTestingModule.create({
9
+ controllers: [SampleReactionController],
10
+ }).compile()
11
+ controller = module.get(SampleReactionController)
12
+ })
13
+
14
+ it('should be defined', () => {
15
+ expect(controller).toBeDefined()
16
+ })
17
+ })