Skip to content

Boilerplate Refactor#3

Open
aarchangel64 wants to merge 4 commits into
mainfrom
template-refactor
Open

Boilerplate Refactor#3
aarchangel64 wants to merge 4 commits into
mainfrom
template-refactor

Conversation

@aarchangel64
Copy link
Copy Markdown
Collaborator

@aarchangel64 aarchangel64 commented May 31, 2026

This PR refactors a lot of the boilerplate, mainly around how commands and events are loaded (plus some other additions).

Instead of dynamically searching the source directories for events and commands at runtime, I feel it's a lot simpler and cleaner to just statically import them like you would with most other functions. I feel this also is a bit easier to understand, especially for contributors who might not be very familiar with ts/js (I saw a few people in the server who wanted to help out even though they hadn't used typescript before).

The bot now also automatically registers commands as guild commands on startup, so you don't need to run a separate deploy script every time you add or modify a command.

Minor Changes:

  • Error handling in commands - a message is now sent to the user if a command invocation throws an unrecoverable error (you can test this out by throwing an error within the ping function)
  • Added a BotContext interface for passing around access to global state. For now it only has the logger but this would make it easier to e.g. give database access to any commands that need it.
  • Added a utility function to get server emoji
  • Made the ping function a bit more fun by displaying a random emoji (mainly to demonstrate emoji function use)
  • Uses pino for structured and prettier logging (not sure how necessary this is for us, I just carried this over from my previous bot that this PR is based on)
  • Uses @t3-oss/env-core + zod for runtime env var validation
  • format and format:check package.json script names were swapped? I'm not 100% sure on this one.
  • Added --env-file=.env and --watch to the pnpm dev script (this fixes issues in fix: env loading and structure resolving #2 )
  • Added an .editorconfig because my editor's format-on-save was conflicting with oxfmt's formatting.
  • Relaxed some oxlint rules I thought were a little much, such as limiting functions to 10 statements.
  • Fixed any remaining oxlint issues.

…ed logging

The old architecture used a glob-based loadStructures() to discover
commands and events at runtime, validating each with zod predicates.
This made startup order implicit and required a separate deploy.ts
script for command registration.

Commands are now a static Collection built from explicit imports.
Events are plain async functions registered directly on the client.
Command registration moves into the ready handler so startup is
self-contained. A BotContext object handles dependency injection
(logger, and future shared resources) into handlers.

Also:
- add pino for structured logging (replaces console.log)
- add @t3-oss/env-core + zod for validated env vars on startup
- fix format/format:check script names (were swapped)
- add --env-file=.env --watch to dev script
- add .editorconfig for consistent editor settings
Copy link
Copy Markdown
Member

@tj-dodd tj-dodd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great, though i'd also like sync or rad to have a look at it as there's a lot of changes happening here

Comment thread src/commands/index.ts
/**
* Defines the structure of a command
* Shared state passed as the second argument to every command's `execute` handler.
* Add shared resources here as the bot grows (e.g. database clients, API wrappers).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really like this approach

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got this idea fom poise-rs, a rust discord library. In TS we I think we could have a global variable for this since we don't have to deal with the ownership and borrowing of rust? But I still like the way this works, so I'm glad others like it too :)

Comment thread src/util/env.ts
* Secret environment variables only. Non-secret settings live in config/config.ts.
* Exits immediately with a clear error on startup if any required variable is missing.
*/
export default createEnv({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something to think about is if discord changes the format for any of these this will be fail, but i don't really think this will be an issue

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's something I thought about too, but I figured if that happens we would probably need to update discord.js and the codebase anyways.

Comment thread src/events/ready.ts Outdated
Comment thread src/index.ts Outdated
Comment on lines +19 to +23
// once() so reconnects don't re-trigger startup logic like command registration
client.once(
Events.ClientReady,
async (readyClient) => await ready(readyClient, ctx),
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should just fire this as part of the deployment workflow instead of in the application?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought is that this will automatically sync any new commands, which makes the dev workflow a easier and also simplifies deployment since we don't have to set up a script for every update. If you think it's better to make it part of the deployment workflow then we can definitely do that instead though!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think that's a good call out on the dev workflow side of things. Maybe let's keep this in mind as we move closer to actual deployment (cuz for serverless it might cause issues and realistically a separate step is easy to add into the CI).

Comment thread oxlint.config.ts Outdated
Comment thread package.json
"dev": "tsx src/index.ts",
"format": "oxfmt --check",
"format:check": "oxfmt",
"dev": "tsx --env-file=.env --watch src/index.ts",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see if that mise fix works, otherwise, let's go with this :)

Comment thread tsconfig.json Outdated
aarchangel64 and others added 2 commits June 1, 2026 12:11
Co-authored-by: Elliot Alexander <elliot@kasa.au>
Co-authored-by: Elliot Alexander <elliot@kasa.au>
- Fix zod import path: `from "zod/v4"` → `from "zod"` (zod v4 changed
  the subpath export)
- Use ctx.logger.child() in event handlers instead of module-level
  import; use pino-conventional `err` key for Error objects
- Exit process on command registration failure instead of swallowing it
- Add unregister-commands utility script and npm run target
- Fix emoji name typo: catsurpised → catsurprised
- Remove unnecessary async/await wrappers on event handler lambdas
Comment thread pnpm-workspace.yaml
@@ -1,2 +1,51 @@
allowBuilds:
esbuild: true
minimumReleaseAgeExclude:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need these overrides? if we do, can we use a pattern instead please https://pnpm.io/settings#minimumreleaseageexclude

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants