meocord 4.1.2 → 4.2.1
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/CHANGELOG.md +362 -0
- package/THIRD_PARTY_NOTICES.md +14 -13
- package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-XKMlcUZz.cjs} +669 -185
- package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-BZgJyfZS.cjs} +218 -75
- package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
- package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CgfPQrNl.cjs} +374 -132
- package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-BUAR1WYG.cjs} +21 -7
- package/dist/cjs/common/index.cjs +4 -4
- package/dist/cjs/core/index.cjs +365 -310
- package/dist/cjs/decorator/index.cjs +85 -61
- package/dist/cjs/testing/index.cjs +2236 -496
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/README.md.template +3 -1
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
- package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
- package/dist/esm/bin/generator.js +4 -5
- package/dist/esm/bin/helper/filter-generator.helper.js +4 -5
- package/dist/esm/bin/helper/guard-generator.helper.js +4 -5
- package/dist/esm/bin/helper/interceptor-generator.helper.js +4 -5
- package/dist/esm/bin/helper/observer-generator.helper.js +4 -5
- package/dist/esm/bin/helper/pipe-generator.helper.js +4 -5
- package/dist/esm/bin/helper/service-generator.helper.js +4 -5
- package/dist/esm/bin/meocord.js +84 -25
- package/dist/esm/build/load-config.pre-entry.js +17 -4
- package/dist/esm/build/rsbuild-config.js +16 -12
- package/dist/esm/build/typed-externals.js +36 -0
- package/dist/esm/common/cooldown-store.js +10 -2
- package/dist/esm/common/decorator.js +6 -6
- package/dist/esm/common/deprecation.js +2 -2
- package/dist/esm/common/errors.js +27 -2
- package/dist/esm/common/execution-context.js +1 -1
- package/dist/esm/common/meocord-text.js +11 -8
- package/dist/esm/common/metadata.js +1 -1
- package/dist/esm/common/response/call-order.js +9 -4
- package/dist/esm/common/response/presenter.js +33 -6
- package/dist/esm/common/response/response-state.js +84 -10
- package/dist/esm/common/route.js +21 -5
- package/dist/esm/common/sharded-cooldown-store.js +1 -1
- package/dist/esm/common/translate-error.js +1 -1
- package/dist/esm/common/translator.js +37 -6
- package/dist/esm/core/command-conflicts.js +34 -34
- package/dist/esm/core/command-registration.js +93 -24
- package/dist/esm/core/command-routes.js +2 -2
- package/dist/esm/core/component-routes.js +74 -31
- package/dist/esm/core/cooldown-runner.js +29 -8
- package/dist/esm/core/dispatcher.js +51 -7
- package/dist/esm/core/fallback.js +20 -11
- package/dist/esm/core/filter-runner.js +2 -2
- package/dist/esm/core/guard-runner.js +16 -3
- package/dist/esm/core/handler-input.js +4 -3
- package/dist/esm/core/handler-pipeline.js +27 -14
- package/dist/esm/core/handler-registry.js +1 -1
- package/dist/esm/core/interceptor-runner.js +2 -2
- package/dist/esm/core/lifecycle-hooks.js +134 -47
- package/dist/esm/core/meocord-factory.js +241 -179
- package/dist/esm/core/meocord.app.js +116 -75
- package/dist/esm/core/message-routes.js +4 -3
- package/dist/esm/core/observer-runner.js +4 -3
- package/dist/esm/core/providers.js +45 -23
- package/dist/esm/core/route-specificity.js +4 -4
- package/dist/esm/core/running-calls.js +43 -0
- package/dist/esm/core/scalar-types.js +15 -3
- package/dist/esm/core/shard-context.js +6 -7
- package/dist/esm/core/shard-manager.js +16 -10
- package/dist/esm/core/startup-roots.js +23 -4
- package/dist/esm/core/theme-resolvers.js +4 -1
- package/dist/esm/core/theme-runtime.js +2 -2
- package/dist/esm/core/theme-scope.js +3 -1
- package/dist/esm/core/theme-validation.js +17 -10
- package/dist/esm/decorator/app.decorator.js +12 -7
- package/dist/esm/decorator/command-builder.decorator.js +4 -4
- package/dist/esm/decorator/controller-class.decorator.js +9 -4
- package/dist/esm/decorator/controller.decorator.js +162 -83
- package/dist/esm/decorator/cooldown.decorator.js +20 -3
- package/dist/esm/decorator/defer.decorator.js +4 -4
- package/dist/esm/decorator/event.decorator.js +5 -5
- package/dist/esm/decorator/filter.decorator.js +7 -7
- package/dist/esm/decorator/guard.decorator.js +11 -10
- package/dist/esm/decorator/inject.decorator.js +4 -4
- package/dist/esm/decorator/interceptor.decorator.js +7 -7
- package/dist/esm/decorator/observer.decorator.js +4 -4
- package/dist/esm/decorator/service.decorator.js +4 -4
- package/dist/esm/decorator/theme.decorator.js +3 -3
- package/dist/esm/decorator/validation.decorator.js +10 -10
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +27 -11
- package/dist/esm/testing/cooldown-store-suite.js +85 -15
- package/dist/esm/testing/discord-defaults.js +249 -21
- package/dist/esm/testing/index.js +4 -2
- package/dist/esm/testing/inspect-handler.js +20 -3
- package/dist/esm/testing/meocord-testing-module.js +352 -205
- package/dist/esm/testing/mock-fn.js +173 -22
- package/dist/esm/testing/mock-interaction.js +1214 -209
- package/dist/esm/testing/response.js +1 -1
- package/dist/esm/testing/routing.js +39 -14
- package/dist/esm/testing/snowflake.js +9 -2
- package/dist/esm/testing/startup-errors.js +26 -0
- package/dist/esm/testing/strict-mocks.js +65 -0
- package/dist/esm/testing/theme.js +1 -1
- package/dist/esm/util/bundle-entry.util.js +2 -1
- package/dist/esm/util/common.util.js +47 -19
- package/dist/esm/util/dev-runner.util.js +8 -8
- package/dist/esm/util/generator-cli.util.js +4 -5
- package/dist/esm/util/interaction.util.js +3 -3
- package/dist/esm/util/meocord-config-loader.util.js +10 -2
- package/dist/esm/util/meocord-config-validation.util.js +4 -5
- package/dist/esm/util/meocord-source-config.util.js +65 -20
- package/dist/esm/util/metadata-keys.js +1 -0
- package/dist/esm/util/method-wrapper.util.js +16 -0
- package/dist/esm/util/platform.util.js +10 -10
- package/dist/esm/util/refusal.util.js +148 -8
- package/dist/esm/util/shard-process.util.js +4 -1
- package/dist/esm/util/sharding-mode.util.js +5 -7
- package/dist/esm/util/shared-state.util.js +21 -0
- package/dist/esm/util/tsconfig-paths.util.js +29 -0
- package/dist/esm/util/tsconfig.util.js +10 -10
- package/dist/esm/util/value.util.js +12 -1
- package/dist/types/common/index.d.cts +8 -8
- package/dist/types/common/index.d.ts +8 -8
- package/dist/types/core/index.d.cts +10 -7
- package/dist/types/core/index.d.ts +10 -7
- package/dist/types/decorator/index.d.cts +31 -29
- package/dist/types/decorator/index.d.ts +31 -29
- package/dist/types/{index-D7n0FBMm.d.ts → index-DKJclIi-.d.ts} +107 -36
- package/dist/types/{index-DEP0NYrU.d.cts → index-Dlf0ZZtS.d.cts} +107 -36
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-C-aMI7Sl.d.cts → route-Bh1U13yy.d.cts} +8 -4
- package/dist/types/{route-C-aMI7Sl.d.ts → route-Bh1U13yy.d.ts} +8 -4
- package/dist/types/testing/index.d.cts +406 -70
- package/dist/types/testing/index.d.ts +406 -70
- package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
- package/dist/types/{translator-J6tIUfdp.d.ts → translator-Bg7hwxDF.d.cts} +21 -8
- package/dist/types/{translator-B4mc1lzJ.d.cts → translator-CeP4bCzp.d.ts} +21 -8
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,367 @@
|
|
|
1
1
|
# meocord
|
|
2
2
|
|
|
3
|
+
## 4.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#577](https://github.com/meocord/meocord/pull/577) [`2b4be88`](https://github.com/meocord/meocord/commit/2b4be88ac101c24e1da1e6cf290e547f76c0a0a1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Catalog checks read a catalog's own keys and shapes, and a refused builder says what is wrong with a name:
|
|
8
|
+
|
|
9
|
+
- `expectCompleteCatalog` reads a group with an `other` key, such as `reasons: { spam, offTopic, other }`, as a group, as the translator's types do. It no longer fails a correct catalog for "lacking" plural forms, and it reports a key such a group misses in a locale.
|
|
10
|
+
- `expectCompleteCatalog` reads only a catalog's own keys, so a missing key named `constructor` or `toString` is reported. It also reports a translation of another shape than the default's, a text for a plural or the reverse, as `n should be a plural, as the default is`.
|
|
11
|
+
- A key with a `.` in its name, such as `'ban.done'`, is never found, because a lookup reads it as a path. `createTranslator` now warns about each one, naming the locale and the key, and `MessageKey`, `StringMessageKey` and `LocalizationKey` leave it out, so using one fails to compile. Nest it as a group: `ban: { done: … }`.
|
|
12
|
+
- `LocaleCatalog<C>` takes a `meocord` group with your translations of MeoCord's own texts, as `createTranslator` does.
|
|
13
|
+
- A builder refused for a name gives Discord's rule for one: letters, numbers, `-` and `_`, lowercase where the script has case, without spaces, up to 32 characters, in every locale.
|
|
14
|
+
|
|
15
|
+
A catalog test that passed with a gap of these kinds can now fail and name it. See [Localisation](https://meocord.dev/docs/4.2/localisation).
|
|
16
|
+
|
|
17
|
+
- [#620](https://github.com/meocord/meocord/pull/620) [`4e395ac`](https://github.com/meocord/meocord/commit/4e395acf4659e30653fe9107cc24598efdc16767) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A testing module asked to `dispatch`, `invoke` or `emit` after `close()` now warns once that it is closed and its services are shut down, or shutting down, including while `close()` is still running. It still runs the call, as before. A misplaced `close()`, such as one in `afterEach` that runs before a test's last call, shows instead of passing quietly on that state. See [Testing](https://meocord.dev/docs/4.2/testing).
|
|
18
|
+
|
|
19
|
+
- [#605](https://github.com/meocord/meocord/pull/605) [`33d724b`](https://github.com/meocord/meocord/commit/33d724b13fc3a3d4e51be89c1ded7288cdf0009d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Command` warns, naming the handler, about a component pattern no customId can match: one that is empty, or whose shortest customId is over Discord's 100 characters. Such a handler never runs; the bot still starts. `route('').build()` throws a RangeError, as it does for an id over 100 characters, rather than returning an empty customId Discord refuses.
|
|
20
|
+
|
|
21
|
+
- [#595](https://github.com/meocord/meocord/pull/595) [`cdb8dcf`](https://github.com/meocord/meocord/commit/cdb8dcf51fa6a9b16eb98eb73b94b912e0108ffe) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `meocord start --dev` checks a saved `meocord.config.ts` as startup does: a config with options of the wrong type, or one that fails to load, is reported once, with every problem, and the running bot is left as it is, rather than starting with that config and failing later. A config that exports only named values is refused with "it must export an object as its default export", naming what it exports, instead of a warning for each export. A config that throws as it loads, such as for a variable it requires, is reported with the line in `meocord.config.ts` it threw at, and a built bot whose config throws says to check the environment it reads, rather than to rebuild.
|
|
22
|
+
|
|
23
|
+
- [#599](https://github.com/meocord/meocord/pull/599) [`e3c727a`](https://github.com/meocord/meocord/commit/e3c727a2d82d2fb96a05df7268c1b1af2373b6a2) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A controller listed twice in `@MeoCord({ controllers })` or `MeoCordTestingModule.create({ controllers })` is one controller: its `@MessageHandler()` listeners run once per message, and its reaction handlers once per reaction, where they ran twice. Commands and components were already routed once.
|
|
24
|
+
|
|
25
|
+
- [#598](https://github.com/meocord/meocord/pull/598) [`585c076`](https://github.com/meocord/meocord/commit/585c076dedcc91bef09198de93538457d82cf625) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A cooldown's `by` may return any string, one holding half of an emoji included, such as text cut short with `slice`. Such a value made the call fail with "URIError: URI malformed" before the handler ran; it now counts under a key of its own. Every value that worked before keeps its key, so running cooldowns carry on across the upgrade.
|
|
26
|
+
|
|
27
|
+
- [#598](https://github.com/meocord/meocord/pull/598) [`585c076`](https://github.com/meocord/meocord/commit/585c076dedcc91bef09198de93538457d82cf625) Thanks [@l7aromeo](https://github.com/l7aromeo)! - With `dmOnCooldown`, a cooldown counted per server, per channel or globally tells each author it refuses once per wait, as `per: 'user'` already did. Only the first author refused in a wait was told, and the rest were refused without a word.
|
|
28
|
+
|
|
29
|
+
- [#579](https://github.com/meocord/meocord/pull/579) [`4aba54d`](https://github.com/meocord/meocord/commit/4aba54dbde09e78c40d9bf9338eee377930f3760) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Cooldown` now warns, naming the handler, when it is given a `bypass` that is not a function, such as `true`, which fails every call it counts, or a `seconds` that is not a number, such as `'5'`, which it reads as one. Both get past the types only from JavaScript or through a cast. They still decorate as before, and the next major version (5.0) refuses them; give `bypass` a function and `seconds` a number to silence the warning. A `bypass` of `false` or `null` still means none.
|
|
30
|
+
|
|
31
|
+
- [#581](https://github.com/meocord/meocord/pull/581) [`150e174`](https://github.com/meocord/meocord/commit/150e174f39787e3440605693aa99d56fc2a801c4) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `testCooldownStore` now catches three ways a store can break the cooldown contract:
|
|
32
|
+
|
|
33
|
+
- a `release` that frees the key's other calls too, which resets a user's cooldown whenever `@Cooldown` gives back a call;
|
|
34
|
+
- a batch refusal that names the first cooldown to refuse rather than the longest wait, which tells users to retry too soon;
|
|
35
|
+
- a wait counted from the oldest call in the window, rather than from the oldest of the newest `uses` calls, after a limit is lowered.
|
|
36
|
+
|
|
37
|
+
A store that keeps the contract passes as before. A store that gets one of these wrong now fails its own suite run, naming the case. See [Cooldown stores](https://meocord.dev/docs/4.2/recipes/cooldown-stores).
|
|
38
|
+
|
|
39
|
+
- [#588](https://github.com/meocord/meocord/pull/588) [`27b03a5`](https://github.com/meocord/meocord/commit/27b03a58d1b22a3adaf802f67a6e2993f1fe6673) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `meocord create` folds accented letters into the app's folder and package name instead of dropping them: "Café Bot" gives `cafe-bot`, where it gave `caf-bot`, and "Straße" gives `strasse`. A name with no Latin letters or digits, such as one written only in another script, is refused with a message that says it needs Latin letters or digits.
|
|
40
|
+
|
|
41
|
+
- [#612](https://github.com/meocord/meocord/pull/612) [`9cb5e9e`](https://github.com/meocord/meocord/commit/9cb5e9e3d563a0919c62855c02dd3ee4d7692b0f) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `respond()` keeps track of which message is the answer, so it no longer edits or deletes a reply that is gone, which Discord refused with 10008 (Unknown Message):
|
|
42
|
+
|
|
43
|
+
- After `delete()`, `send()`, `edit()`, an error answer and `followUp()` send a follow-up, which becomes the answer that later `send()`, `edit()` and `delete()` act on. A follow-up that takes the place of an edit is private when the deleted answer was, unless the payload sets `flags`. `message` is `undefined` until something is sent, and a second `delete()` with nothing sent since throws.
|
|
44
|
+
- After a private `followUp()` on a public deferral, which deletes the deferral, the follow-up is the answer: `send()` and `edit()` edit it, `delete()` deletes it, and `message` is it.
|
|
45
|
+
|
|
46
|
+
A test that expected `editReply` in these sequences now sees a `followUp`, or an edit of the follow-up by its id.
|
|
47
|
+
|
|
48
|
+
- [#571](https://github.com/meocord/meocord/pull/571) [`e9fc8bc`](https://github.com/meocord/meocord/commit/e9fc8bcd331271ee564f1467daf06cae363b079e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `meocord start --dev`, `register --dev` and `build --dev` run as development whatever `NODE_ENV` the shell holds, as their help and the [CLI guide](https://meocord.dev/docs/4.2/cli) say. With `NODE_ENV=production` set in the shell, `start --dev` loaded the config from the production `.env` files, so a token kept only in `.env.development` was reported missing, and `register --dev` registered the commands as production does rather than to `commands.developmentGuild`. `--prod` still keeps a `NODE_ENV` the shell sets.
|
|
49
|
+
|
|
50
|
+
- [#583](https://github.com/meocord/meocord/pull/583) [`0b4be9c`](https://github.com/meocord/meocord/commit/0b4be9cecf42b7798fb46d2c96a3e9fa543020e4) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A direct call on a controller, `module.get(Controller).method(interaction)`, runs its guards in the testing module that made the instance. With two modules compiled from one controller, a direct call on the first ran the second module's guard stubs, services and container, since the last compiled module was the only one recorded on the class. A bot has one container, so nothing changes there. See [Testing](https://meocord.dev/docs/4.2/testing).
|
|
51
|
+
|
|
52
|
+
- [#585](https://github.com/meocord/meocord/pull/585) [`5cfb77d`](https://github.com/meocord/meocord/commit/5cfb77dca37858f6be1dd8261f2c67ea7f38e7e8) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A built bot now loads its packages after its config, and so after `.env`: a package that reads `process.env` as it loads, in a build without `bundleDependencies`, gets the values `.env` sets, as a self-contained build already gave it. A package kept out of the bundle loads where the code importing it runs: an ESM `import` is a dynamic `import()`, and a CommonJS `require` stays a `require` at its call site. A package listed in `externals` that a dependency requires inside a `try` therefore no longer stops the bot at startup when it is missing; an ESM import of one still does.
|
|
53
|
+
|
|
54
|
+
A self-contained build started on another platform stops with MeoCord's message naming both platforms, before any native addon loads, instead of the addon's own error. The message is now printed before the bot's logger is set up, as a plain `[ERROR] [MeoCord]` line.
|
|
55
|
+
|
|
56
|
+
A build without `bundleDependencies` removes the `node_modules`, `package.json` and `meocord.platform.json` a previous self-contained build left in `dist`, so it no longer runs packages from that stale copy. An external named as a file inside a package, such as `externals: ['lodash/fp.js']`, now packs its package.
|
|
57
|
+
|
|
58
|
+
- [#584](https://github.com/meocord/meocord/pull/584) [`f2a9bf4`](https://github.com/meocord/meocord/commit/f2a9bf417f5ff38142f1f68a102720fdbcc88149) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `emit`'s JSDoc, and `EmitResult.ran`'s, now say what a test sees from a guard's `GuardDeniedError` and a handler's `UserError`: they reject `emit` as any error does, because the bot's event fallback doesn't run in a testing module. On a bot, that fallback skips a refused event, and logs a `UserError`, answering it as a reply when the event carries a message. Check them with `rejects`. See [Invoke and dispatch](https://meocord.dev/docs/4.2/invoke-and-dispatch).
|
|
59
|
+
|
|
60
|
+
- [#600](https://github.com/meocord/meocord/pull/600) [`4729b10`](https://github.com/meocord/meocord/commit/4729b10e26dd5948c1e2f60f4a5cb9183b6f8fdc) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A primary entry point command is now registered only globally, as Discord requires. Discord refuses a guild update that holds one (400, code 50222), and before, that refusal took every other command in the update with it.
|
|
61
|
+
|
|
62
|
+
- A production run sends the entry point in the global update, even with `commands.guilds` set, and ignores a `guilds` restriction on its builder, with a warning. With `commands.guilds`, that global update holds the entry point and replaces any global commands left from an earlier configuration.
|
|
63
|
+
- A development run with `developmentGuild`, and `meocord register --guild`, leave the entry point out of the guild update with a warning, so the rest of the commands register. They send nothing global, as a global update from such a run would delete the application's other global commands. A production run or `meocord register` registers the entry point.
|
|
64
|
+
|
|
65
|
+
See [Slash commands](https://meocord.dev/docs/4.2/slash-commands).
|
|
66
|
+
|
|
67
|
+
- [#575](https://github.com/meocord/meocord/pull/575) [`f8c2a51`](https://github.com/meocord/meocord/commit/f8c2a516eeee194be0515ff751a7fb9e184b4b1a) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Help rendered from your catalog's `meocord.help` texts now keeps its lists apart in every language, on Node and Bun alike: params join with `·` and aliases with `, `, as in MeoCord's English. Before, zh-CN ran them together, ja and ru joined them with a space, and de joined the last two with "und". MeoCord's own English help is unchanged. A test that pins translated help sees the new separators. See [Message commands](https://meocord.dev/docs/4.2/message-commands).
|
|
68
|
+
|
|
69
|
+
- [#608](https://github.com/meocord/meocord/pull/608) [`772dbd4`](https://github.com/meocord/meocord/commit/772dbd49f6dfb3753771d9748d8bc3e17f872225) Thanks [@l7aromeo](https://github.com/l7aromeo)! - In a built bot, `import.meta.url` names the running bundle in `dist`, as `import.meta.dirname` and `import.meta.filename` already do. It named the source file on the machine that built the bot, so code finding its files from it worked only there, and the bundle carried the build machine's source path. `new URL('./file', import.meta.url)` assets keep resolving beside the bundle, and `import.meta.env.MODE` is still written in.
|
|
70
|
+
|
|
71
|
+
- [#589](https://github.com/meocord/meocord/pull/589) [`921ff91`](https://github.com/meocord/meocord/commit/921ff917ef602da316a83c44cc68b759a69ce408) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `invoke` checks a handler gets the arguments it declares when it builds none for it, as for a reaction handler given its reaction without its `ReactionEvent`. The call used to reach the handler and fail inside it, with `Cannot destructure property 'user' of 'undefined'`. Now:
|
|
72
|
+
|
|
73
|
+
- under `useStrictMocks()`, `invoke` refuses the call before the handler runs, naming the fix: `Stars.star takes 2 arguments, and invoke was given 1: pass its ReactionEvent after the reaction, as { user, action }.`
|
|
74
|
+
- by default, `invoke` warns once with the same words and calls the handler as before, so a handler that never reads its second argument keeps running.
|
|
75
|
+
|
|
76
|
+
A handler behind `@UseGuard`, or behind `@Command`'s check, keeps the `length` and `name` it was declared with. See [Testing](https://meocord.dev/docs/4.2/testing).
|
|
77
|
+
|
|
78
|
+
- [#596](https://github.com/meocord/meocord/pull/596) [`90034c7`](https://github.com/meocord/meocord/commit/90034c74a0bed91253d5f908d2d7abc4cc85585e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Every service the app makes now gets its lifecycle hooks once, in the bot and in `MeoCordTestingModule`:
|
|
79
|
+
|
|
80
|
+
- A service that only a guard, interceptor, filter, pipe or the presenter injects is made as the bot comes online, and its `onReady` and `onShutdown` run, as `@Service` documents. Before, it never got its hooks, and it was made anew for every call that reached its stage. It is now the one shared instance `@Service` promises. A constructor that throws in such a service is reported as the bot comes online instead of failing each call. A class that injects the call's `ExecutionContext` is still made for each call, without hooks. If you worked around the missing hooks by calling an init method from the guard, or by listing the service in `services`, you can remove that.
|
|
81
|
+
- One instance that two tokens reach, such as a factory alias of a service or one value provided twice, runs its `onReady` and `onShutdown` once instead of twice.
|
|
82
|
+
|
|
83
|
+
- [#610](https://github.com/meocord/meocord/pull/610) [`44a9a8e`](https://github.com/meocord/meocord/commit/44a9a8ef13fa70658f984fa09b38ee9e90e7e345) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Command` and `route()` warn about a customId pattern with a brace pair that isn't a param, such as `profile/{café}`: a param's name is ASCII letters, digits and `_`, so the pair is matched as literal text and a click on `profile/123` never reaches the handler. The pattern matches as before; rename the param, such as to `{cafe}`, to capture the segment.
|
|
84
|
+
|
|
85
|
+
- [#573](https://github.com/meocord/meocord/pull/573) [`c88e7a2`](https://github.com/meocord/meocord/commit/c88e7a26fff4febb60c30d99466b1ede6caf8f73) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `t.localizations()` keeps the default locale's wording where Discord would show another locale's. Discord shows en-US users the en-GB value, en-GB users the en-US one, and es-419 users the es-ES one when their own locale has none. So when your default is en-US, en-GB or es-419 and its partner has a translation, the result now includes the default's own message too. A bot with such a pair re-registers its commands once, and a test that pins `t.localizations()` for one sees the default's entry.
|
|
86
|
+
|
|
87
|
+
An empty translation, `''`, which translation tools export for an untranslated string, now counts as missing:
|
|
88
|
+
|
|
89
|
+
- `t.localizations()` leaves it out, so Discord shows the default there, and a bot with one now starts instead of stopping at startup;
|
|
90
|
+
- a reply falls back to a related locale or the default, where it was empty;
|
|
91
|
+
- `expectCompleteCatalog` reports it as missing.
|
|
92
|
+
|
|
93
|
+
See [Localisation](https://meocord.dev/docs/4.2/localisation).
|
|
94
|
+
|
|
95
|
+
- [#591](https://github.com/meocord/meocord/pull/591) [`31760bf`](https://github.com/meocord/meocord/commit/31760bfbea3ec661a5e449bca2998b7271ab334f) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under `meocord start --dev` with process sharding (`sharding: { mode: 'process', development: true }`), a shard that can't log in is reported as a failed login again: watch mode says the bot could not log in and starts it again on the next change in `src` or `.env`, where it reported a plain exit with code 1. Each app now decides once, as it is created, whether its process is a shard, before discord.js's `ShardingManager` writes the variable that marks one into the manager's own process. Nothing to change in your bot.
|
|
96
|
+
|
|
97
|
+
- [#602](https://github.com/meocord/meocord/pull/602) [`85ff84a`](https://github.com/meocord/meocord/commit/85ff84a6661f5bc17ef47ef5935762ad6f429e60) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A `MemoryCooldownStore` that nothing uses any more is garbage-collected once its calls have expired. Its sweep timer held it for as long as the process ran, so a test run kept one store per testing module that dispatched a cooldown. The sweep now stops when it leaves the store empty and starts again on its next call; counting is unchanged.
|
|
98
|
+
|
|
99
|
+
- [#603](https://github.com/meocord/meocord/pull/603) [`eee2287`](https://github.com/meocord/meocord/commit/eee22875358f6086cc2f7588ea29f0e9114e59b2) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@MeoCord({ providers })` now warns about a provider for `ThemeCache` or `ExecutionContext`, tokens MeoCord binds itself: a `ThemeCache` provider silently replaced the app's theme cache. The bot still starts; the next major version (5.0) refuses such a provider. Remove it. A `Translator` provider without `i18n` stays supported, and `MeoCordTestingModule`'s providers are unchanged.
|
|
100
|
+
|
|
101
|
+
- [#609](https://github.com/meocord/meocord/pull/609) [`0ef1597`](https://github.com/meocord/meocord/commit/0ef15975dd0582b4992f843b0c17d472eacbcb20) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock thread's `createdTimestamp` and `createdAt` are read from its id, as another channel's are, in both modes. `createdTimestamp` read a stub and `createdAt` an Invalid Date.
|
|
102
|
+
|
|
103
|
+
Under `useStrictMocks()`, a user, server or channel made with a generated id was created when the mock was made, as a message and an interaction already were. That covers `createMockUser()`, `createMockGuild()`, `createMockChannel()` and an interaction's own user, so a test of an account-age check gets a new account. `SnowflakeUtil.timestampFrom(id)` still reads the id's own time.
|
|
104
|
+
|
|
105
|
+
In default mode their creation time is still their generated id's, a fixed day in 2025. Reading it warns once, saying to give the mock an id or a `createdTimestamp`, or to call `useStrictMocks()`. An id the test gives decides the time in both modes. The next major version (5.0) reads the time the mock was made without the call.
|
|
106
|
+
|
|
107
|
+
- [#594](https://github.com/meocord/meocord/pull/594) [`111acdd`](https://github.com/meocord/meocord/commit/111acdd4d356a2ae4869cb9370284ad35fabdbdd) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Mocks give the values discord.js always sets where they gave stubs without methods, and the warnings about placeholders name the right reader.
|
|
108
|
+
|
|
109
|
+
- **Calls that threw now work, in both modes.**
|
|
110
|
+
- `String(interaction)` on a slash command writes `/settings email address:a@b.c`, as discord.js does. An interaction made without `options` has an empty resolver.
|
|
111
|
+
- `message.mentions.has(user, { ignoreRepliedUser: true })` answers. `mentions.parsedUsers` and `mentions.crosspostedChannels` are Collections.
|
|
112
|
+
- These are empty collections and bitfields: `message.reactions`, `message.messageSnapshots`, a reaction's `users`, a member's and a user's `flags`, an interaction's `entitlements`, and a server's `presences`. A reaction's `users.fetch()` resolves an empty Collection.
|
|
113
|
+
- A manager's lookups that read discord.js's own cache, such as `guild.presences.resolve(id)` and a thread's `members.resolve(id)`, find what its `cache` holds, else `null`.
|
|
114
|
+
- So in default mode too, these fields hold discord.js's types instead of stubs. A test that relied on a stub's shape there sees the real value.
|
|
115
|
+
- **Under `useStrictMocks()`, data reads as discord.js gives it.**
|
|
116
|
+
- A reaction's `me` is `false`, `mentions.repliedUser` is `null`, and a modal's `message` is `null`.
|
|
117
|
+
- A message's `editedAt` comes from `editedTimestamp`. A member's `presence` is the one its server caches, else `null`. A server's `verified` comes from its `features`. `systemChannel` and a channel's `parent` are the channels their ids name, else `null`.
|
|
118
|
+
- A voice or stage channel's `full` is computed.
|
|
119
|
+
- A reaction made without a message has a whole one of its own, so a dispatched reaction is neither warned about nor fetched.
|
|
120
|
+
- **In default mode, values are unchanged.** Reading one of those values, or a voice channel's `full`, logs a warning once. The warning says what to set, or to call `useStrictMocks()`.
|
|
121
|
+
- When MeoCord's dispatcher reads a reaction's placeholder `partial`, the warning says so. It also says the reaction was fetched because of it, and to set `reaction.partial = false` and `reaction.message.partial = false`.
|
|
122
|
+
- `mentions.has()` reads `repliedUser` without a warning, since that placeholder matches no user.
|
|
123
|
+
- **Under `useStrictMocks()`, a test that asserted `reaction.fetch` or `message.fetch` was called during dispatch changes.** A whole mock reaction is no longer fetched. The next major version (5.0) reads these values as strict mocks do.
|
|
124
|
+
|
|
125
|
+
- [#597](https://github.com/meocord/meocord/pull/597) [`eef4c72`](https://github.com/meocord/meocord/commit/eef4c725f3ba5dc4ed0501251f1273c31f81fc14) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock autocomplete's focused option reads as Discord sends it: a string. `getFocused()`, `getFocused(true).value` and the option's entry in `options.data` now agree, and `data` marks it `focused`.
|
|
126
|
+
|
|
127
|
+
- With no value given, as before anything is typed, the focused option reads `''` instead of `null`, in both modes. A test that checked for `null` changes; discord.js's types rule `null` out.
|
|
128
|
+
- The focused option is now in `options.data`, as Discord sends it, so with no value given an autocomplete handler's params are `{ query: '' }` rather than `{}`, in both modes. A test asserting `toEqual({})` there changes.
|
|
129
|
+
- A number given for it reads as its digits under `useStrictMocks()`, keeping its option type. In default mode it stays the number and warns once when read. The next major version (5.0) gives the string without the call.
|
|
130
|
+
|
|
131
|
+
- [#617](https://github.com/meocord/meocord/pull/617) [`9e10a4e`](https://github.com/meocord/meocord/commit/9e10a4ee7b3a0b4f1aa15147c5f18158e98b4ff6) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock interaction holds each message it sends as it was sent, as Discord does:
|
|
132
|
+
|
|
133
|
+
- `followUp()` resolves to the follow-up it sent, with the content, embeds, components and flags that `fetchReply(id)` reads back, as discord.js resolves it to the message Discord returns. It used to resolve to a message holding only the id, with a stub `content` and flags of 0, so code reading whether a follow-up was private saw a different value than against Discord.
|
|
134
|
+
- `reply()`, `update()` and `followUp()` build what they send when they are called, so a builder changed afterwards no longer changes what `fetchReply()` reads. Reading a follow-up no longer uses up a generated id.
|
|
135
|
+
- Under `useStrictMocks()`, an answer whose components or embeds discord.js refuses to build, such as a button with no label, rejects at the call with discord.js's error and leaves the interaction as it was. In default mode it still resolves, and it warns once. The next major version (5.0) rejects it without the call.
|
|
136
|
+
|
|
137
|
+
- [#638](https://github.com/meocord/meocord/pull/638) [`08b990a`](https://github.com/meocord/meocord/commit/08b990a629e6d67963652564243c5f654a2d696c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Every id a mock generates comes from one count, so a test run gives the same ids each time. A server's `ownerId`, a role made without an id, an interaction's `commandId` and `applicationId`, a thread's `ownerId` and a channel's `guildId` fallback were time-based snowflakes, which differed between runs and sorted apart from every other mock id. A test that matched one of them against a time-based snowflake reads a counter id instead.
|
|
138
|
+
|
|
139
|
+
A role made with `createMockInteraction(Role)` and no id gets its id when it is made, as a user does. Its `createdTimestamp` is still the time the mock was made, in both modes.
|
|
140
|
+
|
|
141
|
+
In default mode, a reply read again, through `fetchReply()` or as `editReply()` resolves it, has the same author, server and channel each time, where each read made new ones. A test comparing two reads of the same reply sees the same objects, and reading it again uses up no ids.
|
|
142
|
+
|
|
143
|
+
- [#607](https://github.com/meocord/meocord/pull/607) [`542cd81`](https://github.com/meocord/meocord/commit/542cd8115867a30f5ec82e3533ce65cf26976311) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under `useStrictMocks()`, a mock interaction is in the place its `message`, `guild`, `member` or `guildId` gives it, as Discord sends it:
|
|
144
|
+
|
|
145
|
+
- An interaction given a `message` is in the message's channel and server. A button built on a default `createMockMessage()` is in that message's server, no longer a DM. When the test also gives the interaction a `channel`, `guild` or `guildId`, a message whose server `createMockMessage()` made itself moves to that place instead.
|
|
146
|
+
- An interaction given a `guild`, or a `member` of a server, is in that server, with its `guildId`.
|
|
147
|
+
- An interaction given a `guildId` alone is in a server the bot isn't in. Its `guild` and `channel` are `null`, and its `member` is a raw member for its user.
|
|
148
|
+
- A `channel`, `guild` or `guildId` that is somewhere other than a message the test placed, by giving it a `channel` or `guild`, is refused, naming both.
|
|
149
|
+
|
|
150
|
+
In default mode, placement is unchanged. Where strict mocks would place the interaction elsewhere, reading its `guildId`, `guild`, `channelId`, `channel` or `member` warns once, saying what to give or to call `useStrictMocks()`, and so do `inGuild()`, `inCachedGuild()`, `memberPermissions` and `appPermissions`, each under its own name, where strict mocks would answer otherwise. The same goes for the message, where strict mocks would move one whose place `createMockMessage()` made. The next major version (5.0) places interactions this way without the call.
|
|
151
|
+
|
|
152
|
+
- [#618](https://github.com/meocord/meocord/pull/618) [`41f908f`](https://github.com/meocord/meocord/commit/41f908f93bebc662fb7d920d86cea2f9f219d140) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under `useStrictMocks()`, a mock interaction's `memberPermissions` applies its channel's permission overwrites, as Discord computes it, so a guard that checks it denies in a test where it denies in production. The overwrites go on top of `member.permissions`, so a value a test sets there still decides the base. As in discord.js:
|
|
153
|
+
|
|
154
|
+
- an Administrator or the server's owner has every permission, `PermissionsBitField.All`;
|
|
155
|
+
- a thread takes its parent's overwrites;
|
|
156
|
+
- a raw member's permissions are kept as given.
|
|
157
|
+
|
|
158
|
+
In default mode `memberPermissions` is still the member's own permissions. Where strict mocks would read otherwise, because of the channel's overwrites or an Administrator's or owner's every permission, reading it warns once, naming the permissions strict mocks read without or with. It, and `appPermissions`, also warn once where the server's @everyone role has no permissions set, since strict mocks give @everyone the permissions Discord gives it in a new server; set `guild.roles.everyone.permissions` to read the same in both modes. The next major version (5.0) applies the overwrites without the call.
|
|
159
|
+
|
|
160
|
+
- [#613](https://github.com/meocord/meocord/pull/613) [`6f54ab0`](https://github.com/meocord/meocord/commit/6f54ab0cacbb3b7cd7926feadf8f42a18874bd51) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock message's `components` and `embeds` are discord.js's classes, as a real message holds them. `createMockMessage({ components, embeds })`, and the messages `editReply()` and `fetchReply()` return, used to keep each one as a bare `{ toJSON }`, so `message.components[0].type` and `message.embeds[0].title` read `undefined`.
|
|
161
|
+
|
|
162
|
+
Each one is now built from its JSON at the time of the call, whether it was given as a builder or as API JSON:
|
|
163
|
+
|
|
164
|
+
- an action row is an `ActionRow` holding a `ButtonComponent`, a select menu component, and so on;
|
|
165
|
+
- Components V2 are `ContainerComponent`, `TextDisplayComponent` and the rest;
|
|
166
|
+
- an embed is an `Embed`;
|
|
167
|
+
- a type discord.js doesn't know is a plain `Component`, as discord.js builds it.
|
|
168
|
+
|
|
169
|
+
A discord.js instance you give is kept as it is. `toJSON()` still returns the same JSON, so no passing test changes.
|
|
170
|
+
|
|
171
|
+
- [#636](https://github.com/meocord/meocord/pull/636) [`379932f`](https://github.com/meocord/meocord/commit/379932ff4f2dd42d383141740955820f1df2e4b4) Thanks [@l7aromeo](https://github.com/l7aromeo)! - What a mock computes for itself no longer calls a method a test may stub, or caches anything a test can see.
|
|
172
|
+
|
|
173
|
+
- A mock message's `member` reads its server's member cache, as discord.js does, without calling `guild.members.resolve()`. A test's `members.resolve.mockReturnValueOnce()` is left for its own code.
|
|
174
|
+
- A mock interaction's `appPermissions` computes the bot's permissions with discord.js's own computation, without calling `channel.permissionsFor()`. Reading it no longer caches the bot's member in `guild.members.cache`. In a thread, it is the bot's permissions in the thread's parent, as Discord gives them, where it was empty.
|
|
175
|
+
- A mock interaction's `channel` is the channel its server caches under `channelId` each time it is read, so a channel a test caches there after a first read is the one read.
|
|
176
|
+
|
|
177
|
+
- [#590](https://github.com/meocord/meocord/pull/590) [`54d76f9`](https://github.com/meocord/meocord/commit/54d76f9d3e85ca9f9f5ac6b0857475d63a3fffa0) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock interaction keeps its reply state, its original response and discord.js's errors whatever a test sets its answers to do.
|
|
178
|
+
|
|
179
|
+
- **A test's own value.** An answer given a value with `mockResolvedValue`, `mockResolvedValueOnce` or `mockImplementation` now replies or defers as a real one would: `reply`, `deferReply`, `followUp`, `editReply`, `showModal`, `update` and `deferUpdate`. `getResponse(interaction).state` then reads `'replied'` or `'deferred'` where it read `'unanswered'`. One that rejects still changes nothing.
|
|
180
|
+
- A second answer after such an answer, which discord.js refuses, still runs in default mode, with a warning.
|
|
181
|
+
- Under `useStrictMocks()` it is refused, as discord.js refuses it. The next major version (5.0) refuses it without the call.
|
|
182
|
+
- **The original response.** `fetchReply()` reads back what `reply()` or `update()` sent instead of an empty message. After `deleteReply()`, fetching, editing or deleting the original response rejects with 10008 (Unknown Message), as Discord answers. So does fetching it before any answer. Follow-ups stay reachable by their id: `editReply({ message: id })`, `fetchReply(id)` and `deleteReply(id)` act on the follow-up, where `editReply` edited the original before.
|
|
183
|
+
- **Flags.** `flags` given as an array, a name or a `MessageFlagsBitField` makes a reply ephemeral, as in discord.js; only a number did before. `null` flags are none. A bigint or an unknown name rejects with discord.js's RangeError, where a bigint read as ephemeral before.
|
|
184
|
+
- **Errors.** A second reply, an answer before any reply and a second autocomplete `respond()` throw discord.js's `DiscordjsError`, with the codes `InteractionAlreadyReplied` and `InteractionNotReplied`. They keep their messages, so a test matching the message as a string or a regex keeps passing. One comparing with `new Error(message)` sees the new name and code.
|
|
185
|
+
|
|
186
|
+
- [#582](https://github.com/meocord/meocord/pull/582) [`3279286`](https://github.com/meocord/meocord/commit/327928614fc930583eb92025ff597b0ea4c946e1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - meocord's mocks now work with each test runner's own mock functions and matchers.
|
|
187
|
+
|
|
188
|
+
- Under `useMockFn(jest.fn)`, a method of a `createMock()` double that has no implementation set returns `undefined`, as jest's mocks do. It no longer overflows the stack. Nested methods, and methods whose name starts with `_`, stay mocks as before.
|
|
189
|
+
- jest's call matchers, such as `toHaveBeenCalled`, read a `createMock()` member as a mock, whether or not `useMockFn` is set. They took it for a jasmine spy and failed with `Cannot read properties of undefined (reading 'map')`.
|
|
190
|
+
- To get this, a nested member named `calls` no longer makes `all` and `count` for itself: `x.y.calls.all` and `x.y.calls.count` read `undefined` until a test sets them.
|
|
191
|
+
- Every other name under it, such as `x.y.calls.foo`, is still a nested mock, and a `calls` member at the root, `x.calls.all`, is a mock as before.
|
|
192
|
+
- Without `useMockFn`, Vitest's `toHaveResolved`, `toHaveResolvedWith`, `toHaveResolvedTimes`, `toHaveLastResolvedWith`, `toHaveNthResolvedWith`, `toHaveBeenCalledBefore` and `toHaveBeenCalledAfter` work on meocord's own mocks. `mock` gains `settledResults`, `invocationCallOrder` and `contexts` for them. The new fields stay out of `mock`'s keys, so a test comparing `mock` as a whole sees the same shape. Call order is counted across meocord's own mocks, so compare two of meocord's mocks, or two of the runner's, rather than one of each.
|
|
193
|
+
- A meocord mock whose implementation returns a promise hands back a promise that settles the same way, not the same object. `expect(fn()).toBe(promise)` no longer holds; `toEqual`, `toHaveReturnedWith` and awaiting the result are unaffected. A property set on the given promise, such as a `cancel` method, isn't carried over.
|
|
194
|
+
- `useMockFn(mock.fn)` under node:test gives meocord's refusal, which says to leave meocord's own mock function in place, with node's error as its `cause`, instead of node's internal TypeError.
|
|
195
|
+
|
|
196
|
+
- [#625](https://github.com/meocord/meocord/pull/625) [`99d9339`](https://github.com/meocord/meocord/commit/99d93399f4f4539c407bd9c529d5949a74d28c56) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Messages a mock sends or fetches are where they belong and hold what was sent:
|
|
197
|
+
|
|
198
|
+
- A message from a channel's `send()`, a message's `reply()` or `forward()`, or an interaction's `followUp()`, `editReply()` or `fetchReply()` holds the content, embeds and components of the call that made it. It is in the channel it was sent in and that channel's server. Before, it sat in a generated channel of a generated server, with a stub `content`.
|
|
199
|
+
- A message's `edit()` changes that message and resolves to it, where it resolved to a new one. `crosspost()` resolves to the message itself.
|
|
200
|
+
- A message fetched with `messages.fetch()` is in the channel it was fetched from, with an author other than the bot.
|
|
201
|
+
- A role made with `roles.create()` or fetched with `roles.fetch()` is in its server. A role, channel, thread or application command a manager's `create()` makes is in that manager's cache, as discord.js caches it. `bans.create()` resolves to the member, user or id it was given and caches nothing, as discord.js's does.
|
|
202
|
+
|
|
203
|
+
Under `useStrictMocks()`, a message the bot sends is its `client.user`'s, so its `editable` reads `true`. One sent in a DM is in that DM: `inGuild()` is `false`, and `guild` and `member` are `null`. In default mode these keep today's values and warn once when read: the author of a message the bot sent, and the server of one sent in a DM. The next major version (5.0) reads them as strict mocks do.
|
|
204
|
+
|
|
205
|
+
- [#623](https://github.com/meocord/meocord/pull/623) [`46e974b`](https://github.com/meocord/meocord/commit/46e974bf74ed0f2601a95f2dc5dd8d641882d63c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock message context menu's target is its `targetMessage`, and `targetId` reads that message's id. Without a `targetMessage`, the menu gets a generated id and a message made for it, as a user context menu already did. That message is in the menu's channel and its server, or in the user's DM channel for a menu used in a DM. Setting `targetMessage` after the mock is made moves `targetId` with it, and setting `targetId` makes a message for the new id. A `targetMessage` with no id gets a generated one. If a `targetId` is given that differs from the `targetMessage`'s id, `useStrictMocks()` refuses it, naming both. In default mode it warns once and reads the message's id.
|
|
206
|
+
|
|
207
|
+
A user or an attachment made with `createMockInteraction(User)` or `createMockInteraction(Attachment)` has a snowflake `id`, from the same count as every other mock's. Before, the id was a stub that stringified to `[object Object]` for every one of them: two users shared a cooldown key, a mention read `<@[object Object]>`, and `createModalFields` keyed uploads by a stub. A user made this way gets its creation time the same way `createMockUser()`'s does: the time the mock was made under `useStrictMocks()`, and its id's time, with a warning, in default mode.
|
|
208
|
+
|
|
209
|
+
- [#593](https://github.com/meocord/meocord/pull/593) [`449bfd4`](https://github.com/meocord/meocord/commit/449bfd43cc8018642272c6b8424be6f261fff65a) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `respond().original.components` is typed as the message's top-level components, `readonly APIMessageTopLevelComponent[]`, rather than `readonly unknown[]`, so a handler can hand them back to `send()`, `editReply()` or a builder without a cast, as it already can the `embeds` beside them. Reading them compiles as before. A cast to an unrelated type, such as `Record<string, unknown>[]`, now fails to compile; drop it. Code that builds its own `ResponseState`, such as a test double, with `unknown[]` components in `original` must type them as Discord's `APIMessageTopLevelComponent[]`.
|
|
210
|
+
|
|
211
|
+
- [#576](https://github.com/meocord/meocord/pull/576) [`9cf754f`](https://github.com/meocord/meocord/commit/9cf754f3774d1f14d7f6d77f5ee5934cb336725c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A customId pattern that repeats a param name, such as `card/{id}/{id}`, or starts one with a digit, such as `card/{1}`, is refused in words that name the param: `Invalid pattern "card/{id}/{id}": {id} appears twice; give each param its own name.` These patterns were already refused, with the regular expression engine's error and the compiled regex in place of the pattern, so only the message changes, for `@Command` and `route()` alike.
|
|
212
|
+
|
|
213
|
+
- [#574](https://github.com/meocord/meocord/pull/574) [`793df0f`](https://github.com/meocord/meocord/commit/793df0fa332062db648a84d819215c640a3323d0) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A command, an option or a modal field named like a member of `Object.prototype`, such as `constructor`, `__proto__` or `toString`, now routes like any other name. Declaring `@Command('constructor', …)` used to throw `metas.findIndex is not a function`, and an undeclared command of such a name, say one left registered, failed with an error log instead of "Command not found!". An option or a modal field named `__proto__` now reaches the handler as an own param, and a customId param named `constructor` is no longer reported as colliding with a field that doesn't exist. Nothing to change in your code.
|
|
214
|
+
|
|
215
|
+
- [#614](https://github.com/meocord/meocord/pull/614) [`f1346ae`](https://github.com/meocord/meocord/commit/f1346ae8c13c6e02f5d4be791cbea5ca2cb101f3) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `createMockInteraction`'s types now refuse a `channel` beside a raw member (`createMockRawMember()`), as the mock already did when built. A raw member is from a server the bot isn't in, where discord.js caches no channel. That covers a server's channel, `channel: null`, a DM channel, and a channel beside `guild: null`. These calls compiled and then threw "leave the channel out" at runtime. `{ guildId, member: createMockRawMember() }` still gives a raw-server interaction.
|
|
216
|
+
|
|
217
|
+
- [#624](https://github.com/meocord/meocord/pull/624) [`405004f`](https://github.com/meocord/meocord/commit/405004f3c7ef1107efb9a1e99bb1ba69a9bc71ca) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A service whose `onReady` is still running when the bot stops, through `app.stop()` or a signal, now gets its `onShutdown` once that `onReady` finishes, so what it opened, such as a connection pool, is closed. Shutdown waits for it within the same `shutdownTimeout`, leaving the `onShutdown` hooks their reserve. An `onReady` still running then is named in a warning, and shutdown goes on without it. An `onReady` that calls `app.stop()` itself isn't waited for.
|
|
218
|
+
|
|
219
|
+
A testing module's `close()` during `init({ ready: true })` waits for the `onReady` hooks within its `shutdownTimeout` in the same way, warning about one that never settles rather than hanging, and an `onReady` that calls `close()` no longer waits for itself.
|
|
220
|
+
|
|
221
|
+
- [#601](https://github.com/meocord/meocord/pull/601) [`d368e8d`](https://github.com/meocord/meocord/commit/d368e8d9b5db3d9ad3a51f9d9e96407d34943e36) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Command registration keeps in step with what Discord holds:
|
|
222
|
+
|
|
223
|
+
- A development start no longer skips a scope as unchanged when the commands were removed, changed or registered again since, from this checkout or another: it checks the scope with one listing first, and sends again when any command's version differs from what it last sent. A scope that `clearOther` cleared, or that a production run wrote, from the same checkout is sent again without the check. Before, only `--force-register` brought such commands back.
|
|
224
|
+
- `meocord register --guild` with a blank guild, as an unset `--guild "$DEV_GUILD_ID"` gives, stops with an error and exits 1, where it registered to `commands.guilds` or globally. That matches what `commands.guilds` already does for an unset id.
|
|
225
|
+
- The warning about commands left in another scope says why they are kept during a development-guild run, instead of advising `commands.clearOther`, which doesn't apply there.
|
|
226
|
+
- The registered-commands table lists only subcommands and subcommand groups under Sub-commands, not a command's plain options.
|
|
227
|
+
|
|
228
|
+
- [#572](https://github.com/meocord/meocord/pull/572) [`dc7cdb9`](https://github.com/meocord/meocord/commit/dc7cdb9a69cabd363938cfdc0047c0d85fd4acac) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Under process sharding, a call to every shard now reaches each one for any shard count: `ShardContext.broadcastEval`, and discord.js's `client.shard.broadcastEval()` and `client.shard.fetchClientValues()`. They were refused with "Shards are still being spawned." unless the bot ran exactly 4 shards. Nothing to change in your bot.
|
|
229
|
+
|
|
230
|
+
- [#619](https://github.com/meocord/meocord/pull/619) [`3517d1a`](https://github.com/meocord/meocord/commit/3517d1a312c47781fc8565bb3f1f8e76d8ee73ff) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A process that loads meocord both as an ES module and through `require()`, such as an ES module bot with a CommonJS helper, a jest CommonJS `setupFiles` beside ES module specs, or `node --require ./setup.cjs`, now behaves as one:
|
|
231
|
+
|
|
232
|
+
- `UserError`, `GuardDeniedError`, `ValidationError` and meocord's other errors from either build are answered as what they are, and `instanceof` holds across the two. An app's own subclass still matches only its own instances.
|
|
233
|
+
- A handler's `@UseTheme` theme, the app's translator for meocord's own texts, and its presenter apply whichever build answers.
|
|
234
|
+
- `respond()` keeps one answer for an interaction, so a `send()` after a `delete()` from the other build follows up rather than editing the deleted reply, and `getResponse()` reads a mock's answers whichever build made the mock or answered it.
|
|
235
|
+
- `useStrictMocks()` and `useMockFn()` called in a setup file in one format apply to mocks made in the other, and the two never give two mocks one id.
|
|
236
|
+
|
|
237
|
+
The two builds share this state only within one installed version. It now outlasts `vi.resetModules()`, `jest.resetModules()` and jest's `resetModules` config, where it was made afresh: mock ids keep counting, and `useStrictMocks()` and `useMockFn()` called in a setup file stay in effect for every test.
|
|
238
|
+
|
|
239
|
+
- [#587](https://github.com/meocord/meocord/pull/587) [`f0a9ff3`](https://github.com/meocord/meocord/commit/f0a9ff3ffac1835466b1913d9305c730b1a3a23f) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Shutdown now waits for the calls under way before the `onShutdown` hooks, in every bot, and stops taking events first:
|
|
240
|
+
|
|
241
|
+
- A handler that awaits `app.stop()`, as an owner-only shutdown command does, no longer holds the shutdown up until `shutdownTimeout`. It isn't waited for.
|
|
242
|
+
- No interaction, message, reaction or `@On`/`@Once` event starts a call once shutdown has begun, and an `@On`/`@Once` handler already running is waited for, as a dispatched call is. Before, a bot whose cooldown store has no `onShutdown` kept dispatching while its hooks ran.
|
|
243
|
+
- An `@On('error')` handler keeps receiving the client's errors until the client is destroyed.
|
|
244
|
+
- `shutdownTimeout` still bounds the whole shutdown. The calls are waited for no longer than the timeout less a reserve kept for the hooks: a quarter of it, at least 1 second and never more than half. A call still running then is named in a warning, and the hooks run in the time left.
|
|
245
|
+
|
|
246
|
+
A `stop()` while the bot starts now waits for the providers being made, at most `shutdownTimeout`, and makes nothing more after it, so no service is constructed once the bot has shut down. A factory still pending after `shutdownTimeout` is named in a warning. `MeoCordTestingModule.close()` during `init()` likewise waits for the providers `init()` is making, and closes them.
|
|
247
|
+
|
|
248
|
+
- [#616](https://github.com/meocord/meocord/pull/616) [`8ea2acf`](https://github.com/meocord/meocord/commit/8ea2acffa07cd061e2c9df7682bb125a5339af59) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `overrideGuard`, `overrideInterceptor` and `overrideFilter` replace a stage that is also a provider. A guard, interceptor or filter listed in `providers`, the app's or the testing module's, made every call fail with `Ambiguous bindings found for service` once it was overridden. Now the stub stands in for it, and wins over an `overrideProvider` of the same class. It runs the `onReady` and `onShutdown` a provider would; a stage applied only with `@UseGuard`, `@UseInterceptor` or `@UseFilter` runs none, as on the bot.
|
|
249
|
+
|
|
250
|
+
`compile()` also checks each stub for its stage's method, `canActivate`, `intercept` or `catch`, on the stub or its prototype. Under `useStrictMocks()`, a stub without it is refused with `overrideGuard(G).useValue(…) has no canActivate method.` By default, `compile()` warns with the same words, once per stub and method, and builds the module, so a test that never reaches the stage passes as before. See [Testing](https://meocord.dev/docs/4.2/testing).
|
|
251
|
+
|
|
252
|
+
- [#604](https://github.com/meocord/meocord/pull/604) [`f30b621`](https://github.com/meocord/meocord/commit/f30b6210a2233b1eacf01f7471799d2cc175a515) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A theme emoji may be the England, Scotland or Wales flag, as theming documents for flags, where the bot stopped at startup calling it "not an emoji".
|
|
253
|
+
|
|
254
|
+
- [#606](https://github.com/meocord/meocord/pull/606) [`e0d3300`](https://github.com/meocord/meocord/commit/e0d3300fb200ec38691fea457f633c0dbf63bc3d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - The warning for a `themeFor` result the theme can't use shows each value as other log lines show outside text: in double quotes, with control characters and line breaks escaped, and cut short after 200 characters. A role name in it is escaped too. A malformed saved value no longer spans several lines. The startup refusal of a theme written in code reads as before.
|
|
255
|
+
|
|
256
|
+
- [#592](https://github.com/meocord/meocord/pull/592) [`b0bcb45`](https://github.com/meocord/meocord/commit/b0bcb45bc3332354da71072b56f80522cb3979d0) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `meocord.config.ts` and the build read `tsconfig.json`'s `paths` as `tsc` does, whatever TypeScript version the project uses: paths inherited through `extends`, paths relative to `baseUrl`, and `${configDir}` in `paths`, `include`, `exclude` and `files`. A config that imports through such a path now loads, rather than failing with "Cannot find module", and a `${configDir}` path no longer breaks the build.
|
|
257
|
+
|
|
258
|
+
- [#586](https://github.com/meocord/meocord/pull/586) [`6fe54a9`](https://github.com/meocord/meocord/commit/6fe54a9fb075288f7a9ed7f711f86000f21dbb45) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A `UserError`, `GuardDeniedError` or `ValidationError` whose message is empty, or longer than Discord takes, is answered as the user's own outcome again, instead of failing as a bot fault with the generic danger-coloured answer and two error logs. The message is fitted before any presenter sees it, your own included:
|
|
259
|
+
|
|
260
|
+
- at most 4096 characters for an embed and 4000 for a Components V2 Text Display, title line included, cut to end in `…`;
|
|
261
|
+
- an empty or whitespace-only message becomes MeoCord's generic error text.
|
|
262
|
+
|
|
263
|
+
A message command's plain-text reply is cut to 2000 characters, its warning emoji included. `PresentedError.message` documents the limit; a presenter that writes the message as its text needs no change.
|
|
264
|
+
|
|
265
|
+
- [#488](https://github.com/meocord/meocord/pull/488) [`b29a23c`](https://github.com/meocord/meocord/commit/b29a23c63b1aff1d029b9582b59ea359947666a4) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A `UserError` thrown from a message command or an `@On` message event is answered with a reply that pings no one its text mentions, as MeoCord's other replies to messages already do. Users, roles, `@everyone` and `@here` in the error's message are shown as written. Nothing changes for a bot whose error messages hold no mentions.
|
|
266
|
+
|
|
267
|
+
## 4.2.0
|
|
268
|
+
|
|
269
|
+
### Minor Changes
|
|
270
|
+
|
|
271
|
+
- [#463](https://github.com/meocord/meocord/pull/463) [`4d6b965`](https://github.com/meocord/meocord/commit/4d6b965ced8ecad9aceedb92ab7d084c0c2761bb) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@MeoCord({ messages: { handlers: 'concurrent' } })` runs a message's matched command and its `@MessageHandler()` listeners together, so a slow or hung command no longer holds back logging, moderation and the other listeners. Each keeps its own guards, interceptors, filters and observers, and the call settles once all have; no order holds between them, so a listener that reads what the command writes for the same message should keep the default, `'sequential'`. ([docs](https://meocord.dev/docs/4.2/message-commands))
|
|
272
|
+
|
|
273
|
+
Under `'sequential'`, a running bot now warns, once per handler, when a message's handler takes 5 seconds or more with listeners waiting after it, naming the option. `messages: { slowHandlerWarning: false }` turns the warning off. A `MeoCordTestingModule` doesn't warn unless `slowHandlerWarning` is `true`, so a test whose fake clock passes 5 seconds inside a handler sees nothing new.
|
|
274
|
+
|
|
275
|
+
- [#460](https://github.com/meocord/meocord/pull/460) [`ded179f`](https://github.com/meocord/meocord/commit/ded179fc74d83e0228f89dc271033d61c04d0809) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Controller({ inheritedRoutes: 'replace' })` makes a handler the class re-decorates answer only the routes the class declares for it, dropping the ones its base classes declare for that method. That covers every kind: commands, component patterns, message patterns and listeners, reactions and autocompletes. A slash or context menu command that no handler answers any more is not registered. A method overridden without decorators keeps every route it inherits. `'keep'`, the default, answers both, as before; the next major version (5.0) replaces them by default.
|
|
276
|
+
|
|
277
|
+
Two things help with the move, without changing what a bot does:
|
|
278
|
+
|
|
279
|
+
- The warning that names a re-declared handler still answering an inherited route now says how to drop that route now.
|
|
280
|
+
- `inspectHandler` reports the routes a handler answers by inheritance, as `inheritedRoutes`.
|
|
281
|
+
|
|
282
|
+
See https://meocord.dev/docs/4.2/how-a-call-runs.
|
|
283
|
+
|
|
284
|
+
- [#459](https://github.com/meocord/meocord/pull/459) [`ff45ac5`](https://github.com/meocord/meocord/commit/ff45ac5b7fedbcfa6a51aef774cbc464c442df19) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `createMockRawMember()` builds the member Discord sends with an interaction from a server the bot isn't in: plain data with `roles` as role ids and `permissions` as a bitfield string, as discord.js keeps it. To test a user-installed command run there, give it as an interaction's `member` with the server's `guildId` and no `guild`:
|
|
285
|
+
|
|
286
|
+
- the interaction reads `inRawGuild()` true, `guild` and `channel` `null` (with its `channelId` kept), `user` the member's user and `memberPermissions` the member's;
|
|
287
|
+
- a user option's member is the resolved member Discord sends;
|
|
288
|
+
- the interaction is typed as a `'raw'` one;
|
|
289
|
+
- a guard that reads `member.roles.cache` throws, as it would in Discord.
|
|
290
|
+
|
|
291
|
+
A raw member with a `channel`, without a `guildId`, or with a different `user` is refused. Given with a `guild`, it reads as a cached server's member, as before, with a warning once to leave the guild out. A `guildId` given alone builds the interaction as before. See https://meocord.dev/docs/4.2/mocks.
|
|
292
|
+
|
|
293
|
+
- [#476](https://github.com/meocord/meocord/pull/476) [`586a493`](https://github.com/meocord/meocord/commit/586a493c39b5a834157d9ab084a5607733ba915c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - customId patterns take two new param types: `{id:snowflake}` and `{id:uuid}`. The handler gets each as text, typed `string`, and `route().build()` refuses text that isn't one. `build()` takes either only as a string and throws a `TypeError` for a number, which may have lost an ID's digits before `build()` sees it: `12345678901234567` arrives as `12345678901234568`.
|
|
294
|
+
|
|
295
|
+
- `{id:snowflake}` takes a Discord ID: 17 to 20 digits with no leading zero, up to the largest 64-bit value. Every ID Discord has made since 2015-01-28 has at least 17 digits, because an ID's top 42 bits count milliseconds since 2015-01-01. The handler keeps the ID as text: from 17 digits on, a JavaScript number can't hold it exactly.
|
|
296
|
+
- `{id:uuid}` takes a UUID in its canonical 8-4-4-4-12 hex form, in either case, kept as written.
|
|
297
|
+
|
|
298
|
+
Both rank ahead of an untyped param, so `ticket/{id:snowflake}` takes an ID before `ticket/{name}` does, whatever the listing. Neither shares a value with `int`, `bool` or the other, so they never tie with those. Shorter digits stay an `int` while a JavaScript number holds them exactly. A 16-digit value above `Number.MAX_SAFE_INTEGER` is neither an `int` nor a snowflake, so it goes to a `number` param or to text. Between a snowflake and a `number` param, which also takes those digits, the snowflake wins. Use `{id:snowflake}` for Discord IDs: `{id:number}` rounds one, giving `12345678901234568` for `12345678901234567`.
|
|
299
|
+
|
|
300
|
+
See https://meocord.dev/docs/4.2/components.
|
|
301
|
+
|
|
302
|
+
- [#462](https://github.com/meocord/meocord/pull/462) [`db96d3d`](https://github.com/meocord/meocord/commit/db96d3ddcdaa940f6f86f32b9fb5c0f2a9f55de9) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `MeoCordFactory.create()` and a testing module's `compile()` report every startup error their checks find, not only the first. These include two handlers of one command, two same-named classes with a cooldown, a provider for a token MeoCord binds itself, and a class nothing can make. Each error is logged with the file it comes from, and then the first is thrown as before: the same error, with the same message, so code and tests that catch it or match its text keep working. A lone error is reported as before.
|
|
303
|
+
|
|
304
|
+
A decorator's startup error, such as an invalid customId pattern, is still thrown as its class is defined, with its message unchanged. It now names what it is about and where:
|
|
305
|
+
|
|
306
|
+
- `error.declaration` is the handler or class it was applied to, such as `Tickets.close`;
|
|
307
|
+
- `error.file` is the source file it is declared in.
|
|
308
|
+
|
|
309
|
+
The built bot's report puts the handler first wherever the message doesn't already name it.
|
|
310
|
+
|
|
311
|
+
New, and opt-in: set `startupErrors: 'all'` in `meocord.config.ts` to report every startup error in one run.
|
|
312
|
+
|
|
313
|
+
- Decorators keep their errors instead of throwing them as each file loads.
|
|
314
|
+
- `create()` logs those together with its own errors, so a bot with three mistakes shows all three in one run.
|
|
315
|
+
- In a test, call `reportAllStartupErrors()` from `meocord/testing` in the setup file instead.
|
|
316
|
+
|
|
317
|
+
The default stays `'first'`, where a decorator throws as its class is defined. The next major version (5.0) makes `'all'` the default. See https://meocord.dev/docs/4.2/configuration.
|
|
318
|
+
|
|
319
|
+
- [#457](https://github.com/meocord/meocord/pull/457) [`ea32209`](https://github.com/meocord/meocord/commit/ea322096a1050dc725e2ab339a1f3fa3f02320f2) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `useStrictMocks()`, called once in a test setup file, has every mock from `meocord/testing` compute the values discord.js computes, where it reads a truthy placeholder otherwise. Those values include:
|
|
320
|
+
|
|
321
|
+
- a message's `editable`, `deletable`, `pinnable`, `crosspostable`, `bulkDeletable`, `hasThread` and `partial`;
|
|
322
|
+
- a member's `manageable`, `kickable`, `bannable` and `moderatable`;
|
|
323
|
+
- a role's `editable`;
|
|
324
|
+
- a channel's and a thread's `viewable`, `manageable`, `deletable` and `joinable`;
|
|
325
|
+
- `partial` on users, channels and reactions.
|
|
326
|
+
|
|
327
|
+
`message.thread` is `null` unless the channel caches a thread under the message's id, and no placeholder warning is logged.
|
|
328
|
+
|
|
329
|
+
To give those values what Discord would, strict mocks:
|
|
330
|
+
|
|
331
|
+
- cache the bot's member in its server from the start;
|
|
332
|
+
- give @everyone the permissions Discord gives it in a new server;
|
|
333
|
+
- give a channel or thread made without a server one of its own.
|
|
334
|
+
|
|
335
|
+
So a default message from another user is not `editable` or `deletable`, and a member is not `kickable` until the bot's member has a role above theirs with the permission. A value a test sets on a mock still wins. The next major version (5.0) computes these values by default. See https://meocord.dev/docs/4.2/mocks.
|
|
336
|
+
|
|
337
|
+
Without the call, mocks read as before. A voice channel's `joinable` and `speakable` and a DM channel's `partial`, which read a placeholder without the warning, now give it too. Every placeholder warning now ends ", or call useStrictMocks() to have the mock compute it now.", so a test that matches a warning's whole text needs the new ending.
|
|
338
|
+
|
|
339
|
+
- [#455](https://github.com/meocord/meocord/pull/455) [`602285b`](https://github.com/meocord/meocord/commit/602285b5e709909ae9e88ede8d296f6924053f50) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `useMockFn(vi.fn)`, called once in a test setup file, makes every mock from `meocord/testing` with the test runner's own mock function. The runner then treats them as its own: Vitest's `clearMocks` and `mockReset` config, `vi.clearAllMocks()` and `vi.mocked(...)` reach them, and bun's matchers, which accept only bun's mocks, read them with `useMockFn(mock)`. jest takes `useMockFn(jest.fn)`. Without the call, mocks are meocord's own as before. Under jest and bun, whose `mockReset` drops a mock's starting behaviour, reset with meocord's `resetAllMocks()`, which puts it back. node:test keeps meocord's own mock function.
|
|
340
|
+
|
|
341
|
+
A new project's `vitest.setup.ts` calls `useMockFn(vi.fn)`. An existing project can add the same line to its setup file, before any mock is made. See https://meocord.dev/docs/4.2/mocks.
|
|
342
|
+
|
|
343
|
+
### Patch Changes
|
|
344
|
+
|
|
345
|
+
- [#466](https://github.com/meocord/meocord/pull/466) [`e646771`](https://github.com/meocord/meocord/commit/e646771ca9a92a28fe00cecd6a265b2d7ede409d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `testCooldownStore` no longer fails a correct store on a slow CI runner. Its sliding-window and `retryAfterMs` cases measured elapsed time against their nominal waits, with margins of tens of milliseconds, so a timer that fired a few hundred milliseconds late failed them. They now check against the times measured around each call, in a 2-second window with a second between calls, and still fail a store that resets fixed buckets or counts from the newest call. The suite takes about 2 seconds longer.
|
|
346
|
+
|
|
347
|
+
- [#474](https://github.com/meocord/meocord/pull/474) [`4f6b307`](https://github.com/meocord/meocord/commit/4f6b307d6d9a886e9b0bbcaa0439b36e9b4bae62) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Component customId patterns now rank segment by segment, left to right, the way most routers rank paths. When several patterns match an id, the first segment where one pattern spells out literal text and the other leaves a param decides it: the literal one runs, whatever order the controllers are listed in. So `profile/me/{section}` takes `profile/me/edit` from `profile/{userId}/edit`, while `profile/123/edit` still goes to `{userId}`. If that leaves a pair tied, the narrower type at the first param where they differ runs, the same type order as before: words to choose from, then `bool`, `int`, `number`, then text.
|
|
348
|
+
|
|
349
|
+
4.1 ranked by how much literal text a whole pattern had. Only these overlapping pairs change handler, and 4.1 named every one of them in its startup warning:
|
|
350
|
+
|
|
351
|
+
- A pattern with more literal text, or more literal segments, now loses to one that spells out an earlier segment. For example, `a/{x}` now runs for `a/abcd` instead of `{x}/abcd`.
|
|
352
|
+
- Pairs 4.1 left to listing order, such as `a/{x}/c` and `a/b/{y}`, now go to the earlier literal whatever the listing, which is the handler 4.1's warning said would run in 5.0.
|
|
353
|
+
- Typed params are compared position by position instead of summed. For example, `{n:int}/{s}` now runs for `7/7` instead of the one listed first among it and `{s}/{n:int}`.
|
|
354
|
+
|
|
355
|
+
Two patterns that the ranking still can't tell apart log a warning at startup, with an id both match. Such a pair has the same literals and equally narrow params at every position, such as `t/{a:on|off}` and `t/{b:off|no}`, which both take `t/off`, and the one listed first runs. Pairs the ranking decides log no warning.
|
|
356
|
+
|
|
357
|
+
`findRouteConflicts` lists only those tied pairs. A test asserting `toEqual([])` passes for every app whose patterns the ranking tells apart. A test expecting a pair the ranking now decides, such as `profile/summary/{uid}` and `profile/{ownerId}/{uid}`, gets `[]` instead.
|
|
358
|
+
|
|
359
|
+
The internal `CommandMetadata`'s `specificity` still holds the value 4.1 gave, but routing no longer reads it. It's deprecated and goes in the next major version (5.0).
|
|
360
|
+
|
|
361
|
+
See https://meocord.dev/docs/4.2/components.
|
|
362
|
+
|
|
363
|
+
- [#484](https://github.com/meocord/meocord/pull/484) [`cecfe6a`](https://github.com/meocord/meocord/commit/cecfe6ad53bd2f834d8f17472eb5690a3be9865b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A new app from `meocord create` turns on strict mocks in its `vitest.setup.ts`, with `useStrictMocks()`, so its mocks compute what discord.js computes, such as a message's `editable` or a member's `kickable`, rather than placeholders. Its sample button and `OwnerGuard` read the owner's id as `{ownerId:snowflake}`, so a customId that isn't a Discord id matches nothing. An existing app can add `useStrictMocks()` to its own setup file, before any mock is made ([docs](https://meocord.dev/docs/4.2/mocks)).
|
|
364
|
+
|
|
3
365
|
## 4.1.2
|
|
4
366
|
|
|
5
367
|
### Patch Changes
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -12,19 +12,20 @@ instead of installed; it is listed last.
|
|
|
12
12
|
|
|
13
13
|
Installed with MeoCord.
|
|
14
14
|
|
|
15
|
-
| Package | License | Source
|
|
16
|
-
| ------------------------------------------------------------------------------------ | ---------- |
|
|
17
|
-
| [@clack/prompts](https://www.npmjs.com/package/@clack/prompts) | MIT | https://github.com/bombshell-dev/clack
|
|
18
|
-
| [@jridgewell/trace-mapping](https://www.npmjs.com/package/@jridgewell/trace-mapping) | MIT | https://github.com/jridgewell/sourcemaps
|
|
19
|
-
| [@rsbuild/core](https://www.npmjs.com/package/@rsbuild/core) | MIT | https://github.com/web-infra-dev/rsbuild
|
|
20
|
-
| [chalk](https://www.npmjs.com/package/chalk) | MIT | https://github.com/chalk/chalk
|
|
21
|
-
| [cli-table3](https://www.npmjs.com/package/cli-table3) | MIT | https://github.com/cli-table/cli-table3
|
|
22
|
-
| [commander](https://www.npmjs.com/package/commander) | MIT | https://github.com/tj/commander.js
|
|
23
|
-
| [dayjs](https://www.npmjs.com/package/dayjs) | MIT | https://github.com/iamkun/dayjs
|
|
24
|
-
| [
|
|
25
|
-
| [
|
|
26
|
-
| [
|
|
27
|
-
| [
|
|
15
|
+
| Package | License | Source |
|
|
16
|
+
| ------------------------------------------------------------------------------------ | ---------- | --------------------------------------------- |
|
|
17
|
+
| [@clack/prompts](https://www.npmjs.com/package/@clack/prompts) | MIT | https://github.com/bombshell-dev/clack |
|
|
18
|
+
| [@jridgewell/trace-mapping](https://www.npmjs.com/package/@jridgewell/trace-mapping) | MIT | https://github.com/jridgewell/sourcemaps |
|
|
19
|
+
| [@rsbuild/core](https://www.npmjs.com/package/@rsbuild/core) | MIT | https://github.com/web-infra-dev/rsbuild |
|
|
20
|
+
| [chalk](https://www.npmjs.com/package/chalk) | MIT | https://github.com/chalk/chalk |
|
|
21
|
+
| [cli-table3](https://www.npmjs.com/package/cli-table3) | MIT | https://github.com/cli-table/cli-table3 |
|
|
22
|
+
| [commander](https://www.npmjs.com/package/commander) | MIT | https://github.com/tj/commander.js |
|
|
23
|
+
| [dayjs](https://www.npmjs.com/package/dayjs) | MIT | https://github.com/iamkun/dayjs |
|
|
24
|
+
| [get-tsconfig](https://www.npmjs.com/package/get-tsconfig) | MIT | https://github.com/privatenumber/get-tsconfig |
|
|
25
|
+
| [inversify](https://www.npmjs.com/package/inversify) | MIT | https://github.com/inversify/monorepo |
|
|
26
|
+
| [jiti](https://www.npmjs.com/package/jiti) | MIT | https://github.com/unjs/jiti |
|
|
27
|
+
| [lodash-es](https://www.npmjs.com/package/lodash-es) | MIT | https://github.com/lodash/lodash |
|
|
28
|
+
| [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) | Apache-2.0 | https://github.com/rbuckton/reflect-metadata |
|
|
28
29
|
|
|
29
30
|
## Peer dependencies
|
|
30
31
|
|