meocord 4.2.1 → 4.2.3

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 (137) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/cjs/_shared/{controller.decorator-XKMlcUZz.cjs → controller.decorator-hp5xdJU4.cjs} +550 -189
  3. package/dist/cjs/_shared/{startup-roots-CgfPQrNl.cjs → event-sources-DzqTODXg.cjs} +274 -88
  4. package/dist/cjs/_shared/{providers-BZgJyfZS.cjs → providers-BBvbnmLP.cjs} +837 -295
  5. package/dist/cjs/_shared/{sharded-cooldown-store-CsJJyCZd.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
  6. package/dist/cjs/_shared/{translate-error-BUAR1WYG.cjs → translate-error-wsgfsUET.cjs} +1 -1
  7. package/dist/cjs/common/index.cjs +4 -104
  8. package/dist/cjs/core/index.cjs +435 -182
  9. package/dist/cjs/decorator/index.cjs +116 -121
  10. package/dist/cjs/testing/index.cjs +1923 -808
  11. package/dist/cli.json +1 -1
  12. package/dist/esm/bin/app-template/package.json.template +9 -9
  13. package/dist/esm/bin/app-template/src/main.ts.template +3 -2
  14. package/dist/esm/bin/generator.js +6 -4
  15. package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
  16. package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
  17. package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
  18. package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
  19. package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
  20. package/dist/esm/bin/helper/service-generator.helper.js +6 -4
  21. package/dist/esm/bin/meocord.js +106 -48
  22. package/dist/esm/build/native-addons.js +75 -19
  23. package/dist/esm/build/rsbuild-config.js +11 -3
  24. package/dist/esm/build/typed-externals.js +15 -2
  25. package/dist/esm/common/cooldown-store.js +38 -14
  26. package/dist/esm/common/deprecation.js +4 -1
  27. package/dist/esm/common/execution-context.js +3 -1
  28. package/dist/esm/common/explained-error.js +7 -3
  29. package/dist/esm/common/log-level.js +13 -9
  30. package/dist/esm/common/logger.js +4 -2
  31. package/dist/esm/common/meocord-text.js +11 -6
  32. package/dist/esm/common/redis-cooldown-store.js +3 -7
  33. package/dist/esm/common/response/flags.js +17 -1
  34. package/dist/esm/common/response/install-context.js +27 -16
  35. package/dist/esm/common/response/presenter.js +7 -3
  36. package/dist/esm/common/response/response-state.js +32 -10
  37. package/dist/esm/common/sharded-cooldown-store.js +11 -9
  38. package/dist/esm/common/theme.js +3 -1
  39. package/dist/esm/common/translator.js +27 -8
  40. package/dist/esm/core/autocomplete-routes.js +26 -0
  41. package/dist/esm/core/command-conflicts.js +15 -7
  42. package/dist/esm/core/command-registration.js +15 -1
  43. package/dist/esm/core/cooldown-runner.js +7 -9
  44. package/dist/esm/core/defer.js +2 -5
  45. package/dist/esm/core/dispatcher.js +92 -66
  46. package/dist/esm/core/event-requirements.js +67 -19
  47. package/dist/esm/core/event-sources.js +100 -0
  48. package/dist/esm/core/fallback.js +3 -1
  49. package/dist/esm/core/filter-runner.js +6 -6
  50. package/dist/esm/core/guard-runner.js +173 -52
  51. package/dist/esm/core/handler-input.js +24 -3
  52. package/dist/esm/core/handler-pipeline.js +54 -32
  53. package/dist/esm/core/handler-registry.js +51 -21
  54. package/dist/esm/core/input-runner.js +7 -8
  55. package/dist/esm/core/interceptor-runner.js +4 -5
  56. package/dist/esm/core/lifecycle-hooks.js +3 -8
  57. package/dist/esm/core/lifecycle-order.js +1 -15
  58. package/dist/esm/core/meocord-classes.js +14 -1
  59. package/dist/esm/core/meocord-factory.js +83 -54
  60. package/dist/esm/core/meocord.app.js +174 -89
  61. package/dist/esm/core/message-help.js +37 -15
  62. package/dist/esm/core/message-params.js +126 -46
  63. package/dist/esm/core/message-routes.js +225 -45
  64. package/dist/esm/core/message-words.js +30 -2
  65. package/dist/esm/core/observer-runner.js +4 -2
  66. package/dist/esm/core/process-state.js +19 -0
  67. package/dist/esm/core/providers.js +39 -4
  68. package/dist/esm/core/scalar-types.js +57 -16
  69. package/dist/esm/core/shard-context.js +11 -10
  70. package/dist/esm/core/shard-exit.js +3 -3
  71. package/dist/esm/core/shard-manager.js +16 -6
  72. package/dist/esm/core/stage-scope.js +2 -1
  73. package/dist/esm/core/startup-roots.js +28 -8
  74. package/dist/esm/core/theme-resolvers.js +26 -11
  75. package/dist/esm/core/theme-runtime.js +23 -13
  76. package/dist/esm/core/theme-scope.js +31 -30
  77. package/dist/esm/core/theme-validation.js +1 -1
  78. package/dist/esm/decorator/app.decorator.js +37 -1
  79. package/dist/esm/decorator/command-builder.decorator.js +14 -0
  80. package/dist/esm/decorator/controller-class.decorator.js +1 -1
  81. package/dist/esm/decorator/controller.decorator.js +48 -19
  82. package/dist/esm/decorator/defer.decorator.js +2 -2
  83. package/dist/esm/decorator/event.decorator.js +4 -5
  84. package/dist/esm/decorator/filter.decorator.js +4 -4
  85. package/dist/esm/decorator/guard.decorator.js +4 -61
  86. package/dist/esm/decorator/inject.decorator.js +4 -1
  87. package/dist/esm/decorator/interceptor.decorator.js +2 -2
  88. package/dist/esm/decorator/theme.decorator.js +4 -3
  89. package/dist/esm/decorator/validation.decorator.js +5 -5
  90. package/dist/esm/package.json.js +1 -1
  91. package/dist/esm/testing/catalog.js +32 -25
  92. package/dist/esm/testing/cooldown-store-suite.js +3 -3
  93. package/dist/esm/testing/discord-defaults.js +449 -335
  94. package/dist/esm/testing/inspect-handler.js +91 -10
  95. package/dist/esm/testing/meocord-testing-module.js +217 -91
  96. package/dist/esm/testing/mock-fn.js +8 -2
  97. package/dist/esm/testing/mock-interaction.js +864 -124
  98. package/dist/esm/testing/response-log.js +8 -0
  99. package/dist/esm/testing/response.js +7 -5
  100. package/dist/esm/testing/routing.js +48 -6
  101. package/dist/esm/testing/strict-mocks.js +8 -4
  102. package/dist/esm/testing/theme.js +3 -2
  103. package/dist/esm/util/common.util.js +30 -9
  104. package/dist/esm/util/generator-cli.util.js +6 -4
  105. package/dist/esm/util/inherited-env.util.js +72 -13
  106. package/dist/esm/util/json.util.js +10 -3
  107. package/dist/esm/util/meocord-cli.util.js +3 -2
  108. package/dist/esm/util/meocord-config-loader.util.js +14 -1
  109. package/dist/esm/util/meocord-config-validation.util.js +13 -3
  110. package/dist/esm/util/meocord-source-config.util.js +13 -3
  111. package/dist/esm/util/platform.util.js +3 -2
  112. package/dist/esm/util/refusal.util.js +29 -11
  113. package/dist/esm/util/registration-mode.util.js +14 -1
  114. package/dist/esm/util/timer-limit.util.js +4 -1
  115. package/dist/esm/util/tsconfig-paths.util.js +38 -7
  116. package/dist/esm/util/tsconfig.util.js +16 -58
  117. package/dist/esm/util/user-text.util.js +8 -4
  118. package/dist/esm/util/value.util.js +33 -5
  119. package/dist/types/common/index.d.cts +17 -10
  120. package/dist/types/common/index.d.ts +17 -10
  121. package/dist/types/core/index.d.cts +21 -11
  122. package/dist/types/core/index.d.ts +21 -11
  123. package/dist/types/decorator/index.d.cts +63 -63
  124. package/dist/types/decorator/index.d.ts +63 -63
  125. package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
  126. package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
  127. package/dist/types/interface/index.d.cts +1 -1
  128. package/dist/types/interface/index.d.ts +1 -1
  129. package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
  130. package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
  131. package/dist/types/testing/index.d.cts +158 -61
  132. package/dist/types/testing/index.d.ts +158 -61
  133. package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
  134. package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
  135. package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
  136. package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
  137. package/package.json +5 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,193 @@
1
1
  # meocord
2
2
 
3
+ ## 4.2.3
4
+
5
+ ### Highlights
6
+
7
+ - **Stopping during startup is clean.** A Ctrl+C, SIGTERM or `app.stop()` before the bot is online exits 0 (your own `app.stop()` once `main.ts` is updated, see Upgrading) and closes what the startup made, such as a connection pool a provider opened. A start that fails closes what it made too, so the process can end ([docs](https://meocord.dev/docs/4.2/lifecycle-hooks)).
8
+ - **`@On` and `@Once` reach every instance the app makes,** including a service only a guard or the presenter injects, a provider's `useClass`, `useValue` or `useFactory`. They were never attached there ([docs](https://meocord.dev/docs/4.2/gateway-events)).
9
+ - **Clearer message command replies.** The usage reply names the word a message left out or got wrong, quotes it as typed, and a pattern's own `--word` is matched where the pattern has it ([docs](https://meocord.dev/docs/4.2/message-commands)).
10
+ - **Mocks closer to discord.js.** Mocks fill in more of what Discord always sends, in both modes, and `useStrictMocks()` covers about twenty more behaviours that 5.0 makes the default. Upgrading your tests, below, lists them ([docs](https://meocord.dev/docs/4.2/mocks)).
11
+ - **One process, two builds.** A bot whose dependencies load both MeoCord's ES module and CommonJS builds shares one theme, one shutdown, one set of warnings and one exit code between them.
12
+
13
+ ### Upgrading from 4.2.2
14
+
15
+ - **`main.ts` and exit codes.** A stop during startup now exits 0, and a built bot exits 1 when `MeoCordFactory.create` refuses its app. New apps' `main.ts` sets the exit code only for an error MeoCord didn't explain. In an existing app, have the catch in `src/main.ts` return early for `isExplainedError(error)`, from `meocord/common`, before it logs and sets `process.exitCode = 1`, or an `app.stop()` of your own during startup still exits with your catch's code ([docs](https://meocord.dev/docs/4.2/lifecycle-hooks)).
16
+ - **`onShutdown` runs on a stop during startup,** for each class and provided value that was made and whose `onReady`, and that of everything it depends on, has finished or doesn't exist. If an `onShutdown` of yours assumed `onReady` had run, give the class an `onReady`, or guard the hook. A failed start closes what it made by the same rule; a `useValue` value stays open until the bot stops. A stop while a failed start shuts down waits for it, within one `shutdownTimeout` ([docs](https://meocord.dev/docs/4.2/lifecycle-hooks)).
17
+ - **`@On` handlers that never ran now run,** on a service only a stage or the presenter injects and on provided instances. Check what they do before upgrading. Handlers of one event attach in lifecycle order, so two listeners of one event can swap ([docs](https://meocord.dev/docs/4.2/gateway-events)).
18
+ - **`@On('interactionCreate')` stages get no `context.response`.** A guard, interceptor or filter there that answered through it should answer through `context.getInteraction()`; MeoCord also dispatches that interaction to its handler.
19
+ - **A message command that works only in a server,** by its `scope`, a `member`, `role` or `channel` param, or such a required flag, answers a DM that starts with a prefix or mention with "This command works in a server only." whatever words it gives, and gives no reply to one without. One with no such param and only optional such flags is listed in a DM's help, and `HandlerRegistry` gives it `scope: 'any'` ([docs](https://meocord.dev/docs/4.2/message-params)).
20
+ - **Observer durations for message and reaction handlers** start when dispatch received the message or reaction, as for interactions, so a listener that waited behind another one reports that wait ([docs](https://meocord.dev/docs/4.2/observers)).
21
+ - **Guards and observers, as now documented.** A guard with `types: ['interaction']` doesn't run before `@Autocomplete` handlers; declare `types: ['interaction', 'autocomplete']` to run it there. `deniedBy` also comes with `outcome: 'error'` when the answer to a denial fails to build, so an observer that counts denials checks `outcome` too ([docs](https://meocord.dev/docs/4.2/guards)).
22
+ - **New startup warnings to act on.** A missing `clientOptions.partials` entry that an `@On` handler's event needs now names the partial to add; without it, discord.js drops the event. `app.registerCommands()` before the bot is online says why it registers nothing; run `meocord register` instead.
23
+ - **`meocord start --prod` and `register` exit 128 plus the signal** when the bot is killed by one the CLI didn't pass on, such as 137 for SIGKILL, where `start --prod` exited 0. A process supervisor now sees the crash ([docs](https://meocord.dev/docs/4.2/cli)).
24
+ - **A referenced TypeScript project's files** resolve imports through the root `tsconfig.json`'s `paths`, not their own. A bot that references another project builds now; one that relied on a referenced project's own `paths` moves them to the root ([docs](https://meocord.dev/docs/4.2/project-structure#imports)).
25
+
26
+ ### Upgrading your tests
27
+
28
+ - **More behaviours under `useStrictMocks()`.** Each warns in default mode and becomes the default in 5.0. Call `useStrictMocks()` in your test setup to see your suite's results under them now ([docs](https://meocord.dev/docs/4.2/mocks)):
29
+ - an autocomplete's `respond()` a test set marks it `responded`, a second one is refused, and `getResponse()` reports it as `'replied'`;
30
+ - `launchActivity()` and `sendPremiumRequired()` count as the first answer, and `update()` on a modal submitted from a command is refused;
31
+ - `followUp()` and `editReply()` mark the interaction `replied` once they land;
32
+ - an autocomplete's entity options read as ids, `getMentionable()` returns the member in a server, and the focused option is read by name;
33
+ - a user context menu's `targetMember` is its `targetUser`'s member;
34
+ - `client.users.createDM()` gives each user their own DM channel, on the user's client, and `client.users.send()` resolves to the message sent;
35
+ - `guild.channels.create()` makes the type named, `create()` and `edit()` apply `name`, and `edit(id)` resolves to that item;
36
+ - a thread made by a channel's thread manager puts that channel in its server's `channels.cache`, a channel's `messages.cache` holds the messages made or sent in it, and `messages.fetch()` with no id lists them, newest first;
37
+ - a member's `joinedTimestamp` and a client's `readyTimestamp` are when the mock was made;
38
+ - a testing module without a presenter answers with the default on a client another module used, and `emit()` sets the module's presenter, theme and translator on the client;
39
+ - `dispatch()` reports a call whose answer can't be built once, as `'error'`, and `invoke()` runs an unrouted `@On` handler as an `'event'`;
40
+ - `compile()` refuses an app whose own providers give `Client`, `CooldownStore`, or `Translator` with `i18n`, as the bot does;
41
+ - `inspectHandler()` lists only the guards, interceptors and observers dispatch runs for the handler's context types, and its `inheritedRoutes` names an inherited `@On` or `@Once` event;
42
+ - `resolveRoute` reads a message without `dm` as sent in a server, and returns `undefined` with `dm: true` for a command that works in servers only.
43
+ - **Mocks fill in more of what Discord sends, in both modes.** A component interaction has the bot's message holding its component, a modal has empty `fields`, a user's `dmChannel` and its `recipient` are set, a mock given only a `guildId` reads a placeholder server with that id, `messages.fetch(id)` finds a message made with that id, and channel types, display colours and `roles.resolve()` read as discord.js gives them. Promise-returning mock methods such as `member.kick()` resolve. A test that asserted a placeholder there sees the real value ([docs](https://meocord.dev/docs/4.2/mocks)).
44
+ - **The testing module matches the bot more closely.** `init()` makes the app's presenter, after the providers; `close()` shuts down what it injects. `overrideProvider(ShardContext)` and `overrideProvider(HandlerRegistry)` replace MeoCord's binding. An app providing `HandlerRegistry` or `ShardContext` is refused at `compile()` with the bot's message ([docs](https://meocord.dev/docs/4.2/testing)).
45
+ - **Routing in tests.** `invoke()` checks an autocomplete against the `@Autocomplete` handler it names, warning in default mode and rejecting under `useStrictMocks()` where dispatch would pick another. A controller replaced with `overrideProvider(Controller).useValue(stub)` now gets its autocompletes. `dispatch()` of a mock message made without `content` runs no handler, where it threw ([docs](https://meocord.dev/docs/4.2/invoke-and-dispatch)).
46
+ - **Changed texts and types.** `fromApp()`, `resolveRoute()` and `findRouteConflicts()` name an `undefined` or module-namespace app, often a circular import, where each threw an empty `TypeError`. `invoke` on a patterned `@MessageHandler` with an empty message says to give the mock content; `respond()`'s dropped-flag warning and the theme refusal reword their advice. `ResponseCall['method']` gains `'respond'`, `'launchActivity'` and `'sendPremiumRequired'`, and `MockResult['type']` gains `'incomplete'`, so an exhaustive `switch` gains those cases ([docs](https://meocord.dev/docs/4.2/invoke-and-dispatch)).
47
+
48
+ ### Deprecations
49
+
50
+ - **Warned now, refused in 5.0.** Each runs as before with a warning at startup or when the class is decorated; 5.0 stops the bot or the command there. Fix the setup the warning names:
51
+ - a handler decorator such as `@Command`, `@MessageHandler` or `@On` on a static method, or `@Inject` on a static property, which never runs: move it to an instance member;
52
+ - a pattern of spaces alone, `@MessageHandler(' ')`, which runs for every message as `@MessageHandler('')` does: write `@MessageHandler()`;
53
+ - two message patterns that differ only in which params are optional: the one with required params ahead answers, and startup names both;
54
+ - two flags whose names differ only in case on a command that reads flags in any case, such as `{--v}` and `{--V}`: rename one, or set `caseSensitive: true`;
55
+ - on a class the app only makes (one only a stage or the presenter injects, or a provider's `useClass`, `useValue` or `useFactory` instance), a handler a listed service's would be refused for, such as `@Cooldown` on an `@On`, or an `@Once` on a class that shares another's name; and an `@On` on a class made for each call because it injects `ExecutionContext`: move it to a service;
56
+ - `messages.deleteUsageRepliesAfter` over 2,147,483 seconds (about 24.8 days), which now keeps usage replies;
57
+ - with `bundleDependencies`, two installed copies of one native addon that bundled code imports, of which one is packed: install one version ([docs](https://meocord.dev/docs/4.2/self-contained-builds)).
58
+ - **Changing in 5.0, warned now:**
59
+ - a static `prefix: []` takes messages with no prefix; in 5.0 it starts no command. Write `''` for no prefix;
60
+ - a `@CommandBuilder` subclass that gives no `guilds` inherits its base's; in 5.0 it registers in the configured scope. Declare `guilds` on the subclass to keep them;
61
+ - a subclass that re-decorates an inherited handler keeps the routes and `@On`/`@Once` events it inherits, and startup names them; in 5.0, `inheritedRoutes: 'replace'` is the default and drops them. Set it now to see that;
62
+ - a builder whose `guilds` holds no id, such as an unset environment variable, isn't registered, and `meocord register` still exits 0; in 5.0 it exits 1 ([docs](https://meocord.dev/docs/4.2/slash-commands)).
63
+
64
+ ### Fixes to 4.2.2 behaviour
65
+
66
+ - **Running the bot**
67
+ - A failed start is no longer among the apps a signal stops, so a later Ctrl+C stops only the running ones.
68
+ - With process sharding, `stop()` during the start ends it: a registration already sending the commands finishes, nothing more asks Discord, no shard is spawned, and `start()` resolves; a failed shard-count request no longer exits the process.
69
+ - A controller whose constructor throws fails only its own handlers; other controllers' message and reaction handlers still run, and the error goes to the filters.
70
+ - One method with `@Command`s of two interaction types, such as a slash command and a button, runs for each route, including an override that adds a route type and calls its base method through `super`; it threw.
71
+ - A subclass that re-declares `@On` or `@Once` for the same event runs once per event, not twice.
72
+ - A `Translator` given through `@MeoCord({ providers })` is accepted wherever it sits in the list.
73
+ - `@Inject` on a method parameter is refused with MeoCord's message, naming the method, not inversify's.
74
+ - With both builds loaded, one shutdown and one pair of signal listeners stop every app, a failed shard ends once, and a later login clears an earlier failed login's exit code.
75
+ - **Message commands**
76
+ - The usage reply names the word a message left out or replaced, such as `close is missing`, and a missing required flag beside it, such as `--in is missing`, and quotes a word with its spaces as typed. With no reason MeoCord has a text for, it's the usage line alone.
77
+ - A pattern's own word starting with `--`, such as `--force` in `deploy --force {env} {--dry}`, is matched where the pattern has it.
78
+ - A refused pattern names the rule it breaks and quotes its params as written.
79
+ - `!"help" mute` answers as `!help mute` does.
80
+ - A `{name:duration}` too long to add up is a usage error, not `Infinity`; `number` and `duration` words are read in time linear in their length.
81
+ - A pattern over 16 words type-checks the handler's params past the sixteenth.
82
+ - A param or flag named like an `Object` member, such as `{__proto__}`, `{constructor:int?}` or `{toString?}`, is its own key in the handler's params; `{__proto__:int}` and a left-out `{constructor:int?}` threw on every message.
83
+ - `@MeoCord({ controllers })` is typed as a list of classes, so a string or symbol there is a type error rather than a stop at startup.
84
+ - **Components**
85
+ - The warning about a customId param that shares a name with a modal field or choice is given per handler, as `Controller.method`.
86
+ - A component pattern whose shortest customId is over 100 characters is warned about, counting each typed param's shortest value.
87
+ - `{café}` in a customId pattern is literal text in the types, as at runtime, so `build({ café })` no longer compiles and then throws.
88
+ - **Themes and translations**
89
+ - A call keeps its theme for its whole run when another app in the process claims the theme outside calls.
90
+ - `@On('messageReactionAdd')`, `messageReactionRemove` and other events about a `User` run with that user's `themeFor.user` theme.
91
+ - A `themeFor` result that throws while being read is left out with a warning and cached, rather than failing every call.
92
+ - A translation is read by the default catalog's shapes, so a group with an `other` message isn't taken for a plural, while plural forms given for a plain default message are still read as plurals; an empty translation of MeoCord's own text falls back to English.
93
+ - `expectCompleteCatalog` reports a `null` translation as missing, where it threw.
94
+ - **Cooldowns**
95
+ - A built-in store's `consume()` refusal carries `retryTimestamp`, so a custom store built on it no longer DMs a cooldown twice in one wait.
96
+ - A `ShardedCooldownStore` verdict's `release()` undoes its call once, however often it's called.
97
+ - `testCooldownStore` passes a correct store built from the other module format.
98
+ - **Logging**
99
+ - Log lines escape U+061C, U+200E and U+200F in text a user wrote, as they escape other bidirectional controls.
100
+ - **Builds and the CLI**
101
+ - An `externals` pattern with the `g` or `y` flag keeps every matching import out of the bundle.
102
+ - With `bundleDependencies`, `os`, `cpu` and `libc` are read as npm reads them, and the bot registers its commands in the mode the `meocord` command runs in.
103
+ - `build` reads `tsconfig.json` as `tsc` does: `extends` on Windows or naming a package, `${configDir}` in `baseUrl`, and `references` now build. One the CLI can't read is reported as `tsconfig.json`.
104
+ - A `tsconfig.json` or `package.json` with a UTF-8 byte order mark is read.
105
+ - A runtime that can't be started is reported and exits 1, not an unhandled error.
106
+ - A config that fails to load is told whether it can't be parsed or its own code threw, on Node and Bun alike; a leftover `webpack` hook gets one message.
107
+ - On Bun, a `.env` value Bun expanded is no longer passed to the bot as a shell variable, so `start --dev` picks up edits to it.
108
+ - `start --dev` runs reloads one at a time, and with `bundleDependencies` no longer runs packages a previous `build` packed.
109
+ - Every `meocord` command prints its help after a usage error.
110
+ - A new app from `meocord create` starts on the latest stable releases of its tools that are at least a day old, such as vitest 5.0.4, eslint 10.12.0 and typescript-eslint 8.71.2; TypeScript stays at 6.0.3, the newest typescript-eslint supports.
111
+ - The package lists `discord-bot-framework` and `discord-framework` among its npm keywords.
112
+ - **Testing**
113
+ - `createMockFn`'s `mock.results` follows call order for a call that calls the mock again inside it.
114
+ - Without strict mocks, a mock channel without a server reads its permissions as `undefined` with a warning, where `permissionsFor()` threw.
115
+ - A mentioned channel or role in a mock message is a full mock of the message's server.
116
+ - `HandlerRegistry.messageHelp()` lists only controllers' message commands from the start.
117
+ - The startup warning about a handler on a class that isn't a controller covers every class the app makes, in the testing module too.
118
+ - Default-mode warnings stay quiet when MeoCord itself reads a mock to answer, such as a mock's `guildId` for the reply or a DM channel MeoCord sends a cooldown DM through.
119
+ - **Documentation**
120
+ - `Jsonified` and `ShardContext.call` say an object arrives with its own fields only: JSON leaves a class's getters out. Return a plain object, or give the class a `toJSON`.
121
+ - `sourceMappedStacks` says what it maps under Bun: `error.stack`, and so every trace the `Logger` prints, not what Bun prints itself.
122
+ - `ExecutionContext.getMessage()` says it reads an event's first argument when that is a message: for `@On('messageUpdate')`, the message as it was before the edit. A reaction event returns `undefined`.
123
+ - `createMockGuild()` says what the @everyone role may do in each mode: nothing by default, and a new server's permissions under `useStrictMocks()`.
124
+ - `MessageParamTypes`' `role` says an optional `role` param that another optional param follows takes a mention or an ID, not a role's name. Ask for a mention or an ID, or move the role param to the end.
125
+ - `resolveRoute`'s `dm` says dispatch answers a server-only command in a DM with "This command works in a server only." when the message used a prefix or a mention, and with nothing otherwise.
126
+
127
+ ### Security
128
+
129
+ - **A handler a subclass overrides keeps its base method's guards and other method-level stages** ([GHSA-r9qr-grw6-8jpx](https://github.com/meocord/meocord/security/advisories/GHSA-r9qr-grw6-8jpx)). In 4.1.0 to 4.2.2, a controller or service subclass that overrode a decorated handler method kept the base method's route but lost its method-level stages. A base `@UseGuard` didn't run for the subclass's route, so the handler ran for users the guard was meant to refuse, and the base method's `@Cooldown`, `@Defer`, `@UseInterceptor`, `@UseFilter`, `@UsePipe` and `@Validate` didn't apply either. This happened whether or not the override had decorators of its own. In 4.0.x, the override's own body ran without the base guard, though a `super` call into the base method still ran it. Class-level and global stages were never affected.
130
+ 4.2.3 runs the base method's stages for an override, base first, with the override's own added after them, on dispatch and on a direct call. Only a bot that extends a controller or service and overrides one of its decorated handlers is affected. Upgrade; until then, repeat the base method's decorators on the override, or move the guard to the base class with a class-level `@UseGuard`, which subclasses inherit.
131
+ - **Other behaviour the fix brings.** An override's own `@UseTheme` goes over the base method's, and its own `@Validate` or `@Defer` takes the place of the base method's; when the override calls `super`, each guard still runs once per call. A direct call to an `@On` or `@Once` handler runs its guards for the `'event'` context type, as dispatch does, so a guard's `types` decide the same way for both. A handler a service declares runs the class-level guards of the classes it extends on a direct call, as a controller's does.
132
+
133
+ ## 4.2.2
134
+
135
+ ### Patch Changes
136
+
137
+ - [#658](https://github.com/meocord/meocord/pull/658) [`e67cd51`](https://github.com/meocord/meocord/commit/e67cd51a6fdef4a21f1db6541e54b2bb421fbeb1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A button, select menu or message modal whose guard returns `false` is now acknowledged invisibly, with or without `@Defer`, so the user no longer sees "This interaction failed". The call still reports `ran: false`, and observers still see the outcome `'denied'`. A command, and a modal opened by a command, are left as they were: their only acknowledgement is a reply of their own.
138
+
139
+ A test that expects `getResponse(interaction).calls` to be `[]` after a denied click now sees `['deferUpdate']`, as a test of a handler with `@Defer` already did.
140
+
141
+ A guard that answers the click itself before returning `false` should await that answer. A reply still on its way when the guard returns races the acknowledgement, and Discord refuses whichever comes second, possibly the reply.
142
+
143
+ An app with both a route and a collector for one customId, where the guard returns `false` so the collector answers the click, should leave the click to one of them. Otherwise the two acknowledgements race, and the collector's can fail with "already acknowledged".
144
+
145
+ - [#653](https://github.com/meocord/meocord/pull/653) [`fcd7cbf`](https://github.com/meocord/meocord/commit/fcd7cbf703618541ff3f42497254cfa33b729939) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `warnUnanswered` now names an `@Autocomplete` handler that returns without calling `interaction.respond()`, as it names any other handler that leaves its interaction unanswered. The user sees the autocomplete options fail to load in that case. The warning is shown once per handler: under `meocord start --dev`, and in tests whose app sets `warnUnanswered: true`. `@MeoCord({ warnUnanswered: false })` turns it off. Nothing about the answer itself changes.
146
+
147
+ - [#656](https://github.com/meocord/meocord/pull/656) [`b951297`](https://github.com/meocord/meocord/commit/b9512974067160eddb0029a69e52ff228ba36e76) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `bindTheme`'s documentation now says that a bound function runs in the whole theme of the call that bound it, the server's and user's themes included. Every later call, such as another member's click on a collector, is answered in the starting member's theme. Its example comment said "the handler's theme", which left that out. Nothing about the behaviour changes.
148
+
149
+ - [#652](https://github.com/meocord/meocord/pull/652) [`f8369b6`](https://github.com/meocord/meocord/commit/f8369b6e556ab317331ca70a14a093a0dbc4af2d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A catalog group whose keys include `other`, such as `reasons: { spam: 'Spam', other: 'Other' }`, is read as a group, as the types and `expectCompleteCatalog` read it. Reading the group's own key, `t('reasons')`, shows the key, and in development logs the warning, as any other group does, where it showed the `other` text. Only untyped code, such as code reading a JSON catalog, can make that call; to show the text, read `t('reasons.other')`. A plural is an object whose every key is a plural category, and a translation's plural without an `other` form still falls back to the next catalog.
150
+
151
+ - [#659](https://github.com/meocord/meocord/pull/659) [`e9d25a4`](https://github.com/meocord/meocord/commit/e9d25a4bd27bf1e3adaa0506c1b165e8fe6ff20c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - The built-in `help` reply over 2,000 characters is now split into messages Discord shows as written:
152
+
153
+ - A line longer than a message is cut after the last whole character that fits. An emoji, a flag, a joined emoji or a letter with its accent is no longer split across two messages.
154
+ - A message that would be only whitespace isn't sent. Discord refused it as empty, and the rest of the reply was lost with it.
155
+
156
+ A help text with no long line and no blank stretch at a cut is split as before.
157
+
158
+ - [#665](https://github.com/meocord/meocord/pull/665) [`1a224e8`](https://github.com/meocord/meocord/commit/1a224e89d747b1dc0a29858f5bae8969d8657091) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under `useStrictMocks()`, a mock interaction is marked `replied`, `deferred` or `responded` once its answer resolves, as discord.js marks it after Discord takes the answer, rather than when the answer is called. Code that doesn't await an answer now fails in a test as it would on a bot:
159
+
160
+ - an `editReply()`, `followUp()` or `deleteReply()` made before the first answer resolves rejects with `InteractionNotReplied`;
161
+ - a second answer sent while the first is in flight passes discord.js's check and is refused with Discord's 40060, unless the first one failed;
162
+ - `replied`, `deferred`, `responded` and `ephemeral` read in the same run as an un-awaited answer are as before it.
163
+
164
+ This catches overlaps within one synchronous run of the test's code. A mock answer resolves on the next microtask, so an answer still in flight across an `await`, as a guard's `void interaction.reply()` followed by `return false` is on a bot, isn't caught.
165
+
166
+ In both modes, an answer that fails no longer undoes an answer made after it. A test could make the first of two un-awaited replies reject, and the interaction then read as unanswered though the second reply went through; it now reads as answered by the second. A failed answer's change is undone once every change after it is undone too, so an interaction whose answers all fail still reads as unanswered.
167
+
168
+ In default mode the flags are still set at the call. The first time an interaction does what a bot would refuse, an edit or a second answer while its first is in flight, a warning says what the bot would do. In the next major version (5.0), every mock interaction marks its answers when they resolve, as strict mocks do now.
169
+
170
+ - [#663](https://github.com/meocord/meocord/pull/663) [`3894f77`](https://github.com/meocord/meocord/commit/3894f77b099bc2bb82bed37b43944f9e6dc09feb) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under `useStrictMocks()`, a mock interaction's `ephemeral` is `null` until `reply()` or `deferReply()` decides it, as discord.js has it; `update()` and `deferUpdate()` leave it `null`. In default mode it still reads `false` before an answer, and a test reading it there logs a warning once. MeoCord's own reads while it answers log nothing. The next major version (5.0) reads `null` without the call.
171
+
172
+ - [#651](https://github.com/meocord/meocord/pull/651) [`8a56464`](https://github.com/meocord/meocord/commit/8a56464a42c47bddc3959bddc0748a710f656ffd) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock guild's `emojis`, `stickers`, `scheduledEvents`, `autoModerationRules` and `invites` are managers, in both modes, as on a message's guild. Before, calling `create()`, `fetch()` or `cache.get()` on one threw a `TypeError`.
173
+
174
+ - `create()` resolves to a mock emoji, sticker, scheduled event or AutoMod rule in the guild, and caches it, as discord.js does.
175
+ - `invites.create(channel)` resolves to an invite for that channel in the guild, keyed by its `code`, and caches nothing, as discord.js does.
176
+ - `fetch(id)` resolves to the cached item, or makes and caches one; a list fetch resolves to an empty collection.
177
+ - Each item is named, and what discord.js formats from it computes: an emoji's mention (`String(emoji)`) and `identifier`, and a scheduled event's and an invite's `url`.
178
+
179
+ - [#657](https://github.com/meocord/meocord/pull/657) [`8d1d3fc`](https://github.com/meocord/meocord/commit/8d1d3fc6eb0d4f59139ffc3033b1c416a507f804) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `MockedFunction`'s and `MockInstance`'s documentation says what their types check: calling a mock is checked against the function it mocks, and the values its mock API takes and records, such as `mockResolvedValue`'s and each of `mock.calls`' arguments, are not, for any function. A partial fake such as `guild.members.fetch.mockResolvedValue({ id })` type-checks. Types are unchanged.
180
+
181
+ - [#654](https://github.com/meocord/meocord/pull/654) [`1cc5a4f`](https://github.com/meocord/meocord/commit/1cc5a4fd2243ff00be9ae3d89b389f11df3b337c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Wherever MeoCord takes a class and is given something else, it now refuses the value and names where it was given, instead of crashing with an empty `TypeError` or reporting "This is a bug in MeoCord". TypeScript already rejects these values, so the refusal matters for plain JavaScript, `any` and casts.
182
+
183
+ - **`@MeoCord`'s `presenter`, `cooldownStore`, `controllers` and `services`** refuse an object or a function that isn't a class where the app is declared, for example `App: @MeoCord({ presenter }) takes a class implementing ResponsePresenter, not an object.`
184
+ - **A service listed by a string or symbol token** that no provider provides is refused as the app is created, naming the token.
185
+ - **Stage entries** given to `@UseGuard`, `@UseInterceptor`, `@UseFilter`, `@UsePipe` and `@MeoCord({ guards, interceptors, filters })` refuse an arrow function, a method, an async function or a generator function: "a function is not a class". A `function` declaration still works as a class.
186
+ - **`@Catch`** warns about an arrow function, as it already does for any other value that isn't a class, and treats it as matching no error. Before this fix, every call whose error reached that filter rejected with a `TypeError` from `instanceof`. A bound error class still matches.
187
+ - **A testing module** refuses a controller or an observer that isn't a class, and reports the refusal together with the other startup errors. After `reportAllStartupErrors()`, `fromApp()` on an app whose `@MeoCord` was refused throws the reason, not "is not a @MeoCord app".
188
+
189
+ A bot that started before starts unchanged.
190
+
3
191
  ## 4.2.1
4
192
 
5
193
  ### Patch Changes