4.1.0-beta.8 changelog
Published
Entries marked Breaking change a working bot; the migration guide says what to do about them. Every release of the line is in the changelog.
Minor Changes
#273
ebd546eThanks @l7aromeo! - A message command can tell its author, in a direct message, what the channel doesn't show. Without a filter, a message command's unexpected error is only logged and a cooldown refusal is skipped silently, since a reply in the channel can't be private. Two options turn on a direct message instead, both off by default:@MeoCord({ messages: { dmOnError: true } })tells the author the command failed, naming the command, the channel and the server. The error is still logged.@MeoCord({ messages: { dmOnCooldown: true } })tells the author how long to wait, once per wait: retries before it ends send nothing more. The notice is counted in the app's cooldown store, so it holds across shards with a shared store.
Only patterned message handlers are answered, and only when no filter handled the error. A command sent in a direct message is answered there. A member whose direct messages are closed is not told, at debug level. Guard, validation, usage and
UserErrorreplies are unchanged. The texts aremeocord.dm.errorandmeocord.dm.cooldown, translatable like MeoCord's other texts, in the server's language.#269
a18e948Thanks @l7aromeo! -meocord/testing:createMockUser(props?)andcreateMockChannel(Class, props?)take values for the mock's properties, ascreateMockInteractiondoes, socreateMockUser({ bot: true })makes a bot andcreateMockChannel(TextChannel, { name: 'general' })a named channel. Neither took any before, so a bot user tookObject.assign. The managers of a mock channel,messages,threadsand a thread'smembers, and a mock guild'sbans, have a real, emptycache, as a guild'smembers,rolesandchannelsdo, where readingcache.sizegave a stub object.
Patch Changes
#274
4827e0fThanks @l7aromeo! -meocord start --devruns one bot at a time. A build that finished while the previous bot was still shutting down, which happens when the watcher reports one change twice or a file changes again during a slowonShutdown, started a second bot at once, alongside the one stopping. Once that one exited, a third started. The second was left running and connected to Discord, and a restart or Ctrl+C no longer reached it. Now builds that finish during a restart start a single bot from the latest build, once the previous one has exited, and a Ctrl+C during a restart waits for the stopping bot and starts nothing.#266
6be7120Thanks @l7aromeo! -meocord start --devrestarts the bot once per change, and the new bot always starts. The watcher took the tsconfig copy MeoCord writes for each build for a change of its own and rebuilt right after the first build, restarting the bot while it was still logging in. A stop that lands during login also left the old process running: discord.js'sdestroy()does not settle while the gateway waits for READY, so the bot came online after "Shutting down bot..." and the replacement never started.- A stop during login now ends the start at once, and ends with "Bot has shut down" as any other stop does: no
onReadyhook runs,start()rejects with an errorisExplainedError()recognises, and the process exits 0. The client is closed once its login completes. This also fixes a Ctrl+C during login in production, which did nothing until a second one forced exit 1. - Edits to
tsconfig.jsonnow rebuild and restart the bot understart --dev, as edits tomeocord.config.tsdo. A changedmeocord.config.tsis compiled again before the rebuild, and one that does not compile, such as a file saved mid-edit, is reported and leaves the running bot in place instead of ending the session. - If an application still has not exited after its
shutdownTimeoutand a short grace period,start --devkills it with a warning and starts the new build.
- A stop during login now ends the start at once, and ends with "Bot has shut down" as any other stop does: no
#265
2d1ca03Thanks @l7aromeo! - An answer MeoCord fails to build is reported as a fault, not passed off as Discord refusing it. When the text of a usage reply, aUserErrorreply or the answer to an interaction's error cannot be written, or a presenter throws while building an error view, the bot logs an error naming the call, and observers are told the call ended in'error'with that fault. Before, it was logged only at debug level as "Could not reply", and the message went unanswered with no trace at the default log level. A send that fails is logged at debug level only when Discord refused it, aDiscordAPIErrorsuch as a missing permission; any other failure is logged as an error. In a testing module, such a fault rejectsdispatch().respond(interaction).error()still never throws, and logs a presenter that fails as an error.#275
7d70abfThanks @l7aromeo! - Two JSDoc corrections.@Onand@Oncesaid an event handler runs with the app's global guards, interceptors and filters; its class's and its own run too, as they always have.MeoCordTestingModule.fromApp's remarks now say plainly thatcompile()runs no factory andinit()runs each one the module provides, so a factory the test replaces never runs, and one it keeps runs atinit().#270
9cb86a4Thanks @l7aromeo! - A mistake MeoCord refuses as the bot loads is reported as one line, and the bot exits 1. This covers an invalid customId pattern, a builder that fails, a@Cooldownit cannot count, and whatMeoCordFactory.create()refuses, such as a message pattern or two handlers for one command. Before, each came out as Node's uncaught-exception report: MeoCord's own source line, a stack through its bundle, and often none of it pointing at your file. Now the line names the handler, and the source file where the stack shows it. Understart --devthe watch session keeps running, so the next edit rebuilds. Any other error keeps the runtime's own report.- Every refusal begins with what it is on,
Class.method:,Class:orApp:for@MeoCordoptions, followed by the decorator and the problem, such asSampleButtonController.handleButtonWithId: Invalid pattern …orApp: @MeoCord({ guards }): null is not a class.A test that matches a refusal's whole text may need its expected message updated. @Cooldown,@Validategiven something that isn't a Standard Schema, andSetMetadatawith a key MeoCord reserves now throw where they are applied, rather than where they are called, so the message can nameClass.method. Written as decorators, both happen in the same statement. A composite made withapplyDecoratorsthat includes one of them throws where it is applied; one that is defined but never applied no longer throws.- New projects'
src/main.tscallsMeoCordFactory.create()insidebootstrap(), and sets exit code 1 for any startup error. An existingmain.tsneeds no change:create()reports what it refuses itself, and marks it soisExplainedError()returnstrue.
- Every refusal begins with what it is on,
#268
5a69c71Thanks @l7aromeo! -meocord/testing: mocks read the data Discord always sends as Discord sends it. A mock guild'spreferredLocalewas an empty stub object, sot.forGuild(createMockGuild()), and a message command's usage reply on a mock message, came out in the translator's default language whatever the test meant. It is now'en-US', and a guild'sname, a user'susername, a message'spinned, a channel'stypeand the rest have Discord's values:falsefor a flag,nullfor what may be absent, and snowflake ids.tag,displayName,createdAtandurlare computed from them as discord.js does.createMockGuildtakesnameandpreferredLocale, socreateMockGuild({ preferredLocale: Locale.Indonesian })gives a server that speaks Indonesian.An interaction with a
guildIdhas its user as itsmember, and itsguildLocaleis thepreferredLocaleof theguildit is given, as Discord sends it, where it was always'en-US'. A mock message has its author as itsmemberin a server, andnullin a direct message, and itsinGuild()answers as discord.js's does, where it returnedundefined. A test that reads a property a mock has no value for now gets that value instead of a stub object; one that relied on the stub there sets the property itself.commandName,customIdand a message'scontentstay the test's to give.#272
a8f454dThanks @l7aromeo! - The JSDoc says where every pipeline stage runs, so the API reference can show it on each entry.@Guard,@Interceptor,@Catchand@Pipegain the@pipelinetag their@UseGuard,@UseInterceptor,@UseFilterand@UsePipecounterparts already had, and so doGuardInterface,InterceptorInterface,ExceptionFilter,PipeInterfaceandDispatchObserver. The stage names are those of the pipeline figure on meocord.dev:@Observerruns atobservers-startandobservers-settled, and@Defer's second step atdefer-lock.#263
925225dThanks @l7aromeo! - The JSDoc ofContextMenuCommandBuilder.setType, as MeoCord types it, says to leave a context menu builder'sbuild()return type inferred. Written out asContextMenuCommandBuilder, it drops the kindsetType()gave, so a handler of the other kind compiles and is caught only as the bot starts.#271
8b1d9dbThanks @l7aromeo! - A new app'spackage.jsonhas astartscript,meocord start --prod, sonpm start,bun run startand a host that runsnpm startfor you, as many Node hosts do by default, start the production build.start:prodis unchanged, and building stays its own step: runbuild:prodbeforestart. An existing app can add the line to itsscriptsto get the same.