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.
- package/README.md +138 -15
- package/dist/cjs/decorator/index.cjs +1 -1
- package/dist/esm/bin/app-template/README.md.template +61 -0
- package/dist/esm/bin/app-template/_env.example.template +2 -0
- package/dist/esm/bin/app-template/_gitignore.template +22 -0
- package/dist/esm/bin/app-template/_prettierrc.mjs.template +10 -0
- package/dist/esm/bin/app-template/eslint.config.ts.template +25 -0
- package/dist/esm/bin/app-template/meocord.config.ts.template +25 -0
- package/dist/esm/bin/app-template/package.json.template +38 -0
- package/dist/esm/bin/app-template/src/app.ts.template +44 -0
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +19 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/builders/sample.builder.ts.template +10 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.ts.template +13 -0
- package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.ts.template +28 -0
- package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.ts.template +13 -0
- package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.ts.template +29 -0
- package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.ts.template +11 -0
- package/dist/esm/bin/app-template/src/controllers/slash/builders/sample.builder.ts.template +10 -0
- package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.ts.template +19 -0
- package/dist/esm/bin/app-template/src/guards/rate-limit.guard.spec.ts.template +13 -0
- package/dist/esm/bin/app-template/src/guards/rate-limit.guard.ts.template +52 -0
- package/dist/esm/bin/app-template/src/main.ts.template +14 -0
- package/dist/esm/bin/app-template/src/services/sample.service.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/services/sample.service.ts.template +9 -0
- package/dist/esm/bin/app-template/tsconfig.eslint.json.template +5 -0
- package/dist/esm/bin/app-template/tsconfig.json.template +30 -0
- package/dist/esm/bin/app-template/tsconfig.test.json.template +8 -0
- package/dist/esm/bin/app-template/vitest.config.ts.template +35 -0
- package/dist/esm/bin/builder-template/controller/button.controller.template +1 -1
- package/dist/esm/bin/helper/app-generator.helper.js +78 -0
- package/dist/esm/bin/meocord.js +156 -75
- package/dist/esm/decorator/guard.decorator.js +1 -1
- package/dist/esm/util/common.util.js +11 -3
- package/dist/esm/util/generator-cli.util.js +21 -4
- package/dist/esm/util/package-manager.util.js +9 -2
- package/dist/esm/util/package-version.util.js +32 -0
- package/dist/esm/util/runtime.util.js +72 -0
- package/dist/types/core/index.d.ts +2 -2
- package/dist/types/decorator/index.d.ts +1 -1
- package/dist/types/interface/index.d.ts +5 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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',
|
|
92
|
-
@UseGuard({ provide:
|
|
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
|
-
|
|
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
|
-
###
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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
|
|
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,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,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 {}
|
package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template
ADDED
|
@@ -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
|
+
}
|
package/dist/esm/bin/app-template/src/controllers/context-menu/builders/sample.builder.ts.template
ADDED
|
@@ -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
|
+
}
|
package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.spec.ts.template
ADDED
|
@@ -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
|
+
})
|
package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.ts.template
ADDED
|
@@ -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
|
+
})
|