@nectar-js/nectar 0.2.0 → 0.3.0

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.
@@ -1,4 +1,4 @@
1
- import { o as stableStringify } from "./plugins-BCYwtuo-.js";
1
+ import { o as stableStringify } from "./plugins-C2uwN3-D.js";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -601,4 +601,4 @@ function writeCache(dir, cache) {
601
601
  //#endregion
602
602
  export { checkIntents as a, configFor as c, scopeKey as i, defineConfig as l, syncCommands as n, requiredIntents as o, RegistrationError as r, ConfigError as s, UnsafeSyncError as t, validateConfig as u };
603
603
 
604
- //# sourceMappingURL=registration-BN3P3MDg.js.map
604
+ //# sourceMappingURL=registration-mUZJidnF.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registration-BN3P3MDg.js","names":[],"sources":["../src/config.ts","../src/events/intents.ts","../src/registration/normalize.ts","../src/registration/diff.ts","../src/registration/remote.ts","../src/registration/errors.ts","../src/registration/sync.ts"],"sourcesContent":["import type { ClientOptions } from \"discord.js\";\nimport type { NectarPlugin } from \"./plugins/index.js\";\nimport type { LoggerOptions } from \"./runtime/logger.js\";\nimport type { Signal } from \"./runtime/signals.js\";\nimport type { Env } from \"./runtime/types.js\";\n\n/** `nectar.config.ts`: `export default defineConfig({ ... })`. */\nexport interface NectarConfig {\n /**\n * Bot token. Usually `process.env.DISCORD_TOKEN`; the CLI falls back to that variable when\n * this is omitted or empty.\n */\n token?: string | undefined;\n /** Application ID, for command registration. Falls back to `DISCORD_APPLICATION_ID`. */\n applicationId?: string | undefined;\n intents: ClientOptions[\"intents\"];\n partials?: ClientOptions[\"partials\"];\n /** Extra discord.js client options. `intents` and `partials` above take precedence. */\n client?: Partial<ClientOptions>;\n /** Import every handler at startup. Defaults to `true` in production, `false` otherwise. */\n eager?: boolean;\n /** Overrides `NODE_ENV`. */\n env?: Env;\n /**\n * Framework log level and sink. The default sink prints to the console; pass `sink` to hand\n * records to your own logger. Handlers are free to log however they like.\n */\n logger?: LoggerOptions;\n /** Called with every framework signal: interaction lifecycle, failures, gateway state, shutdown. */\n observe?: (signal: Signal) => void;\n /** Plugins, in the order their hooks run. This is the only way to register one. */\n plugins?: NectarPlugin[];\n /** Route directory, relative to the project root. Defaults to `app`. */\n appDir?: string;\n /** Build output, relative to the project root. Defaults to `.nectar`. */\n outDir?: string;\n dev?: {\n /** Guilds that receive commands instantly while developing. */\n guilds?: string[];\n };\n commands?: {\n /**\n * Where commands are registered outside development: everywhere, or only in the listed\n * guilds. Defaults to `\"global\"`. Development always uses `dev.guilds`.\n */\n target?: \"global\" | string[];\n };\n /**\n * Overrides for one environment, applied once the environment is known. Each key replaces\n * the value above it; nested objects are not merged.\n */\n environments?: Partial<Record<Env, Partial<Omit<NectarConfig, \"env\" | \"environments\">>>>;\n}\n\nexport function defineConfig(config: NectarConfig): NectarConfig {\n return config;\n}\n\n/** The config one environment runs with: `environments[env]` over the rest. */\nexport function configFor(config: NectarConfig, env: Env): NectarConfig {\n const { environments, ...base } = config;\n return { ...base, ...environments?.[env] };\n}\n\nexport class ConfigError extends Error {\n constructor(\n readonly file: string,\n readonly detail: string,\n ) {\n super(`${file}: ${detail}`);\n this.name = \"ConfigError\";\n }\n}\n\nconst ENVS = new Set<string>([\"development\", \"test\", \"production\"]);\nconst LEVELS = new Set<string>([\"debug\", \"info\", \"warn\", \"error\"]);\n\n/** Every option, so a misspelled or removed one fails instead of being ignored. */\nconst OPTIONS: Record<keyof NectarConfig, true> = {\n token: true,\n applicationId: true,\n intents: true,\n partials: true,\n client: true,\n eager: true,\n env: true,\n logger: true,\n observe: true,\n plugins: true,\n appDir: true,\n outDir: true,\n dev: true,\n commands: true,\n environments: true,\n};\nconst DEV_OPTIONS: Record<keyof NonNullable<NectarConfig[\"dev\"]>, true> = { guilds: true };\nconst COMMANDS_OPTIONS: Record<keyof NonNullable<NectarConfig[\"commands\"]>, true> = {\n target: true,\n};\nconst LOGGER_OPTIONS: Record<keyof LoggerOptions, true> = { level: true, sink: true };\n\n/** Checks a loaded config's shape. Discord validates intent and partial values itself at login. */\nexport function validateConfig(value: unknown, file: string): NectarConfig {\n const fail = (detail: string): never => {\n throw new ConfigError(file, detail);\n };\n if (!isRecord(value)) fail(\"the default export must be an object. Use defineConfig({ ... }).\");\n const config = value as Record<string, unknown>;\n checkKeys(config, OPTIONS, \"\", fail);\n for (const [key, options] of [\n [\"dev\", DEV_OPTIONS],\n [\"commands\", COMMANDS_OPTIONS],\n [\"logger\", LOGGER_OPTIONS],\n ] as const) {\n if (isRecord(config[key])) checkKeys(config[key], options, `${key}.`, fail);\n }\n\n for (const key of [\"token\", \"applicationId\"] as const) {\n if (config[key] !== undefined && typeof config[key] !== \"string\") {\n fail(`\\`${key}\\` must be a string, usually read from process.env.`);\n }\n }\n if (config.intents === undefined) fail(\"`intents` is required. Use [] for none.\");\n if (!isBitfield(config.intents)) {\n fail(\"`intents` must be an array of intent names or bits, a single bit, or a bigint.\");\n }\n if (config.partials !== undefined && !Array.isArray(config.partials)) {\n fail(\"`partials` must be an array.\");\n }\n if (config.client !== undefined && !isRecord(config.client)) fail(\"`client` must be an object.\");\n if (config.eager !== undefined && typeof config.eager !== \"boolean\") {\n fail(\"`eager` must be a boolean.\");\n }\n if (config.env !== undefined && (typeof config.env !== \"string\" || !ENVS.has(config.env))) {\n fail('`env` must be \"development\", \"test\", or \"production\".');\n }\n if (config.logger !== undefined) {\n if (!isRecord(config.logger)) fail(\"`logger` must be an object.\");\n const { level, sink } = config.logger as Record<string, unknown>;\n if (level !== undefined && (typeof level !== \"string\" || !LEVELS.has(level))) {\n fail('`logger.level` must be \"debug\", \"info\", \"warn\", or \"error\".');\n }\n if (sink !== undefined && typeof sink !== \"function\") {\n fail(\"`logger.sink` must be a function that receives log records.\");\n }\n }\n if (config.observe !== undefined && typeof config.observe !== \"function\") {\n fail(\"`observe` must be a function that receives signals.\");\n }\n if (config.plugins !== undefined) validatePlugins(config.plugins, fail);\n for (const key of [\"appDir\", \"outDir\"] as const) {\n const dir = config[key];\n if (dir !== undefined && (typeof dir !== \"string\" || dir === \"\")) {\n fail(`\\`${key}\\` must be a non-empty string.`);\n }\n }\n if (config.dev !== undefined) {\n if (!isRecord(config.dev)) fail(\"`dev` must be an object.\");\n const guilds = (config.dev as Record<string, unknown>).guilds;\n if (guilds !== undefined && !isGuildList(guilds)) {\n fail(\"`dev.guilds` must be an array of guild ID strings.\");\n }\n }\n if (config.commands !== undefined) {\n if (!isRecord(config.commands)) fail(\"`commands` must be an object.\");\n const target = (config.commands as Record<string, unknown>).target;\n if (target !== undefined && target !== \"global\" && !isGuildList(target)) {\n fail('`commands.target` must be \"global\" or an array of guild ID strings.');\n }\n }\n if (config.environments !== undefined) validateEnvironments(config, file, fail);\n return config as unknown as NectarConfig;\n}\n\n/** Each override must name an environment, and the config it produces must be valid. */\nfunction validateEnvironments(\n config: Record<string, unknown>,\n file: string,\n fail: (detail: string) => never,\n): void {\n if (!isRecord(config.environments)) fail(\"`environments` must be an object.\");\n for (const [name, override] of Object.entries(config.environments as Record<string, unknown>)) {\n const where = `\\`environments.${name}\\``;\n if (!ENVS.has(name)) fail(`${where}: use \"development\", \"test\", or \"production\" as the key.`);\n if (!isRecord(override)) fail(`${where} must be an object.`);\n for (const key of [\"env\", \"environments\"]) {\n if (key in (override as Record<string, unknown>)) fail(`${where} cannot set \\`${key}\\`.`);\n }\n try {\n validateConfig(configFor(config as unknown as NectarConfig, name as Env), file);\n } catch (error) {\n if (error instanceof ConfigError) fail(`${where}: ${error.detail}`);\n throw error;\n }\n }\n}\n\n/** Names `nectar` already answers to. A plugin command cannot take one. */\nconst BUILTIN_COMMANDS = new Set([\n \"dev\",\n \"build\",\n \"check\",\n \"routes\",\n \"manifest\",\n \"sync\",\n \"start\",\n \"clean\",\n \"info\",\n \"help\",\n]);\n\nfunction validatePlugins(value: unknown, fail: (detail: string) => never): void {\n if (!Array.isArray(value)) fail(\"`plugins` must be an array of plugins.\");\n const names = new Set<string>();\n const commands = new Map<string, string>();\n value.forEach((plugin: unknown, index) => {\n if (!isRecord(plugin) || typeof plugin.name !== \"string\" || plugin.name === \"\") {\n fail(\n `\\`plugins[${index}]\\` must be an object with a non-empty \\`name\\`. Use definePlugin({ ... }).`,\n );\n }\n const name = plugin.name as string;\n if (names.has(name)) fail(`Plugin \"${name}\" is listed twice.`);\n names.add(name);\n for (const hook of [\"transform\", \"types\", \"start\", \"stop\", \"startGlobal\", \"stopGlobal\"]) {\n if (plugin[hook] !== undefined && typeof plugin[hook] !== \"function\") {\n fail(`Plugin \"${name}\": \\`${hook}\\` must be a function.`);\n }\n }\n if (plugin.commands === undefined) return;\n if (!Array.isArray(plugin.commands)) fail(`Plugin \"${name}\": \\`commands\\` must be an array.`);\n for (const command of plugin.commands as unknown[]) {\n if (\n !isRecord(command) ||\n typeof command.name !== \"string\" ||\n !/^[a-z][a-z0-9-]*$/.test(command.name) ||\n typeof command.description !== \"string\" ||\n typeof command.run !== \"function\"\n ) {\n fail(\n `Plugin \"${name}\": every command needs a lowercase \\`name\\`, a \\`description\\`, and a \\`run\\` function.`,\n );\n }\n const commandName = command.name as string;\n if (BUILTIN_COMMANDS.has(commandName)) {\n fail(`Plugin \"${name}\": command \"${commandName}\" is built into nectar. Pick another name.`);\n }\n const owner = commands.get(commandName);\n if (owner !== undefined && owner !== name) {\n fail(`Plugins \"${owner}\" and \"${name}\" both define the command \"${commandName}\".`);\n }\n commands.set(commandName, name);\n }\n });\n}\n\n/** Fails on the first key `options` doesn't have, naming a likely intended one when there is one. */\nfunction checkKeys(\n value: Record<string, unknown>,\n options: Record<string, true>,\n prefix: string,\n fail: (detail: string) => never,\n): void {\n for (const key of Object.keys(value)) {\n if (Object.hasOwn(options, key)) continue;\n const near = Object.keys(options).find(\n (option) =>\n option.toLowerCase() === key.toLowerCase() || option === `${key}s` || `${option}s` === key,\n );\n fail(\n `\\`${prefix}${key}\\` isn't a config option.${\n near === undefined\n ? \" The options are listed at https://nectar-js.github.io/nectar/reference/config.\"\n : ` Did you mean \\`${prefix}${near}\\`?`\n }`,\n );\n }\n}\n\nfunction isGuildList(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((g) => typeof g === \"string\" && /^\\d+$/.test(g));\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBitfield(value: unknown): boolean {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"string\") {\n return true;\n }\n if (Array.isArray(value)) {\n return value.every(\n (v) => typeof v === \"number\" || typeof v === \"string\" || typeof v === \"bigint\",\n );\n }\n return isRecord(value) && \"bitfield\" in value;\n}\n","import { type BitFieldResolvable, GatewayIntentBits, IntentsBitField } from \"discord.js\";\nimport type { Diagnostic, DiagnosticCode } from \"../compiler/diagnostics.js\";\nimport type { CompiledEvent } from \"./compile.js\";\n\ntype Intent = keyof typeof GatewayIntentBits;\n\nconst PRIVILEGED: ReadonlySet<Intent> = new Set([\n \"GuildMembers\",\n \"GuildPresences\",\n \"MessageContent\",\n]);\n\nconst guildOrDm = (guild: Intent, dm: Intent): Intent[] => [guild, dm];\n\n/**\n * Which intent a gateway event needs. A list means any one of them is enough: message events\n * arrive with either the guild or the direct message intent. Events missing here need none.\n */\nconst REQUIRED: Record<string, Intent[]> = {\n guildCreate: [\"Guilds\"],\n guildUpdate: [\"Guilds\"],\n guildDelete: [\"Guilds\"],\n guildAvailable: [\"Guilds\"],\n guildUnavailable: [\"Guilds\"],\n channelCreate: [\"Guilds\"],\n channelUpdate: [\"Guilds\"],\n channelDelete: [\"Guilds\"],\n channelPinsUpdate: [\"Guilds\"],\n threadCreate: [\"Guilds\"],\n threadUpdate: [\"Guilds\"],\n threadDelete: [\"Guilds\"],\n threadListSync: [\"Guilds\"],\n threadMemberUpdate: [\"Guilds\"],\n threadMembersUpdate: [\"GuildMembers\"],\n stageInstanceCreate: [\"Guilds\"],\n stageInstanceUpdate: [\"Guilds\"],\n stageInstanceDelete: [\"Guilds\"],\n roleCreate: [\"Guilds\"],\n roleUpdate: [\"Guilds\"],\n roleDelete: [\"Guilds\"],\n guildMemberAdd: [\"GuildMembers\"],\n guildMemberUpdate: [\"GuildMembers\"],\n guildMemberRemove: [\"GuildMembers\"],\n guildMemberAvailable: [\"GuildMembers\"],\n guildMembersChunk: [\"GuildMembers\"],\n userUpdate: [\"GuildMembers\"],\n guildBanAdd: [\"GuildModeration\"],\n guildBanRemove: [\"GuildModeration\"],\n guildAuditLogEntryCreate: [\"GuildModeration\"],\n emojiCreate: [\"GuildExpressions\"],\n emojiUpdate: [\"GuildExpressions\"],\n emojiDelete: [\"GuildExpressions\"],\n stickerCreate: [\"GuildExpressions\"],\n stickerUpdate: [\"GuildExpressions\"],\n stickerDelete: [\"GuildExpressions\"],\n guildSoundboardSoundCreate: [\"GuildExpressions\"],\n guildSoundboardSoundUpdate: [\"GuildExpressions\"],\n guildSoundboardSoundDelete: [\"GuildExpressions\"],\n guildSoundboardSoundsUpdate: [\"GuildExpressions\"],\n guildIntegrationsUpdate: [\"GuildIntegrations\"],\n webhooksUpdate: [\"GuildWebhooks\"],\n inviteCreate: [\"GuildInvites\"],\n inviteDelete: [\"GuildInvites\"],\n voiceStateUpdate: [\"GuildVoiceStates\"],\n voiceChannelEffectSend: [\"GuildVoiceStates\"],\n presenceUpdate: [\"GuildPresences\"],\n messageCreate: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageUpdate: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageDelete: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageDeleteBulk: [\"GuildMessages\"],\n messageReactionAdd: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemove: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemoveAll: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemoveEmoji: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n typingStart: guildOrDm(\"GuildMessageTyping\", \"DirectMessageTyping\"),\n messagePollVoteAdd: guildOrDm(\"GuildMessagePolls\", \"DirectMessagePolls\"),\n messagePollVoteRemove: guildOrDm(\"GuildMessagePolls\", \"DirectMessagePolls\"),\n guildScheduledEventCreate: [\"GuildScheduledEvents\"],\n guildScheduledEventUpdate: [\"GuildScheduledEvents\"],\n guildScheduledEventDelete: [\"GuildScheduledEvents\"],\n guildScheduledEventUserAdd: [\"GuildScheduledEvents\"],\n guildScheduledEventUserRemove: [\"GuildScheduledEvents\"],\n autoModerationRuleCreate: [\"AutoModerationConfiguration\"],\n autoModerationRuleUpdate: [\"AutoModerationConfiguration\"],\n autoModerationRuleDelete: [\"AutoModerationConfiguration\"],\n autoModerationActionExecution: [\"AutoModerationExecution\"],\n};\n\n/** The intents an event route depends on, for tooling. Empty when it needs none. */\nexport function requiredIntents(event: string): readonly Intent[] {\n return REQUIRED[event] ?? [];\n}\n\n/**\n * One warning per event whose handlers can never fire with the configured intents. Never\n * changes the config: privileged intents in particular must be a deliberate choice, made here\n * and in the developer portal.\n */\nexport function checkIntents(\n events: readonly CompiledEvent[],\n intents: BitFieldResolvable<Intent, number>,\n configFile: string,\n): Diagnostic[] {\n const enabled = new IntentsBitField(IntentsBitField.resolve(intents));\n const diagnostics: Diagnostic[] = [];\n for (const event of events) {\n const needed = requiredIntents(event.name);\n if (needed.length === 0 || needed.some((intent) => enabled.has(GatewayIntentBits[intent]))) {\n continue;\n }\n const first = event.handlers[0];\n if (first === undefined) continue;\n const names = needed.map((i) => `\"${i}\"`).join(\" or \");\n const privileged = needed.filter((i) => PRIVILEGED.has(i));\n diagnostics.push({\n code: \"missing-intent\" satisfies DiagnosticCode,\n severity: \"warning\",\n message: `\"${event.name}\" never fires without the ${names} intent. Add it to intents in ${configFile}.${\n privileged.length === 0\n ? \"\"\n : ` ${privileged.map((i) => `\"${i}\"`).join(\" and \")} is privileged, so also turn it on under Bot in the Discord Developer Portal.`\n }`,\n file: first.route.file,\n route: first.route.id,\n });\n }\n return diagnostics;\n}\n","import type {\n APIApplicationCommand,\n APIApplicationCommandOption,\n RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { ApplicationCommandType, ApplicationIntegrationType } from \"discord-api-types/v10\";\n\n/**\n * A command reduced to the fields that decide whether Discord would consider it changed.\n *\n * Discord fills in defaults (`nsfw: false`, `integration_types: [0]`, `required: false`, empty\n * localization maps as `null`) and adds bookkeeping (`id`, `version`, `application_id`,\n * `dm_permission`) when it echoes a command back. Both sides pass through here so a no-op sync\n * produces an empty diff.\n */\nexport type NormalizedCommand = Record<string, unknown>;\n\nexport type AnyCommand = RESTPostAPIApplicationCommandsJSONBody | APIApplicationCommand;\n\n/** `<type>:<name>`. Discord allows the same name across command types. */\nexport function commandKey(command: { type?: number | undefined; name: string }): string {\n return `${command.type ?? ApplicationCommandType.ChatInput}:${command.name}`;\n}\n\nexport function normalizeCommand(command: AnyCommand): NormalizedCommand {\n const c = command as unknown as Record<string, unknown>;\n const type = (c.type as number | undefined) ?? ApplicationCommandType.ChatInput;\n return compact({\n type,\n name: c.name,\n name_localizations: localizations(c.name_localizations),\n description: type === ApplicationCommandType.ChatInput ? (c.description ?? \"\") : \"\",\n description_localizations: localizations(c.description_localizations),\n options: options(c.options),\n default_member_permissions:\n c.default_member_permissions == null ? undefined : String(c.default_member_permissions),\n nsfw: c.nsfw === true ? true : undefined,\n contexts: numbers(c.contexts),\n integration_types: numbers(c.integration_types) ?? [ApplicationIntegrationType.GuildInstall],\n });\n}\n\nfunction options(value: unknown): NormalizedCommand[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n return (value as APIApplicationCommandOption[]).map((option) => {\n const o = option as unknown as Record<string, unknown>;\n return compact({\n type: o.type,\n name: o.name,\n name_localizations: localizations(o.name_localizations),\n description: o.description,\n description_localizations: localizations(o.description_localizations),\n required: o.required === true ? true : undefined,\n autocomplete: o.autocomplete === true ? true : undefined,\n choices: choices(o.choices),\n options: options(o.options),\n channel_types: numbers(o.channel_types),\n min_value: number(o.min_value),\n max_value: number(o.max_value),\n min_length: number(o.min_length),\n max_length: number(o.max_length),\n });\n });\n}\n\nfunction choices(value: unknown): NormalizedCommand[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n return value.map((choice: Record<string, unknown>) =>\n compact({\n name: choice.name,\n value: choice.value,\n name_localizations: localizations(choice.name_localizations),\n }),\n );\n}\n\nfunction localizations(value: unknown): Record<string, string> | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const entries = Object.entries(value as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n );\n if (entries.length === 0) return undefined;\n entries.sort(([a], [b]) => a.localeCompare(b));\n return Object.fromEntries(entries);\n}\n\n/** Sorted, deduplicated. Discord treats these as sets. */\nfunction numbers(value: unknown): number[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return [...new Set(value as number[])].sort((a, b) => a - b);\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n\nfunction compact(object: Record<string, unknown>): NormalizedCommand {\n const out: NormalizedCommand = {};\n for (const [key, value] of Object.entries(object)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n","import { stableStringify } from \"../manifest/emit.js\";\nimport { type AnyCommand, commandKey, normalizeCommand } from \"./normalize.js\";\n\nexport interface CommandDiff {\n /** Command names, `type:name` when the type is not chat input. */\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n hasChanges: boolean;\n}\n\n/** Compares what the app wants registered with what Discord currently has. */\nexport function diffCommands(desired: AnyCommand[], remote: AnyCommand[]): CommandDiff {\n const want = new Map(desired.map((c) => [commandKey(c), stableStringify(normalizeCommand(c))]));\n const have = new Map(remote.map((c) => [commandKey(c), stableStringify(normalizeCommand(c))]));\n\n const diff: CommandDiff = {\n added: [],\n removed: [],\n changed: [],\n unchanged: [],\n hasChanges: false,\n };\n for (const [key, body] of want) {\n const current = have.get(key);\n if (current === undefined) diff.added.push(label(key));\n else if (current === body) diff.unchanged.push(label(key));\n else diff.changed.push(label(key));\n }\n for (const key of have.keys()) {\n if (!want.has(key)) diff.removed.push(label(key));\n }\n for (const list of [diff.added, diff.removed, diff.changed, diff.unchanged]) list.sort();\n diff.hasChanges = diff.added.length + diff.removed.length + diff.changed.length > 0;\n return diff;\n}\n\nfunction label(key: string): string {\n return key.startsWith(\"1:\") ? key.slice(2) : key;\n}\n","import type {\n APIApplicationCommand,\n RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { Routes } from \"discord-api-types/v10\";\nimport { RegistrationError } from \"./errors.js\";\n\n/** The two calls registration needs. discord.js's `REST` satisfies this. */\nexport interface CommandRest {\n get(route: `/${string}`, options?: { query: URLSearchParams }): Promise<unknown>;\n put(route: `/${string}`, options: { body: unknown }): Promise<unknown>;\n}\n\nexport type Scope = \"global\" | { guild: string };\n\n/** `global` or `guild:<id>`. Used for cache keys and messages. */\nexport function scopeKey(scope: Scope): string {\n return scope === \"global\" ? \"global\" : `guild:${scope.guild}`;\n}\n\nfunction scopeRoute(applicationId: string, scope: Scope): `/${string}` {\n return scope === \"global\"\n ? Routes.applicationCommands(applicationId)\n : Routes.applicationGuildCommands(applicationId, scope.guild);\n}\n\n/** With full localization maps, which Discord leaves out unless asked. */\nexport async function fetchCommands(\n rest: CommandRest,\n applicationId: string,\n scope: Scope,\n): Promise<APIApplicationCommand[]> {\n return (await rest.get(scopeRoute(applicationId, scope), {\n query: new URLSearchParams({ with_localizations: \"true\" }),\n })) as APIApplicationCommand[];\n}\n\n/** Bulk overwrite: Discord replaces the scope's whole command set with `commands`. */\nexport async function putCommands(\n rest: CommandRest,\n applicationId: string,\n scope: Scope,\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n): Promise<void> {\n try {\n await rest.put(scopeRoute(applicationId, scope), { body: commands });\n } catch (error) {\n throw RegistrationError.from(error, scope, commands) ?? error;\n }\n}\n","import type { RESTPostAPIApplicationCommandsJSONBody } from \"discord-api-types/v10\";\nimport { type Scope, scopeKey } from \"./remote.js\";\n\nexport interface RegistrationProblem {\n /** Command name, or `null` when Discord rejected the request as a whole. */\n command: string | null;\n /** Dotted path inside the command, with option and choice indices replaced by their names. */\n field: string;\n message: string;\n}\n\n/** Discord rejected a bulk overwrite. Wraps the `DiscordAPIError` with per-command detail. */\nexport class RegistrationError extends Error {\n constructor(\n readonly scope: Scope,\n readonly problems: RegistrationProblem[],\n override readonly cause: unknown,\n ) {\n super(\n `Discord rejected the ${scopeKey(scope)} command registration:\\n${problems\n .map(\n (p) =>\n ` ${p.command ?? \"(request)\"}${p.field === \"\" ? \"\" : ` ${p.field}`}: ${p.message}`,\n )\n .join(\"\\n\")}`,\n );\n this.name = \"RegistrationError\";\n }\n\n /** `null` when `error` is not a Discord API error. */\n static from(\n error: unknown,\n scope: Scope,\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n ): RegistrationError | null {\n if (!isDiscordApiError(error)) return null;\n const problems: RegistrationProblem[] = [];\n if (error.rawError.errors !== undefined) {\n collect(error.rawError.errors, [], commands, problems);\n }\n if (problems.length === 0) {\n problems.push({ command: null, field: \"\", message: error.rawError.message });\n }\n return new RegistrationError(scope, problems, error);\n }\n}\n\ninterface DiscordApiErrorLike {\n rawError: { message: string; errors?: unknown };\n}\n\nfunction isDiscordApiError(error: unknown): error is DiscordApiErrorLike {\n if (typeof error !== \"object\" || error === null || !(\"rawError\" in error)) return false;\n const raw = (error as { rawError: unknown }).rawError;\n return (\n typeof raw === \"object\" &&\n raw !== null &&\n typeof (raw as { message?: unknown }).message === \"string\"\n );\n}\n\n/**\n * Discord nests errors by request path, `{ \"0\": { options: { \"1\": { description: { _errors } } } } }`.\n * The top-level index is the command in the bulk body; deeper indices are options and choices.\n */\nfunction collect(\n node: unknown,\n path: (string | number)[],\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n out: RegistrationProblem[],\n) {\n if (typeof node === \"string\") {\n out.push(problem(path, commands, node));\n return;\n }\n if (typeof node !== \"object\" || node === null) return;\n for (const [key, value] of Object.entries(node)) {\n if (key === \"_errors\" && Array.isArray(value)) {\n for (const entry of value) {\n const message =\n typeof entry === \"object\" && entry !== null && \"message\" in entry\n ? String((entry as { message: unknown }).message)\n : String(entry);\n out.push(problem(path, commands, message));\n }\n } else {\n collect(value, [...path, /^\\d+$/.test(key) ? Number(key) : key], commands, out);\n }\n }\n}\n\nfunction problem(\n path: (string | number)[],\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n message: string,\n): RegistrationProblem {\n const [first, ...rest] = path;\n const command = typeof first === \"number\" ? commands[first] : undefined;\n if (command === undefined) return { command: null, field: path.join(\".\"), message };\n\n const field: string[] = [];\n let cursor: unknown = command;\n for (const segment of rest) {\n if (typeof segment === \"number\" && Array.isArray(cursor)) {\n cursor = cursor[segment];\n const name = (cursor as { name?: unknown } | undefined)?.name;\n field.push(typeof name === \"string\" ? name : String(segment));\n } else {\n cursor = (cursor as Record<string, unknown> | undefined)?.[segment];\n field.push(String(segment));\n }\n }\n return { command: command.name, field: field.join(\".\"), message };\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type APIApplicationCommand,\n ApplicationCommandType,\n type RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { stableStringify } from \"../manifest/emit.js\";\nimport { type CommandDiff, diffCommands } from \"./diff.js\";\nimport { commandKey, normalizeCommand } from \"./normalize.js\";\nimport { type CommandRest, fetchCommands, putCommands, type Scope, scopeKey } from \"./remote.js\";\n\nexport const REGISTRATION_CACHE_FILE = \"registration.json\";\nconst CACHE_VERSION = 1;\n\n/**\n * The command types an app declares. Other commands in a scope, like the Entry Point command\n * Discord creates for Activities, go back unchanged in every overwrite: Discord rejects one\n * that drops them, with error 50240.\n */\nconst DECLARED_TYPES: ReadonlySet<number> = new Set([\n ApplicationCommandType.ChatInput,\n ApplicationCommandType.User,\n ApplicationCommandType.Message,\n]);\n\n/** What the last successful sync sent, so an unchanged app skips the remote read. */\ninterface RegistrationCache {\n version: typeof CACHE_VERSION;\n applicationId: string;\n /** Scope key to hash of the normalized payloads registered there. */\n scopes: Record<string, string>;\n}\n\nexport interface SyncOptions {\n rest: CommandRest;\n applicationId: string;\n commands: RESTPostAPIApplicationCommandsJSONBody[];\n scopes: Scope[];\n /** Directory holding `registration.json`. No cache when omitted. */\n cacheDir?: string;\n /** Compute diffs and report, but write nothing to Discord or the cache. */\n dryRun?: boolean;\n /** Proceed past the safety guard. */\n force?: boolean;\n}\n\nexport interface ScopeSync {\n scope: Scope;\n /** `null` when the cache proved nothing changed and Discord was not consulted. */\n diff: CommandDiff | null;\n /** A bulk overwrite was sent. */\n applied: boolean;\n}\n\nexport interface SyncResult {\n scopes: ScopeSync[];\n /** Why the guard would block this sync. Empty when it is safe. */\n unsafe: string[];\n}\n\n/** Thrown instead of applying when the guard trips and `force` is not set. */\nexport class UnsafeSyncError extends Error {\n constructor(readonly reasons: string[]) {\n super(\n `Refusing to register commands:\\n${reasons.map((r) => ` ${r}`).join(\"\\n\")}\\nPass force to do it anyway.`,\n );\n this.name = \"UnsafeSyncError\";\n }\n}\n\n/**\n * Reconciles every scope: read remote, diff, bulk overwrite only when something differs.\n * All scopes are read and checked before any is written, so a guard failure changes nothing.\n */\nexport async function syncCommands(options: SyncOptions): Promise<SyncResult> {\n const {\n rest,\n applicationId,\n commands,\n scopes,\n cacheDir,\n dryRun = false,\n force = false,\n } = options;\n const cache = cacheDir === undefined ? null : readCache(cacheDir);\n const hash = hashCommands(commands);\n const unsafe: string[] = [];\n\n if (cache !== null && cache.applicationId !== applicationId) {\n unsafe.push(\n `The application ID changed from ${cache.applicationId} to ${applicationId}. Commands registered under the old application are left as they are.`,\n );\n }\n const targets = new Set(scopes.map(scopeKey));\n for (const key of Object.keys(cache?.scopes ?? {})) {\n if (!targets.has(key)) {\n unsafe.push(\n `${key} received commands last time but is no longer a target. Its commands stay registered on Discord until removed.`,\n );\n }\n }\n\n const results: ScopeSync[] = [];\n /** Remote commands of other types, by scope key, to send back in the overwrite. */\n const kept = new Map<string, RESTPostAPIApplicationCommandsJSONBody[]>();\n for (const scope of scopes) {\n const key = scopeKey(scope);\n const cached =\n cache !== null && cache.applicationId === applicationId && cache.scopes[key] === hash;\n if (cached) {\n results.push({ scope, diff: null, applied: false });\n continue;\n }\n const remote = await fetchCommands(rest, applicationId, scope);\n const declared = remote.filter((c) => DECLARED_TYPES.has(c.type));\n kept.set(key, remote.filter((c) => !DECLARED_TYPES.has(c.type)).map(resubmit));\n const diff = diffCommands(commands, declared);\n if (commands.length === 0 && declared.length > 0) {\n unsafe.push(`${key} has ${declared.length} command(s) registered and the app declares none.`);\n }\n results.push({ scope, diff, applied: false });\n }\n\n if (dryRun) return { scopes: results, unsafe };\n if (unsafe.length > 0 && !force) throw new UnsafeSyncError(unsafe);\n\n const next: RegistrationCache = { version: CACHE_VERSION, applicationId, scopes: {} };\n for (const result of results) {\n if (result.diff?.hasChanges) {\n const body = [...commands, ...(kept.get(scopeKey(result.scope)) ?? [])];\n await putCommands(rest, applicationId, result.scope, body);\n result.applied = true;\n }\n next.scopes[scopeKey(result.scope)] = hash;\n // Persist after every scope so a failure halfway does not forget the ones already done.\n if (cacheDir !== undefined) writeCache(cacheDir, next);\n }\n return { scopes: results, unsafe };\n}\n\n/** A fetched command as an overwrite takes it, without the fields Discord fills in itself. */\nfunction resubmit(command: APIApplicationCommand): RESTPostAPIApplicationCommandsJSONBody {\n const {\n id: _id,\n application_id: _application,\n guild_id: _guild,\n version: _version,\n name_localized: _name,\n description_localized: _description,\n ...body\n } = command;\n // Its type is one Nectar doesn't declare, so the body is passed through as Discord gave it.\n return body as RESTPostAPIApplicationCommandsJSONBody;\n}\n\n/** Order-insensitive, like the diff: reordering commands is not a change. */\nfunction hashCommands(commands: RESTPostAPIApplicationCommandsJSONBody[]): string {\n const normalized = commands\n .map((c) => [commandKey(c), normalizeCommand(c)] as const)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([, c]) => c);\n return createHash(\"sha256\").update(stableStringify(normalized)).digest(\"hex\");\n}\n\nfunction readCache(dir: string): RegistrationCache | null {\n let text: string;\n try {\n text = readFileSync(path.join(dir, REGISTRATION_CACHE_FILE), \"utf8\");\n } catch {\n return null;\n }\n const parsed: unknown = JSON.parse(text);\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n (parsed as { version?: unknown }).version !== CACHE_VERSION\n ) {\n return null;\n }\n return parsed as RegistrationCache;\n}\n\nfunction writeCache(dir: string, cache: RegistrationCache) {\n mkdirSync(dir, { recursive: true });\n writeFileSync(path.join(dir, REGISTRATION_CACHE_FILE), `${stableStringify(cache)}\\n`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"registration-mUZJidnF.js","names":[],"sources":["../src/config.ts","../src/events/intents.ts","../src/registration/normalize.ts","../src/registration/diff.ts","../src/registration/remote.ts","../src/registration/errors.ts","../src/registration/sync.ts"],"sourcesContent":["import type { ClientOptions } from \"discord.js\";\nimport type { NectarPlugin } from \"./plugins/index.js\";\nimport type { LoggerOptions } from \"./runtime/logger.js\";\nimport type { Signal } from \"./runtime/signals.js\";\nimport type { Env } from \"./runtime/types.js\";\n\n/** `nectar.config.ts`: `export default defineConfig({ ... })`. */\nexport interface NectarConfig {\n /**\n * Bot token. Usually `process.env.DISCORD_TOKEN`; the CLI falls back to that variable when\n * this is omitted or empty.\n */\n token?: string | undefined;\n /** Application ID, for command registration. Falls back to `DISCORD_APPLICATION_ID`. */\n applicationId?: string | undefined;\n intents: ClientOptions[\"intents\"];\n partials?: ClientOptions[\"partials\"];\n /** Extra discord.js client options. `intents` and `partials` above take precedence. */\n client?: Partial<ClientOptions>;\n /** Import every handler at startup. Defaults to `true` in production, `false` otherwise. */\n eager?: boolean;\n /** Overrides `NODE_ENV`. */\n env?: Env;\n /**\n * Framework log level and sink. The default sink prints to the console; pass `sink` to hand\n * records to your own logger. Handlers are free to log however they like.\n */\n logger?: LoggerOptions;\n /** Called with every framework signal: interaction lifecycle, failures, gateway state, shutdown. */\n observe?: (signal: Signal) => void;\n /** Plugins, in the order their hooks run. This is the only way to register one. */\n plugins?: NectarPlugin[];\n /** Route directory, relative to the project root. Defaults to `app`. */\n appDir?: string;\n /** Build output, relative to the project root. Defaults to `.nectar`. */\n outDir?: string;\n dev?: {\n /** Guilds that receive commands instantly while developing. */\n guilds?: string[];\n };\n commands?: {\n /**\n * Where commands are registered outside development: everywhere, or only in the listed\n * guilds. Defaults to `\"global\"`. Development always uses `dev.guilds`.\n */\n target?: \"global\" | string[];\n };\n /**\n * Overrides for one environment, applied once the environment is known. Each key replaces\n * the value above it; nested objects are not merged.\n */\n environments?: Partial<Record<Env, Partial<Omit<NectarConfig, \"env\" | \"environments\">>>>;\n}\n\nexport function defineConfig(config: NectarConfig): NectarConfig {\n return config;\n}\n\n/** The config one environment runs with: `environments[env]` over the rest. */\nexport function configFor(config: NectarConfig, env: Env): NectarConfig {\n const { environments, ...base } = config;\n return { ...base, ...environments?.[env] };\n}\n\nexport class ConfigError extends Error {\n constructor(\n readonly file: string,\n readonly detail: string,\n ) {\n super(`${file}: ${detail}`);\n this.name = \"ConfigError\";\n }\n}\n\nconst ENVS = new Set<string>([\"development\", \"test\", \"production\"]);\nconst LEVELS = new Set<string>([\"debug\", \"info\", \"warn\", \"error\"]);\n\n/** Every option, so a misspelled or removed one fails instead of being ignored. */\nconst OPTIONS: Record<keyof NectarConfig, true> = {\n token: true,\n applicationId: true,\n intents: true,\n partials: true,\n client: true,\n eager: true,\n env: true,\n logger: true,\n observe: true,\n plugins: true,\n appDir: true,\n outDir: true,\n dev: true,\n commands: true,\n environments: true,\n};\nconst DEV_OPTIONS: Record<keyof NonNullable<NectarConfig[\"dev\"]>, true> = { guilds: true };\nconst COMMANDS_OPTIONS: Record<keyof NonNullable<NectarConfig[\"commands\"]>, true> = {\n target: true,\n};\nconst LOGGER_OPTIONS: Record<keyof LoggerOptions, true> = { level: true, sink: true };\n\n/** Checks a loaded config's shape. Discord validates intent and partial values itself at login. */\nexport function validateConfig(value: unknown, file: string): NectarConfig {\n const fail = (detail: string): never => {\n throw new ConfigError(file, detail);\n };\n if (!isRecord(value)) fail(\"the default export must be an object. Use defineConfig({ ... }).\");\n const config = value as Record<string, unknown>;\n checkKeys(config, OPTIONS, \"\", fail);\n for (const [key, options] of [\n [\"dev\", DEV_OPTIONS],\n [\"commands\", COMMANDS_OPTIONS],\n [\"logger\", LOGGER_OPTIONS],\n ] as const) {\n if (isRecord(config[key])) checkKeys(config[key], options, `${key}.`, fail);\n }\n\n for (const key of [\"token\", \"applicationId\"] as const) {\n if (config[key] !== undefined && typeof config[key] !== \"string\") {\n fail(`\\`${key}\\` must be a string, usually read from process.env.`);\n }\n }\n if (config.intents === undefined) fail(\"`intents` is required. Use [] for none.\");\n if (!isBitfield(config.intents)) {\n fail(\"`intents` must be an array of intent names or bits, a single bit, or a bigint.\");\n }\n if (config.partials !== undefined && !Array.isArray(config.partials)) {\n fail(\"`partials` must be an array.\");\n }\n if (config.client !== undefined && !isRecord(config.client)) fail(\"`client` must be an object.\");\n if (config.eager !== undefined && typeof config.eager !== \"boolean\") {\n fail(\"`eager` must be a boolean.\");\n }\n if (config.env !== undefined && (typeof config.env !== \"string\" || !ENVS.has(config.env))) {\n fail('`env` must be \"development\", \"test\", or \"production\".');\n }\n if (config.logger !== undefined) {\n if (!isRecord(config.logger)) fail(\"`logger` must be an object.\");\n const { level, sink } = config.logger as Record<string, unknown>;\n if (level !== undefined && (typeof level !== \"string\" || !LEVELS.has(level))) {\n fail('`logger.level` must be \"debug\", \"info\", \"warn\", or \"error\".');\n }\n if (sink !== undefined && typeof sink !== \"function\") {\n fail(\"`logger.sink` must be a function that receives log records.\");\n }\n }\n if (config.observe !== undefined && typeof config.observe !== \"function\") {\n fail(\"`observe` must be a function that receives signals.\");\n }\n if (config.plugins !== undefined) validatePlugins(config.plugins, fail);\n for (const key of [\"appDir\", \"outDir\"] as const) {\n const dir = config[key];\n if (dir !== undefined && (typeof dir !== \"string\" || dir === \"\")) {\n fail(`\\`${key}\\` must be a non-empty string.`);\n }\n }\n if (config.dev !== undefined) {\n if (!isRecord(config.dev)) fail(\"`dev` must be an object.\");\n const guilds = (config.dev as Record<string, unknown>).guilds;\n if (guilds !== undefined && !isGuildList(guilds)) {\n fail(\"`dev.guilds` must be an array of guild ID strings.\");\n }\n }\n if (config.commands !== undefined) {\n if (!isRecord(config.commands)) fail(\"`commands` must be an object.\");\n const target = (config.commands as Record<string, unknown>).target;\n if (target !== undefined && target !== \"global\" && !isGuildList(target)) {\n fail('`commands.target` must be \"global\" or an array of guild ID strings.');\n }\n }\n if (config.environments !== undefined) validateEnvironments(config, file, fail);\n return config as unknown as NectarConfig;\n}\n\n/** Each override must name an environment, and the config it produces must be valid. */\nfunction validateEnvironments(\n config: Record<string, unknown>,\n file: string,\n fail: (detail: string) => never,\n): void {\n if (!isRecord(config.environments)) fail(\"`environments` must be an object.\");\n for (const [name, override] of Object.entries(config.environments as Record<string, unknown>)) {\n const where = `\\`environments.${name}\\``;\n if (!ENVS.has(name)) fail(`${where}: use \"development\", \"test\", or \"production\" as the key.`);\n if (!isRecord(override)) fail(`${where} must be an object.`);\n for (const key of [\"env\", \"environments\"]) {\n if (key in (override as Record<string, unknown>)) fail(`${where} cannot set \\`${key}\\`.`);\n }\n try {\n validateConfig(configFor(config as unknown as NectarConfig, name as Env), file);\n } catch (error) {\n if (error instanceof ConfigError) fail(`${where}: ${error.detail}`);\n throw error;\n }\n }\n}\n\n/** Names `nectar` already answers to. A plugin command cannot take one. */\nconst BUILTIN_COMMANDS = new Set([\n \"dev\",\n \"build\",\n \"check\",\n \"routes\",\n \"manifest\",\n \"sync\",\n \"start\",\n \"clean\",\n \"info\",\n \"help\",\n]);\n\nfunction validatePlugins(value: unknown, fail: (detail: string) => never): void {\n if (!Array.isArray(value)) fail(\"`plugins` must be an array of plugins.\");\n const names = new Set<string>();\n const commands = new Map<string, string>();\n value.forEach((plugin: unknown, index) => {\n if (!isRecord(plugin) || typeof plugin.name !== \"string\" || plugin.name === \"\") {\n fail(\n `\\`plugins[${index}]\\` must be an object with a non-empty \\`name\\`. Use definePlugin({ ... }).`,\n );\n }\n const name = plugin.name as string;\n if (names.has(name)) fail(`Plugin \"${name}\" is listed twice.`);\n names.add(name);\n for (const hook of [\"transform\", \"types\", \"start\", \"stop\", \"startGlobal\", \"stopGlobal\"]) {\n if (plugin[hook] !== undefined && typeof plugin[hook] !== \"function\") {\n fail(`Plugin \"${name}\": \\`${hook}\\` must be a function.`);\n }\n }\n if (plugin.commands === undefined) return;\n if (!Array.isArray(plugin.commands)) fail(`Plugin \"${name}\": \\`commands\\` must be an array.`);\n for (const command of plugin.commands as unknown[]) {\n if (\n !isRecord(command) ||\n typeof command.name !== \"string\" ||\n !/^[a-z][a-z0-9-]*$/.test(command.name) ||\n typeof command.description !== \"string\" ||\n typeof command.run !== \"function\"\n ) {\n fail(\n `Plugin \"${name}\": every command needs a lowercase \\`name\\`, a \\`description\\`, and a \\`run\\` function.`,\n );\n }\n const commandName = command.name as string;\n if (BUILTIN_COMMANDS.has(commandName)) {\n fail(`Plugin \"${name}\": command \"${commandName}\" is built into nectar. Pick another name.`);\n }\n const owner = commands.get(commandName);\n if (owner !== undefined && owner !== name) {\n fail(`Plugins \"${owner}\" and \"${name}\" both define the command \"${commandName}\".`);\n }\n commands.set(commandName, name);\n }\n });\n}\n\n/** Fails on the first key `options` doesn't have, naming a likely intended one when there is one. */\nfunction checkKeys(\n value: Record<string, unknown>,\n options: Record<string, true>,\n prefix: string,\n fail: (detail: string) => never,\n): void {\n for (const key of Object.keys(value)) {\n if (Object.hasOwn(options, key)) continue;\n const near = Object.keys(options).find(\n (option) =>\n option.toLowerCase() === key.toLowerCase() || option === `${key}s` || `${option}s` === key,\n );\n fail(\n `\\`${prefix}${key}\\` isn't a config option.${\n near === undefined\n ? \" The options are listed at https://nectar-js.github.io/nectar/reference/config.\"\n : ` Did you mean \\`${prefix}${near}\\`?`\n }`,\n );\n }\n}\n\nfunction isGuildList(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((g) => typeof g === \"string\" && /^\\d+$/.test(g));\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBitfield(value: unknown): boolean {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"string\") {\n return true;\n }\n if (Array.isArray(value)) {\n return value.every(\n (v) => typeof v === \"number\" || typeof v === \"string\" || typeof v === \"bigint\",\n );\n }\n return isRecord(value) && \"bitfield\" in value;\n}\n","import { type BitFieldResolvable, GatewayIntentBits, IntentsBitField } from \"discord.js\";\nimport type { Diagnostic, DiagnosticCode } from \"../compiler/diagnostics.js\";\nimport type { CompiledEvent } from \"./compile.js\";\n\ntype Intent = keyof typeof GatewayIntentBits;\n\nconst PRIVILEGED: ReadonlySet<Intent> = new Set([\n \"GuildMembers\",\n \"GuildPresences\",\n \"MessageContent\",\n]);\n\nconst guildOrDm = (guild: Intent, dm: Intent): Intent[] => [guild, dm];\n\n/**\n * Which intent a gateway event needs. A list means any one of them is enough: message events\n * arrive with either the guild or the direct message intent. Events missing here need none.\n */\nconst REQUIRED: Record<string, Intent[]> = {\n guildCreate: [\"Guilds\"],\n guildUpdate: [\"Guilds\"],\n guildDelete: [\"Guilds\"],\n guildAvailable: [\"Guilds\"],\n guildUnavailable: [\"Guilds\"],\n channelCreate: [\"Guilds\"],\n channelUpdate: [\"Guilds\"],\n channelDelete: [\"Guilds\"],\n channelPinsUpdate: [\"Guilds\"],\n threadCreate: [\"Guilds\"],\n threadUpdate: [\"Guilds\"],\n threadDelete: [\"Guilds\"],\n threadListSync: [\"Guilds\"],\n threadMemberUpdate: [\"Guilds\"],\n threadMembersUpdate: [\"GuildMembers\"],\n stageInstanceCreate: [\"Guilds\"],\n stageInstanceUpdate: [\"Guilds\"],\n stageInstanceDelete: [\"Guilds\"],\n roleCreate: [\"Guilds\"],\n roleUpdate: [\"Guilds\"],\n roleDelete: [\"Guilds\"],\n guildMemberAdd: [\"GuildMembers\"],\n guildMemberUpdate: [\"GuildMembers\"],\n guildMemberRemove: [\"GuildMembers\"],\n guildMemberAvailable: [\"GuildMembers\"],\n guildMembersChunk: [\"GuildMembers\"],\n userUpdate: [\"GuildMembers\"],\n guildBanAdd: [\"GuildModeration\"],\n guildBanRemove: [\"GuildModeration\"],\n guildAuditLogEntryCreate: [\"GuildModeration\"],\n emojiCreate: [\"GuildExpressions\"],\n emojiUpdate: [\"GuildExpressions\"],\n emojiDelete: [\"GuildExpressions\"],\n stickerCreate: [\"GuildExpressions\"],\n stickerUpdate: [\"GuildExpressions\"],\n stickerDelete: [\"GuildExpressions\"],\n guildSoundboardSoundCreate: [\"GuildExpressions\"],\n guildSoundboardSoundUpdate: [\"GuildExpressions\"],\n guildSoundboardSoundDelete: [\"GuildExpressions\"],\n guildSoundboardSoundsUpdate: [\"GuildExpressions\"],\n guildIntegrationsUpdate: [\"GuildIntegrations\"],\n webhooksUpdate: [\"GuildWebhooks\"],\n inviteCreate: [\"GuildInvites\"],\n inviteDelete: [\"GuildInvites\"],\n voiceStateUpdate: [\"GuildVoiceStates\"],\n voiceChannelEffectSend: [\"GuildVoiceStates\"],\n presenceUpdate: [\"GuildPresences\"],\n messageCreate: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageUpdate: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageDelete: guildOrDm(\"GuildMessages\", \"DirectMessages\"),\n messageDeleteBulk: [\"GuildMessages\"],\n messageReactionAdd: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemove: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemoveAll: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n messageReactionRemoveEmoji: guildOrDm(\"GuildMessageReactions\", \"DirectMessageReactions\"),\n typingStart: guildOrDm(\"GuildMessageTyping\", \"DirectMessageTyping\"),\n messagePollVoteAdd: guildOrDm(\"GuildMessagePolls\", \"DirectMessagePolls\"),\n messagePollVoteRemove: guildOrDm(\"GuildMessagePolls\", \"DirectMessagePolls\"),\n guildScheduledEventCreate: [\"GuildScheduledEvents\"],\n guildScheduledEventUpdate: [\"GuildScheduledEvents\"],\n guildScheduledEventDelete: [\"GuildScheduledEvents\"],\n guildScheduledEventUserAdd: [\"GuildScheduledEvents\"],\n guildScheduledEventUserRemove: [\"GuildScheduledEvents\"],\n autoModerationRuleCreate: [\"AutoModerationConfiguration\"],\n autoModerationRuleUpdate: [\"AutoModerationConfiguration\"],\n autoModerationRuleDelete: [\"AutoModerationConfiguration\"],\n autoModerationActionExecution: [\"AutoModerationExecution\"],\n};\n\n/** The intents an event route depends on, for tooling. Empty when it needs none. */\nexport function requiredIntents(event: string): readonly Intent[] {\n return REQUIRED[event] ?? [];\n}\n\n/**\n * One warning per event whose handlers can never fire with the configured intents. Never\n * changes the config: privileged intents in particular must be a deliberate choice, made here\n * and in the developer portal.\n */\nexport function checkIntents(\n events: readonly CompiledEvent[],\n intents: BitFieldResolvable<Intent, number>,\n configFile: string,\n): Diagnostic[] {\n const enabled = new IntentsBitField(IntentsBitField.resolve(intents));\n const diagnostics: Diagnostic[] = [];\n for (const event of events) {\n const needed = requiredIntents(event.name);\n if (needed.length === 0 || needed.some((intent) => enabled.has(GatewayIntentBits[intent]))) {\n continue;\n }\n const first = event.handlers[0];\n if (first === undefined) continue;\n const names = needed.map((i) => `\"${i}\"`).join(\" or \");\n const privileged = needed.filter((i) => PRIVILEGED.has(i));\n diagnostics.push({\n code: \"missing-intent\" satisfies DiagnosticCode,\n severity: \"warning\",\n message: `\"${event.name}\" never fires without the ${names} intent. Add it to intents in ${configFile}.${\n privileged.length === 0\n ? \"\"\n : ` ${privileged.map((i) => `\"${i}\"`).join(\" and \")} is privileged, so also turn it on under Bot in the Discord Developer Portal.`\n }`,\n file: first.route.file,\n route: first.route.id,\n });\n }\n return diagnostics;\n}\n","import type {\n APIApplicationCommand,\n APIApplicationCommandOption,\n RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { ApplicationCommandType, ApplicationIntegrationType } from \"discord-api-types/v10\";\n\n/**\n * A command reduced to the fields that decide whether Discord would consider it changed.\n *\n * Discord fills in defaults (`nsfw: false`, `integration_types: [0]`, `required: false`, empty\n * localization maps as `null`) and adds bookkeeping (`id`, `version`, `application_id`,\n * `dm_permission`) when it echoes a command back. Both sides pass through here so a no-op sync\n * produces an empty diff.\n */\nexport type NormalizedCommand = Record<string, unknown>;\n\nexport type AnyCommand = RESTPostAPIApplicationCommandsJSONBody | APIApplicationCommand;\n\n/** `<type>:<name>`. Discord allows the same name across command types. */\nexport function commandKey(command: { type?: number | undefined; name: string }): string {\n return `${command.type ?? ApplicationCommandType.ChatInput}:${command.name}`;\n}\n\nexport function normalizeCommand(command: AnyCommand): NormalizedCommand {\n const c = command as unknown as Record<string, unknown>;\n const type = (c.type as number | undefined) ?? ApplicationCommandType.ChatInput;\n return compact({\n type,\n name: c.name,\n name_localizations: localizations(c.name_localizations),\n description: type === ApplicationCommandType.ChatInput ? (c.description ?? \"\") : \"\",\n description_localizations: localizations(c.description_localizations),\n options: options(c.options),\n default_member_permissions:\n c.default_member_permissions == null ? undefined : String(c.default_member_permissions),\n nsfw: c.nsfw === true ? true : undefined,\n contexts: numbers(c.contexts),\n integration_types: numbers(c.integration_types) ?? [ApplicationIntegrationType.GuildInstall],\n });\n}\n\nfunction options(value: unknown): NormalizedCommand[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n return (value as APIApplicationCommandOption[]).map((option) => {\n const o = option as unknown as Record<string, unknown>;\n return compact({\n type: o.type,\n name: o.name,\n name_localizations: localizations(o.name_localizations),\n description: o.description,\n description_localizations: localizations(o.description_localizations),\n required: o.required === true ? true : undefined,\n autocomplete: o.autocomplete === true ? true : undefined,\n choices: choices(o.choices),\n options: options(o.options),\n channel_types: numbers(o.channel_types),\n min_value: number(o.min_value),\n max_value: number(o.max_value),\n min_length: number(o.min_length),\n max_length: number(o.max_length),\n });\n });\n}\n\nfunction choices(value: unknown): NormalizedCommand[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n return value.map((choice: Record<string, unknown>) =>\n compact({\n name: choice.name,\n value: choice.value,\n name_localizations: localizations(choice.name_localizations),\n }),\n );\n}\n\nfunction localizations(value: unknown): Record<string, string> | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const entries = Object.entries(value as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n );\n if (entries.length === 0) return undefined;\n entries.sort(([a], [b]) => a.localeCompare(b));\n return Object.fromEntries(entries);\n}\n\n/** Sorted, deduplicated. Discord treats these as sets. */\nfunction numbers(value: unknown): number[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return [...new Set(value as number[])].sort((a, b) => a - b);\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n\nfunction compact(object: Record<string, unknown>): NormalizedCommand {\n const out: NormalizedCommand = {};\n for (const [key, value] of Object.entries(object)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n","import { stableStringify } from \"../manifest/emit.js\";\nimport { type AnyCommand, commandKey, normalizeCommand } from \"./normalize.js\";\n\nexport interface CommandDiff {\n /** Command names, `type:name` when the type is not chat input. */\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n hasChanges: boolean;\n}\n\n/** Compares what the app wants registered with what Discord currently has. */\nexport function diffCommands(desired: AnyCommand[], remote: AnyCommand[]): CommandDiff {\n const want = new Map(desired.map((c) => [commandKey(c), stableStringify(normalizeCommand(c))]));\n const have = new Map(remote.map((c) => [commandKey(c), stableStringify(normalizeCommand(c))]));\n\n const diff: CommandDiff = {\n added: [],\n removed: [],\n changed: [],\n unchanged: [],\n hasChanges: false,\n };\n for (const [key, body] of want) {\n const current = have.get(key);\n if (current === undefined) diff.added.push(label(key));\n else if (current === body) diff.unchanged.push(label(key));\n else diff.changed.push(label(key));\n }\n for (const key of have.keys()) {\n if (!want.has(key)) diff.removed.push(label(key));\n }\n for (const list of [diff.added, diff.removed, diff.changed, diff.unchanged]) list.sort();\n diff.hasChanges = diff.added.length + diff.removed.length + diff.changed.length > 0;\n return diff;\n}\n\nfunction label(key: string): string {\n return key.startsWith(\"1:\") ? key.slice(2) : key;\n}\n","import type {\n APIApplicationCommand,\n RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { Routes } from \"discord-api-types/v10\";\nimport { RegistrationError } from \"./errors.js\";\n\n/** The two calls registration needs. discord.js's `REST` satisfies this. */\nexport interface CommandRest {\n get(route: `/${string}`, options?: { query: URLSearchParams }): Promise<unknown>;\n put(route: `/${string}`, options: { body: unknown }): Promise<unknown>;\n}\n\nexport type Scope = \"global\" | { guild: string };\n\n/** `global` or `guild:<id>`. Used for cache keys and messages. */\nexport function scopeKey(scope: Scope): string {\n return scope === \"global\" ? \"global\" : `guild:${scope.guild}`;\n}\n\nfunction scopeRoute(applicationId: string, scope: Scope): `/${string}` {\n return scope === \"global\"\n ? Routes.applicationCommands(applicationId)\n : Routes.applicationGuildCommands(applicationId, scope.guild);\n}\n\n/** With full localization maps, which Discord leaves out unless asked. */\nexport async function fetchCommands(\n rest: CommandRest,\n applicationId: string,\n scope: Scope,\n): Promise<APIApplicationCommand[]> {\n return (await rest.get(scopeRoute(applicationId, scope), {\n query: new URLSearchParams({ with_localizations: \"true\" }),\n })) as APIApplicationCommand[];\n}\n\n/** Bulk overwrite: Discord replaces the scope's whole command set with `commands`. */\nexport async function putCommands(\n rest: CommandRest,\n applicationId: string,\n scope: Scope,\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n): Promise<void> {\n try {\n await rest.put(scopeRoute(applicationId, scope), { body: commands });\n } catch (error) {\n throw RegistrationError.from(error, scope, commands) ?? error;\n }\n}\n","import type { RESTPostAPIApplicationCommandsJSONBody } from \"discord-api-types/v10\";\nimport { type Scope, scopeKey } from \"./remote.js\";\n\nexport interface RegistrationProblem {\n /** Command name, or `null` when Discord rejected the request as a whole. */\n command: string | null;\n /** Dotted path inside the command, with option and choice indices replaced by their names. */\n field: string;\n message: string;\n}\n\n/** Discord rejected a bulk overwrite. Wraps the `DiscordAPIError` with per-command detail. */\nexport class RegistrationError extends Error {\n constructor(\n readonly scope: Scope,\n readonly problems: RegistrationProblem[],\n override readonly cause: unknown,\n ) {\n super(\n `Discord rejected the ${scopeKey(scope)} command registration:\\n${problems\n .map(\n (p) =>\n ` ${p.command ?? \"(request)\"}${p.field === \"\" ? \"\" : ` ${p.field}`}: ${p.message}`,\n )\n .join(\"\\n\")}`,\n );\n this.name = \"RegistrationError\";\n }\n\n /** `null` when `error` is not a Discord API error. */\n static from(\n error: unknown,\n scope: Scope,\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n ): RegistrationError | null {\n if (!isDiscordApiError(error)) return null;\n const problems: RegistrationProblem[] = [];\n if (error.rawError.errors !== undefined) {\n collect(error.rawError.errors, [], commands, problems);\n }\n if (problems.length === 0) {\n problems.push({ command: null, field: \"\", message: error.rawError.message });\n }\n return new RegistrationError(scope, problems, error);\n }\n}\n\ninterface DiscordApiErrorLike {\n rawError: { message: string; errors?: unknown };\n}\n\nfunction isDiscordApiError(error: unknown): error is DiscordApiErrorLike {\n if (typeof error !== \"object\" || error === null || !(\"rawError\" in error)) return false;\n const raw = (error as { rawError: unknown }).rawError;\n return (\n typeof raw === \"object\" &&\n raw !== null &&\n typeof (raw as { message?: unknown }).message === \"string\"\n );\n}\n\n/**\n * Discord nests errors by request path, `{ \"0\": { options: { \"1\": { description: { _errors } } } } }`.\n * The top-level index is the command in the bulk body; deeper indices are options and choices.\n */\nfunction collect(\n node: unknown,\n path: (string | number)[],\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n out: RegistrationProblem[],\n) {\n if (typeof node === \"string\") {\n out.push(problem(path, commands, node));\n return;\n }\n if (typeof node !== \"object\" || node === null) return;\n for (const [key, value] of Object.entries(node)) {\n if (key === \"_errors\" && Array.isArray(value)) {\n for (const entry of value) {\n const message =\n typeof entry === \"object\" && entry !== null && \"message\" in entry\n ? String((entry as { message: unknown }).message)\n : String(entry);\n out.push(problem(path, commands, message));\n }\n } else {\n collect(value, [...path, /^\\d+$/.test(key) ? Number(key) : key], commands, out);\n }\n }\n}\n\nfunction problem(\n path: (string | number)[],\n commands: RESTPostAPIApplicationCommandsJSONBody[],\n message: string,\n): RegistrationProblem {\n const [first, ...rest] = path;\n const command = typeof first === \"number\" ? commands[first] : undefined;\n if (command === undefined) return { command: null, field: path.join(\".\"), message };\n\n const field: string[] = [];\n let cursor: unknown = command;\n for (const segment of rest) {\n if (typeof segment === \"number\" && Array.isArray(cursor)) {\n cursor = cursor[segment];\n const name = (cursor as { name?: unknown } | undefined)?.name;\n field.push(typeof name === \"string\" ? name : String(segment));\n } else {\n cursor = (cursor as Record<string, unknown> | undefined)?.[segment];\n field.push(String(segment));\n }\n }\n return { command: command.name, field: field.join(\".\"), message };\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type APIApplicationCommand,\n ApplicationCommandType,\n type RESTPostAPIApplicationCommandsJSONBody,\n} from \"discord-api-types/v10\";\nimport { stableStringify } from \"../manifest/emit.js\";\nimport { type CommandDiff, diffCommands } from \"./diff.js\";\nimport { commandKey, normalizeCommand } from \"./normalize.js\";\nimport { type CommandRest, fetchCommands, putCommands, type Scope, scopeKey } from \"./remote.js\";\n\nexport const REGISTRATION_CACHE_FILE = \"registration.json\";\nconst CACHE_VERSION = 1;\n\n/**\n * The command types an app declares. Other commands in a scope, like the Entry Point command\n * Discord creates for Activities, go back unchanged in every overwrite: Discord rejects one\n * that drops them, with error 50240.\n */\nconst DECLARED_TYPES: ReadonlySet<number> = new Set([\n ApplicationCommandType.ChatInput,\n ApplicationCommandType.User,\n ApplicationCommandType.Message,\n]);\n\n/** What the last successful sync sent, so an unchanged app skips the remote read. */\ninterface RegistrationCache {\n version: typeof CACHE_VERSION;\n applicationId: string;\n /** Scope key to hash of the normalized payloads registered there. */\n scopes: Record<string, string>;\n}\n\nexport interface SyncOptions {\n rest: CommandRest;\n applicationId: string;\n commands: RESTPostAPIApplicationCommandsJSONBody[];\n scopes: Scope[];\n /** Directory holding `registration.json`. No cache when omitted. */\n cacheDir?: string;\n /** Compute diffs and report, but write nothing to Discord or the cache. */\n dryRun?: boolean;\n /** Proceed past the safety guard. */\n force?: boolean;\n}\n\nexport interface ScopeSync {\n scope: Scope;\n /** `null` when the cache proved nothing changed and Discord was not consulted. */\n diff: CommandDiff | null;\n /** A bulk overwrite was sent. */\n applied: boolean;\n}\n\nexport interface SyncResult {\n scopes: ScopeSync[];\n /** Why the guard would block this sync. Empty when it is safe. */\n unsafe: string[];\n}\n\n/** Thrown instead of applying when the guard trips and `force` is not set. */\nexport class UnsafeSyncError extends Error {\n constructor(readonly reasons: string[]) {\n super(\n `Refusing to register commands:\\n${reasons.map((r) => ` ${r}`).join(\"\\n\")}\\nPass force to do it anyway.`,\n );\n this.name = \"UnsafeSyncError\";\n }\n}\n\n/**\n * Reconciles every scope: read remote, diff, bulk overwrite only when something differs.\n * All scopes are read and checked before any is written, so a guard failure changes nothing.\n */\nexport async function syncCommands(options: SyncOptions): Promise<SyncResult> {\n const {\n rest,\n applicationId,\n commands,\n scopes,\n cacheDir,\n dryRun = false,\n force = false,\n } = options;\n const cache = cacheDir === undefined ? null : readCache(cacheDir);\n const hash = hashCommands(commands);\n const unsafe: string[] = [];\n\n if (cache !== null && cache.applicationId !== applicationId) {\n unsafe.push(\n `The application ID changed from ${cache.applicationId} to ${applicationId}. Commands registered under the old application are left as they are.`,\n );\n }\n const targets = new Set(scopes.map(scopeKey));\n for (const key of Object.keys(cache?.scopes ?? {})) {\n if (!targets.has(key)) {\n unsafe.push(\n `${key} received commands last time but is no longer a target. Its commands stay registered on Discord until removed.`,\n );\n }\n }\n\n const results: ScopeSync[] = [];\n /** Remote commands of other types, by scope key, to send back in the overwrite. */\n const kept = new Map<string, RESTPostAPIApplicationCommandsJSONBody[]>();\n for (const scope of scopes) {\n const key = scopeKey(scope);\n const cached =\n cache !== null && cache.applicationId === applicationId && cache.scopes[key] === hash;\n if (cached) {\n results.push({ scope, diff: null, applied: false });\n continue;\n }\n const remote = await fetchCommands(rest, applicationId, scope);\n const declared = remote.filter((c) => DECLARED_TYPES.has(c.type));\n kept.set(key, remote.filter((c) => !DECLARED_TYPES.has(c.type)).map(resubmit));\n const diff = diffCommands(commands, declared);\n if (commands.length === 0 && declared.length > 0) {\n unsafe.push(`${key} has ${declared.length} command(s) registered and the app declares none.`);\n }\n results.push({ scope, diff, applied: false });\n }\n\n if (dryRun) return { scopes: results, unsafe };\n if (unsafe.length > 0 && !force) throw new UnsafeSyncError(unsafe);\n\n const next: RegistrationCache = { version: CACHE_VERSION, applicationId, scopes: {} };\n for (const result of results) {\n if (result.diff?.hasChanges) {\n const body = [...commands, ...(kept.get(scopeKey(result.scope)) ?? [])];\n await putCommands(rest, applicationId, result.scope, body);\n result.applied = true;\n }\n next.scopes[scopeKey(result.scope)] = hash;\n // Persist after every scope so a failure halfway does not forget the ones already done.\n if (cacheDir !== undefined) writeCache(cacheDir, next);\n }\n return { scopes: results, unsafe };\n}\n\n/** A fetched command as an overwrite takes it, without the fields Discord fills in itself. */\nfunction resubmit(command: APIApplicationCommand): RESTPostAPIApplicationCommandsJSONBody {\n const {\n id: _id,\n application_id: _application,\n guild_id: _guild,\n version: _version,\n name_localized: _name,\n description_localized: _description,\n ...body\n } = command;\n // Its type is one Nectar doesn't declare, so the body is passed through as Discord gave it.\n return body as RESTPostAPIApplicationCommandsJSONBody;\n}\n\n/** Order-insensitive, like the diff: reordering commands is not a change. */\nfunction hashCommands(commands: RESTPostAPIApplicationCommandsJSONBody[]): string {\n const normalized = commands\n .map((c) => [commandKey(c), normalizeCommand(c)] as const)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([, c]) => c);\n return createHash(\"sha256\").update(stableStringify(normalized)).digest(\"hex\");\n}\n\nfunction readCache(dir: string): RegistrationCache | null {\n let text: string;\n try {\n text = readFileSync(path.join(dir, REGISTRATION_CACHE_FILE), \"utf8\");\n } catch {\n return null;\n }\n const parsed: unknown = JSON.parse(text);\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n (parsed as { version?: unknown }).version !== CACHE_VERSION\n ) {\n return null;\n }\n return parsed as RegistrationCache;\n}\n\nfunction writeCache(dir: string, cache: RegistrationCache) {\n mkdirSync(dir, { recursive: true });\n writeFileSync(path.join(dir, REGISTRATION_CACHE_FILE), `${stableStringify(cache)}\\n`);\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- import { E as decodeCustomId, b as loadModule, h as paramValidatorsOf, m as findInvalidParam, t as PluginError, u as registerComponentRoutes } from "./plugins-BCYwtuo-.js";
1
+ import { E as decodeCustomId, b as loadModule, h as paramValidatorsOf, m as findInvalidParam, t as PluginError, u as registerComponentRoutes } from "./plugins-C2uwN3-D.js";
2
2
  import path from "node:path";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { Client, DiscordjsError, DiscordjsErrorCodes, Events } from "discord.js";
5
- import { ApplicationCommandType, MessageFlags } from "discord-api-types/v10";
5
+ import { ApplicationCommandOptionType, ApplicationCommandType, MessageFlags } from "discord-api-types/v10";
6
6
  //#region src/components/matcher.ts
7
7
  /**
8
8
  * Resolves incoming custom IDs to routes.
@@ -63,6 +63,57 @@ function loadManifest(file) {
63
63
  };
64
64
  }
65
65
  //#endregion
66
+ //#region src/commands/options.ts
67
+ const OPTION_TYPE = {
68
+ [ApplicationCommandOptionType.String]: "string",
69
+ [ApplicationCommandOptionType.Integer]: "integer",
70
+ [ApplicationCommandOptionType.Number]: "number",
71
+ [ApplicationCommandOptionType.Boolean]: "boolean",
72
+ [ApplicationCommandOptionType.User]: "user",
73
+ [ApplicationCommandOptionType.Channel]: "channel",
74
+ [ApplicationCommandOptionType.Role]: "role",
75
+ [ApplicationCommandOptionType.Mentionable]: "mentionable",
76
+ [ApplicationCommandOptionType.Attachment]: "attachment"
77
+ };
78
+ /**
79
+ * The options of one handler position (`""`, `"sub"`, or `"group/sub"`) of a registration
80
+ * payload, in declaration order. Subcommands and groups are not options of the handler.
81
+ */
82
+ function optionsAt(payload, key) {
83
+ let options = payload.options;
84
+ for (const part of key === "" ? [] : key.split("/")) options = options?.find((o) => o.name === part)?.options;
85
+ const out = [];
86
+ for (const option of options ?? []) {
87
+ const type = option.type === void 0 ? void 0 : OPTION_TYPE[option.type];
88
+ if (option.name !== void 0 && type !== void 0) out.push({
89
+ name: option.name,
90
+ type,
91
+ required: option.required === true
92
+ });
93
+ }
94
+ return out;
95
+ }
96
+ const READ = {
97
+ string: (o, n) => o.getString(n),
98
+ integer: (o, n) => o.getInteger(n),
99
+ number: (o, n) => o.getNumber(n),
100
+ boolean: (o, n) => o.getBoolean(n),
101
+ user: (o, n) => o.getUser(n),
102
+ channel: (o, n) => o.getChannel(n),
103
+ role: (o, n) => o.getRole(n),
104
+ mentionable: (o, n) => o.getMentionable(n),
105
+ attachment: (o, n) => o.getAttachment(n)
106
+ };
107
+ /**
108
+ * Every option of the handler by name, read through discord.js's resolver. Options the user
109
+ * left out are `null`, so the handler's `ctx.options` always has every declared key.
110
+ */
111
+ function resolveOptions(interaction, specs) {
112
+ const out = {};
113
+ for (const { name, type } of specs) out[name] = READ[type](interaction.options, name);
114
+ return out;
115
+ }
116
+ //#endregion
66
117
  //#region src/runtime/signals.ts
67
118
  /**
68
119
  * Fan-out for framework signals. Listeners run synchronously in subscription order; one that
@@ -182,9 +233,10 @@ async function defaultBoundary(error, ctx, logger, middleware) {
182
233
  if (!("interaction" in ctx)) return;
183
234
  const interaction = ctx.interaction;
184
235
  if (typeof interaction.isRepliable !== "function" || !interaction.isRepliable()) return;
185
- if (interaction.replied || interaction.deferred) return;
236
+ if (interaction.replied) return;
186
237
  try {
187
- await interaction.reply({
238
+ if (interaction.deferred) await interaction.editReply({ content: GENERIC_ERROR_REPLY });
239
+ else await interaction.reply({
188
240
  content: GENERIC_ERROR_REPLY,
189
241
  flags: MessageFlags.Ephemeral
190
242
  });
@@ -371,7 +423,7 @@ function createInteractionDispatcher(state) {
371
423
  const key = [interaction.options.getSubcommandGroup(false), interaction.options.getSubcommand(false)].filter((p) => p !== null).join("/");
372
424
  const route = tables.commands.get(commandKey(ApplicationCommandType.ChatInput, interaction.commandName, key));
373
425
  if (route === void 0) return unknown(state, interaction, meta, `chat input command /${meta.command}`);
374
- return run(state, route, interaction, meta, {}, receivedAt);
426
+ return run(state, route, interaction, meta, {}, receivedAt, resolveOptions(interaction, tables.options.get(route.id) ?? []));
375
427
  }
376
428
  if (interaction.isContextMenuCommand()) {
377
429
  const route = tables.commands.get(commandKey(interaction.commandType, interaction.commandName, ""));
@@ -384,7 +436,7 @@ function createInteractionDispatcher(state) {
384
436
  const route = command === void 0 ? void 0 : tables.autocomplete.get(command.id);
385
437
  const option = interaction.options.getFocused(true).name;
386
438
  if (route === void 0 || !route.options.includes(option)) return unknown(state, interaction, meta, `autocomplete for /${meta.command} "${option}"`);
387
- return run(state, route, interaction, meta, {}, receivedAt, option);
439
+ return run(state, route, interaction, meta, {}, receivedAt, {}, option);
388
440
  }
389
441
  const component = interaction.isButton() ? ["button", interaction.customId] : interaction.isAnySelectMenu() ? ["select", interaction.customId] : interaction.isModalSubmit() ? ["modal", interaction.customId] : null;
390
442
  if (component === null) {
@@ -416,12 +468,13 @@ function createInteractionDispatcher(state) {
416
468
  return run(state, match.route, interaction, meta, match.params, receivedAt);
417
469
  };
418
470
  }
419
- async function run(state, route, interaction, meta, params, receivedAt, autocompleteOption) {
471
+ async function run(state, route, interaction, meta, params, receivedAt, options = {}, autocompleteOption) {
420
472
  const ctx = {
421
473
  interaction,
422
474
  client: state.client,
423
475
  route: routeInfo(state, route),
424
476
  params,
477
+ options,
425
478
  env: state.env,
426
479
  trace: {
427
480
  id: interaction.id,
@@ -462,7 +515,8 @@ async function run(state, route, interaction, meta, params, receivedAt, autocomp
462
515
  return;
463
516
  }
464
517
  }
465
- await runChain(middleware, ctx, handler, {
518
+ const deferred = route.kind === "command" && route.defer !== null ? route.defer : null;
519
+ await runChain(middleware, ctx, deferred === null ? handler : deferring(handler, deferred), {
466
520
  middleware: (index) => state.signals.emit({
467
521
  type: "middleware:enter",
468
522
  ...tag,
@@ -506,6 +560,17 @@ async function run(state, route, interaction, meta, params, receivedAt, autocomp
506
560
  if (autocompleteOption !== void 0) await closeAutocomplete(interaction);
507
561
  }
508
562
  }
563
+ /**
564
+ * Defers the reply right before the handler, after the middleware chain, so a policy check can
565
+ * still answer with its own message. A middleware that already replied or deferred wins.
566
+ */
567
+ function deferring(handler, mode) {
568
+ return async (ctx) => {
569
+ const interaction = ctx.interaction;
570
+ if (!interaction.replied && !interaction.deferred) await interaction.deferReply(mode === "ephemeral" ? { flags: MessageFlags.Ephemeral } : {});
571
+ return handler(ctx);
572
+ };
573
+ }
509
574
  /** Discord shows a spinner until autocomplete answers, so a failed handler answers with nothing. */
510
575
  async function closeAutocomplete(interaction) {
511
576
  if (interaction.responded) return;
@@ -552,12 +617,16 @@ function buildTables(routes, commands) {
552
617
  else if (route.kind === "autocomplete") autocomplete.set(route.id, route);
553
618
  else if (route.kind !== "event") components.push(route);
554
619
  const table = /* @__PURE__ */ new Map();
620
+ const options = /* @__PURE__ */ new Map();
555
621
  for (const command of commands) for (const [key, id] of Object.entries(command.handlers)) {
556
622
  const route = commandRoutes.get(id);
557
- if (route !== void 0) table.set(commandKey(command.type, command.name, key), route);
623
+ if (route === void 0) continue;
624
+ table.set(commandKey(command.type, command.name, key), route);
625
+ options.set(id, optionsAt(command.payload, key));
558
626
  }
559
627
  return {
560
628
  commands: table,
629
+ options,
561
630
  autocomplete,
562
631
  matcher: createMatcher(components)
563
632
  };
@@ -898,6 +967,6 @@ function envFromProcess() {
898
967
  return value === "production" || value === "test" ? value : "development";
899
968
  }
900
969
  //#endregion
901
- export { bindEvents as a, ModuleRegistry as c, createLogger as i, createSignals as l, createRuntime as n, createInteractionDispatcher as o, manifestFiles as r, HandlerLoadError as s, LoginError as t, loadManifest as u };
970
+ export { bindEvents as a, ModuleRegistry as c, loadManifest as d, createLogger as i, createSignals as l, createRuntime as n, createInteractionDispatcher as o, manifestFiles as r, HandlerLoadError as s, LoginError as t, optionsAt as u };
902
971
 
903
- //# sourceMappingURL=runtime-B_bjrg1W.js.map
972
+ //# sourceMappingURL=runtime-B7mCDO5Q.js.map