meocord 4.2.0 → 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 +264 -0
- package/THIRD_PARTY_NOTICES.md +14 -13
- package/dist/cjs/_shared/{controller.decorator-CRn9kWob.cjs → controller.decorator-XKMlcUZz.cjs} +376 -100
- package/dist/cjs/_shared/{providers-ChvMqnLd.cjs → providers-BZgJyfZS.cjs} +145 -36
- package/dist/cjs/_shared/{sharded-cooldown-store-BdAUTdYs.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
- package/dist/cjs/_shared/{startup-roots-CQo8mSP8.cjs → startup-roots-CgfPQrNl.cjs} +219 -63
- package/dist/cjs/_shared/{translate-error-DhDVbT5M.cjs → translate-error-BUAR1WYG.cjs} +10 -5
- package/dist/cjs/common/index.cjs +3 -3
- package/dist/cjs/core/index.cjs +185 -168
- package/dist/cjs/decorator/index.cjs +21 -6
- package/dist/cjs/testing/index.cjs +1594 -286
- package/dist/cli.json +1 -1
- 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 +13 -3
- 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/errors.js +25 -0
- package/dist/esm/common/meocord-text.js +11 -8
- 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 +11 -4
- package/dist/esm/common/sharded-cooldown-store.js +1 -1
- package/dist/esm/common/translator.js +37 -6
- package/dist/esm/core/command-registration.js +93 -24
- package/dist/esm/core/command-routes.js +2 -2
- package/dist/esm/core/cooldown-runner.js +29 -8
- package/dist/esm/core/fallback.js +20 -11
- package/dist/esm/core/guard-runner.js +16 -3
- package/dist/esm/core/handler-input.js +4 -3
- package/dist/esm/core/lifecycle-hooks.js +134 -47
- package/dist/esm/core/meocord-factory.js +55 -32
- package/dist/esm/core/meocord.app.js +116 -75
- package/dist/esm/core/providers.js +7 -2
- package/dist/esm/core/running-calls.js +43 -0
- package/dist/esm/core/shard-context.js +5 -6
- package/dist/esm/core/shard-manager.js +16 -10
- package/dist/esm/core/startup-roots.js +21 -1
- package/dist/esm/core/theme-resolvers.js +4 -1
- package/dist/esm/core/theme-scope.js +3 -1
- package/dist/esm/core/theme-validation.js +17 -10
- package/dist/esm/decorator/controller.decorator.js +56 -20
- package/dist/esm/decorator/cooldown.decorator.js +17 -0
- package/dist/esm/decorator/guard.decorator.js +3 -2
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +26 -10
- package/dist/esm/testing/cooldown-store-suite.js +67 -5
- package/dist/esm/testing/discord-defaults.js +205 -30
- package/dist/esm/testing/meocord-testing-module.js +134 -28
- package/dist/esm/testing/mock-fn.js +107 -26
- package/dist/esm/testing/mock-interaction.js +1026 -172
- package/dist/esm/testing/snowflake.js +9 -2
- package/dist/esm/testing/strict-mocks.js +37 -17
- 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 +2 -2
- package/dist/esm/util/meocord-config-loader.util.js +10 -2
- package/dist/esm/util/meocord-config-validation.util.js +3 -5
- package/dist/esm/util/meocord-source-config.util.js +65 -20
- package/dist/esm/util/method-wrapper.util.js +16 -0
- package/dist/esm/util/platform.util.js +10 -10
- 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 +6 -6
- package/dist/types/common/index.d.ts +6 -6
- package/dist/types/core/index.d.cts +3 -2
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/decorator/index.d.cts +1 -0
- package/dist/types/decorator/index.d.ts +1 -0
- package/dist/types/{index-CdXsFgjE.d.ts → index-DKJclIi-.d.ts} +45 -21
- package/dist/types/{index-nn5p7OoX.d.cts → index-Dlf0ZZtS.d.cts} +45 -21
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-BZgFoNeB.d.cts → route-Bh1U13yy.d.cts} +3 -2
- package/dist/types/{route-BZgFoNeB.d.ts → route-Bh1U13yy.d.ts} +3 -2
- package/dist/types/testing/index.d.cts +175 -53
- package/dist/types/testing/index.d.ts +175 -53
- package/dist/types/{theme-resolvers-UKr3-6Ys.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
- package/dist/types/{theme-resolvers-BcOJVzR9.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
- package/dist/types/{translator-DHMkkCpg.d.cts → translator-Bg7hwxDF.d.cts} +21 -8
- package/dist/types/{translator-BViWKtLJ.d.ts → translator-CeP4bCzp.d.ts} +21 -8
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var controller_decorator = require('./controller.decorator-
|
|
3
|
+
var controller_decorator = require('./controller.decorator-XKMlcUZz.cjs');
|
|
4
4
|
require('reflect-metadata');
|
|
5
5
|
var node_async_hooks = require('node:async_hooks');
|
|
6
6
|
var inversify = require('inversify');
|
|
@@ -505,14 +505,27 @@ const wrapperCount = perHandler((prototype, methodName)=>{
|
|
|
505
505
|
else marks.delete(methodName);
|
|
506
506
|
return true;
|
|
507
507
|
}
|
|
508
|
+
/** The container that made each instance, so a direct call runs in its own app or testing module. */ const instanceContainers = new WeakMap();
|
|
509
|
+
/**
|
|
510
|
+
* Records each instance `container` makes of `classes` as its own. Two testing modules made from one class each run a
|
|
511
|
+
* direct call on their own instance in themselves, where the class's metadata names only the last.
|
|
512
|
+
*/ function recordInstanceContainers(container, classes) {
|
|
513
|
+
for (const cls of classes){
|
|
514
|
+
container.onActivation(cls, (_context, instance)=>{
|
|
515
|
+
if (typeof instance === 'object' && instance !== null) instanceContainers.set(instance, container);
|
|
516
|
+
return instance;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
508
520
|
/**
|
|
509
521
|
* Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves them, then the
|
|
510
522
|
* call with a pass for each wrapper inside the one entered, so no guard runs twice.
|
|
511
523
|
*/ async function runDirectCall(instance, methodName, args, call) {
|
|
512
524
|
const prototype = Object.getPrototypeOf(instance);
|
|
513
525
|
const controller = instance.constructor;
|
|
514
|
-
// The app that made the class
|
|
515
|
-
|
|
526
|
+
// The container that made the instance, or else the app that made the class or a class it extends: a subclass the
|
|
527
|
+
// app's code makes runs as its base does
|
|
528
|
+
const container = instanceContainers.get(instance) ?? Reflect.getMetadata(controller_decorator.META.container, controller);
|
|
516
529
|
if (!container) {
|
|
517
530
|
throw new Error(`${controller.name}.${methodName}: @UseGuard runs its guards through the app that made ${controller.name}, and this ` + `instance was made outside one. Inject ${controller.name}, or call it on an instance the app made.`);
|
|
518
531
|
}
|
|
@@ -767,19 +780,37 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
|
|
|
767
780
|
const value = by ? await by(context, params) : undefined;
|
|
768
781
|
// Encoded, so a value holding a colon cannot count under another value's key. Without a value, `:by` alone keeps
|
|
769
782
|
// the call apart from a cooldown with no `by` over the same window.
|
|
770
|
-
const suffix = !by ? '' : value === undefined ? ':by' : `:by:${
|
|
783
|
+
const suffix = !by ? '' : value === undefined ? ':by' : `:by:${encodeKeyPart(String(value))}`;
|
|
784
|
+
const scope = scopeId(per, first);
|
|
785
|
+
// The caller, for a notice of a refusal under a scope that counts more than one caller
|
|
786
|
+
const user = scope.startsWith('user:') ? undefined : caller(first).user;
|
|
771
787
|
counted.push({
|
|
772
|
-
key: `${controller.name}.${methodName}#${id}:${per}:${
|
|
788
|
+
key: `${controller.name}.${methodName}#${id}:${per}:${scope}${suffix}`,
|
|
773
789
|
windowMs,
|
|
774
790
|
uses,
|
|
775
|
-
per
|
|
791
|
+
per,
|
|
792
|
+
user
|
|
776
793
|
});
|
|
777
794
|
}
|
|
778
795
|
return counted;
|
|
779
796
|
}
|
|
797
|
+
// A surrogate with no partner beside it, which encodeURIComponent refuses
|
|
798
|
+
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
780
799
|
/**
|
|
781
|
-
*
|
|
782
|
-
*
|
|
800
|
+
* A value as part of a store key: each well-formed run as `encodeURIComponent` writes it, and each lone surrogate as `%u`
|
|
801
|
+
* and its four hex digits, which no other value encodes to, since `encodeURIComponent` writes `%` only before two.
|
|
802
|
+
*/ function encodeKeyPart(value) {
|
|
803
|
+
let encoded = '';
|
|
804
|
+
let from = 0;
|
|
805
|
+
for (const { 0: surrogate, index } of value.matchAll(LONE_SURROGATE)){
|
|
806
|
+
encoded += `${encodeURIComponent(value.slice(from, index))}%u${surrogate.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
807
|
+
from = index + 1;
|
|
808
|
+
}
|
|
809
|
+
return encoded + encodeURIComponent(value.slice(from));
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* The store key and window of the cooldown each refusal came from, when its wait ends by the store's clock if the
|
|
813
|
+
* store said, and the refused caller when the key counts more than one, for {@link claimCooldownNotice}.
|
|
783
814
|
*/ const refusalKeys = new WeakMap();
|
|
784
815
|
/** Asks the store about the call under the app's policy, and throws what the answer means for it. */ async function ask(container, counted, peek, call) {
|
|
785
816
|
const store = cooldownStoreOf(container);
|
|
@@ -812,7 +843,8 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
|
|
|
812
843
|
refusalKeys.set(refusal, {
|
|
813
844
|
key: blocking.key,
|
|
814
845
|
windowMs: blocking.windowMs,
|
|
815
|
-
endsAt: verdict.retryTimestamp
|
|
846
|
+
endsAt: verdict.retryTimestamp,
|
|
847
|
+
user: blocking.user
|
|
816
848
|
});
|
|
817
849
|
throw refusal;
|
|
818
850
|
}
|
|
@@ -835,16 +867,18 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
|
|
|
835
867
|
// retry landing a moment late is refused on the earlier, which its wait holds, before taking the later, which a wait
|
|
836
868
|
// ending then needs for its own notice.
|
|
837
869
|
const bucket = Math.floor(refusal.retryAt.getTime() / NOTICE_BUCKET_MS);
|
|
870
|
+
// A key counting a server, a channel or everyone is shared by every caller it refuses, each told once
|
|
871
|
+
const key = refused.user === undefined ? refused.key : `${refused.key}:user:${refused.user}`;
|
|
838
872
|
const notices = refused.endsAt !== undefined ? [
|
|
839
873
|
{
|
|
840
|
-
key: `${
|
|
874
|
+
key: `${key}:notice:at:${refused.endsAt}`,
|
|
841
875
|
limit
|
|
842
876
|
}
|
|
843
877
|
] : [
|
|
844
878
|
bucket - 1,
|
|
845
879
|
bucket
|
|
846
880
|
].map((at)=>({
|
|
847
|
-
key: `${
|
|
881
|
+
key: `${key}:notice:${at}`,
|
|
848
882
|
limit
|
|
849
883
|
}));
|
|
850
884
|
try {
|
|
@@ -1066,19 +1100,35 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1066
1100
|
const guilds = options.config?.guilds ?? [];
|
|
1067
1101
|
return !everythingTarget(options) && guilds.length > 0 && ids(guilds).length === 0;
|
|
1068
1102
|
}
|
|
1103
|
+
/** Whether a command is an activity's primary entry point, which Discord takes only globally. */ const isEntryPoint = (command)=>command.body.type === discord_js.ApplicationCommandType.PrimaryEntryPoint;
|
|
1104
|
+
/** What a blank guild to register every command to says, as an unset variable gives one. */ const BLANK_GUILD = 'No commands were registered: the guild to register to is blank. Give a guild id, or leave it out to register where commands.* says.';
|
|
1069
1105
|
/**
|
|
1070
1106
|
* Where each command is sent: one bulk update per scope. A scope's update replaces everything the application has
|
|
1071
1107
|
* there, so the default scope is included even when it ends up empty, unless `commands.guilds` lists no guild id.
|
|
1108
|
+
*
|
|
1109
|
+
* A primary entry point command goes in the global update, as Discord refuses a guild update that holds one. A run
|
|
1110
|
+
* that sends everything to one guild sends nothing global, so it leaves the entry point out.
|
|
1072
1111
|
*/ function planTargets(commands, options, logger) {
|
|
1112
|
+
// Registering to the default scopes instead could publish everywhere
|
|
1113
|
+
if (options.onlyGuild !== undefined && !options.onlyGuild.trim()) {
|
|
1114
|
+
logger.error(BLANK_GUILD);
|
|
1115
|
+
return [];
|
|
1116
|
+
}
|
|
1073
1117
|
const everythingTo = everythingTarget(options);
|
|
1074
|
-
if (everythingTo)
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
},
|
|
1079
|
-
commands
|
|
1118
|
+
if (everythingTo) {
|
|
1119
|
+
// A global update from here would hold only what this run sends, deleting the application's other global commands
|
|
1120
|
+
for (const command of commands.filter(isEntryPoint)){
|
|
1121
|
+
logger.warn(`Entry point command "${command.name}" is registered globally only; a production run or \`meocord register\` registers it.`);
|
|
1080
1122
|
}
|
|
1081
|
-
|
|
1123
|
+
return [
|
|
1124
|
+
{
|
|
1125
|
+
scope: {
|
|
1126
|
+
guild: everythingTo
|
|
1127
|
+
},
|
|
1128
|
+
commands: commands.filter((command)=>!isEntryPoint(command))
|
|
1129
|
+
}
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1082
1132
|
const defaultGuilds = ids(options.config?.guilds);
|
|
1083
1133
|
const nowhere = defaultGuildsEmpty(options);
|
|
1084
1134
|
const targets = new Map();
|
|
@@ -1096,7 +1146,12 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1096
1146
|
});
|
|
1097
1147
|
const unregistered = [];
|
|
1098
1148
|
for (const command of commands){
|
|
1099
|
-
if (command
|
|
1149
|
+
if (isEntryPoint(command)) {
|
|
1150
|
+
if (command.guilds) {
|
|
1151
|
+
logger.warn(`Entry point command "${command.name}" can only be registered globally, so the guilds its builder lists are ignored.`);
|
|
1152
|
+
}
|
|
1153
|
+
add('global', command);
|
|
1154
|
+
} else if (command.guilds) {
|
|
1100
1155
|
const own = ids(command.guilds);
|
|
1101
1156
|
// Falling back to the default scope could publish a staff-only command everywhere.
|
|
1102
1157
|
if (own.length === 0) logger.warn(`Command "${command.name}" lists no guild ids in its builder, so it is not registered.`);
|
|
@@ -1114,7 +1169,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1114
1169
|
}
|
|
1115
1170
|
}
|
|
1116
1171
|
if (nowhere) {
|
|
1117
|
-
const outcome = unregistered.length === 0 ? 'nothing is registered globally' : `${unregistered.join(', ')} ${unregistered.length === 1 ? 'is' : 'are'} not registered, rather than registered globally`;
|
|
1172
|
+
const outcome = unregistered.length === 0 ? targets.has('global') ? 'only the entry point command is registered globally' : 'nothing is registered globally' : `${unregistered.join(', ')} ${unregistered.length === 1 ? 'is' : 'are'} not registered, rather than registered globally`;
|
|
1118
1173
|
logger.warn(`commands.guilds lists no guild id, as an unset environment variable leaves it, so ${outcome}. ` + `Set the guild ids, or remove commands.guilds to register globally.`);
|
|
1119
1174
|
}
|
|
1120
1175
|
return [
|
|
@@ -1143,28 +1198,57 @@ const describeScope = (scope)=>scope === 'global' ? 'globally' : `to guild ${sco
|
|
|
1143
1198
|
return path__default.default.join(nodeModules, '.cache', 'meocord', `commands-${applicationId}-${scopeKey(scope)}.json`);
|
|
1144
1199
|
}
|
|
1145
1200
|
const hashOf = (commands)=>crypto.createHash('sha256').update(JSON.stringify(commands.map(({ body })=>body))).digest('hex');
|
|
1146
|
-
function
|
|
1201
|
+
function readRecord(file, logger) {
|
|
1147
1202
|
if (!file || !fs.existsSync(file)) return undefined;
|
|
1148
1203
|
try {
|
|
1149
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
1204
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1150
1205
|
} catch (error) {
|
|
1151
1206
|
logger.debug(`Could not read the registered commands, so they are sent again: ${String(error)}`);
|
|
1152
1207
|
return undefined;
|
|
1153
1208
|
}
|
|
1154
1209
|
}
|
|
1155
|
-
function
|
|
1210
|
+
function writeRecord(file, record, logger) {
|
|
1156
1211
|
if (!file) return;
|
|
1157
1212
|
try {
|
|
1158
1213
|
fs.mkdirSync(path__default.default.dirname(file), {
|
|
1159
1214
|
recursive: true
|
|
1160
1215
|
});
|
|
1161
|
-
fs.writeFileSync(file, `${JSON.stringify(
|
|
1162
|
-
hash
|
|
1163
|
-
})}\n`);
|
|
1216
|
+
fs.writeFileSync(file, `${JSON.stringify(record)}\n`);
|
|
1164
1217
|
} catch (error) {
|
|
1165
1218
|
logger.debug(`Could not record the registered commands: ${String(error)}`);
|
|
1166
1219
|
}
|
|
1167
1220
|
}
|
|
1221
|
+
/** Forgets what development last sent to a scope, once that scope is written some other way. */ function forgetRecord(file, logger) {
|
|
1222
|
+
if (!file || !fs.existsSync(file)) return;
|
|
1223
|
+
try {
|
|
1224
|
+
fs.rmSync(file);
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
logger.debug(`Could not forget the registered commands: ${String(error)}`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Each command's `id:version`, sorted, from what Discord lists or answers a bulk update with; `undefined` for anything
|
|
1231
|
+
* else. Discord gives a command a new version whenever it is updated, so the set changes with any edit, removal or
|
|
1232
|
+
* re-registration, from this checkout or any other.
|
|
1233
|
+
*/ function versionsOf(commands) {
|
|
1234
|
+
if (!Array.isArray(commands)) return undefined;
|
|
1235
|
+
const versions = commands.map((command)=>{
|
|
1236
|
+
const { id, version } = command ?? {};
|
|
1237
|
+
return typeof id === 'string' && typeof version === 'string' ? `${id}:${version}` : undefined;
|
|
1238
|
+
});
|
|
1239
|
+
return versions.every((version)=>version !== undefined) ? versions.sort() : undefined;
|
|
1240
|
+
}
|
|
1241
|
+
/** Whether Discord still holds a scope as development last sent it, by each command's version; a failed listing doesn't. */ async function stillAsSent(rest, route, record, scope, logger) {
|
|
1242
|
+
if (!record.versions) return false;
|
|
1243
|
+
let registered;
|
|
1244
|
+
try {
|
|
1245
|
+
registered = await rest.get(route);
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
logger.debug(`Could not list the commands registered ${describeScope(scope)}, so they are sent again: ${String(error)}`);
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
return versionsOf(registered)?.join('\n') === record.versions.join('\n');
|
|
1251
|
+
}
|
|
1168
1252
|
function logRegistered(target, logger) {
|
|
1169
1253
|
const table = new Table__default.default({
|
|
1170
1254
|
head: [
|
|
@@ -1187,22 +1271,29 @@ function logRegistered(target, logger) {
|
|
|
1187
1271
|
};
|
|
1188
1272
|
for (const { name, body } of target.commands){
|
|
1189
1273
|
const options = Array.isArray(body.options) ? body.options : [];
|
|
1274
|
+
// A subcommand or a subcommand group, not a plain option
|
|
1275
|
+
const subcommands = options.filter((option)=>option.type === 1 || option.type === 2);
|
|
1190
1276
|
table.push([
|
|
1191
1277
|
name,
|
|
1192
1278
|
typeNames[typeof body.type === 'number' ? body.type : 1] ?? 'Command',
|
|
1193
|
-
|
|
1279
|
+
subcommands.map((option)=>option.name).join(', ')
|
|
1194
1280
|
]);
|
|
1195
1281
|
}
|
|
1196
1282
|
logger.log(`Registered ${target.commands.length} bot commands ${describeScope(target.scope)}:\n${table.toString()}`);
|
|
1197
1283
|
}
|
|
1198
1284
|
/**
|
|
1199
|
-
* Registers the commands, one bulk update per scope; development skips a scope unchanged since its last send
|
|
1200
|
-
* `force`. Once one is sent, and not to `onlyGuild`, the other scopes the configuration names are checked for
|
|
1285
|
+
* Registers the commands, one bulk update per scope; development skips a scope unchanged since its last send and still
|
|
1286
|
+
* registered as sent, which one listing per skipped scope checks by each command's version, unless `force`. A blank `onlyGuild` registers nothing. Once one is sent, and not to `onlyGuild`, the other scopes the configuration names are checked for
|
|
1201
1287
|
* leftovers, which `clearOther` removes; a development-guild run or a `commands.guilds` with no id only warns. Never
|
|
1202
1288
|
* throws.
|
|
1203
1289
|
* @returns Whether every scope was sent or unchanged, and `commands.guilds`, if set, lists a guild id.
|
|
1204
1290
|
*/ async function registerCommands(options) {
|
|
1205
1291
|
const { rest, applicationId, controllerClasses, logger, config, development, onlyGuild, force = false } = options;
|
|
1292
|
+
// An unset variable passed as the guild; registering to the default scopes instead could publish everywhere
|
|
1293
|
+
if (onlyGuild !== undefined && !onlyGuild.trim()) {
|
|
1294
|
+
logger.error(BLANK_GUILD);
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1206
1297
|
const commands = collectCommands(controllerClasses, logger);
|
|
1207
1298
|
if (!commands) return false;
|
|
1208
1299
|
const targets = planTargets(commands, {
|
|
@@ -1219,18 +1310,26 @@ function logRegistered(target, logger) {
|
|
|
1219
1310
|
let succeeded = !guildsEmpty;
|
|
1220
1311
|
let sent = false;
|
|
1221
1312
|
for (const target of targets){
|
|
1222
|
-
const file =
|
|
1313
|
+
const file = cacheFile(applicationId, target.scope);
|
|
1223
1314
|
const hash = hashOf(target.commands);
|
|
1224
|
-
|
|
1315
|
+
const route = routeFor(applicationId, target.scope);
|
|
1316
|
+
// Unchanged since development last sent it, and still what Discord holds there
|
|
1317
|
+
const record = development && !force ? readRecord(file, logger) : undefined;
|
|
1318
|
+
if (record?.hash === hash && await stillAsSent(rest, route, record, target.scope, logger)) {
|
|
1225
1319
|
logger.log(`Commands ${describeScope(target.scope)} are unchanged; not registering them again.`);
|
|
1226
1320
|
continue;
|
|
1227
1321
|
}
|
|
1228
1322
|
try {
|
|
1229
|
-
await rest.put(
|
|
1323
|
+
const answered = await rest.put(route, {
|
|
1230
1324
|
body: target.commands.map(({ body })=>body)
|
|
1231
1325
|
});
|
|
1232
1326
|
sent = true;
|
|
1233
|
-
|
|
1327
|
+
// Outside development the record would be stale, so the next development start sends again
|
|
1328
|
+
if (development) writeRecord(file, {
|
|
1329
|
+
hash,
|
|
1330
|
+
versions: versionsOf(answered)
|
|
1331
|
+
}, logger);
|
|
1332
|
+
else forgetRecord(file, logger);
|
|
1234
1333
|
logRegistered(target, logger);
|
|
1235
1334
|
} catch (error) {
|
|
1236
1335
|
succeeded = false;
|
|
@@ -1259,13 +1358,17 @@ function logRegistered(target, logger) {
|
|
|
1259
1358
|
if (!Array.isArray(existing) || existing.length === 0) continue;
|
|
1260
1359
|
const names = existing.map(({ name })=>name).join(', ');
|
|
1261
1360
|
if (!config?.clearOther || keptBecause) {
|
|
1262
|
-
|
|
1361
|
+
// Advising clearOther when it would not apply sends the reader to a setting that changes nothing
|
|
1362
|
+
const advice = !keptBecause ? 'Set commands.clearOther to remove them.' : config?.clearOther ? `clearOther is on, but they are not removed ${keptBecause}.` : `They are not removed ${keptBecause}, even with commands.clearOther.`;
|
|
1363
|
+
logger.warn(`${existing.length} command(s) are still registered ${describeScope(scope)} (${names}), which this ` + `configuration does not register to, so Discord keeps showing them there. ${advice}`);
|
|
1263
1364
|
continue;
|
|
1264
1365
|
}
|
|
1265
1366
|
try {
|
|
1266
1367
|
await rest.put(routeFor(applicationId, scope), {
|
|
1267
1368
|
body: []
|
|
1268
1369
|
});
|
|
1370
|
+
// What development last sent there is gone, so its next start sends again
|
|
1371
|
+
forgetRecord(cacheFile(applicationId, scope), logger);
|
|
1269
1372
|
logger.log(`Removed ${existing.length} command(s) left registered ${describeScope(scope)}: ${names}`);
|
|
1270
1373
|
} catch (error) {
|
|
1271
1374
|
logger.error(`Error removing the commands left registered ${describeScope(scope)}:`, error);
|
|
@@ -4854,15 +4957,20 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
|
|
|
4854
4957
|
}
|
|
4855
4958
|
/**
|
|
4856
4959
|
* Runs every factory in `order`, awaiting those that return a promise, so each value is made before
|
|
4857
|
-
* anything that injects it is resolved. Rejects with {@link providerFailure} for the first that fails.
|
|
4858
|
-
|
|
4960
|
+
* anything that injects it is resolved. Rejects with {@link providerFailure} for the first that fails. With `control`,
|
|
4961
|
+
* it makes no further value once the app is stopping.
|
|
4962
|
+
*/ async function resolveProviders(container, providers, order, control) {
|
|
4859
4963
|
for (const token of order){
|
|
4860
4964
|
const provider = providers.get(token);
|
|
4861
4965
|
if (!provider || !isFactoryProvider(provider)) continue;
|
|
4966
|
+
if (control?.stopped()) return;
|
|
4967
|
+
control?.pending(tokenName(token));
|
|
4862
4968
|
try {
|
|
4863
4969
|
await container.getAsync(token);
|
|
4864
4970
|
} catch (error) {
|
|
4865
4971
|
throw providerFailure(token, error);
|
|
4972
|
+
} finally{
|
|
4973
|
+
control?.pending(undefined);
|
|
4866
4974
|
}
|
|
4867
4975
|
}
|
|
4868
4976
|
}
|
|
@@ -4941,6 +5049,7 @@ exports.prepareHandlerStages = prepareHandlerStages;
|
|
|
4941
5049
|
exports.prepareThemes = prepareThemes;
|
|
4942
5050
|
exports.providerMap = providerMap;
|
|
4943
5051
|
exports.reachableClasses = reachableClasses;
|
|
5052
|
+
exports.recordInstanceContainers = recordInstanceContainers;
|
|
4944
5053
|
exports.registerClientTheme = registerClientTheme;
|
|
4945
5054
|
exports.registerCommands = registerCommands;
|
|
4946
5055
|
exports.registrationKey = registrationKey;
|
package/dist/cjs/_shared/{sharded-cooldown-store-BdAUTdYs.cjs → sharded-cooldown-store-CsJJyCZd.cjs}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var node_crypto = require('node:crypto');
|
|
4
|
-
var controller_decorator = require('./controller.decorator-
|
|
4
|
+
var controller_decorator = require('./controller.decorator-XKMlcUZz.cjs');
|
|
5
5
|
|
|
6
6
|
/** The `fatal` code of a shard whose app MeoCord refused, which every shard would refuse alike. */ const REFUSED_CODE = 'Refused';
|
|
7
7
|
/** Whether an IPC message is one of MeoCord's own, rather than one of discord.js's. */ function isShardMessage(message) {
|
|
@@ -9,7 +9,10 @@ var controller_decorator = require('./controller.decorator-CRn9kWob.cjs');
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/** Set by discord.js in every process its ShardingManager spawns. */ const SHARDING_MANAGER_ENV = 'SHARDING_MANAGER';
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Whether a process with `env` was spawned as a shard by a sharding manager. Read before anything builds a
|
|
14
|
+
* `ShardingManager`, whose constructor writes the variable into its own process too.
|
|
15
|
+
*/ function isShardProcess(env) {
|
|
13
16
|
return env[SHARDING_MANAGER_ENV] !== undefined;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -21,10 +24,10 @@ var controller_decorator = require('./controller.decorator-CRn9kWob.cjs');
|
|
|
21
24
|
return env.NODE_ENV !== 'development' || config.sharding.development === true;
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
|
-
* The role of this process: a shard when a manager spawned it, the manager when process sharding is
|
|
25
|
-
* and otherwise a single process running every shard it has.
|
|
26
|
-
*/ function shardingRole(config, env = process.env) {
|
|
27
|
-
if (
|
|
27
|
+
* The role of this process: a shard when a manager spawned it, as `shard` says, the manager when process sharding is
|
|
28
|
+
* on, and otherwise a single process running every shard it has.
|
|
29
|
+
*/ function shardingRole(config, shard, env = process.env) {
|
|
30
|
+
if (shard) return 'shard';
|
|
28
31
|
return processShardingEnabled(config, env) ? 'manager' : 'single';
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
@@ -68,7 +71,7 @@ var controller_decorator = require('./controller.decorator-CRn9kWob.cjs');
|
|
|
68
71
|
* never held forever. MeoCord itself fails the call after `cooldownStoreTimeoutMs`, a second by default.
|
|
69
72
|
*/ const SHARDED_COOLDOWN_ABANDON_MS = 30000;
|
|
70
73
|
/** A shard's channel to its manager, or none outside process sharding. */ function managerChannel() {
|
|
71
|
-
return isShardProcess() && typeof process.send === 'function' ? channelOver(process) : undefined;
|
|
74
|
+
return isShardProcess(process.env) && typeof process.send === 'function' ? channelOver(process) : undefined;
|
|
72
75
|
}
|
|
73
76
|
/**
|
|
74
77
|
* The channel over a process's IPC. A send on a closed channel does not throw: without a callback, Node raises an
|