Discord bot for the Coder community server. TypeScript on Bun, using discord.js v14.
- Runtime: Bun (ESM,
"type": "module"). No build step for local dev, Bun runs the TypeScript entrypoint directly. - Library: discord.js v14 (Gateway + slash/context-menu commands, Components V2). For the underlying platform, see the Discord developer docs.
- Tooling: Biome for formatting and linting.
- HTTP:
ofetchfor outbound API calls (e.g. ProductBoard). - Config:
@uwu/configmasher.
| Task | Command | Notes |
|---|---|---|
| Run | bun start |
Starts the bot (bun .). |
| Watch | bun watch |
Restarts on file change. |
| Format | bun format |
Biome, writes changes. |
| Lint | bun lint |
Biome, writes fixes. |
| Deploy commands | bun src/deploy-commands.ts |
Registers guild slash commands. |
CI runs bun format:ci and bun lint:ci (report-only, non-writing). Run
bun format and bun lint locally before pushing so CI passes.
There is no test suite.
src/
index.ts Entrypoint: builds the Client, registers events, logs in.
deploy-commands.ts Registers slash/context commands with the guild.
commands/
index.ts Aggregates every command into a name -> command map.
util/ close, reopen, walkthrough.
product/ notes (ProductBoard context-menu command).
events/ commands, messages, channels, walkthrough handlers.
lib/
config.ts Typed config loader + mandatory field list.
discord/ channels, users, messages helpers.
ui/components/ StringSelectMenu builders for the walkthrough.
assets/tags.json Canned response text.
- Imports: Use the
.jsextension on relative imports (ESM/NodeNext), even though the source is.ts. Prefer thetsconfigpath aliases:@lib/*,@commands/*,@events/*,@components/*. - Formatting (enforced by Biome): 2-space indent, 80-char line width.
Biome ignores
package.json,bun.lockb, and*.md. - Adding a command: create it under
src/commands/, export a{ data, execute }default, then add it to the array insrc/commands/index.ts. Runbun src/deploy-commands.tsto register it. - Adding an event handler: export a
registerEvents(client)default and call it fromsrc/index.ts. - Commits:
type(scope): message(e.g.chore: update example config).
@uwu/configmasher merges, in order: defaults -> config.json ->
environment file -> process environment. Keys are case-sensitive.
- Copy
config.json.exampletoconfig.json(gitignored) for local IDs. - Secrets come from the environment, e.g.
Codercord_token(the bot token). - Mandatory fields are declared in
src/lib/config.ts; the process exits if any are missing.
- Stay stateless: the bot keeps no persistent store. All state lives on Discord's servers (channels, tags, pinned messages) plus a few clever hacks, so any change should ideally survive a restart. Avoid relying on in-memory state that is lost when the process dies; if you must hold state in memory, keep it ephemeral and reconstructable from Discord.
- Intents:
GuildsandGuildMessagesonly. - The bot targets a single guild (
serverId); commands are registered per-guild, not globally. - The help forum flow (walkthrough,
/close,/reopen, auto-tagging) keys offhelpChannelIDs and tag IDs from config.
Pushing to main triggers .github/workflows/deploy-docker.yaml, which
builds the Dockerfile (oven/bun:1) and publishes the image to GHCR with a
build-provenance attestation.