noot
Some checks failed
commit-tag / commit-tag-image (map[context:./migrations file:./migrations/Dockerfile name:migrations]) (push) Successful in 16s
commit-tag / commit-tag-image (map[context:./ts file:./ts/Dockerfile name:ts]) (push) Failing after 36s

This commit is contained in:
a 2025-06-14 16:17:42 -05:00
parent 24c7c2b9b1
commit a8295b09d6
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB
10 changed files with 1659 additions and 103 deletions

Binary file not shown.

View File

@ -2,14 +2,24 @@
"name": "backend", "name": "backend",
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728", "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
"scripts": { "scripts": {
"barrels": "barrelsby -c barrelsby.json --delete" "barrels": "barrelsby -c barrelsby.json --delete",
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:typecheck": "vitest typecheck",
"typecheck": "tsc --noEmit"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.4",
"@types/object-hash": "^3", "@types/object-hash": "^3",
"@vitest/runner": "^3.2.3",
"@vitest/ui": "^3.2.3",
"barrelsby": "^2.8.1", "barrelsby": "^2.8.1",
"happy-dom": "^18.0.1",
"knip": "^5.45.0", "knip": "^5.45.0",
"rollup": "^4.34.8", "rollup": "^4.34.8",
"typescript": "5.7.3" "typescript": "5.7.3",
"vitest": "^3.2.3"
}, },
"dependencies": { "dependencies": {
"@discordeno/types": "^21.0.0", "@discordeno/types": "^21.0.0",
@ -21,7 +31,6 @@
"@temporalio/workflow": "^1.11.7", "@temporalio/workflow": "^1.11.7",
"@ts-rest/core": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema", "@ts-rest/core": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema",
"@ts-rest/fastify": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema", "@ts-rest/fastify": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema",
"@types/node": "^22.13.4",
"any-date-parser": "^2.0.3", "any-date-parser": "^2.0.3",
"arktype": "2.1.1", "arktype": "2.1.1",
"axios": "^1.7.9", "axios": "^1.7.9",

View File

@ -0,0 +1,428 @@
import { describe, it, expect, vi } from 'vitest'
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from '@discordeno/types'
import { createCommandHandler, ExtractCommands } from './command_parser'
import type { InteractionData } from '..'
// Test command definitions
const TEST_COMMANDS = [
{
name: 'simple',
description: 'A simple command',
type: ApplicationCommandTypes.ChatInput,
options: [
{
name: 'message',
description: 'A message to echo',
type: ApplicationCommandOptionTypes.String,
required: true,
},
{
name: 'count',
description: 'Number of times',
type: ApplicationCommandOptionTypes.Integer,
required: false,
},
],
},
{
name: 'complex',
description: 'A command with subcommands',
type: ApplicationCommandTypes.ChatInput,
options: [
{
name: 'list',
description: 'List items',
type: ApplicationCommandOptionTypes.SubCommand,
options: [
{
name: 'filter',
description: 'Filter string',
type: ApplicationCommandOptionTypes.String,
required: false,
},
],
},
{
name: 'create',
description: 'Create item',
type: ApplicationCommandOptionTypes.SubCommand,
options: [
{
name: 'name',
description: 'Item name',
type: ApplicationCommandOptionTypes.String,
required: true,
},
{
name: 'enabled',
description: 'Is enabled',
type: ApplicationCommandOptionTypes.Boolean,
required: false,
},
],
},
],
},
] as const satisfies CreateApplicationCommand[]
describe('createCommandHandler', () => {
it('should handle a simple command with required string argument', async () => {
const simpleHandler = vi.fn()
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: simpleHandler,
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler,
})
const interactionData: InteractionData = {
name: 'simple',
options: [
{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello world' },
],
}
await handler(interactionData)
expect(simpleHandler).toHaveBeenCalledWith({ message: 'Hello world' })
expect(notFoundHandler).not.toHaveBeenCalled()
})
it('should handle a simple command with optional arguments', async () => {
const simpleHandler = vi.fn()
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: simpleHandler,
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler,
})
const interactionData: InteractionData = {
name: 'simple',
options: [
{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello' },
{ name: 'count', type: ApplicationCommandOptionTypes.Integer, value: 5 },
],
}
await handler(interactionData)
expect(simpleHandler).toHaveBeenCalledWith({ message: 'Hello', count: 5 })
})
it('should handle subcommands correctly', async () => {
const listHandler = vi.fn()
const createHandler = vi.fn()
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: vi.fn(),
complex: {
list: listHandler,
create: createHandler,
},
},
notFoundHandler,
})
const interactionData: InteractionData = {
name: 'complex',
options: [
{
name: 'create',
type: ApplicationCommandOptionTypes.SubCommand,
options: [
{ name: 'name', type: ApplicationCommandOptionTypes.String, value: 'Test Item' },
{ name: 'enabled', type: ApplicationCommandOptionTypes.Boolean, value: true },
],
},
],
}
await handler(interactionData)
expect(createHandler).toHaveBeenCalledWith({ name: 'Test Item', enabled: true })
expect(listHandler).not.toHaveBeenCalled()
expect(notFoundHandler).not.toHaveBeenCalled()
})
it('should call notFoundHandler for unknown commands', async () => {
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: vi.fn(),
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler,
})
const interactionData: InteractionData = {
name: 'unknown',
options: [],
}
await handler(interactionData)
expect(notFoundHandler).toHaveBeenCalledWith({})
})
it('should call notFoundHandler for unknown subcommands', async () => {
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: vi.fn(),
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler,
})
const interactionData: InteractionData = {
name: 'complex',
options: [
{
name: 'unknown',
type: ApplicationCommandOptionTypes.SubCommand,
options: [],
},
],
}
await handler(interactionData)
expect(notFoundHandler).toHaveBeenCalledWith({})
})
it('should handle missing interaction data', async () => {
const notFoundHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: vi.fn(),
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler,
})
await handler(null as any)
expect(notFoundHandler).toHaveBeenCalledWith({})
await handler({} as any)
expect(notFoundHandler).toHaveBeenCalledTimes(2)
})
it('should handle commands without options', async () => {
const simpleHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: simpleHandler,
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler: vi.fn(),
})
const interactionData: InteractionData = {
name: 'simple',
// No options provided
}
await handler(interactionData)
expect(simpleHandler).toHaveBeenCalledWith({})
})
it('should ignore subcommand options when parsing top-level args', async () => {
const simpleHandler = vi.fn()
const handler = createCommandHandler<typeof TEST_COMMANDS>({
handler: {
simple: simpleHandler,
complex: {
list: vi.fn(),
create: vi.fn(),
},
},
notFoundHandler: vi.fn(),
})
const interactionData: InteractionData = {
name: 'simple',
options: [
{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello' },
// This should be ignored in parseOptions
{ name: 'subcommand', type: ApplicationCommandOptionTypes.SubCommand, options: [] },
],
}
await handler(interactionData)
expect(simpleHandler).toHaveBeenCalledWith({ message: 'Hello' })
})
})
describe('ExtractCommands type utility', () => {
it('should correctly extract command types', () => {
// Type tests - these compile-time checks ensure the types work correctly
type TestHandlers = ExtractCommands<typeof TEST_COMMANDS>
// Test that the type structure is correct
const handlers: TestHandlers = {
simple: async (args) => {
// TypeScript should know that args has message as required string and count as optional number
const message: string = args.message
const count: number | undefined = args.count
expect(message).toBeDefined()
expect(count).toBeDefined()
},
complex: {
list: async (args) => {
// TypeScript should know that args has filter as optional string
const filter: string | undefined = args.filter
expect(filter).toBeDefined()
},
create: async (args) => {
// TypeScript should know that args has name as required string and enabled as optional boolean
const name: string = args.name
const enabled: boolean | undefined = args.enabled
expect(name).toBeDefined()
expect(enabled).toBeDefined()
},
},
}
// This is just to satisfy TypeScript - the real test is that the above compiles
expect(handlers).toBeDefined()
})
it('should infer types correctly without manual typing', () => {
// This test verifies that args are inferred correctly without manual type annotations
const handler = createCommandHandler<typeof TEST_COMMANDS>({
notFoundHandler: async () => {},
handler: {
simple: async (args) => {
// args should be automatically typed
// This would error if args.message wasn't a string
const upper = args.message.toUpperCase()
// This would error if args.count wasn't number | undefined
const doubled = args.count ? args.count * 2 : 0
expect(upper).toBeDefined()
expect(doubled).toBeDefined()
},
complex: {
list: async (args) => {
// This would error if args.filter wasn't string | undefined
const filtered = args.filter?.toLowerCase()
expect(filtered !== undefined).toBeDefined()
},
create: async (args) => {
// This would error if types weren't correct
const nameLength = args.name.length
const isEnabled = args.enabled === true
expect(nameLength).toBeDefined()
expect(isEnabled).toBeDefined()
},
},
},
})
expect(handler).toBeDefined()
})
it('should handle commands with no options', () => {
const EMPTY_COMMANDS = [
{
name: 'ping',
description: 'Ping command',
type: ApplicationCommandTypes.ChatInput,
},
] as const satisfies CreateApplicationCommand[]
type EmptyHandlers = ExtractCommands<typeof EMPTY_COMMANDS>
const handlers: EmptyHandlers = {
ping: async (args) => {
// args should be an empty object
expect(args).toEqual({})
},
}
expect(handlers).toBeDefined()
})
it('should handle different option types', () => {
const TYPE_TEST_COMMANDS = [
{
name: 'types',
description: 'Test different types',
type: ApplicationCommandTypes.ChatInput,
options: [
{ name: 'str', description: 'String param', type: ApplicationCommandOptionTypes.String, required: true },
{ name: 'int', description: 'Integer param', type: ApplicationCommandOptionTypes.Integer, required: true },
{ name: 'bool', description: 'Boolean param', type: ApplicationCommandOptionTypes.Boolean, required: true },
{ name: 'user', description: 'User param', type: ApplicationCommandOptionTypes.User, required: true },
{ name: 'channel', description: 'Channel param', type: ApplicationCommandOptionTypes.Channel, required: true },
{ name: 'role', description: 'Role param', type: ApplicationCommandOptionTypes.Role, required: true },
{ name: 'num', description: 'Number param', type: ApplicationCommandOptionTypes.Number, required: true },
{ name: 'mention', description: 'Mentionable param', type: ApplicationCommandOptionTypes.Mentionable, required: true },
{ name: 'attach', description: 'Attachment param', type: ApplicationCommandOptionTypes.Attachment, required: true },
],
},
] as const satisfies CreateApplicationCommand[]
type TypeHandlers = ExtractCommands<typeof TYPE_TEST_COMMANDS>
const handlers: TypeHandlers = {
types: async (args) => {
// Test that all types are correctly mapped
const str: string = args.str
const int: number = args.int
const bool: boolean = args.bool
const user: string = args.user
const channel: string = args.channel
const role: string = args.role
const num: number = args.num
const mention: string = args.mention
const attach: string = args.attach
expect(str).toBeDefined()
expect(int).toBeDefined()
expect(bool).toBeDefined()
expect(user).toBeDefined()
expect(channel).toBeDefined()
expect(role).toBeDefined()
expect(num).toBeDefined()
expect(mention).toBeDefined()
expect(attach).toBeDefined()
},
}
expect(handlers).toBeDefined()
})
})

