diff --git a/.gitignore b/.gitignore index f06235c..0e75fe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules dist +coverage diff --git a/.vscode/settings.json b/.vscode/settings.json index f8865b9..d0a9eee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "antfu", + "Autowired", "bumpp", "Containerable", "Naily", diff --git a/eslint.config.js b/eslint.config.js index a8c3259..aa14f18 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,5 +6,6 @@ export default antfu({ 'antfu/curly': 'off', 'antfu/if-newline': 'off', 'ts/method-signature-style': 'off', + 'ts/no-wrapper-object-types': 'off', }, }) diff --git a/package.json b/package.json index 89934bd..ac7afb8 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,14 @@ "build:ioc": "pnpm -F @nailyjs/ioc build", "lint": "eslint .", "postinstall": "npx simple-git-hooks", - "test": "vitest" + "test": "vitest --ui --coverage" }, "devDependencies": { "@antfu/eslint-config": "^3.7.3", "@swc/core": "^1.7.42", "@types/js-yaml": "^4.0.9", "@types/node": "^22.7.5", + "@vitest/ui": "2.1.4", "bumpp": "^9.7.1", "eslint": "^9.12.0", "js-yaml": "^4.1.0", @@ -27,6 +28,7 @@ "tsup": "^8.3.0", "tsx": "^4.19.1", "typescript": "^5.6.3", + "unplugin-swc": "^1.5.1", "vitest": "^2.1.2" }, "simple-git-hooks": { diff --git a/packages/ioc/package.json b/packages/ioc/package.json index 14c156b..2e9ac4d 100644 --- a/packages/ioc/package.json +++ b/packages/ioc/package.json @@ -17,6 +17,9 @@ "publishConfig": { "access": "public" }, + "files": [ + "dist" + ], "scripts": { "build": "tsup", "watch": "tsup -w" diff --git a/packages/ioc/src/constant.ts b/packages/ioc/src/constant.ts index 16acb26..0ae1a1b 100644 --- a/packages/ioc/src/constant.ts +++ b/packages/ioc/src/constant.ts @@ -3,3 +3,4 @@ import 'reflect-metadata' export const InjectableWatermark = '__naily_injectable__' export const InjectWatermark = '__naily_inject__' export const FilterWatermark = '__naily_filter__' +export const PostConstructWatermark = '__naily_post_construct__' diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index c0fa9fc..2ef53de 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -1,8 +1,10 @@ import type { Saveable } from './protocols' import type { Class, InjectionToken } from './types' +import { Injectable } from './decorators' import { ClassWrapper } from './wrappers/class-wrapper' import { ConstantWrapper } from './wrappers/constant-wrapper' +@Injectable() export class Container implements Saveable { private static map = new Map() @@ -22,7 +24,8 @@ export class Container implements Saveable { return new ConstantWrapper(injectionToken, value) } - save(wrapper: ClassWrapper | ConstantWrapper): void { + save(wrapper: ClassWrapper | ConstantWrapper): this { Container.map.set(wrapper.getInjectionToken(), wrapper) + return this } } diff --git a/packages/ioc/src/decorators/constant.decorator.ts b/packages/ioc/src/decorators/constant.decorator.ts new file mode 100644 index 0000000..0ad7094 --- /dev/null +++ b/packages/ioc/src/decorators/constant.decorator.ts @@ -0,0 +1,8 @@ +import type { InjectionToken } from '../types' +import { Container } from '../container' + +export function Constant(token: InjectionToken, value: Value): PropertyDecorator & ClassDecorator & MethodDecorator { + return (() => { + new Container().createConstantWrapper(token, value).save() + }) as ClassDecorator & PropertyDecorator +} diff --git a/packages/ioc/src/decorators/index.ts b/packages/ioc/src/decorators/index.ts new file mode 100644 index 0000000..005e7f9 --- /dev/null +++ b/packages/ioc/src/decorators/index.ts @@ -0,0 +1,4 @@ +export * from './constant.decorator' +export * from './inject.decorator' +export * from './injectable.decorator' +export * from './post-construct.decorator' diff --git a/packages/ioc/src/decorators/inject.decorator.ts b/packages/ioc/src/decorators/inject.decorator.ts index b0001cb..7ab9600 100644 --- a/packages/ioc/src/decorators/inject.decorator.ts +++ b/packages/ioc/src/decorators/inject.decorator.ts @@ -1,27 +1,43 @@ -import type { InjectOptions } from '../types' +import type { InjectionToken, InjectOptions } from '../types' import { InjectWatermark } from '../constant' export function Inject(options: Partial> = {}): ParameterDecorator & PropertyDecorator { return ((target, propertyKey, parameterIndex) => { - Reflect.defineMetadata(InjectWatermark, [ - ...(Reflect.getMetadata(InjectWatermark, typeof parameterIndex === 'number' ? target : target.constructor) || []), + const oldMetadata: InjectOptions[] = Reflect.getMetadata(InjectWatermark, typeof parameterIndex === 'number' ? target : target.constructor) || [] + const equal = (injectOptions: InjectOptions): boolean => injectOptions.parameterIndex === parameterIndex && injectOptions.propertyKey === propertyKey + const injectOptions = oldMetadata.find(equal) + if (!injectOptions) return Reflect.defineMetadata(InjectWatermark, [ + ...oldMetadata, { + optional: false, ...options, - parameterIndex: typeof parameterIndex === 'number' ? parameterIndex : undefined, - propertyKey: typeof parameterIndex === 'number' ? undefined : propertyKey, + parameterIndex, + propertyKey, }, - ] as InjectOptions[], typeof parameterIndex === 'number' ? target.constructor : target) + ] as InjectOptions[], typeof parameterIndex === 'number' ? target : target.constructor) + + Reflect.defineMetadata(InjectWatermark, oldMetadata.map((injectOptions) => { + return equal(injectOptions) + ? { + ...injectOptions, + ...options, + } + : injectOptions + }), typeof parameterIndex === 'number' ? target : target.constructor) }) as ParameterDecorator & PropertyDecorator } +export function Autowired(injectionToken?: InjectionToken, options: Partial> = {}): ParameterDecorator & PropertyDecorator { + return Inject({ ...options, injectionToken }) +} + export function Optional(): ParameterDecorator & PropertyDecorator { return ((target, propertyKey, parameterIndex) => { const metadata: InjectOptions[] = Reflect.getMetadata(InjectWatermark, typeof parameterIndex === 'number' ? target : target.constructor) || [] - const injectOptions = metadata.find((injectOptions) => { - if (typeof parameterIndex === 'number') return injectOptions.parameterIndex === parameterIndex && injectOptions.propertyKey === propertyKey - return injectOptions.propertyKey === propertyKey - }) - if (!injectOptions) return Inject({ optional: true })(target, propertyKey, parameterIndex) - else return Inject({ ...injectOptions, optional: true })(target, propertyKey, parameterIndex) + const injectOptions = metadata.find(injectOptions => + (injectOptions.parameterIndex === parameterIndex) && (injectOptions.propertyKey === propertyKey), + ) + + return Inject({ ...(injectOptions || {}), optional: true })(target, propertyKey, parameterIndex) }) as ParameterDecorator & PropertyDecorator } diff --git a/packages/ioc/src/decorators/injectable.decorator.ts b/packages/ioc/src/decorators/injectable.decorator.ts index 0349c3b..44dce08 100644 --- a/packages/ioc/src/decorators/injectable.decorator.ts +++ b/packages/ioc/src/decorators/injectable.decorator.ts @@ -1,8 +1,10 @@ -import type { InjectableOptions } from '../types' -import { InjectWatermark } from '../constant' +import type { Class, InjectableOptions } from '../types' +import { InjectableWatermark } from '../constant' +import { Container } from '../container' export function Injectable(options: Partial = {}): ClassDecorator { - return ((target) => { - Reflect.defineMetadata(InjectWatermark, options || {}, target) + return ((target: Class) => { + Reflect.defineMetadata(InjectableWatermark, options || {}, target) + new Container().createClassWrapper(target).save() }) as ClassDecorator } diff --git a/packages/ioc/src/decorators/post-construct.decorator.ts b/packages/ioc/src/decorators/post-construct.decorator.ts new file mode 100644 index 0000000..296433f --- /dev/null +++ b/packages/ioc/src/decorators/post-construct.decorator.ts @@ -0,0 +1,14 @@ +import type { CallType, PostConstructMetadata } from '../types' +import { PostConstructWatermark } from '../constant' + +export function PostConstruct(callType: CallType = 'parallel'): MethodDecorator { + return ((target: Object, propertyKey: string | symbol) => { + Reflect.defineMetadata(PostConstructWatermark, [ + ...(Reflect.getMetadata(PostConstructWatermark, target) || []), + { + propertyKey, + callType, + }, + ] as PostConstructMetadata[], target.constructor) + }) as MethodDecorator +} diff --git a/packages/ioc/src/index.ts b/packages/ioc/src/index.ts index a7e12af..1dd103e 100644 --- a/packages/ioc/src/index.ts +++ b/packages/ioc/src/index.ts @@ -1,5 +1,6 @@ export * from './constant' export * from './container' +export * from './decorators' export * from './metadata-scanner' export * from './protocols' export * from './types' diff --git a/packages/ioc/src/metadata-scanner.ts b/packages/ioc/src/metadata-scanner.ts index 8225482..b2200ed 100644 --- a/packages/ioc/src/metadata-scanner.ts +++ b/packages/ioc/src/metadata-scanner.ts @@ -39,4 +39,8 @@ export class MetadataScanner { getMethodParamTypes(propertyKey: string | symbol): any[] { return this.getClassWrapper().getPropertyMetadata('design:paramtypes', propertyKey) || [] } + + getPropertyType(propertyKey: string | symbol): any { + return this.getClassWrapper().getPropertyMetadata('design:type', propertyKey, true) + } } diff --git a/packages/ioc/src/protocols.ts b/packages/ioc/src/protocols.ts index fef10a7..dd5aa34 100644 --- a/packages/ioc/src/protocols.ts +++ b/packages/ioc/src/protocols.ts @@ -4,7 +4,7 @@ import type { InjectionToken } from './types' import type { ClassWrapper } from './wrappers/class-wrapper' export interface Saveable { - save(...args: any[]): void + save(...args: any[]): void | this } export interface GetInjectionTokenable { getInjectionToken(): InjectionToken | undefined diff --git a/packages/ioc/src/task-runner.ts b/packages/ioc/src/task-runner.ts new file mode 100644 index 0000000..f140af0 --- /dev/null +++ b/packages/ioc/src/task-runner.ts @@ -0,0 +1,25 @@ +export class TaskRunner { + async runTasksSequentially(tasks: Array<() => any>): Promise { + for (const task of tasks) { + try { + await task() + } + catch (error) { + return error as any + } + } + } + + async runTasksInParallel(tasks: Array<() => any>): Promise { + const taskPromises = tasks.map(async (task) => { + try { + await task() + } + catch (error) { + return error + } + }) + + await Promise.allSettled(taskPromises) + } +} diff --git a/packages/ioc/src/types.ts b/packages/ioc/src/types.ts index feec866..9e224fd 100644 --- a/packages/ioc/src/types.ts +++ b/packages/ioc/src/types.ts @@ -13,3 +13,8 @@ export interface InjectOptions { parameterIndex: number | undefined propertyKey: string | symbol | undefined } +export type CallType = 'parallel' | 'series' +export interface PostConstructMetadata { + propertyKey: string | symbol + callType: CallType +} diff --git a/packages/ioc/src/wrappers/class-wrapper.ts b/packages/ioc/src/wrappers/class-wrapper.ts index bef67e4..4d1c620 100644 --- a/packages/ioc/src/wrappers/class-wrapper.ts +++ b/packages/ioc/src/wrappers/class-wrapper.ts @@ -1,7 +1,9 @@ import type { ContainerWrapper } from '../protocols' -import type { Class, InjectionToken } from '../types' +import type { Class, InjectionToken, PostConstructMetadata } from '../types' +import { PostConstructWatermark } from '../constant' import { Container } from '../container' import { MetadataScanner } from '../metadata-scanner' +import { TaskRunner } from '../task-runner' import { InjectableFactory } from './injectable-factory' export class ClassWrapper implements ContainerWrapper { @@ -39,6 +41,19 @@ export class ClassWrapper implements ContainerWrapper { return this._injectableFactory } + private _taskRunner: TaskRunner | null = null + /** + * ### Get task runner. + * + * @description Get task runner for the class. + * @param cache - if false, it will create a new instance of task runner. Default is `true`. + */ + getTaskRunner(cache: boolean = true): TaskRunner { + if (this._taskRunner && cache) return this._taskRunner + this._taskRunner = new TaskRunner() + return this._taskRunner + } + getInjectionToken(): InjectionToken { return this.getMetadataScanner() .getInjectableMetadata() @@ -48,6 +63,27 @@ export class ClassWrapper implements ContainerWrapper { private singletonInstance: null | Instance = null setSingletonInstance(instance: Instance): void { + const taskRunner = this.getTaskRunner() + const tasks: PostConstructMetadata[] = this.getMetadata(PostConstructWatermark) || [] + // 这是全部一起开始执行的并行任务list + const parallelTasks = tasks.filter(({ callType }) => callType === 'parallel') + // 这是上一个任务执行完后下一个任务才会开始的串行任务list + const seriesTasks = tasks.filter(({ callType }) => callType === 'series') + + // 串行任务 + taskRunner.runTasksSequentially( + seriesTasks + .filter(({ propertyKey }) => typeof (instance as Record)[propertyKey] === 'function') + .map(({ propertyKey }) => () => (instance as Record)[propertyKey]()), + ) + + // 并行任务 + taskRunner.runTasksInParallel( + parallelTasks + .filter(({ propertyKey }) => typeof (instance as Record)[propertyKey] === 'function') + .map(({ propertyKey }) => () => (instance as Record)[propertyKey]()), + ) + this.singletonInstance = instance } @@ -67,12 +103,12 @@ export class ClassWrapper implements ContainerWrapper { return Reflect.getMetadata(key, this.target) } - getPropertyMetadata(key: 'design:paramtypes', propertyKey: Key): any[] | undefined - getPropertyMetadata(key: 'design:type', propertyKey: Key): any | undefined - getPropertyMetadata(key: 'design:returntype', propertyKey: Key): any | undefined - getPropertyMetadata(key: Key, propertyKey: Key): Value | undefined - getPropertyMetadata(key: string, propertyKey: Key): Value | undefined { - return Reflect.getMetadata(key, this.target, propertyKey) + getPropertyMetadata(key: 'design:paramtypes', propertyKey: Key, prototype?: boolean): any[] | undefined + getPropertyMetadata(key: 'design:type', propertyKey: Key, prototype?: boolean): any | undefined + getPropertyMetadata(key: 'design:returntype', propertyKey: Key, prototype?: boolean): any | undefined + getPropertyMetadata(key: Key, propertyKey: Key, prototype?: boolean): Value | undefined + getPropertyMetadata(key: string, propertyKey: Key, prototype: boolean = false): Value | undefined { + return Reflect.getMetadata(key, prototype === true ? this.target.prototype : this.target, propertyKey) } hasMetadata(key: Key): boolean { @@ -90,7 +126,8 @@ export class ClassWrapper implements ContainerWrapper { return this._classWrapperContainer } - save(): void { + save(): this { this.getGlobalContainer().save(this) + return this } } diff --git a/packages/ioc/src/wrappers/constant-wrapper.ts b/packages/ioc/src/wrappers/constant-wrapper.ts index aafb3c9..ac34634 100644 --- a/packages/ioc/src/wrappers/constant-wrapper.ts +++ b/packages/ioc/src/wrappers/constant-wrapper.ts @@ -2,8 +2,8 @@ import type { ContainerWrapper } from '../protocols' import type { InjectionToken } from '../types' import { Container } from '../container' -export class ConstantWrapper implements ContainerWrapper { - constructor(private readonly token: InjectionToken, private readonly value: any) {} +export class ConstantWrapper implements ContainerWrapper { + constructor(private readonly token: InjectionToken, private value: Value) {} wrapperType = 'constant' as const @@ -15,7 +15,17 @@ export class ConstantWrapper implements ContainerWrapper { return new Container() } - save(): void { + getValue(): Value { + return this.value + } + + setValue(value: any): this { + this.value = value + return this + } + + save(): this { new Container().save(this) + return this } } diff --git a/packages/ioc/src/wrappers/injectable-factory.ts b/packages/ioc/src/wrappers/injectable-factory.ts index 2348a90..d9368a8 100644 --- a/packages/ioc/src/wrappers/injectable-factory.ts +++ b/packages/ioc/src/wrappers/injectable-factory.ts @@ -1,9 +1,9 @@ import type { Container } from '../container' import type { MetadataScanner } from '../metadata-scanner' import type { ClassWrapperProvider } from '../protocols' -import type { ClassWrapper } from './class-wrapper' -import type { ConstantWrapper } from './constant-wrapper' import type { SingleInjectOptionWrapper } from './single-inject-option-wrapper' +import { ClassWrapper } from './class-wrapper' +import { ConstantWrapper } from './constant-wrapper' export class InjectableFactory implements ClassWrapperProvider { constructor(private readonly classWrapper: ClassWrapper) {} @@ -45,15 +45,21 @@ export class InjectableFactory implements ClassWrapperProvider { return dependencies } - getReflectConstructorDependencies(): (ClassWrapper | ConstantWrapper)[] { - const dependencies: (ClassWrapper | ConstantWrapper)[] = [] + getReflectConstructorDependencies(): (ClassWrapper | ConstantWrapper | undefined)[] { + const dependencies: (ClassWrapper | ConstantWrapper | undefined)[] = [] const designParamTypes = this.getMetadataScanner().getConstructorParamTypes() + const markedInjected: SingleInjectOptionWrapper[] = this.getMetadataScanner() + .getInjectMetadata() + .getInjectOptions() + .filter(injectOptions => injectOptions.isConstructorInjection()) for (let i = 0; i < designParamTypes.length; i++) { const designParamType = designParamTypes[i] if (designParamType === undefined) continue const wrapper = this.getGlobalContainer().getContainer().get(designParamType) - if (!wrapper) continue + const injectInfo = markedInjected.find(injectOptions => injectOptions.getParameterIndex() === i) + if (!wrapper && (!injectInfo || injectInfo.isRequired())) + throw new Error(`Dependency not found for designParamType ${designParamType.toString()}.`) dependencies[i] = wrapper } @@ -63,12 +69,68 @@ export class InjectableFactory implements ClassWrapperProvider { getConstructorDependencies(): (ClassWrapper | ConstantWrapper | undefined)[] { const injectedDependencies = this.getInjectedConstructorDependencies() const reflectDependencies = this.getReflectConstructorDependencies() - const dependencies: (ClassWrapper | ConstantWrapper)[] = [] + const dependencies: (ClassWrapper | ConstantWrapper | undefined)[] = [] // 如果 injectedDependencies 有值,就用 injectedDependencies,否则用 reflectDependencies - for (let i = 0; i < reflectDependencies.length; i++) + const length = Math.max(injectedDependencies.length, reflectDependencies.length) + for (let i = 0; i < length; i++) dependencies[i] = injectedDependencies[i] || reflectDependencies[i] return dependencies } + + getPropertyDependencies(): Map { + const dependencies = new Map() + const markedInjected: SingleInjectOptionWrapper[] = this.getMetadataScanner() + .getInjectMetadata() + .getInjectOptions() + .filter(injectOptions => injectOptions.isPropertyInjection()) + + for (const inject of markedInjected) { + const propertyKey = inject.getPropertyKey() + if (!propertyKey) continue + const propertyType = this.getMetadataScanner().getPropertyType(propertyKey) + const injectionToken = inject.getInjectionToken() + if (injectionToken) { + const wrapper = this.getGlobalContainer().getContainer().get(injectionToken) + if (!wrapper && inject.isRequired()) throw new Error(`Dependency not found for injectionToken ${injectionToken.toString()}.`) + dependencies.set(propertyKey, wrapper) + } + else if (propertyType) { + const wrapper = this.getGlobalContainer().getContainer().get(propertyType) + if (!wrapper && inject.isRequired()) throw new Error(`Dependency not found for propertyType ${propertyType.toString()}.`) + dependencies.set(propertyKey, wrapper) + } + else { + throw new Error(`Property type not found for propertyKey ${propertyKey.toString()} in class ${this.classWrapper.getTarget().name}.`) + } + } + + return dependencies + } + + private createInstanceByWrapper(wrapper: ClassWrapper | ConstantWrapper | undefined): any | undefined { + if (!wrapper) return undefined + if (wrapper instanceof ClassWrapper) return wrapper.getClassFactory().getOrCreateInstance() + else if (wrapper instanceof ConstantWrapper) return wrapper.getValue() + else return undefined + } + + getOrCreateInstance(): Instance { + const currentSingletonInstance = this.classWrapper.getSingletonInstance() + if (currentSingletonInstance) return currentSingletonInstance + + const constructorDeps = this.getConstructorDependencies() + const propertyDeps = this.getPropertyDependencies() + const args = constructorDeps.map(this.createInstanceByWrapper.bind(this)) + const instance = this.createRawInstance(args) + + for (const [key, wrapper] of propertyDeps) + // eslint-disable-next-line ts/ban-ts-comment + // @ts-expect-error + instance[key] = this.createInstanceByWrapper(wrapper) + + this.classWrapper.setSingletonInstance(instance) + return instance + } } diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts new file mode 100644 index 0000000..03b51dc --- /dev/null +++ b/packages/ioc/test/index.test.ts @@ -0,0 +1,99 @@ +import { Autowired, ClassWrapper, ConstantWrapper, Container, Inject, Injectable, Optional, PostConstruct } from '../src' +import { AbstractBootstrap } from '../src/bootstrap' + +it('should automatic analyze deps', () => { + @Injectable() + class BarService { + } + + @Injectable() + class FooService { + constructor( + barService: BarService, + @Inject() + bazService: BarService, + @Inject() + @Optional() + existService: Object, + @Autowired() + baz2Service: BarService, + container: Container, + ) { + expect(barService).toBeInstanceOf(BarService) + expect(bazService).toBeInstanceOf(BarService) + expect(baz2Service).toBeInstanceOf(BarService) + expect(container).toBeInstanceOf(Container) + + // 属性注入的时候,在constructor 里 thisBarService 还没有被注入 + expect(this.thisBarService).toBeUndefined() + } + + @Autowired() + thisBarService: Container + + @PostConstruct() + postConstructFunc() { + // 在 postConstruct 里 thisBarService 已经被注入 + expect(this.thisBarService).toBeInstanceOf(Container) + } + } + + class Bootstrap extends AbstractBootstrap { + async run(): Promise { + const wrapper = this.createClassWrapper(FooService).save() + expect(wrapper.getMetadataScanner().isInjectable()).toBeTruthy() + expect(wrapper.getMetadataScanner().isFilter()).toBeFalsy() + const classFactory = wrapper.getClassFactory() + const constructorDeps = classFactory.getConstructorDependencies() + const propertyDeps = classFactory.getPropertyDependencies() + expect(propertyDeps.get('thisBarService')).toBeInstanceOf(ClassWrapper) + expect(constructorDeps[0]).toBeInstanceOf(ClassWrapper) + expect(constructorDeps[1]).toBeInstanceOf(ClassWrapper) + expect(constructorDeps[2]).toBeUndefined() + expect(constructorDeps[3]).toBeInstanceOf(ClassWrapper) + + const instance: FooService = classFactory.getOrCreateInstance() + expect(instance).toBeInstanceOf(FooService) + expect(instance.thisBarService).toBeInstanceOf(Container) + } + } + new Bootstrap().run() +}) + +it('should create constant', () => { + class Bootstrap extends AbstractBootstrap { + async run() { + this.createConstantWrapper('foo', 'bar').save() + expect(this.getContainer().get('foo')).toBeInstanceOf(ConstantWrapper) + expect((this.getContainer().get('foo') as ConstantWrapper).getValue()).toBe('bar') + } + } + new Bootstrap().run() +}) + +it('should use a plugin', () => { + class Bootstrap extends AbstractBootstrap { + async run() { + this.use({ + name: 'naily:test-plugin', + beforeRun(container) { + container.createConstantWrapper('foo2', 'bar2').save() + expect(container.getContainer().get('foo')).toBeInstanceOf(ConstantWrapper) + expect((container.getContainer().get('foo') as ConstantWrapper).getValue()).toBe('bar') + }, + }) + } + } + new Bootstrap().run() +}) + +it('should replace container', () => { + class Bootstrap extends AbstractBootstrap { + async run() { + const map = new Map() + this.replaceContainer(map) + expect(this.getContainer()).toBe(map) + } + } + new Bootstrap().run() +}) diff --git a/packages/ioc/tsconfig.json b/packages/ioc/tsconfig.json index eb9e61d..c11c892 100644 --- a/packages/ioc/tsconfig.json +++ b/packages/ioc/tsconfig.json @@ -1,11 +1,13 @@ { "compilerOptions": { - "target": "ES2022", + "target": "ES2018", "emitDecoratorMetadata": true, "experimentalDecorators": true, "module": "ES2022", "moduleResolution": "Bundler", - "strict": true + "types": ["vitest/globals"], + "strict": true, + "strictPropertyInitialization": false }, - "include": ["src"] + "include": ["src", "test", "../../shims.d.ts"] } diff --git a/packages/ioc/vitest.config.ts b/packages/ioc/vitest.config.ts new file mode 100644 index 0000000..c4b1395 --- /dev/null +++ b/packages/ioc/vitest.config.ts @@ -0,0 +1,24 @@ +import swc from 'unplugin-swc' +import { defineProject } from 'vitest/config' + +export default defineProject({ + test: { + globals: true, + }, + + plugins: [ + swc.vite({ + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + tsx: true, + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + }, + }, + }), + ], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14855bd..75f697b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^3.7.3 - version: 3.8.0(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)) + version: 3.8.0(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4)) '@swc/core': specifier: ^1.7.42 version: 1.7.42 @@ -20,9 +20,15 @@ importers: '@types/node': specifier: ^22.7.5 version: 22.8.4 + '@vitest/coverage-v8': + specifier: 2.1.4 + version: 2.1.4(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4)) + '@vitest/ui': + specifier: 2.1.4 + version: 2.1.4(vitest@2.1.4) bumpp: specifier: ^9.7.1 - version: 9.8.0 + version: 9.8.0(magicast@0.3.5) eslint: specifier: ^9.12.0 version: 9.13.0(jiti@1.21.6) @@ -44,9 +50,12 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 + unplugin-swc: + specifier: ^1.5.1 + version: 1.5.1(@swc/core@1.7.42)(rollup@4.24.3) vitest: specifier: ^2.1.2 - version: 2.1.4(@types/node@22.8.4) + version: 2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4) packages/ioc: dependencies: @@ -60,6 +69,10 @@ importers: packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@antfu/eslint-config@3.8.0': resolution: {integrity: sha512-O5QSufPHpKTm0wk1OQ5c2mOZVzCqYV3hIDrt5zt+cOWqiG8YXLPkSOD4fFwjomATtOuUbcLUwkcgY5dErM7aIw==} hasBin: true @@ -133,6 +146,9 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@clack/core@0.3.4': resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} @@ -648,6 +664,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -690,6 +710,18 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + + '@rollup/pluginutils@5.1.3': + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.24.3': resolution: {integrity: sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==} cpu: [arm] @@ -945,6 +977,15 @@ packages: resolution: {integrity: sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/coverage-v8@2.1.4': + resolution: {integrity: sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==} + peerDependencies: + '@vitest/browser': 2.1.4 + vitest: 2.1.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/eslint-plugin@1.1.7': resolution: {integrity: sha512-pTWGW3y6lH2ukCuuffpan6kFxG6nIuoesbhMiQxskyQMRcCN5t9SXsKrNHvEw3p8wcCsgJoRqFZVkOTn6TjclA==} peerDependencies: @@ -984,6 +1025,11 @@ packages: '@vitest/spy@2.1.4': resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} + '@vitest/ui@2.1.4': + resolution: {integrity: sha512-Zd9e5oU063c+j9N9XzGJagCLNvG71x/2tOme3Js4JEZKX55zsgxhJwUgLI8hkN6NjMLpdJO8d7nVUUuPGAA58Q==} + peerDependencies: + vitest: 2.1.4 + '@vitest/utils@2.1.4': resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} @@ -1549,6 +1595,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1652,6 +1701,9 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -1718,6 +1770,22 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1836,6 +1904,13 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2010,6 +2085,10 @@ packages: mlly@1.7.2: resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==} + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2318,6 +2397,10 @@ packages: resolution: {integrity: sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg==} hasBin: true + sirv@3.0.0: + resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2429,6 +2512,10 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2469,6 +2556,10 @@ packages: resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -2555,6 +2646,20 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unplugin-swc@1.5.1: + resolution: {integrity: sha512-/ZLrPNjChhGx3Z95pxJ4tQgfI6rWqukgYHKflrNB4zAV1izOQuDhkTn55JWeivpBxDCoK7M/TStb2aS/14PS/g==} + peerDependencies: + '@swc/core': ^1.2.108 + + unplugin@1.15.0: + resolution: {integrity: sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==} + engines: {node: '>=14.0.0'} + peerDependencies: + webpack-sources: ^3 + peerDependenciesMeta: + webpack-sources: + optional: true + update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -2640,6 +2745,9 @@ packages: webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -2711,7 +2819,12 @@ packages: snapshots: - '@antfu/eslint-config@3.8.0(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4))': + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@antfu/eslint-config@3.8.0(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4))': dependencies: '@antfu/install-pkg': 0.4.1 '@clack/prompts': 0.7.0 @@ -2720,7 +2833,7 @@ snapshots: '@stylistic/eslint-plugin': 2.9.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) '@typescript-eslint/eslint-plugin': 8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) '@typescript-eslint/parser': 8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@vitest/eslint-plugin': 1.1.7(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)) + '@vitest/eslint-plugin': 1.1.7(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4)) eslint: 9.13.0(jiti@1.21.6) eslint-config-flat-gitignore: 0.3.0(eslint@9.13.0(jiti@1.21.6)) eslint-flat-config-utils: 0.4.0 @@ -2783,6 +2896,8 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} + '@clack/core@0.3.4': dependencies: picocolors: 1.1.1 @@ -3097,6 +3212,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -3138,6 +3255,16 @@ snapshots: '@pkgr/core@0.1.1': {} + '@polka/url@1.0.0-next.28': {} + + '@rollup/pluginutils@5.1.3(rollup@4.24.3)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.24.3 + '@rollup/rollup-android-arm-eabi@4.24.3': optional: true @@ -3361,13 +3488,31 @@ snapshots: '@typescript-eslint/types': 8.12.2 eslint-visitor-keys: 3.4.3 - '@vitest/eslint-plugin@1.1.7(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4))': + '@vitest/coverage-v8@2.1.4(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.12 + magicast: 0.3.5 + std-env: 3.7.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4) + transitivePeerDependencies: + - supports-color + + '@vitest/eslint-plugin@1.1.7(@typescript-eslint/utils@8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4))': dependencies: '@typescript-eslint/utils': 8.12.2(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) eslint: 9.13.0(jiti@1.21.6) optionalDependencies: typescript: 5.6.3 - vitest: 2.1.4(@types/node@22.8.4) + vitest: 2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4) '@vitest/expect@2.1.4': dependencies: @@ -3403,6 +3548,17 @@ snapshots: dependencies: tinyspy: 3.0.2 + '@vitest/ui@2.1.4(vitest@2.1.4)': + dependencies: + '@vitest/utils': 2.1.4 + fflate: 0.8.2 + flatted: 3.3.1 + pathe: 1.1.2 + sirv: 3.0.0 + tinyglobby: 0.2.10 + tinyrainbow: 1.2.0 + vitest: 2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4) + '@vitest/utils@2.1.4': dependencies: '@vitest/pretty-format': 2.1.4 @@ -3509,10 +3665,10 @@ snapshots: builtin-modules@3.3.0: {} - bumpp@9.8.0: + bumpp@9.8.0(magicast@0.3.5): dependencies: '@jsdevtools/ez-spawn': 3.0.4 - c12: 1.11.2 + c12: 1.11.2(magicast@0.3.5) cac: 6.7.14 escalade: 3.2.0 js-yaml: 4.1.0 @@ -3528,7 +3684,7 @@ snapshots: esbuild: 0.24.0 load-tsconfig: 0.2.5 - c12@1.11.2: + c12@1.11.2(magicast@0.3.5): dependencies: chokidar: 3.6.0 confbox: 0.1.8 @@ -3542,6 +3698,8 @@ snapshots: perfect-debounce: 1.0.0 pkg-types: 1.2.1 rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 cac@6.7.14: {} @@ -4109,6 +4267,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4208,6 +4368,8 @@ snapshots: hosted-git-info@2.8.9: {} + html-escaper@2.0.2: {} + human-signals@5.0.0: {} ignore@5.3.2: {} @@ -4255,6 +4417,27 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -4372,6 +4555,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.1 + '@babel/types': 7.26.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + markdown-table@3.0.4: {} mdast-util-find-and-replace@3.0.1: @@ -4712,6 +4905,8 @@ snapshots: pkg-types: 1.2.1 ufo: 1.5.4 + mrmime@2.0.0: {} + ms@2.1.3: {} mz@2.7.0: @@ -4999,6 +5194,12 @@ snapshots: simple-git-hooks@2.11.1: {} + sirv@3.0.0: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 + totalist: 3.0.1 + sisteransi@1.0.5: {} slashes@3.0.12: {} @@ -5116,6 +5317,12 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-table@0.2.0: {} thenify-all@1.6.0: @@ -5149,6 +5356,8 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 + totalist@3.0.1: {} + tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -5235,6 +5444,21 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + unplugin-swc@1.5.1(@swc/core@1.7.42)(rollup@4.24.3): + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.24.3) + '@swc/core': 1.7.42 + load-tsconfig: 0.2.5 + unplugin: 1.15.0 + transitivePeerDependencies: + - rollup + - webpack-sources + + unplugin@1.15.0: + dependencies: + acorn: 8.14.0 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -5278,7 +5502,7 @@ snapshots: '@types/node': 22.8.4 fsevents: 2.3.3 - vitest@2.1.4(@types/node@22.8.4): + vitest@2.1.4(@types/node@22.8.4)(@vitest/ui@2.1.4): dependencies: '@vitest/expect': 2.1.4 '@vitest/mocker': 2.1.4(vite@5.4.10(@types/node@22.8.4)) @@ -5302,6 +5526,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.8.4 + '@vitest/ui': 2.1.4(vitest@2.1.4) transitivePeerDependencies: - less - lightningcss @@ -5328,6 +5553,8 @@ snapshots: webidl-conversions@4.0.2: {} + webpack-virtual-modules@0.6.2: {} + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 diff --git a/shims.d.ts b/shims.d.ts new file mode 100644 index 0000000..9896c47 --- /dev/null +++ b/shims.d.ts @@ -0,0 +1 @@ +/// diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..94b09e3 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + }, + + ui: true, + }, +})