@nectar-js/nectar 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type AnySelectMenuInteraction,\n type Attachment,\n type AutocompleteInteraction,\n BaseInteraction,\n type ButtonInteraction,\n type Channel,\n Client,\n type ClientEvents,\n CommandInteractionOptionResolver,\n type GuildMember,\n type Interaction,\n type ModalSubmitInteraction,\n type Role,\n SnowflakeUtil,\n type User,\n} from \"discord.js\";\nimport {\n ApplicationCommandOptionType,\n ApplicationCommandType,\n ComponentType,\n InteractionType,\n} from \"discord-api-types/v10\";\nimport type { OptionType } from \"./commands/meta.js\";\nimport { customIdFor, registerComponentRoutes, type SelectKind } from \"./components/index.js\";\nimport type {\n CommandContext,\n CommandPath,\n CommandRoutes,\n ComponentContext,\n ComponentParams,\n ComponentPath,\n} from \"./define.js\";\nimport type { NectarRoutes, NectarServices } from \"./index.js\";\nimport {\n loadManifest,\n type Manifest,\n type ManifestCommand,\n type ManifestComponentRoute,\n} from \"./manifest/index.js\";\nimport {\n bindEvents,\n createInteractionDispatcher,\n createLogger,\n createSignals,\n type Env,\n type InteractionContext,\n type Logger,\n ModuleRegistry,\n type RuntimeState,\n type Signal,\n} from \"./runtime/index.js\";\n\ntype Empty = Record<never, never>;\n\n/**\n * A stand-in for a discord.js object. Property names are checked against the real type;\n * values are not, so pass whatever the handler reads, like `guild`, `member`, or `values`.\n */\nexport type StubFields<T> = T extends unknown ? { [K in keyof T]?: unknown } : never;\n\n/** What a test passes for each option type. */\ninterface OptionValues {\n string: string;\n integer: number;\n number: number;\n boolean: boolean;\n user: StubFields<User>;\n channel: StubFields<Channel>;\n role: StubFields<Role>;\n mentionable: StubFields<User | GuildMember | Role>;\n attachment: StubFields<Attachment>;\n}\n\ntype OptionTypes<P extends CommandPath> = CommandRoutes[P] extends { options: infer O }\n ? O\n : Record<string, OptionType>;\n\n/** Option values by name, typed from the command's generated options. */\nexport type CommandOptions<P extends CommandPath> = [keyof OptionTypes<P>] extends [never]\n ? Record<string, never>\n : { [K in keyof OptionTypes<P>]?: OptionValues[OptionTypes<P>[K] & OptionType] };\n\ntype AutocompleteRoutes = NectarRoutes extends { autocomplete: infer A }\n ? A\n : Record<string, string>;\n\n/** Command paths that have an `autocomplete.ts`. */\nexport type AutocompletePath = keyof AutocompleteRoutes & string;\n\n/** A route's context with the interaction narrowed to the kind being tested. */\ntype With<C, I> = Omit<C, \"interaction\"> & { interaction: I };\n\ntype ComponentArgs<P extends ComponentPath, I> =\n Empty extends ComponentParams<P>\n ? [params?: ComponentParams<P>, interaction?: StubFields<I>]\n : [params: ComponentParams<P>, interaction?: StubFields<I>];\n\ntype SelectInteraction<P extends ComponentPath> = Extract<\n ComponentContext<P>[\"interaction\"],\n AnySelectMenuInteraction\n>;\n\ntype ResponseMethod =\n | \"reply\"\n | \"deferReply\"\n | \"editReply\"\n | \"followUp\"\n | \"deleteReply\"\n | \"update\"\n | \"deferUpdate\"\n | \"showModal\"\n | \"respond\";\n\nexport interface TestResponse {\n method: ResponseMethod;\n /** The first argument the handler passed. */\n options: unknown;\n}\n\n/**\n * The signal that ended dispatch: `interaction:complete` (with `handled: false` when a\n * middleware stopped the chain), `interaction:fail` (with the error and the boundary that took\n * it), or `interaction:reject` (refused before any application code ran).\n */\nexport type Outcome = Extract<\n Signal,\n { type: \"interaction:complete\" | \"interaction:fail\" | \"interaction:reject\" }\n>;\n\nexport interface InteractionResult<I, C = InteractionContext<I>> {\n /** The stub that was dispatched. */\n interaction: I;\n /** What the route sent back through the interaction, in call order. */\n responses: TestResponse[];\n /** What the handler was called with, middleware additions included. `null` if it never ran. */\n context: C | null;\n outcome: Outcome;\n /** Every signal the dispatch emitted, `interaction:start` through the outcome. */\n signals: Signal[];\n}\n\nexport interface EventResult {\n /** One per handler that threw, naming the boundary that took the error. */\n failures: Extract<Signal, { type: \"event:fail\" }>[];\n}\n\nexport interface TestAppOptions {\n /** Handlers see it as `ctx.client`. Defaults to a discord.js client that never logs in. */\n client?: Client;\n /** Defaults to `\"test\"`. */\n env?: Env;\n /** Receives dispatch warnings and the default error boundary's output. Defaults to the console. */\n logger?: Logger;\n /** What handlers find on `ctx.services`, in place of plugin `start` hooks. */\n services?: Partial<NectarServices>;\n}\n\n/**\n * Runs routes from a built manifest through the runtime's own dispatch, without a gateway.\n * The interaction methods build a stubbed discord.js interaction for the route and resolve\n * once the middleware chain, handler, and error boundaries are done.\n */\nexport interface TestApp {\n readonly client: Client;\n /**\n * Runs a chat input or context menu command. `options` are values by name; the handler\n * reads them through discord.js's own option resolver.\n */\n command<P extends CommandPath>(\n path: P,\n options?: CommandOptions<P>,\n interaction?: StubFields<CommandContext<P>[\"interaction\"]>,\n ): Promise<InteractionResult<CommandContext<P>[\"interaction\"], CommandContext<P>>>;\n /**\n * Runs the autocomplete handler for `focused`, whose value is `options[focused]` or `\"\"`.\n * As in a real autocomplete, user, channel, role, and attachment options arrive as IDs only.\n */\n autocomplete<P extends AutocompletePath>(\n path: P,\n focused: AutocompleteRoutes[P] & string,\n options?: CommandOptions<P & CommandPath>,\n interaction?: StubFields<AutocompleteInteraction>,\n ): Promise<\n InteractionResult<\n AutocompleteInteraction,\n With<CommandContext<P & CommandPath>, AutocompleteInteraction>\n >\n >;\n /** Clicks a button. The custom ID is encoded from `params`, then decoded and validated. */\n button<P extends ComponentPath>(\n path: P,\n ...args: ComponentArgs<P, ButtonInteraction>\n ): Promise<InteractionResult<ButtonInteraction, With<ComponentContext<P>, ButtonInteraction>>>;\n /** Submits a select menu. `values` starts empty; set it, or `users` and the like, on the stub. */\n select<P extends ComponentPath>(\n path: P,\n ...args: ComponentArgs<P, SelectInteraction<P>>\n ): Promise<\n InteractionResult<SelectInteraction<P>, With<ComponentContext<P>, SelectInteraction<P>>>\n >;\n /** Submits a modal. Set `fields` on the stub when the handler reads inputs. */\n modal<P extends ComponentPath>(\n path: P,\n ...args: ComponentArgs<P, ModalSubmitInteraction>\n ): Promise<\n InteractionResult<ModalSubmitInteraction, With<ComponentContext<P>, ModalSubmitInteraction>>\n >;\n /**\n * Emits a discord.js event to its routes, in manifest order and mode, and resolves once they\n * finish. A `once` handler runs on the first call only, as it would in the runtime.\n */\n event<N extends keyof ClientEvents>(name: N, ...args: ClientEvents[N]): Promise<EventResult>;\n}\n\n/** `manifestFile` is a `.nectar/manifest.json` written by `nectar build` or `nectar dev`. */\nexport function createTestApp(manifestFile: string | URL, options: TestAppOptions = {}): TestApp {\n const file = manifestFile instanceof URL ? fileURLToPath(manifestFile) : manifestFile;\n if (!existsSync(file)) {\n throw new Error(`${file} not found. Run \\`nectar build\\` before the tests.`);\n }\n const { manifest, appDir } = loadManifest(file);\n const client = options.client ?? new Client({ intents: [] });\n const logger = options.logger ?? createLogger();\n const signals = createSignals(logger);\n const contexts = new Map<string, InteractionContext>();\n const handlers = manifest.routes\n .filter((r) => r.kind !== \"event\")\n .map((r) => path.join(appDir, ...r.file.split(\"/\")));\n const state: RuntimeState = {\n manifest,\n appDir,\n client,\n modules: new RecordingModules(new Set(handlers), contexts),\n env: options.env ?? \"test\",\n logger,\n signals,\n services: (options.services ?? {}) as NectarServices,\n };\n registerComponentRoutes(manifest.routes.filter(isComponentRoute));\n const dispatch = createInteractionDispatcher(state);\n const events = bindEvents(state);\n\n async function invoke<I, C>({ interaction, responses }: Stub): Promise<InteractionResult<I, C>> {\n const seen: Signal[] = [];\n const off = signals.on((signal) => {\n if (\"trace\" in signal && signal.trace === interaction.id) seen.push(signal);\n });\n try {\n await dispatch(interaction as unknown as Interaction);\n } finally {\n off();\n }\n const outcome = seen.at(-1);\n if (outcome === undefined || !isOutcome(outcome)) {\n throw new Error(`Dispatch of ${interaction.id} ended without an outcome signal.`);\n }\n const context = contexts.get(interaction.id) ?? null;\n contexts.delete(interaction.id);\n return {\n interaction: interaction as I,\n responses,\n context: context as C | null,\n outcome,\n signals: seen,\n };\n }\n\n function component(\n kind: ManifestComponentRoute[\"kind\"],\n path: string,\n params: ComponentParams<ComponentPath> = {},\n fields?: object,\n ): Stub {\n const route = manifest.routes.find(\n (r): r is ManifestComponentRoute => r.kind === kind && r.path === path,\n );\n if (route === undefined) throw missing(kind, path);\n const customId = customIdFor(route, params);\n const base = { customId, replied: false, deferred: false };\n if (route.kind === \"modal\") {\n return stub(client, { ...base, type: InteractionType.ModalSubmit }, MODAL, fields);\n }\n const componentType =\n route.selectKind === null ? ComponentType.Button : SELECT_TYPES[route.selectKind];\n const values = route.kind === \"select\" ? { values: [] } : {};\n return stub(\n client,\n { ...base, ...values, type: InteractionType.MessageComponent, componentType },\n COMPONENT,\n fields,\n );\n }\n\n return {\n client,\n\n async command(path, values = {}, fields) {\n const { command, position } = findCommand(manifest, path);\n return invoke(\n stub(\n client,\n {\n type: InteractionType.ApplicationCommand,\n commandType: command.type,\n commandName: command.name,\n options: resolver(client, optionTree(command, position, values)),\n replied: false,\n deferred: false,\n },\n COMMAND,\n fields,\n ),\n );\n },\n\n async autocomplete(path, focused, values = {}, fields) {\n const route = manifest.routes.find((r) => r.kind === \"autocomplete\" && r.path === path);\n if (route?.kind !== \"autocomplete\") throw missing(\"autocomplete\", path);\n if (!route.options.includes(focused)) {\n throw new Error(\n `Route ${route.id} has no autocomplete handler for \"${focused}\". It handles: ${route.options.join(\", \")}.`,\n );\n }\n const { command, position } = findCommand(manifest, path);\n return invoke(\n stub(\n client,\n {\n type: InteractionType.ApplicationCommandAutocomplete,\n commandType: ApplicationCommandType.ChatInput,\n commandName: command.name,\n options: resolver(client, optionTree(command, position, values, focused)),\n responded: false,\n },\n AUTOCOMPLETE,\n fields,\n ),\n );\n },\n\n async button(path, ...args) {\n return invoke(component(\"button\", path, args[0], args[1]));\n },\n\n async select(path, ...args) {\n return invoke(component(\"select\", path, args[0], args[1]));\n },\n\n async modal(path, ...args) {\n return invoke(component(\"modal\", path, args[0], args[1]));\n },\n\n async event(name, ...args) {\n const binding = events.find((b) => b.name === name);\n if (binding === undefined) throw missing(\"event\", name);\n const failures: EventResult[\"failures\"] = [];\n const off = signals.on((signal) => {\n if (signal.type === \"event:fail\" && signal.event === name) failures.push(signal);\n });\n try {\n await binding.listener(...args);\n } finally {\n off();\n }\n return { failures };\n },\n };\n}\n\n/**\n * Hands out interaction handlers behind a proxy that records the context each call receives,\n * keyed by trace ID. Middleware, error boundaries, and event handlers pass through untouched.\n */\nclass RecordingModules extends ModuleRegistry {\n private readonly proxies = new WeakMap<object, unknown>();\n\n constructor(\n private readonly handlers: ReadonlySet<string>,\n private readonly contexts: Map<string, InteractionContext>,\n ) {\n super();\n }\n\n override async loadDefault<T>(file: string, what: string): Promise<T> {\n return this.record(file, await super.loadDefault<T>(file, what));\n }\n\n override async loadNamed<T>(file: string, name: string, what: string): Promise<T> {\n return this.record(file, await super.loadNamed<T>(file, name, what));\n }\n\n private record<T>(file: string, handler: T): T {\n if (!this.handlers.has(file) || typeof handler !== \"function\") return handler;\n let proxy = this.proxies.get(handler);\n if (proxy === undefined) {\n // A proxy rather than a wrapper, so `params` validators on the handler stay readable.\n proxy = new Proxy(handler, {\n apply: (target, self, args: [InteractionContext]) => {\n this.contexts.set(args[0].trace.id, args[0]);\n return Reflect.apply(target, self, args);\n },\n });\n this.proxies.set(handler, proxy);\n }\n return proxy as T;\n }\n}\n\ninterface Stub {\n interaction: Record<string, unknown> & { id: string };\n responses: TestResponse[];\n}\n\nconst REPLIES: ResponseMethod[] = [\"reply\", \"deferReply\", \"editReply\", \"followUp\", \"deleteReply\"];\nconst COMMAND: ResponseMethod[] = [...REPLIES, \"showModal\"];\nconst COMPONENT: ResponseMethod[] = [...REPLIES, \"update\", \"deferUpdate\", \"showModal\"];\nconst MODAL: ResponseMethod[] = [...REPLIES, \"update\", \"deferUpdate\"];\nconst AUTOCOMPLETE: ResponseMethod[] = [\"respond\"];\n\n/** The flag discord.js sets when each response goes through. */\nconst FLAGS: Partial<Record<ResponseMethod, \"replied\" | \"deferred\" | \"responded\">> = {\n reply: \"replied\",\n update: \"replied\",\n showModal: \"replied\",\n deferReply: \"deferred\",\n deferUpdate: \"deferred\",\n respond: \"responded\",\n};\n\n/**\n * A plain object on discord.js's `BaseInteraction` prototype, so `isButton()`, `inGuild()`,\n * `inCachedGuild()`, and the other guards are discord.js's own checks against these fields.\n * Response methods record the call and set the flag discord.js would. Nothing reaches Discord.\n */\nfunction stub(\n client: Client,\n fields: Record<string, unknown>,\n methods: readonly ResponseMethod[],\n overrides: object = {},\n): Stub {\n const responses: TestResponse[] = [];\n const interaction: Stub[\"interaction\"] = Object.create(BaseInteraction.prototype);\n const own: Record<string, unknown> = {\n client,\n id: SnowflakeUtil.generate().toString(),\n guildId: null,\n channelId: null,\n member: null,\n ...fields,\n };\n for (const method of methods) {\n own[method] = async (options?: unknown) => {\n responses.push({ method, options });\n const flag = FLAGS[method];\n if (flag !== undefined) interaction[flag] = true;\n };\n }\n // Defined rather than assigned: `guild` and `channel` are getters on the prototype.\n Object.defineProperties(interaction, Object.getOwnPropertyDescriptors({ ...own, ...overrides }));\n return { interaction, responses };\n}\n\nfunction findCommand(\n manifest: Manifest,\n path: string,\n): { command: ManifestCommand; position: string } {\n const id = `command:${path}`;\n for (const command of manifest.commands) {\n for (const [position, route] of Object.entries(command.handlers)) {\n if (route === id) return { command, position };\n }\n }\n throw missing(\"command\", path);\n}\n\n/**\n * The options Discord would send for `values`, nested under the subcommand and group at\n * `position`. Each option's type comes from the registration payload. With `focused` this is\n * an autocomplete payload: user, channel, role, and attachment options carry only their ID.\n */\nfunction optionTree(\n command: ManifestCommand,\n position: string,\n values: Record<string, unknown>,\n focused?: string,\n): object[] {\n const [group, sub] = position.includes(\"/\") ? position.split(\"/\") : [undefined, position];\n let schema: readonly SchemaOption[] =\n \"options\" in command.payload ? (command.payload.options ?? []) : [];\n for (const name of [group, sub]) {\n if (name) schema = schema.find((o) => o.name === name)?.options ?? [];\n }\n\n const route = command.handlers[position] ?? command.name;\n const entries = Object.entries(values);\n if (focused !== undefined && !(focused in values)) entries.push([focused, \"\"]);\n let tree: object[] = entries.map(([name, value]) => {\n const built = option(schema, name, value, route, focused === undefined);\n return name === focused ? { ...built, focused: true } : built;\n });\n if (sub) tree = [{ name: sub, type: ApplicationCommandOptionType.Subcommand, options: tree }];\n if (group) {\n tree = [{ name: group, type: ApplicationCommandOptionType.SubcommandGroup, options: tree }];\n }\n return tree;\n}\n\n/** The part of a registration payload option this module reads. */\ninterface SchemaOption {\n name: string;\n type: ApplicationCommandOptionType;\n options?: readonly SchemaOption[] | undefined;\n}\n\n/** Where discord.js keeps the resolved object for option types that have one. */\nconst RESOLVED: Partial<Record<ApplicationCommandOptionType, string>> = {\n [ApplicationCommandOptionType.User]: \"user\",\n [ApplicationCommandOptionType.Channel]: \"channel\",\n [ApplicationCommandOptionType.Role]: \"role\",\n [ApplicationCommandOptionType.Mentionable]: \"user\",\n [ApplicationCommandOptionType.Attachment]: \"attachment\",\n};\n\nfunction option(\n schema: readonly SchemaOption[],\n name: string,\n value: unknown,\n route: string,\n resolve: boolean,\n): object {\n const found = schema.find((o) => o.name === name);\n if (found === undefined) {\n const known = schema.map((o) => o.name);\n throw new TypeError(\n `Route ${route} has no option \"${name}\". ${known.length === 0 ? \"It takes none.\" : `It takes: ${known.join(\", \")}.`}`,\n );\n }\n const resolved = RESOLVED[found.type];\n if (resolved === undefined) return { name, type: found.type, value };\n const id = typeof value === \"object\" && value !== null && \"id\" in value ? value.id : undefined;\n return resolve\n ? { name, type: found.type, value: id, [resolved]: value }\n : { name, type: found.type, value: id };\n}\n\n/** discord.js marks the constructor private; it is the same resolver real interactions carry. */\nconst Resolver = CommandInteractionOptionResolver as unknown as new (\n client: Client,\n options: readonly object[],\n) => CommandInteractionOptionResolver;\n\nfunction resolver(client: Client, options: readonly object[]): CommandInteractionOptionResolver {\n return new Resolver(client, options);\n}\n\nconst SELECT_TYPES: Record<SelectKind, ComponentType> = {\n string: ComponentType.StringSelect,\n user: ComponentType.UserSelect,\n role: ComponentType.RoleSelect,\n channel: ComponentType.ChannelSelect,\n mentionable: ComponentType.MentionableSelect,\n};\n\nfunction isComponentRoute(route: Manifest[\"routes\"][number]): route is ManifestComponentRoute {\n return route.kind === \"button\" || route.kind === \"select\" || route.kind === \"modal\";\n}\n\nconst OUTCOMES: ReadonlySet<string> = new Set([\n \"interaction:complete\",\n \"interaction:fail\",\n \"interaction:reject\",\n]);\n\nfunction isOutcome(signal: Signal): signal is Outcome {\n return OUTCOMES.has(signal.type);\n}\n\nfunction missing(kind: string, path: string): Error {\n return new Error(\n `No ${kind} route \"${path}\" in the manifest. Run \\`nectar build\\` if you just added it.`,\n );\n}\n"],"mappings":";;;;;;;;;AA2NA,SAAgB,cAAc,cAA4B,UAA0B,CAAC,GAAY;CAC/F,MAAM,OAAO,wBAAwB,MAAM,cAAc,YAAY,IAAI;CACzE,IAAI,CAAC,WAAW,IAAI,GAClB,MAAM,IAAI,MAAM,GAAG,KAAK,mDAAmD;CAE7E,MAAM,EAAE,UAAU,WAAW,aAAa,IAAI;CAC9C,MAAM,SAAS,QAAQ,UAAU,IAAI,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;CAC3D,MAAM,SAAS,QAAQ,UAAU,aAAa;CAC9C,MAAM,UAAU,cAAc,MAAM;CACpC,MAAM,2BAAW,IAAI,IAAgC;CACrD,MAAM,WAAW,SAAS,OACvB,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,CACjC,KAAK,MAAM,KAAK,KAAK,QAAQ,GAAG,EAAE,KAAK,MAAM,GAAG,CAAC,CAAC;CACrD,MAAM,QAAsB;EAC1B;EACA;EACA;EACA,SAAS,IAAI,iBAAiB,IAAI,IAAI,QAAQ,GAAG,QAAQ;EACzD,KAAK,QAAQ,OAAO;EACpB;EACA;EACA,UAAW,QAAQ,YAAY,CAAC;CAClC;CACA,wBAAwB,SAAS,OAAO,OAAO,gBAAgB,CAAC;CAChE,MAAM,WAAW,4BAA4B,KAAK;CAClD,MAAM,SAAS,WAAW,KAAK;CAE/B,eAAe,OAAa,EAAE,aAAa,aAAqD;EAC9F,MAAM,OAAiB,CAAC;EACxB,MAAM,MAAM,QAAQ,IAAI,WAAW;GACjC,IAAI,WAAW,UAAU,OAAO,UAAU,YAAY,IAAI,KAAK,KAAK,MAAM;EAC5E,CAAC;EACD,IAAI;GACF,MAAM,SAAS,WAAqC;EACtD,UAAU;GACR,IAAI;EACN;EACA,MAAM,UAAU,KAAK,GAAG,EAAE;EAC1B,IAAI,YAAY,KAAA,KAAa,CAAC,UAAU,OAAO,GAC7C,MAAM,IAAI,MAAM,eAAe,YAAY,GAAG,kCAAkC;EAElF,MAAM,UAAU,SAAS,IAAI,YAAY,EAAE,KAAK;EAChD,SAAS,OAAO,YAAY,EAAE;EAC9B,OAAO;GACQ;GACb;GACS;GACT;GACA,SAAS;EACX;CACF;CAEA,SAAS,UACP,MACA,MACA,SAAyC,CAAC,GAC1C,QACM;EACN,MAAM,QAAQ,SAAS,OAAO,MAC3B,MAAmC,EAAE,SAAS,QAAQ,EAAE,SAAS,IACpE;EACA,IAAI,UAAU,KAAA,GAAW,MAAM,QAAQ,MAAM,IAAI;EAEjD,MAAM,OAAO;GAAE,UADE,YAAY,OAAO,MACd;GAAG,SAAS;GAAO,UAAU;EAAM;EACzD,IAAI,MAAM,SAAS,SACjB,OAAO,KAAK,QAAQ;GAAE,GAAG;GAAM,MAAM,gBAAgB;EAAY,GAAG,OAAO,MAAM;EAEnF,MAAM,gBACJ,MAAM,eAAe,OAAO,cAAc,SAAS,aAAa,MAAM;EACxE,MAAM,SAAS,MAAM,SAAS,WAAW,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC;EAC3D,OAAO,KACL,QACA;GAAE,GAAG;GAAM,GAAG;GAAQ,MAAM,gBAAgB;GAAkB;EAAc,GAC5E,WACA,MACF;CACF;CAEA,OAAO;EACL;EAEA,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG,QAAQ;GACvC,MAAM,EAAE,SAAS,aAAa,YAAY,UAAU,IAAI;GACxD,OAAO,OACL,KACE,QACA;IACE,MAAM,gBAAgB;IACtB,aAAa,QAAQ;IACrB,aAAa,QAAQ;IACrB,SAAS,SAAS,QAAQ,WAAW,SAAS,UAAU,MAAM,CAAC;IAC/D,SAAS;IACT,UAAU;GACZ,GACA,SACA,MACF,CACF;EACF;EAEA,MAAM,aAAa,MAAM,SAAS,SAAS,CAAC,GAAG,QAAQ;GACrD,MAAM,QAAQ,SAAS,OAAO,MAAM,MAAM,EAAE,SAAS,kBAAkB,EAAE,SAAS,IAAI;GACtF,IAAI,OAAO,SAAS,gBAAgB,MAAM,QAAQ,gBAAgB,IAAI;GACtE,IAAI,CAAC,MAAM,QAAQ,SAAS,OAAO,GACjC,MAAM,IAAI,MACR,SAAS,MAAM,GAAG,oCAAoC,QAAQ,iBAAiB,MAAM,QAAQ,KAAK,IAAI,EAAE,EAC1G;GAEF,MAAM,EAAE,SAAS,aAAa,YAAY,UAAU,IAAI;GACxD,OAAO,OACL,KACE,QACA;IACE,MAAM,gBAAgB;IACtB,aAAa,uBAAuB;IACpC,aAAa,QAAQ;IACrB,SAAS,SAAS,QAAQ,WAAW,SAAS,UAAU,QAAQ,OAAO,CAAC;IACxE,WAAW;GACb,GACA,cACA,MACF,CACF;EACF;EAEA,MAAM,OAAO,MAAM,GAAG,MAAM;GAC1B,OAAO,OAAO,UAAU,UAAU,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;EAC3D;EAEA,MAAM,OAAO,MAAM,GAAG,MAAM;GAC1B,OAAO,OAAO,UAAU,UAAU,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;EAC3D;EAEA,MAAM,MAAM,MAAM,GAAG,MAAM;GACzB,OAAO,OAAO,UAAU,SAAS,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;EAC1D;EAEA,MAAM,MAAM,MAAM,GAAG,MAAM;GACzB,MAAM,UAAU,OAAO,MAAM,MAAM,EAAE,SAAS,IAAI;GAClD,IAAI,YAAY,KAAA,GAAW,MAAM,QAAQ,SAAS,IAAI;GACtD,MAAM,WAAoC,CAAC;GAC3C,MAAM,MAAM,QAAQ,IAAI,WAAW;IACjC,IAAI,OAAO,SAAS,gBAAgB,OAAO,UAAU,MAAM,SAAS,KAAK,MAAM;GACjF,CAAC;GACD,IAAI;IACF,MAAM,QAAQ,SAAS,GAAG,IAAI;GAChC,UAAU;IACR,IAAI;GACN;GACA,OAAO,EAAE,SAAS;EACpB;CACF;AACF;;;;;AAMA,IAAM,mBAAN,cAA+B,eAAe;CAIzB;CACA;CAJnB,0BAA2B,IAAI,QAAyB;CAExD,YACE,UACA,UACA;EACA,MAAM;EAHW,KAAA,WAAA;EACA,KAAA,WAAA;CAGnB;CAEA,MAAe,YAAe,MAAc,MAA0B;EACpE,OAAO,KAAK,OAAO,MAAM,MAAM,MAAM,YAAe,MAAM,IAAI,CAAC;CACjE;CAEA,MAAe,UAAa,MAAc,MAAc,MAA0B;EAChF,OAAO,KAAK,OAAO,MAAM,MAAM,MAAM,UAAa,MAAM,MAAM,IAAI,CAAC;CACrE;CAEA,OAAkB,MAAc,SAAe;EAC7C,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,OAAO,YAAY,YAAY,OAAO;EACtE,IAAI,QAAQ,KAAK,QAAQ,IAAI,OAAO;EACpC,IAAI,UAAU,KAAA,GAAW;GAEvB,QAAQ,IAAI,MAAM,SAAS,EACzB,QAAQ,QAAQ,MAAM,SAA+B;IACnD,KAAK,SAAS,IAAI,KAAK,EAAE,CAAC,MAAM,IAAI,KAAK,EAAE;IAC3C,OAAO,QAAQ,MAAM,QAAQ,MAAM,IAAI;GACzC,EACF,CAAC;GACD,KAAK,QAAQ,IAAI,SAAS,KAAK;EACjC;EACA,OAAO;CACT;AACF;AAOA,MAAM,UAA4B;CAAC;CAAS;CAAc;CAAa;CAAY;AAAa;AAChG,MAAM,UAA4B,CAAC,GAAG,SAAS,WAAW;AAC1D,MAAM,YAA8B;CAAC,GAAG;CAAS;CAAU;CAAe;AAAW;AACrF,MAAM,QAA0B;CAAC,GAAG;CAAS;CAAU;AAAa;AACpE,MAAM,eAAiC,CAAC,SAAS;;AAGjD,MAAM,QAA+E;CACnF,OAAO;CACP,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,aAAa;CACb,SAAS;AACX;;;;;;AAOA,SAAS,KACP,QACA,QACA,SACA,YAAoB,CAAC,GACf;CACN,MAAM,YAA4B,CAAC;CACnC,MAAM,cAAmC,OAAO,OAAO,gBAAgB,SAAS;CAChF,MAAM,MAA+B;EACnC;EACA,IAAI,cAAc,SAAS,CAAC,CAAC,SAAS;EACtC,SAAS;EACT,WAAW;EACX,QAAQ;EACR,GAAG;CACL;CACA,KAAK,MAAM,UAAU,SACnB,IAAI,UAAU,OAAO,YAAsB;EACzC,UAAU,KAAK;GAAE;GAAQ;EAAQ,CAAC;EAClC,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,KAAA,GAAW,YAAY,QAAQ;CAC9C;CAGF,OAAO,iBAAiB,aAAa,OAAO,0BAA0B;EAAE,GAAG;EAAK,GAAG;CAAU,CAAC,CAAC;CAC/F,OAAO;EAAE;EAAa;CAAU;AAClC;AAEA,SAAS,YACP,UACA,MACgD;CAChD,MAAM,KAAK,WAAW;CACtB,KAAK,MAAM,WAAW,SAAS,UAC7B,KAAK,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,QAAQ,QAAQ,GAC7D,IAAI,UAAU,IAAI,OAAO;EAAE;EAAS;CAAS;CAGjD,MAAM,QAAQ,WAAW,IAAI;AAC/B;;;;;;AAOA,SAAS,WACP,SACA,UACA,QACA,SACU;CACV,MAAM,CAAC,OAAO,OAAO,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,IAAI,CAAC,KAAA,GAAW,QAAQ;CACxF,IAAI,SACF,aAAa,QAAQ,UAAW,QAAQ,QAAQ,WAAW,CAAC,IAAK,CAAC;CACpE,KAAK,MAAM,QAAQ,CAAC,OAAO,GAAG,GAC5B,IAAI,MAAM,SAAS,OAAO,MAAM,MAAM,EAAE,SAAS,IAAI,CAAC,EAAE,WAAW,CAAC;CAGtE,MAAM,QAAQ,QAAQ,SAAS,aAAa,QAAQ;CACpD,MAAM,UAAU,OAAO,QAAQ,MAAM;CACrC,IAAI,YAAY,KAAA,KAAa,EAAE,WAAW,SAAS,QAAQ,KAAK,CAAC,SAAS,EAAE,CAAC;CAC7E,IAAI,OAAiB,QAAQ,KAAK,CAAC,MAAM,WAAW;EAClD,MAAM,QAAQ,OAAO,QAAQ,MAAM,OAAO,OAAO,YAAY,KAAA,CAAS;EACtE,OAAO,SAAS,UAAU;GAAE,GAAG;GAAO,SAAS;EAAK,IAAI;CAC1D,CAAC;CACD,IAAI,KAAK,OAAO,CAAC;EAAE,MAAM;EAAK,MAAM,6BAA6B;EAAY,SAAS;CAAK,CAAC;CAC5F,IAAI,OACF,OAAO,CAAC;EAAE,MAAM;EAAO,MAAM,6BAA6B;EAAiB,SAAS;CAAK,CAAC;CAE5F,OAAO;AACT;;AAUA,MAAM,WAAkE;EACrE,6BAA6B,OAAO;EACpC,6BAA6B,UAAU;EACvC,6BAA6B,OAAO;EACpC,6BAA6B,cAAc;EAC3C,6BAA6B,aAAa;AAC7C;AAEA,SAAS,OACP,QACA,MACA,OACA,OACA,SACQ;CACR,MAAM,QAAQ,OAAO,MAAM,MAAM,EAAE,SAAS,IAAI;CAChD,IAAI,UAAU,KAAA,GAAW;EACvB,MAAM,QAAQ,OAAO,KAAK,MAAM,EAAE,IAAI;EACtC,MAAM,IAAI,UACR,SAAS,MAAM,kBAAkB,KAAK,KAAK,MAAM,WAAW,IAAI,mBAAmB,aAAa,MAAM,KAAK,IAAI,EAAE,IACnH;CACF;CACA,MAAM,WAAW,SAAS,MAAM;CAChC,IAAI,aAAa,KAAA,GAAW,OAAO;EAAE;EAAM,MAAM,MAAM;EAAM;CAAM;CACnE,MAAM,KAAK,OAAO,UAAU,YAAY,UAAU,QAAQ,QAAQ,QAAQ,MAAM,KAAK,KAAA;CACrF,OAAO,UACH;EAAE;EAAM,MAAM,MAAM;EAAM,OAAO;GAAK,WAAW;CAAM,IACvD;EAAE;EAAM,MAAM,MAAM;EAAM,OAAO;CAAG;AAC1C;;AAGA,MAAM,WAAW;AAKjB,SAAS,SAAS,QAAgB,SAA8D;CAC9F,OAAO,IAAI,SAAS,QAAQ,OAAO;AACrC;AAEA,MAAM,eAAkD;CACtD,QAAQ,cAAc;CACtB,MAAM,cAAc;CACpB,MAAM,cAAc;CACpB,SAAS,cAAc;CACvB,aAAa,cAAc;AAC7B;AAEA,SAAS,iBAAiB,OAAoE;CAC5F,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS,YAAY,MAAM,SAAS;AAC9E;AAEA,MAAM,2BAAgC,IAAI,IAAI;CAC5C;CACA;CACA;AACF,CAAC;AAED,SAAS,UAAU,QAAmC;CACpD,OAAO,SAAS,IAAI,OAAO,IAAI;AACjC;AAEA,SAAS,QAAQ,MAAc,MAAqB;CAClD,uBAAO,IAAI,MACT,MAAM,KAAK,UAAU,KAAK,8DAC5B;AACF"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@nectar-js/nectar",
3
+ "version": "0.1.0",
4
+ "description": "A filesystem-based meta-framework for discord.js.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.18"
9
+ },
10
+ "bin": {
11
+ "nectar": "./dist/cli.js"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./testing": {
19
+ "types": "./dist/testing.d.ts",
20
+ "default": "./dist/testing.js"
21
+ },
22
+ "./start": {
23
+ "types": "./dist/start.d.ts",
24
+ "default": "./dist/start.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "peerDependencies": {
31
+ "discord.js": "^14.27.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/ws": "^8.18.1",
35
+ "discord.js": "^14.27.0",
36
+ "ws": "^8.21.3"
37
+ },
38
+ "dependencies": {
39
+ "discord-api-types": "^0.38.55"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "typecheck": "tsc --noEmit"
44
+ }
45
+ }