View File

@ -15,15 +15,18 @@ type OptionTypeMap = {
[ApplicationCommandOptionTypes.Attachment]: string; // attachment ID [ApplicationCommandOptionTypes.Attachment]: string; // attachment ID
}; };
// Helper type to get option by name
type GetOption<Options, Name> = Options extends readonly any[]
? Options[number] extends infer O
? O extends { name: Name }
? O
: never
: never
: never;
// Extract the argument types from command options // Extract the argument types from command options
type ExtractArgs<Options extends readonly any[]> = { export type ExtractArgs<Options extends readonly any[]> = {
[K in Options[number] as K extends { name: infer N; type: infer T } [K in Options[number]['name']]: GetOption<Options, K> extends { type: infer T; required?: infer R }
? T extends keyof OptionTypeMap
? N extends string
? N
: never
: never
: never]: Options[number] extends { name: K; type: infer T; required?: infer R }
? T extends keyof OptionTypeMap ? T extends keyof OptionTypeMap
? R extends true ? R extends true
? OptionTypeMap[T] ? OptionTypeMap[T]
@ -35,31 +38,54 @@ type ExtractArgs<Options extends readonly any[]> = {
// Handler function type that accepts typed arguments // Handler function type that accepts typed arguments
type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void; type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void;
// Helper types to extract command structure with proper argument types // Get subcommand by name
type ExtractSubcommands<T> = T extends { options: infer O } type GetSubcommand<Options, Name> = Options extends readonly any[]
? O extends readonly any[] ? Options[number] extends infer O
? { ? O extends { name: Name; type: ApplicationCommandOptionTypes.SubCommand }
[K in O[number] as K extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup } ? O
? N extends string
? N
: never : never
: never]: O[number] extends { name: K; options?: infer SubO }
? SubO extends readonly any[]
? HandlerFunction<ExtractArgs<SubO>>
: HandlerFunction<{}>
: HandlerFunction<{}>
}
: never : never
: never; : never;
type ExtractCommands<T extends readonly CreateApplicationCommand[]> = { // Check if all options are subcommands
[K in T[number] as K['name']]: ExtractSubcommands<K> extends never type HasOnlySubcommands<Options extends readonly any[]> =
? T[number] extends { name: K; options?: infer O } Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand }
? O extends readonly any[] ? true
? HandlerFunction<ExtractArgs<O>> : false;
// Extract subcommand names from options
type SubcommandNames<Options extends readonly any[]> =
Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
? N extends string
? N
: never
: never;
// Type to extract subcommand handlers
export type SubcommandHandlers<Options extends readonly any[]> = {
[K in SubcommandNames<Options>]: GetSubcommand<Options, K> extends { options?: infer SubOpts }
? SubOpts extends readonly any[]
? HandlerFunction<ExtractArgs<SubOpts>>
: HandlerFunction<{}>
: HandlerFunction<{}>
};
// Get command by name from array
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C
? C extends { name: Name }
? C
: never
: never;
// Main type to extract command handlers from slash commands
export type ExtractCommands<T extends readonly any[]> = {
[Name in T[number]['name']]: GetCommand<T, Name> extends { options?: infer Options }
? Options extends readonly any[]
? HasOnlySubcommands<Options> extends true
? SubcommandHandlers<Options>
: HandlerFunction<ExtractArgs<Options>>
: HandlerFunction<{}> : HandlerFunction<{}>
: HandlerFunction<{}> : HandlerFunction<{}>
: ExtractSubcommands<K>
}; };
// The actual command handler type based on SLASH_COMMANDS // The actual command handler type based on SLASH_COMMANDS
@ -128,62 +154,8 @@ export function createCommandHandler<T extends readonly CreateApplicationCommand
// Parse arguments from subcommand options // Parse arguments from subcommand options
const args = parseOptions(subcommand.options); const args = parseOptions(subcommand.options);
await subHandler(args); await (subHandler as HandlerFunction<any>)(args);
} }
} }
} }
// Function to validate that all commands are implemented
export function validateCommandImplementation(handlers: CommandHandlers): void {
for (const command of SLASH_COMMANDS) {
const commandName = command.name;
if (!(commandName in handlers)) {
throw new Error(`Command "${commandName}" is not implemented`);
}
// Check subcommands if they exist
if (command.options) {
const subcommands = command.options.filter(
opt => opt.type === ApplicationCommandOptionTypes.SubCommand
);
if (subcommands.length > 0) {
const handlerObj = handlers[commandName as keyof CommandHandlers];
if (typeof handlerObj === 'function') {
throw new Error(`Command "${commandName}" has subcommands but is implemented as a function`);
}
for (const subcommand of subcommands) {
if (!(subcommand.name in handlerObj)) {
throw new Error(`Subcommand "${commandName}.${subcommand.name}" is not implemented`);
}
}
}
}
}
}
// Helper function to create command handlers with type safety
export function createCommandHandlers<T extends CommandHandlers>(handlers: T): T {
return handlers;
}
// Example usage showing the type-safe implementation
export const exampleHandlers = createCommandHandlers({
guild: {
leaderboard: async (args: {}) => {
console.log("Guild leaderboard command");
},
info: async (args: {}) => {
console.log("Guild info command");
},
online: async (args: {}) => {
console.log("Guild online command");
},
},
admin: {
set_wynn_guild: async (args: {}) => {
console.log("Admin set_wynn_guild command");
},
},
});

View File

@ -37,5 +37,24 @@ export const SLASH_COMMANDS = [
type: ApplicationCommandOptionTypes.SubCommand, type: ApplicationCommandOptionTypes.SubCommand,
}, },
], ],
},{
name: "player",
description: "player commands",
type: ApplicationCommandTypes.ChatInput,
options: [
{
name: "lookup",
description: "view player information",
type: ApplicationCommandOptionTypes.SubCommand,
options: [
{
name: "player",
description: "player name",
type: ApplicationCommandOptionTypes.String,
required: true,
},
],
}
],
} }
] as const satisfies CreateApplicationCommand[] ] as const satisfies CreateApplicationCommand[]

View File

@ -2,6 +2,7 @@ import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow'
import type * as activities from '#/activities'; import type * as activities from '#/activities';
import { InteractionTypes } from '@discordeno/types'; import { InteractionTypes } from '@discordeno/types';
import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild_messages'; import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild_messages';
import { handleCommandPlayerLookup } from './player_messages';
import { createCommandHandler } from '#/discord/botevent/command_parser'; import { createCommandHandler } from '#/discord/botevent/command_parser';
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands'; import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands';
import { InteractionCreatePayload} from '#/discord'; import { InteractionCreatePayload} from '#/discord';
@ -34,9 +35,19 @@ const workflowHandleApplicationCommand = async (
notFoundHandler: async () => { notFoundHandler: async () => {
await notFoundHandler(`command not found`); await notFoundHandler(`command not found`);
}, },
handler:{ handler: {
player: {
lookup: async (args) => {
const { workflowId } = workflowInfo();
const handle = await startChild(handleCommandPlayerLookup, {
args: [{ ref, args }],
workflowId: `${workflowId}-player-lookup`,
});
await handle.result();
},
},
guild: { guild: {
info: async (args: {}) => { info: async (args) => {
const { workflowId } = workflowInfo(); const { workflowId } = workflowInfo();
const handle = await startChild(handleCommandGuildInfo, { const handle = await startChild(handleCommandGuildInfo, {
args: [{ ref }], args: [{ ref }],
@ -44,7 +55,7 @@ const workflowHandleApplicationCommand = async (
}); });
await handle.result(); await handle.result();
}, },
online: async (args: {}) => { online: async (args) => {
const { workflowId } = workflowInfo(); const { workflowId } = workflowInfo();
const handle = await startChild(handleCommandGuildOnline, { const handle = await startChild(handleCommandGuildOnline, {
args: [{ ref }], args: [{ ref }],
@ -52,7 +63,7 @@ const workflowHandleApplicationCommand = async (
}); });
await handle.result(); await handle.result();
}, },
leaderboard: async (args: {}) => { leaderboard: async (args) => {
const { workflowId } = workflowInfo(); const { workflowId } = workflowInfo();
const handle = await startChild(handleCommandGuildLeaderboard, { const handle = await startChild(handleCommandGuildLeaderboard, {
args: [{ ref }], args: [{ ref }],
@ -62,7 +73,7 @@ const workflowHandleApplicationCommand = async (
}, },
}, },
admin: { admin: {
set_wynn_guild: async (args: {}) => { set_wynn_guild: async (args) => {
await reply_to_interaction({ await reply_to_interaction({
ref, ref,
type: 4, type: 4,

View File

@ -6,4 +6,5 @@ export * from "./discord";
export * from "./guild_messages"; export * from "./guild_messages";
export * from "./guilds"; export * from "./guilds";
export * from "./items"; export * from "./items";
export * from "./player_messages";
export * from "./players"; export * from "./players";

View File

@ -0,0 +1,43 @@
import { proxyActivities } from "@temporalio/workflow";
import type * as activities from "#/activities";
import { InteractionRef } from "#/discord";
const {
reply_to_interaction
} = proxyActivities<typeof activities>({
startToCloseTimeout: '30 seconds',
});
interface CommandPayload {
ref: InteractionRef;
args: {
player: string;
};
}
export async function handleCommandPlayerLookup(payload: CommandPayload): Promise<void> {
const { ref, args } = payload;
const playerName = args.player;
try {
// For now, we'll send a simple response
// In the future, we can fetch player stats from Wynncraft API
await reply_to_interaction({
ref,
type: 4,
options: {
content: `Looking up player: **${playerName}**\n\n*Player lookup functionality coming soon!*`,
isPrivate: false,
},
});
} catch (error) {
await reply_to_interaction({
ref,
type: 4,
options: {
content: `Error looking up player: ${playerName}`,
isPrivate: true,
},
});
}
}

18
ts/vitest.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
typecheck: {
enabled: true,
tsconfig: './tsconfig.json',
include: ['**/*.{test,spec}.{ts,tsx}'],
},
},
resolve: {
alias: {
'#': new URL('./src', import.meta.url).pathname,
},
},
})

File diff suppressed because it is too large Load Diff