From 5e630be33ad6b2b8c191086a0e94b5a671e28556 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 5 Mar 2026 10:34:26 +0100 Subject: [PATCH 1/3] test: add unit and e2e tests --- .github/workflows/tests.yml | 2 +- .husky/pre-push | 6 +- .prettierignore | 1 + e2e/game.test.ts | 46 + e2e/game/.gitignore | 1 + .../client/components/example.component.ts | 55 + e2e/game/client/main.ts | 50 + e2e/game/client/systems/example.system.ts | 31 + e2e/game/nanoforge.config.json | 21 + e2e/game/package.json | 30 + .../server/components/example.component.ts | 55 + e2e/game/server/main.ts | 36 + e2e/game/server/systems/example.system.ts | 31 + e2e/game/server/ticker.library.ts | 32 + e2e/game/tsconfig.json | 13 + e2e/run-server.mjs | 29 + package.json | 3 +- .../test/asset-manager.library.spec.ts | 84 +- packages/common/package.json | 4 +- packages/common/test/exceptions.spec.ts | 105 ++ packages/common/test/library.spec.ts | 184 +++ packages/common/tsconfig.spec.json | 10 + packages/core/package.json | 4 +- packages/core/test/config-registry.spec.ts | 54 + .../test/editable-library-manager.spec.ts | 182 +++ packages/core/test/relationship.spec.ts | 135 ++ packages/core/tsconfig.spec.json | 10 + .../test/ecs-client-library.spec.ts | 169 ++- packages/ecs-lib/test/Registry.spec.ts | 273 ++-- packages/ecs-lib/test/SparseArray.spec.ts | 110 +- packages/ecs-lib/test/Zipper.spec.ts | 182 ++- .../test/ecs-server-library.spec.ts | 169 ++- .../test/graphics-2d.library.spec.ts | 35 +- packages/input/package.json | 4 +- packages/input/test/input.library.spec.ts | 100 ++ packages/music/package.json | 4 +- packages/music/test/music.library.spec.ts | 106 ++ packages/network-client/package.json | 4 +- .../test/client.network.library.spec.ts | 106 ++ .../network-client/test/tcp.client.spec.ts | 82 ++ .../network-client/test/udp.client.spec.ts | 63 + packages/network-client/test/utils.spec.ts | 80 ++ packages/network-server/package.json | 4 +- .../test/server.network.library.spec.ts | 89 ++ .../network-server/test/tcp.server.spec.ts | 121 ++ .../network-server/test/udp.server.spec.ts | 67 + packages/network-server/test/utils.spec.ts | 107 ++ packages/sound/package.json | 4 +- packages/sound/test/sound.library.spec.ts | 97 ++ pnpm-lock.yaml | 1165 ++++++++++------- pnpm-workspace.yaml | 25 +- turbo.json | 8 +- vitest.config.e2e.ts | 10 + 53 files changed, 3535 insertions(+), 863 deletions(-) create mode 100644 e2e/game.test.ts create mode 100644 e2e/game/.gitignore create mode 100644 e2e/game/client/components/example.component.ts create mode 100644 e2e/game/client/main.ts create mode 100644 e2e/game/client/systems/example.system.ts create mode 100644 e2e/game/nanoforge.config.json create mode 100644 e2e/game/package.json create mode 100644 e2e/game/server/components/example.component.ts create mode 100644 e2e/game/server/main.ts create mode 100644 e2e/game/server/systems/example.system.ts create mode 100644 e2e/game/server/ticker.library.ts create mode 100644 e2e/game/tsconfig.json create mode 100644 e2e/run-server.mjs create mode 100644 packages/common/test/exceptions.spec.ts create mode 100644 packages/common/test/library.spec.ts create mode 100644 packages/common/tsconfig.spec.json create mode 100644 packages/core/test/config-registry.spec.ts create mode 100644 packages/core/test/editable-library-manager.spec.ts create mode 100644 packages/core/test/relationship.spec.ts create mode 100644 packages/core/tsconfig.spec.json create mode 100644 packages/input/test/input.library.spec.ts create mode 100644 packages/music/test/music.library.spec.ts create mode 100644 packages/network-client/test/client.network.library.spec.ts create mode 100644 packages/network-client/test/tcp.client.spec.ts create mode 100644 packages/network-client/test/udp.client.spec.ts create mode 100644 packages/network-client/test/utils.spec.ts create mode 100644 packages/network-server/test/server.network.library.spec.ts create mode 100644 packages/network-server/test/tcp.server.spec.ts create mode 100644 packages/network-server/test/udp.server.spec.ts create mode 100644 packages/network-server/test/utils.spec.ts create mode 100644 packages/sound/test/sound.library.spec.ts create mode 100644 vitest.config.e2e.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f36664c7..7aac6e89 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: uses: ./.github/actions/prepare - name: "Run tests" - run: pnpm test:unit + run: pnpm test - name: "Run linter" run: pnpm lint diff --git a/.husky/pre-push b/.husky/pre-push index 64e3d4e1..842469b0 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,3 @@ -pnpm lint -pnpm build -pnpm test:unit +pnpm run build +pnpm run lint +pnpm run test diff --git a/.prettierignore b/.prettierignore index 819ff66e..f44cc24f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,7 @@ packages/ example/ utils/ releases/ +.nanoforge/ .turbo/ node_modules/ diff --git a/e2e/game.test.ts b/e2e/game.test.ts new file mode 100644 index 00000000..7f0784a6 --- /dev/null +++ b/e2e/game.test.ts @@ -0,0 +1,46 @@ +import { execSync } from "child_process"; +import { existsSync, rmSync } from "fs"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +const PROJECT_DIR = join(dirname(fileURLToPath(import.meta.url)), "./game"); + +describe("E2E Game", () => { + beforeAll(() => { + rmSync(join(PROJECT_DIR, ".nanoforge"), { recursive: true, force: true }); + execSync("nf build", { + cwd: PROJECT_DIR, + stdio: "pipe", + timeout: 120_000, + }); + }, 130_000); + + afterAll(() => { + rmSync(join(PROJECT_DIR, ".nanoforge"), { recursive: true, force: true }); + }, 130_000); + + describe("Build", () => { + it("should produce a server bundle", () => { + expect(existsSync(join(PROJECT_DIR, ".nanoforge/server/main.js"))).toBe(true); + }); + + it("should produce a client bundle directory", () => { + expect(existsSync(join(PROJECT_DIR, ".nanoforge/client"))).toBe(true); + }); + + it("should include the WASM file in the server bundle", () => { + expect(existsSync(join(PROJECT_DIR, ".nanoforge/server/libecs.wasm"))).toBe(true); + }); + }); + + describe("Server", () => { + it("should start, run for 5 ticks, and exit cleanly", () => { + execSync("bun run ../run-server.mjs", { + cwd: PROJECT_DIR, + stdio: "pipe", + timeout: 15_000, + }); + }, 20_000); + }); +}); diff --git a/e2e/game/.gitignore b/e2e/game/.gitignore new file mode 100644 index 00000000..3b333905 --- /dev/null +++ b/e2e/game/.gitignore @@ -0,0 +1 @@ +.nanoforge/ \ No newline at end of file diff --git a/e2e/game/client/components/example.component.ts b/e2e/game/client/components/example.component.ts new file mode 100644 index 00000000..eb7cb8f0 --- /dev/null +++ b/e2e/game/client/components/example.component.ts @@ -0,0 +1,55 @@ +import { type EditorComponentManifest } from "@nanoforge-dev/ecs-client"; + +export class ExampleComponent { + name = this.constructor.name; + + constructor( + public paramA: string, + public paramB: number, + public paramC: boolean = false, + ) {} + + get foo() { + return "bar"; + } + + get paramAOrDefault() { + return this.paramC ? this.paramA : "default"; + } + + addOne() { + this.paramB += 1; + } +} + +// * Required to generate code +export default ExampleComponent.name; + +// * Required for the editor to display the component and generate code +export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = { + name: "Example", + description: "Example component description", + params: { + paramA: { + type: "string", + name: "Param A", + description: "Param A description", + example: "Example value", + }, + paramB: { + type: "number", + name: "Param B", + description: "Param B description", + example: 3, + }, + paramC: { + type: "boolean", + name: "Param C", + description: "Param C description", + example: true, + default: false, + // Not required because it has a default value + optional: true, + }, + }, +}; diff --git a/e2e/game/client/main.ts b/e2e/game/client/main.ts new file mode 100644 index 00000000..1e9ca74d --- /dev/null +++ b/e2e/game/client/main.ts @@ -0,0 +1,50 @@ +import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager"; +import { type IRunOptions } from "@nanoforge-dev/common"; +import { NanoforgeFactory } from "@nanoforge-dev/core"; +import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"; +import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d"; +import { InputLibrary } from "@nanoforge-dev/input"; +import { MusicLibrary } from "@nanoforge-dev/music"; +import { NetworkClientLibrary } from "@nanoforge-dev/network-client"; +import { SoundLibrary } from "@nanoforge-dev/sound"; + +import { ExampleComponent } from "./components/example.component"; +import { exampleSystem } from "./systems/example.system"; + +export async function main(options: IRunOptions) { + const app = NanoforgeFactory.createClient({ + tickRate: 60, + environment: { + serverAddress: "127.0.0.1", + serverTcpPort: "4445", + serverUdpPort: "4444", + }, + }); + + const assetManager = new AssetManagerLibrary(); + const ecs = new ECSClientLibrary(); + const graphics = new Graphics2DLibrary(); + const input = new InputLibrary(); + const music = new MusicLibrary(); + const network = new NetworkClientLibrary(); + const sound = new SoundLibrary(); + + app.useAssetManager(assetManager); + app.useComponentSystem(ecs); + app.useGraphics(graphics); + app.useInput(input); + app.useSound(sound); + app.useNetwork(network); + app.use(Symbol("music"), music); + + await app.init(options); + + const registry = ecs.registry; + + const exampleEntity = registry.spawnEntity(); + registry.addComponent(exampleEntity, new ExampleComponent("example", 10)); + + registry.addSystem(exampleSystem); + + await app.run(); +} diff --git a/e2e/game/client/systems/example.system.ts b/e2e/game/client/systems/example.system.ts new file mode 100644 index 00000000..39625aad --- /dev/null +++ b/e2e/game/client/systems/example.system.ts @@ -0,0 +1,31 @@ +import { type Context } from "@nanoforge-dev/common"; +import { type EditorSystemManifest, type Registry } from "@nanoforge-dev/ecs-client"; + +import { ExampleComponent } from "../components/example.component"; + +export const exampleSystem = (registry: Registry, ctx: Context) => { + const entities = registry.getZipper([ExampleComponent]); + + entities.forEach((entity) => { + if (entity.ExampleComponent.paramA === "end") { + ctx.app.setIsRunning(false); + return; + } + + if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end"; + + if (entity.ExampleComponent.paramB >= 0) + entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1; + }); +}; + +// * Required to generate code +export default exampleSystem.name; + +// * Required for the editor to display the system and generate code +export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = { + name: "Example", + description: + "This system end the game when paramB reaches 0 for any entity with ExampleComponent", + dependencies: ["ExampleComponent"], +}; diff --git a/e2e/game/nanoforge.config.json b/e2e/game/nanoforge.config.json new file mode 100644 index 00000000..1e4becd5 --- /dev/null +++ b/e2e/game/nanoforge.config.json @@ -0,0 +1,21 @@ +{ + "client": { + "build": { + "entryFile": "client/main.ts", + "outDir": ".nanoforge/client" + }, + "runtime": { + "dir": ".nanoforge/client" + } + }, + "server": { + "enable": true, + "build": { + "entryFile": "server/main.ts", + "outDir": ".nanoforge/server" + }, + "runtime": { + "dir": ".nanoforge/server" + } + } +} diff --git a/e2e/game/package.json b/e2e/game/package.json new file mode 100644 index 00000000..ef1cfb25 --- /dev/null +++ b/e2e/game/package.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@nanoforge-dev/e2e-game", + "version": "0.0.0", + "description": "NanoForge Engine - End-to-End test game", + "type": "module", + "scripts": { + "build": "nf build", + "start": "nf start" + }, + "devDependencies": { + "@nanoforge-dev/asset-manager": "workspace:*", + "@nanoforge-dev/common": "workspace:*", + "@nanoforge-dev/core": "workspace:*", + "@nanoforge-dev/ecs-client": "workspace:*", + "@nanoforge-dev/ecs-server": "workspace:*", + "@nanoforge-dev/graphics-2d": "workspace:*", + "@nanoforge-dev/input": "workspace:*", + "@nanoforge-dev/music": "workspace:*", + "@nanoforge-dev/network-client": "workspace:*", + "@nanoforge-dev/network-server": "workspace:*", + "@nanoforge-dev/sound": "workspace:*", + "typescript": "catalog:core", + "vitest": "catalog:test" + }, + "private": true, + "engines": { + "node": "25" + } +} diff --git a/e2e/game/server/components/example.component.ts b/e2e/game/server/components/example.component.ts new file mode 100644 index 00000000..1142860d --- /dev/null +++ b/e2e/game/server/components/example.component.ts @@ -0,0 +1,55 @@ +import { type EditorComponentManifest } from "@nanoforge-dev/ecs-server"; + +export class ExampleComponent { + name = this.constructor.name; + + constructor( + public paramA: string, + public paramB: number, + public paramC: boolean = false, + ) {} + + get foo() { + return "bar"; + } + + get paramAOrDefault() { + return this.paramC ? this.paramA : "default"; + } + + addOne() { + this.paramB += 1; + } +} + +// * Required to generate code +export default ExampleComponent.name; + +// * Required for the editor to display the component and generate code +export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = { + name: "Example", + description: "Example component description", + params: { + paramA: { + type: "string", + name: "Param A", + description: "Param A description", + example: "Example value", + }, + paramB: { + type: "number", + name: "Param B", + description: "Param B description", + example: 3, + }, + paramC: { + type: "boolean", + name: "Param C", + description: "Param C description", + example: true, + default: false, + // Not required because it has a default value + optional: true, + }, + }, +}; diff --git a/e2e/game/server/main.ts b/e2e/game/server/main.ts new file mode 100644 index 00000000..b2e236c0 --- /dev/null +++ b/e2e/game/server/main.ts @@ -0,0 +1,36 @@ +import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager"; +import { type IRunOptions } from "@nanoforge-dev/common"; +import { NanoforgeFactory } from "@nanoforge-dev/core"; +import { ECSServerLibrary } from "@nanoforge-dev/ecs-server"; + +import { ExampleComponent } from "./components/example.component"; +import { exampleSystem } from "./systems/example.system"; +import { TickerLibrary } from "./ticker.library"; + +const TICKER = Symbol.for("ticker"); + +export async function main(options: IRunOptions) { + const app = NanoforgeFactory.createServer({ tickRate: 60 }); + + const assetManager = new AssetManagerLibrary(); + const ecs = new ECSServerLibrary(); + + const ticker = new TickerLibrary(5); + + app.useAssetManager(assetManager); + app.useComponentSystem(ecs); + app.use(TICKER, ticker); + + await app.init(options); + + const registry = ecs.registry; + + const exampleEntity = registry.spawnEntity(); + registry.addComponent(exampleEntity, new ExampleComponent("example", 10)); + + registry.addSystem(exampleSystem); + + await app.run(); + + await ticker.done; +} diff --git a/e2e/game/server/systems/example.system.ts b/e2e/game/server/systems/example.system.ts new file mode 100644 index 00000000..7400df48 --- /dev/null +++ b/e2e/game/server/systems/example.system.ts @@ -0,0 +1,31 @@ +import { type Context } from "@nanoforge-dev/common"; +import { type EditorSystemManifest, type Registry } from "@nanoforge-dev/ecs-server"; + +import { ExampleComponent } from "../components/example.component"; + +export const exampleSystem = (registry: Registry, ctx: Context) => { + const entities = registry.getZipper([ExampleComponent]); + + entities.forEach((entity) => { + if (entity.ExampleComponent.paramA === "end") { + ctx.app.setIsRunning(false); + return; + } + + if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end"; + + if (entity.ExampleComponent.paramB >= 0) + entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1; + }); +}; + +// * Required to generate code +export default exampleSystem.name; + +// * Required for the editor to display the system and generate code +export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = { + name: "Example", + description: + "This system end the game when paramB reaches 0 for any entity with ExampleComponent", + dependencies: ["ExampleComponent"], +}; diff --git a/e2e/game/server/ticker.library.ts b/e2e/game/server/ticker.library.ts new file mode 100644 index 00000000..de5729a3 --- /dev/null +++ b/e2e/game/server/ticker.library.ts @@ -0,0 +1,32 @@ +import { BaseNetworkLibrary, type Context } from "@nanoforge-dev/common"; + +/** + * A runner library that stops the game loop after a fixed number of ticks. + * Reuses BaseNetworkLibrary (no abstract methods) so it can be registered + * as a standard library. Exposes `done` which resolves when the game stops. + */ +export class TickerLibrary extends BaseNetworkLibrary { + private _remaining: number; + private _resolve!: () => void; + + public readonly done: Promise = new Promise((resolve) => { + this._resolve = resolve; + }); + + constructor(ticks: number) { + super(); + this._remaining = ticks; + } + + get __name(): string { + return "TickerLibrary"; + } + + async __run(ctx: Context): Promise { + this._remaining--; + if (this._remaining <= 0) { + ctx.app.setIsRunning(false); + this._resolve(); + } + } +} diff --git a/e2e/game/tsconfig.json b/e2e/game/tsconfig.json new file mode 100644 index 00000000..e9e60621 --- /dev/null +++ b/e2e/game/tsconfig.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.json", + "compilerOptions": { + "allowUnreachableCode": true, + "allowUnusedLabels": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "strict": false + }, + "include": ["client/**/*.ts", "server/**/*.ts"], + "exclude": ["node_modules", ".nanoforge"] +} diff --git a/e2e/run-server.mjs b/e2e/run-server.mjs new file mode 100644 index 00000000..5a183bb4 --- /dev/null +++ b/e2e/run-server.mjs @@ -0,0 +1,29 @@ +import { dirname, resolve } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PROJECT_DIR = resolve(__dirname, "./game"); + +const wasmPath = resolve(PROJECT_DIR, "../../packages/ecs-server/dist/assets/libecs.wasm"); + +const timeout = setTimeout(() => { + console.error("[E2E] Server timed out after 10s — aborting"); + process.exit(1); +}, 10_000); + +try { + const { main } = await import("./game/.nanoforge/server/main.js"); + + const files = new Map([["/libecs.wasm", wasmPath]]); + console.log(`[E2E] Starting server (WASM: ${wasmPath})`); + + await main({ files }); + + clearTimeout(timeout); + console.log("[E2E] Server completed successfully"); + process.exit(0); +} catch (err) { + clearTimeout(timeout); + console.error("[E2E] Server failed:", err); + process.exit(1); +} diff --git a/package.json b/package.json index 85265923..269671a5 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,9 @@ "build": "turbo run build --concurrency=5", "format": "prettier --write . && turbo run format --concurrency=5", "lint": "prettier --check . && turbo run lint --concurrency=5", + "test": "pnpm run test:unit && pnpm run test:e2e", "test:unit": "turbo run test:unit --concurrency=5", - "test:e2e": "turbo run test:e2e --concurrency=5", + "test:e2e": "vitest run --config vitest.config.e2e.ts", "prepare": "husky" }, "dependencies": { diff --git a/packages/asset-manager/test/asset-manager.library.spec.ts b/packages/asset-manager/test/asset-manager.library.spec.ts index a3845cef..ea0c2c4a 100644 --- a/packages/asset-manager/test/asset-manager.library.spec.ts +++ b/packages/asset-manager/test/asset-manager.library.spec.ts @@ -1,47 +1,79 @@ -import { type IConfigRegistry, InitContext } from "@nanoforge-dev/common"; -import { describe, expect, it } from "vitest"; +import { type IConfigRegistry, InitContext, NfNotFound } from "@nanoforge-dev/common"; +import { beforeEach, describe, expect, it } from "vitest"; import { EditableApplicationContext } from "../../core/src/common/context/contexts/application.editable-context"; import { EditableLibraryManager } from "../../core/src/common/library/manager/library.manager"; import { AssetManagerLibrary } from "../src"; -describe("Asset Manager Library", () => { - const library = new AssetManagerLibrary(); +const makeContext = (files: Map) => { const libraryManager = new EditableLibraryManager(); const appContext = new EditableApplicationContext(libraryManager); const configRegistry = {} as IConfigRegistry; - const context = new InitContext(appContext, libraryManager, configRegistry, { + return new InitContext(appContext, libraryManager, configRegistry, { // @ts-ignore canvas: null, - files: new Map([ - ["/test.png", "blob:http://localhost:3000/test.png"], - ["/test.wasm", "blob:http://localhost:3000/test.wasm"], - ["/test.wgsl", "blob:http://localhost:3000/test.wgsl"], - ]), + files, }); - library.__init(context); +}; - it("Should get asset", () => { - expect(library.getAsset("test.png").path).toEqual("blob:http://localhost:3000/test.png"); - }); +const TEST_FILES = new Map([ + ["/test.png", "blob:http://localhost:3000/test.png"], + ["/test.wasm", "blob:http://localhost:3000/test.wasm"], + ["/test.wgsl", "blob:http://localhost:3000/test.wgsl"], +]); - it("Should throw on unknown asset", () => { - expect(() => library.getAsset("test-unknown.png")).toThrow(); +describe("AssetManagerLibrary", () => { + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new AssetManagerLibrary().__name).toBe("AssetManagerLibrary"); + }); }); - it("Should get wasm", () => { - expect(library.getAsset("test.wasm").path).toEqual("blob:http://localhost:3000/test.wasm"); + describe("before initialization", () => { + it("should throw when getAsset is called before __init", () => { + const library = new AssetManagerLibrary(); + expect(() => library.getAsset("test.png")).toThrow(); + }); }); - it("Should throw on unknown wasm", () => { - expect(() => library.getAsset("test-unknown.wasm")).toThrow(); - }); + describe("getAsset", () => { + let library: AssetManagerLibrary; - it("Should get wgsl", () => { - expect(library.getAsset("test.wgsl").path).toEqual("blob:http://localhost:3000/test.wgsl"); - }); + beforeEach(async () => { + library = new AssetManagerLibrary(); + await library.__init(makeContext(TEST_FILES)); + }); + + it("should return the correct path for a png asset", () => { + expect(library.getAsset("test.png").path).toBe("blob:http://localhost:3000/test.png"); + }); + + it("should return the correct path for a wasm asset", () => { + expect(library.getAsset("test.wasm").path).toBe("blob:http://localhost:3000/test.wasm"); + }); + + it("should return the correct path for a wgsl asset", () => { + expect(library.getAsset("test.wgsl").path).toBe("blob:http://localhost:3000/test.wgsl"); + }); + + it("should normalize path with a leading slash", () => { + expect(library.getAsset("/test.png").path).toBe("blob:http://localhost:3000/test.png"); + }); + + it("should normalize path with multiple leading slashes", () => { + expect(library.getAsset("//test.png").path).toBe("blob:http://localhost:3000/test.png"); + }); + + it("should throw NfNotFound for an unknown asset", () => { + expect(() => library.getAsset("unknown.png")).toThrow(NfNotFound); + }); + + it("should throw NfNotFound for an unknown wasm", () => { + expect(() => library.getAsset("unknown.wasm")).toThrow(NfNotFound); + }); - it("Should throw on unknown wgsl", () => { - expect(() => library.getAsset("test-unknown.wgsl")).toThrow(); + it("should throw NfNotFound for an unknown wgsl", () => { + expect(() => library.getAsset("unknown.wgsl")).toThrow(NfNotFound); + }); }); }); diff --git a/packages/common/package.json b/packages/common/package.json index 24f251d3..77ae8ade 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/common/*'", "release": "cliff-jumper" @@ -62,7 +63,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/common/test/exceptions.spec.ts b/packages/common/test/exceptions.spec.ts new file mode 100644 index 00000000..4f82f289 --- /dev/null +++ b/packages/common/test/exceptions.spec.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from "vitest"; + +import { NfConfigException, NfFetchException, NfNotFound, NfNotInitializedException } from "../src"; + +describe("NfNotInitializedException", () => { + it("should have code 404", () => { + expect(new NfNotInitializedException("Foo").code).toBe(404); + }); + + it("should include item name in message", () => { + expect(new NfNotInitializedException("Foo").message).toContain("Foo not initialized."); + }); + + it("should include type prefix when provided", () => { + expect(new NfNotInitializedException("Foo", "Library").message).toContain( + "Library - Foo not initialized.", + ); + }); + + it("should omit type prefix when not provided", () => { + expect(new NfNotInitializedException("Foo").message).not.toContain(" - "); + }); + + it("should have [NANOFORGE] prefix", () => { + expect(new NfNotInitializedException("Foo").message).toMatch(/^\[NANOFORGE]/); + }); + + it("should be an instance of Error", () => { + expect(new NfNotInitializedException("Foo")).toBeInstanceOf(Error); + }); +}); + +describe("NfNotFound", () => { + it("should have code 404", () => { + expect(new NfNotFound("Foo").code).toBe(404); + }); + + it("should include item name in message", () => { + expect(new NfNotFound("Foo").message).toContain("Foo not found."); + }); + + it("should include type prefix when provided", () => { + expect(new NfNotFound("Foo", "Asset").message).toContain("Asset - Foo not found."); + }); + + it("should omit type prefix when not provided", () => { + expect(new NfNotFound("Foo").message).not.toContain(" - "); + }); + + it("should have [NANOFORGE] prefix", () => { + expect(new NfNotFound("Foo").message).toMatch(/^\[NANOFORGE]/); + }); + + it("should be an instance of Error", () => { + expect(new NfNotFound("Foo")).toBeInstanceOf(Error); + }); +}); + +describe("NfFetchException", () => { + it("should reflect the given status code", () => { + expect(new NfFetchException(404, "Not Found").code).toBe(404); + }); + + it("should reflect a different status code", () => { + expect(new NfFetchException(500, "Internal Server Error").code).toBe(500); + }); + + it("should include status code and text in message", () => { + expect(new NfFetchException(404, "Not Found").message).toContain("404 - Not Found"); + }); + + it("should have [NANOFORGE] prefix", () => { + expect(new NfFetchException(404, "Not Found").message).toMatch(/^\[NANOFORGE]/); + }); + + it("should be an instance of Error", () => { + expect(new NfFetchException(404, "Not Found")).toBeInstanceOf(Error); + }); +}); + +describe("NfConfigException", () => { + it("should have code 400", () => { + expect(new NfConfigException("bad value").code).toBe(400); + }); + + it("should include message in output", () => { + expect(new NfConfigException("bad value").message).toContain("bad value"); + }); + + it("should include library name when provided", () => { + expect(new NfConfigException("bad value", "MyLib").message).toContain("(MyLib)"); + }); + + it("should omit library name when not provided", () => { + expect(new NfConfigException("bad value").message).not.toContain("("); + }); + + it("should have [NANOFORGE] prefix", () => { + expect(new NfConfigException("bad value").message).toMatch(/^\[NANOFORGE]/); + }); + + it("should be an instance of Error", () => { + expect(new NfConfigException("bad value")).toBeInstanceOf(Error); + }); +}); diff --git a/packages/common/test/library.spec.ts b/packages/common/test/library.spec.ts new file mode 100644 index 00000000..9f8c3489 --- /dev/null +++ b/packages/common/test/library.spec.ts @@ -0,0 +1,184 @@ +import { beforeEach, describe, expect, it } from "vitest"; + +import { + LibraryContext, + LibraryHandle, + LibraryStatusEnum, + NfNotInitializedException, +} from "../src"; +import { Library } from "../src/library/libraries/library"; +import { BaseLibraryManager } from "../src/library/manager/managers/base-library.manager"; +import { RelationshipHandler } from "../src/library/relationship/relationship-handler"; + +class TestLibrary extends Library { + private readonly _name: string; + + constructor(name = "TestLibrary", options?: ConstructorParameters[0]) { + super(options); + this._name = name; + } + + get __name(): string { + return this._name; + } + + public expose_throwNotInitializedError(): never { + return this.throwNotInitializedError(); + } +} + +class TestableBaseLibraryManager extends BaseLibraryManager { + public expose_setNewLibrary(sym: symbol, library: TestLibrary, context: LibraryContext): void { + this.setNewLibrary(sym, library, context); + } +} + +describe("RelationshipHandler", () => { + it("should store dependencies", () => { + const dep = Symbol("dep"); + const handler = new RelationshipHandler([dep], [], []); + expect(handler.dependencies).toContain(dep); + }); + + it("should store runBefore", () => { + const before = Symbol("before"); + const handler = new RelationshipHandler([], [before], []); + expect(handler.runBefore).toContain(before); + }); + + it("should store runAfter", () => { + const after = Symbol("after"); + const handler = new RelationshipHandler([], [], [after]); + expect(handler.runAfter).toContain(after); + }); + + it("should return empty arrays when initialised with empty arrays", () => { + const handler = new RelationshipHandler([], [], []); + expect(handler.dependencies).toEqual([]); + expect(handler.runBefore).toEqual([]); + expect(handler.runAfter).toEqual([]); + }); + + it("should store multiple symbols", () => { + const a = Symbol("a"); + const b = Symbol("b"); + const handler = new RelationshipHandler([a, b], [], []); + expect(handler.dependencies).toEqual([a, b]); + }); +}); + +describe("Library", () => { + describe("defaults", () => { + it("should have empty relationship arrays when no options are given", () => { + const lib = new TestLibrary(); + expect(lib.__relationship.dependencies).toEqual([]); + expect(lib.__relationship.runBefore).toEqual([]); + expect(lib.__relationship.runAfter).toEqual([]); + }); + + it("should expose the correct __name", () => { + expect(new TestLibrary("MyLib").__name).toBe("MyLib"); + }); + }); + + describe("options", () => { + it("should use partial dependencies option", () => { + const dep = Symbol("dep"); + const lib = new TestLibrary("TestLibrary", { dependencies: [dep] }); + expect(lib.__relationship.dependencies).toContain(dep); + }); + + it("should use partial runBefore option", () => { + const before = Symbol("before"); + const lib = new TestLibrary("TestLibrary", { runBefore: [before] }); + expect(lib.__relationship.runBefore).toContain(before); + }); + + it("should use partial runAfter option", () => { + const after = Symbol("after"); + const lib = new TestLibrary("TestLibrary", { runAfter: [after] }); + expect(lib.__relationship.runAfter).toContain(after); + }); + }); + + describe("throwNotInitializedError", () => { + it("should throw NfNotInitializedException", () => { + const lib = new TestLibrary(); + expect(() => lib.expose_throwNotInitializedError()).toThrow(NfNotInitializedException); + }); + + it("should include the library name in the error message", () => { + const lib = new TestLibrary("MySpecialLib"); + expect(() => lib.expose_throwNotInitializedError()).toThrow(/MySpecialLib/); + }); + }); + + describe("__init and __clear", () => { + it("__init should resolve without error by default", async () => { + const lib = new TestLibrary(); + await expect(lib.__init({} as any)).resolves.toBeUndefined(); + }); + + it("__clear should resolve without error by default", async () => { + const lib = new TestLibrary(); + await expect(lib.__clear({} as any)).resolves.toBeUndefined(); + }); + }); +}); + +describe("LibraryContext", () => { + it("should start with UNLOADED status", () => { + const ctx = new LibraryContext(); + expect(ctx.status).toBe(LibraryStatusEnum.UNLOADED); + }); +}); + +describe("LibraryHandle", () => { + it("should expose the symbol", () => { + const sym = Symbol("test"); + const handle = new LibraryHandle(sym, new TestLibrary(), new LibraryContext()); + expect(handle.symbol).toBe(sym); + }); + + it("should expose the library", () => { + const lib = new TestLibrary(); + const handle = new LibraryHandle(Symbol("test"), lib, new LibraryContext()); + expect(handle.library).toBe(lib); + }); + + it("should expose the context", () => { + const ctx = new LibraryContext(); + const handle = new LibraryHandle(Symbol("test"), new TestLibrary(), ctx); + expect(handle.context).toBe(ctx); + }); +}); + +describe("BaseLibraryManager", () => { + let manager: TestableBaseLibraryManager; + + beforeEach(() => { + manager = new TestableBaseLibraryManager(); + }); + + it("should retrieve a library by symbol after setting it", () => { + const sym = Symbol.for("myLib"); + const lib = new TestLibrary(); + const ctx = new LibraryContext(); + + manager.expose_setNewLibrary(Symbol.for("filler"), new TestLibrary(), ctx); + manager.expose_setNewLibrary(sym, lib, ctx); + + const handle = manager.get(sym); + expect(handle.library).toBe(lib); + expect(handle.symbol).toBe(sym); + }); + + it("should throw when getting a library that was not set", () => { + const sym = Symbol.for("unknown"); + expect(() => manager.get(sym)).toThrow(); + }); + + it("should throw when getting a library by unknown index", () => { + expect(() => (manager as any)._get(99)).toThrow(); + }); +}); diff --git a/packages/common/tsconfig.spec.json b/packages/common/tsconfig.spec.json new file mode 100644 index 00000000..8270caba --- /dev/null +++ b/packages/common/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "skipLibCheck": true + }, + "include": ["test/**/*.spec.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/core/package.json b/packages/core/package.json index 53323d34..02f4305d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/core/*'", "release": "cliff-jumper" @@ -69,7 +70,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/core/test/config-registry.spec.ts b/packages/core/test/config-registry.spec.ts new file mode 100644 index 00000000..0a1e2844 --- /dev/null +++ b/packages/core/test/config-registry.spec.ts @@ -0,0 +1,54 @@ +import { Expose } from "class-transformer"; +import { IsString } from "class-validator"; +import { describe, expect, it } from "vitest"; + +import { ConfigRegistry } from "../src/config/config-registry"; + +class ValidConfig { + @Expose() + @IsString() + name!: string; +} + +class OptionalConfig { + @Expose() + @IsString() + name!: string; + + @Expose() + host?: string; +} + +describe("ConfigRegistry", () => { + describe("registerConfig", () => { + it("should return a transformed config instance when env is valid", async () => { + const registry = new ConfigRegistry({ name: "hello" }); + const config = await registry.registerConfig(ValidConfig); + expect(config).toBeInstanceOf(ValidConfig); + expect(config.name).toBe("hello"); + }); + + it("should exclude values not decorated with @Expose", async () => { + const registry = new ConfigRegistry({ name: "hello", extra: "ignored" }); + const config = await registry.registerConfig(ValidConfig); + expect((config as any)["extra"]).toBeUndefined(); + }); + + it("should throw when a required field is missing", async () => { + const registry = new ConfigRegistry({}); + await expect(registry.registerConfig(ValidConfig)).rejects.toThrow(); + }); + + it("should throw when a field has the wrong type", async () => { + const registry = new ConfigRegistry({ name: 42 }); + await expect(registry.registerConfig(ValidConfig)).rejects.toThrow(); + }); + + it("should map multiple env fields correctly", async () => { + const registry = new ConfigRegistry({ name: "world", host: "localhost" }); + const config = await registry.registerConfig(OptionalConfig); + expect(config.name).toBe("world"); + expect(config.host).toBe("localhost"); + }); + }); +}); diff --git a/packages/core/test/editable-library-manager.spec.ts b/packages/core/test/editable-library-manager.spec.ts new file mode 100644 index 00000000..0e808baa --- /dev/null +++ b/packages/core/test/editable-library-manager.spec.ts @@ -0,0 +1,182 @@ +import { COMPONENT_SYSTEM_LIBRARY, type ILibrary, LibraryStatusEnum } from "@nanoforge-dev/common"; +import { beforeEach, describe, expect, it } from "vitest"; + +import { Library } from "../../common/src/library/libraries/library"; +import { EditableLibraryManager } from "../src/common/library/manager/library.manager"; + +class StubLibrary extends Library { + private readonly _name: string; + + constructor(name: string, options?: ConstructorParameters[0]) { + super(options); + this._name = name; + } + + get __name(): string { + return this._name; + } +} + +class StubRunnerLibrary extends StubLibrary { + async __run(): Promise {} +} + +class StubMutableLibrary extends StubLibrary { + mute(): void {} +} + +describe("EditableLibraryManager", () => { + let manager: EditableLibraryManager; + + beforeEach(() => { + manager = new EditableLibraryManager(); + }); + + describe("typed setters and getters", () => { + it("should store and retrieve a component system library", () => { + const lib = new StubLibrary("ComponentSystem"); + manager.setComponentSystem(lib as any); + expect(manager.getComponentSystem().library).toBe(lib); + }); + + it("should store and retrieve a graphics library", () => { + const lib = new StubLibrary("Graphics"); + manager.setGraphics(lib as any); + expect(manager.getGraphics().library).toBe(lib); + }); + + it("should store and retrieve an asset manager library", () => { + const lib = new StubLibrary("AssetManager"); + manager.setAssetManager(lib as any); + expect(manager.getAssetManager().library).toBe(lib); + }); + + it("should store and retrieve a network library", () => { + const lib = new StubLibrary("Network"); + manager.setNetwork(lib as any); + expect(manager.getNetwork().library).toBe(lib); + }); + + it("should store and retrieve an input library", () => { + const lib = new StubLibrary("Input"); + manager.setInput(lib as any); + expect(manager.getInput().library).toBe(lib); + }); + + it("should store and retrieve a sound library", () => { + const lib = new StubLibrary("Sound"); + manager.setSound(lib as any); + expect(manager.getSound().library).toBe(lib); + }); + + it("should store and retrieve a music library", () => { + const lib = new StubLibrary("Music"); + manager.setMusic(lib as any); + expect(manager.getMusic().library).toBe(lib); + }); + + it("should throw when getting a typed library that was not set", () => { + expect(() => manager.getComponentSystem()).toThrow(); + }); + }); + + describe("set and get (custom symbol)", () => { + it("should store and retrieve a library by Symbol.for key", () => { + const sym = Symbol.for("customLib"); + const lib = new StubLibrary("Custom"); + + manager.setAssetManager(new StubLibrary("Asset") as any); + manager.set(sym, lib as unknown as ILibrary); + + expect(manager.get(sym).library).toBe(lib); + }); + }); + + describe("getLibraries", () => { + it("should return the list of all set libraries", () => { + const lib = new StubLibrary("ComponentSystem"); + manager.setComponentSystem(lib as any); + const libs = manager.getLibraries().filter(Boolean); + expect(libs.some((h) => h.library === (lib as unknown as ILibrary))).toBe(true); + }); + }); + + describe("getInitLibraries", () => { + it("should return libraries in dependency order", () => { + const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] }); + const libB = new StubLibrary("B"); + + manager.setComponentSystem(libB as any); + manager.setGraphics(libA as any); + + const order = manager.getInitLibraries().map((h) => h.library.__name); + const idxB = order.indexOf("B"); + const idxA = order.indexOf("A"); + + expect(idxB).toBeLessThan(idxA); + }); + + it("should return all set libraries", () => { + manager.setAssetManager(new StubLibrary("Asset") as any); + manager.setGraphics(new StubLibrary("Graphics") as any); + + expect(manager.getInitLibraries().length).toBe(2); + }); + }); + + describe("getClearLibraries", () => { + it("should return libraries in reverse dependency order", () => { + const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] }); + const libB = new StubLibrary("B"); + + manager.setComponentSystem(libB as any); + manager.setGraphics(libA as any); + + const order = manager.getClearLibraries().map((h) => h.library.__name); + const idxA = order.indexOf("A"); + const idxB = order.indexOf("B"); + + expect(idxA).toBeLessThan(idxB); + }); + }); + + describe("getExecutionLibraries", () => { + it("should only return libraries that implement __run", () => { + manager.setComponentSystem(new StubLibrary("NotARunner") as any); + manager.setGraphics(new StubRunnerLibrary("Runner") as any); + + const runners = manager.getExecutionLibraries(); + expect(runners.every((h) => typeof (h.library as any).__run === "function")).toBe(true); + expect(runners.some((h) => h.library.__name === "Runner")).toBe(true); + expect(runners.some((h) => h.library.__name === "NotARunner")).toBe(false); + }); + + it("should return empty when no runner libraries are set", () => { + manager.setComponentSystem(new StubLibrary("Static") as any); + expect(manager.getExecutionLibraries()).toHaveLength(0); + }); + }); + + describe("getMutableLibraries", () => { + it("should only return libraries that implement mute", () => { + manager.setSound(new StubMutableLibrary("MutableSound") as any); + manager.setGraphics(new StubLibrary("NonMutableGraphics") as any); + + const mutable = manager.getMutableLibraries(); + expect(mutable.some((h) => h.library.__name === "MutableSound")).toBe(true); + expect(mutable.some((h) => h.library.__name === "NonMutableGraphics")).toBe(false); + }); + + it("should return empty when no mutable libraries are set", () => { + manager.setGraphics(new StubLibrary("Graphics") as any); + expect(manager.getMutableLibraries()).toHaveLength(0); + }); + }); + + describe("library context status", () => { + it("should start with UNLOADED status", () => { + manager.setComponentSystem(new StubLibrary("Comp") as any); + expect(manager.getComponentSystem().context.status).toBe(LibraryStatusEnum.UNLOADED); + }); + }); +}); diff --git a/packages/core/test/relationship.spec.ts b/packages/core/test/relationship.spec.ts new file mode 100644 index 00000000..776fa493 --- /dev/null +++ b/packages/core/test/relationship.spec.ts @@ -0,0 +1,135 @@ +import { type ILibrary, LibraryContext, LibraryHandle } from "@nanoforge-dev/common"; +import { describe, expect, it } from "vitest"; + +import { Library } from "../../common/src/library/libraries/library"; +import { Relationship } from "../src/common/library/relationship-functions"; + +class StubLibrary extends Library { + private readonly _name: string; + + constructor(name: string, options?: ConstructorParameters[0]) { + super(options); + this._name = name; + } + + get __name(): string { + return this._name; + } +} + +const makeHandle = ( + sym: symbol, + name: string, + options?: ConstructorParameters[0], +): LibraryHandle => { + return new LibraryHandle( + sym, + new StubLibrary(name, options) as unknown as ILibrary, + new LibraryContext(), + ); +}; + +describe("Relationship.getLibrariesByDependencies", () => { + it("should return libraries in same order when no dependencies are declared", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A"); + const handleB = makeHandle(symB, "B"); + + const result = Relationship.getLibrariesByDependencies([handleA, handleB]); + expect(result.map((h) => h.library.__name)).toEqual(["A", "B"]); + }); + + it("should put a dependency before the library that depends on it", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleB = makeHandle(symB, "B"); + const handleA = makeHandle(symA, "A", { dependencies: [symB] }); + + const result = Relationship.getLibrariesByDependencies([handleA, handleB]); + const names = result.map((h) => h.library.__name); + expect(names.indexOf("B")).toBeLessThan(names.indexOf("A")); + }); + + it("should not duplicate a shared dependency", () => { + const symDep = Symbol("Dep"); + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleDep = makeHandle(symDep, "Dep"); + const handleA = makeHandle(symA, "A", { dependencies: [symDep] }); + const handleB = makeHandle(symB, "B", { dependencies: [symDep] }); + + const result = Relationship.getLibrariesByDependencies([handleA, handleB, handleDep]); + const names = result.map((h) => h.library.__name); + expect(names.filter((n) => n === "Dep")).toHaveLength(1); + }); + + it("should return libraries in reverse dependency order when reverse=true", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleB = makeHandle(symB, "B"); + const handleA = makeHandle(symA, "A", { dependencies: [symB] }); + + const result = Relationship.getLibrariesByDependencies([handleA, handleB], true); + const names = result.map((h) => h.library.__name); + expect(names.indexOf("A")).toBeLessThan(names.indexOf("B")); + }); + + it("should throw on circular dependencies", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A", { dependencies: [symB] }); + const handleB = makeHandle(symB, "B", { dependencies: [symA] }); + + expect(() => Relationship.getLibrariesByDependencies([handleA, handleB])).toThrow( + /[Cc]ircular/, + ); + }); +}); + +describe("Relationship.getLibrariesByRun", () => { + it("should return all runner libraries when no ordering is specified", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A"); + const handleB = makeHandle(symB, "B"); + + const result = Relationship.getLibrariesByRun([handleA, handleB]); + expect(result).toHaveLength(2); + }); + + it("should place a library before another when runBefore is set", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A", { runBefore: [symB] }); + const handleB = makeHandle(symB, "B"); + + const result = Relationship.getLibrariesByRun([handleA, handleB]); + const names = result.map((h) => h.library.__name); + expect(names.indexOf("B")).toBeLessThan(names.indexOf("A")); + }); + + it("should place a library after another when runAfter is set", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A"); + const handleB = makeHandle(symB, "B", { runAfter: [symA] }); + + const result = Relationship.getLibrariesByRun([handleA, handleB]); + const names = result.map((h) => h.library.__name); + expect(names.indexOf("B")).toBeLessThan(names.indexOf("A")); + }); + + it("should throw on circular run dependencies", () => { + const symA = Symbol("A"); + const symB = Symbol("B"); + const handleA = makeHandle(symA, "A", { runBefore: [symB] }); + const handleB = makeHandle(symB, "B", { runBefore: [symA] }); + + expect(() => Relationship.getLibrariesByRun([handleA, handleB])).toThrow(/[Cc]ircular/); + }); + + it("should return empty array for empty input", () => { + expect(Relationship.getLibrariesByRun([])).toHaveLength(0); + }); +}); diff --git a/packages/core/tsconfig.spec.json b/packages/core/tsconfig.spec.json new file mode 100644 index 00000000..8270caba --- /dev/null +++ b/packages/core/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "skipLibCheck": true + }, + "include": ["test/**/*.spec.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/ecs-client/test/ecs-client-library.spec.ts b/packages/ecs-client/test/ecs-client-library.spec.ts index 0434da16..7a9e83b0 100644 --- a/packages/ecs-client/test/ecs-client-library.spec.ts +++ b/packages/ecs-client/test/ecs-client-library.spec.ts @@ -12,27 +12,36 @@ class Position { constructor( public x: number, public y: number, - ) { - this.x = x; - this.y = y; - } + ) {} } +class Velocity { + name: string = "Velocity"; + constructor( + public x: number, + public y: number, + ) {} +} + +const getRunSystemsParams = (registry: Registry) => ({ + libs: { getComponentSystem: () => ({ registry }) }, +}); + +const assetManager = new AssetManagerLibrary(); +const libraryManager = new EditableLibraryManager(); +const appContext = new EditableApplicationContext(libraryManager); +const configRegistry = {} as IConfigRegistry; +const initContext = new InitContext(appContext, libraryManager, configRegistry, { + // @ts-ignore + canvas: null, + files: new Map([["/libecs.wasm", "./lib/libecs.wasm"]]), +}); +const clearContext = new ClearContext(appContext, libraryManager); +libraryManager.setAssetManager(assetManager); + describe("ECSClientLibrary", () => { let ecs: ECSClientLibrary; let registry: Registry; - const assetManager = new AssetManagerLibrary(); - const libraryManager = new EditableLibraryManager(); - const appContext = new EditableApplicationContext(libraryManager); - const configRegistry = {} as IConfigRegistry; - const initContext = new InitContext(appContext, libraryManager, configRegistry, { - // @ts-ignore - canvas: null, - files: new Map([["/libecs.wasm", "./lib/libecs.wasm"]]), - }); - - const clearContext = new ClearContext(appContext, libraryManager); - libraryManager.setAssetManager(assetManager); beforeAll(async () => { await assetManager.__init(initContext); @@ -44,22 +53,124 @@ describe("ECSClientLibrary", () => { registry = ecs.registry; }); - it("init and spawn entity", async () => { - const entity = registry.spawnEntity(); - expect(entity).toBeDefined(); - expect(entity.getId()).toBe(0); + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(ecs.__name).toBe("ECSLibrary"); + }); + }); + + describe("entity management", () => { + it("should spawn an entity with id 0", () => { + const entity = registry.spawnEntity(); + expect(entity).toBeDefined(); + expect(entity.getId()).toBe(0); + }); + + it("should assign incrementing ids to spawned entities", () => { + const e0 = registry.spawnEntity(); + const e1 = registry.spawnEntity(); + const e2 = registry.spawnEntity(); + expect(e0.getId()).toBe(0); + expect(e1.getId()).toBe(1); + expect(e2.getId()).toBe(2); + }); + + it("should kill an entity and remove its components", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.killEntity(entity); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + }); + + it("should kill an entity with multiple components", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.addComponent(entity, new Velocity(3, 4)); + registry.killEntity(entity); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + expect(registry.getComponents(Velocity).get(entity.getId())).toBeUndefined(); + }); + }); + + describe("component management", () => { + it("should add a component to an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(1, 2), + ); + }); + + it("should override a component on an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.addComponent(entity, new Position(9, 9)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(9, 9), + ); + }); + + it("should add multiple component types to the same entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(3, 4)); + registry.addComponent(entity, new Velocity(1, 0)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(3, 4), + ); + expect(registry.getComponents(Velocity).get(entity.getId())).toStrictEqual( + new Velocity(1, 0), + ); + }); + + it("should remove a component from an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.removeComponent(entity, Position); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + }); + + it("should track component count across multiple entities", () => { + const e0 = registry.spawnEntity(); + const e1 = registry.spawnEntity(); + registry.addComponent(e0, new Position(0, 0)); + registry.addComponent(e1, new Position(1, 1)); + expect(registry.getComponents(Position).size()).toBe(2); + }); }); - it("add component to entity", async () => { - const entity = registry.spawnEntity(); - const pos = new Position(1, 2); - registry.addComponent(entity, pos); - const components = registry.getComponents(Position); - expect(components.get(entity.getId())).toStrictEqual(new Position(1, 2)); - expect(components.size()).toBe(1); + describe("system management", () => { + it("should run a registered system once per call", () => { + let counter = 0; + registry.addSystem(() => { + counter += 1; + }); + registry.runSystems(getRunSystemsParams(registry)); + expect(counter).toBe(1); + }); + + it("should run all registered systems in order", () => { + const order: number[] = []; + registry.addSystem(() => order.push(1)); + registry.addSystem(() => order.push(2)); + registry.runSystems(getRunSystemsParams(registry)); + expect(order).toStrictEqual([1, 2]); + }); + + it("should run systems multiple times across multiple calls", () => { + let counter = 0; + registry.addSystem(() => { + counter += 1; + }); + for (let i = 0; i < 5; i++) { + registry.runSystems(getRunSystemsParams(registry)); + } + expect(counter).toBe(5); + }); }); - it("clear", async () => { - await ecs.__clear(clearContext); + describe("lifecycle", () => { + it("should clear without error", async () => { + await expect(ecs.__clear(clearContext)).resolves.not.toThrow(); + }); }); }); diff --git a/packages/ecs-lib/test/Registry.spec.ts b/packages/ecs-lib/test/Registry.spec.ts index 23f3b92d..78cde9d4 100644 --- a/packages/ecs-lib/test/Registry.spec.ts +++ b/packages/ecs-lib/test/Registry.spec.ts @@ -1,197 +1,162 @@ -import { assert, describe, expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import Module from "../lib/libecs"; class Velocity { name: string = "Velocity"; - x: number; - y: number; - - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + constructor( + public x: number, + public y: number, + ) {} } class Position { name: string = "Position"; - x: number; - y: number; - - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + constructor( + public x: number, + public y: number, + ) {} } -const getRunSystemsParams = (registry: any) => { - return { - libs: { - getComponentSystem: () => ({ registry: registry }), - }, - }; -}; +const getRunSystemsParams = (registry: any) => ({ + libs: { getComponentSystem: () => ({ registry }) }, +}); describe("Registry", () => { - it("1 entity 2 components", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); - - const vel = new Velocity(1, 2); - const pos = new Position(4, 5); - - const e = r.spawnEntity(); - expect(e.getId()).toBe(0); - - r.addComponent(e, vel); - r.addComponent(e, pos); - - const velocities = r.getComponents(Velocity); - const positions = r.getComponents(Position); - - expect(velocities.get(e.getId())).toStrictEqual(new Velocity(1, 2)); - expect(positions.get(e.getId())).toStrictEqual(new Position(4, 5)); - }); + describe("entity management", () => { + it("should spawn an entity with id 0", async () => { + const m = await Module(); + const r = new m.Registry(); + const e = r.spawnEntity(); + expect(e.getId()).toBe(0); + }); - it("override components", async () => { - const m = await Module(); - const r = new m.Registry(); + it("should assign incrementing ids to spawned entities", async () => { + const m = await Module(); + const r = new m.Registry(); + const e0 = r.spawnEntity(); + const e1 = r.spawnEntity(); + expect(e0.getId()).toBe(0); + expect(e1.getId()).toBe(1); + }); - const vel = new Velocity(1, 2); - const vel2 = new Velocity(4, 5); + it("should kill an entity and remove all its components", async () => { + const m = await Module(); + const r = new m.Registry(); - const e = r.spawnEntity(); + r.spawnEntity(); + const e = r.spawnEntity(); + expect(e.getId()).toBe(1); - r.addComponent(e, vel); - expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(1, 2)); + r.addComponent(e, new Velocity(1, 2)); + r.addComponent(e, new Position(4, 5)); - r.addComponent(e, vel2); - expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(4, 5)); + r.killEntity(e); + expect(r.getComponents(Velocity).get(e.getId())).toBeUndefined(); + expect(r.getComponents(Position).get(e.getId())).toBeUndefined(); + }); }); - it("basic remove", async () => { - const m = await Module(); - const r = new m.Registry(); + describe("component management", () => { + it("should add multiple components to a single entity", async () => { + const m = await Module(); + const r = new m.Registry(); - const vel = new Velocity(1, 2); - const e = r.spawnEntity(); + const e = r.spawnEntity(); + r.addComponent(e, new Velocity(1, 2)); + r.addComponent(e, new Position(4, 5)); - r.addComponent(e, vel); - expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(1, 2)); - - r.removeComponent(e, Velocity); - expect(r.getComponents(Velocity).size()).toEqual(1); - expect(r.getComponents(Velocity).get(e.getId())).toBeUndefined(); - }); - - it("basic kill", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); + expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(1, 2)); + expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(4, 5)); + }); - const vel = new Velocity(1, 2); - const pos = new Position(4, 5); + it("should override an existing component on an entity", async () => { + const m = await Module(); + const r = new m.Registry(); - r.spawnEntity(); - const e = r.spawnEntity(); - expect(e.getId()).toBe(1); + const e = r.spawnEntity(); + r.addComponent(e, new Velocity(1, 2)); + expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(1, 2)); - r.addComponent(e, vel); - r.addComponent(e, pos); + r.addComponent(e, new Velocity(4, 5)); + expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(4, 5)); + }); - const velocities = r.getComponents(Velocity); - const positions = r.getComponents(Position); + it("should remove a component from an entity", async () => { + const m = await Module(); + const r = new m.Registry(); - expect(positions.size()).toEqual(2); - expect(velocities.get(e.getId())).toStrictEqual(new Velocity(1, 2)); - expect(positions.get(e.getId())).toStrictEqual(new Position(4, 5)); + const e = r.spawnEntity(); + r.addComponent(e, new Velocity(1, 2)); + expect(r.getComponents(Velocity).get(e.getId())).toStrictEqual(new Velocity(1, 2)); - r.killEntity(e); - expect(r.getComponents(Velocity).get(e.getId())).toBeUndefined(); - expect(r.getComponents(Position).get(e.getId())).toBeUndefined(); - }); - - it("system incrementing a variable", async () => { - const m = await Module(); - const r = new m.Registry(); + r.removeComponent(e, Velocity); + expect(r.getComponents(Velocity).size()).toEqual(1); + expect(r.getComponents(Velocity).get(e.getId())).toBeUndefined(); + }); - let counter = 0; + it("should reject the reserved 'entity' component name", async () => { + const m = await Module(); + const r = new m.Registry(); - r.addSystem(() => { - counter += 1; + const e = r.spawnEntity(); + expect(() => r.addComponent(e, { name: "entity" })).toThrow(); }); - - for (let i = 0; i <= 15; i++) { - expect(counter).toBe(i); - r.runSystems(getRunSystemsParams(r)); - } - expect(counter).toBe(16); }); - it("system incrementing component positions by velocity", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); - - const e = r.spawnEntity(); - const e2 = r.spawnEntity(); - expect(e2.getId()).toBe(1); - const e3 = r.spawnEntity(); + describe("system management", () => { + it("should run a system that increments a counter", async () => { + const m = await Module(); + const r = new m.Registry(); - r.addComponent(e, new Velocity(1, 1)); - r.addComponent(e, new Position(-2, -2)); + let counter = 0; + r.addSystem(() => { + counter += 1; + }); - r.addComponent(e2, new Velocity(-1, -1)); - r.addComponent(e2, new Position(2, 2)); - - r.addComponent(e3, new Position(0, 0)); - - r.addSystem(() => { - const velocities = r.getComponents(Velocity); - const positions = r.getComponents(Position); - for (let i = 0; i < velocities.size() && i < positions.size(); i++) { - if (velocities.get(i) === undefined || positions.get(i) === undefined) { - continue; - } - positions.get(i).x += velocities.get(i).x; - positions.get(i).y += velocities.get(i).y; + for (let i = 0; i <= 15; i++) { + expect(counter).toBe(i); + r.runSystems(getRunSystemsParams(r)); } + expect(counter).toBe(16); }); - expect(r.getComponents(Position).size()).toEqual(3); - - expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(-2, -2)); - expect(r.getComponents(Position).get(e2.getId())).toStrictEqual(new Position(2, 2)); - expect(r.getComponents(Position).get(e3.getId())).toStrictEqual(new Position(0, 0)); - r.runSystems(getRunSystemsParams(r)); - - expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(-1, -1)); - expect(r.getComponents(Position).get(e2.getId())).toStrictEqual(new Position(1, 1)); - expect(r.getComponents(Position).get(e3.getId())).toStrictEqual(new Position(0, 0)); - r.runSystems(getRunSystemsParams(r)); - - expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(0, 0)); - expect(r.getComponents(Position).get(e2.getId())).toStrictEqual(new Position(0, 0)); - expect(r.getComponents(Position).get(e3.getId())).toStrictEqual(new Position(0, 0)); - }); - - it("Try unallowed component name", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); + it("should run a system that updates positions by velocity", async () => { + const m = await Module(); + const r = new m.Registry(); + + const e = r.spawnEntity(); + const e2 = r.spawnEntity(); + expect(e2.getId()).toBe(1); + const e3 = r.spawnEntity(); + + r.addComponent(e, new Velocity(1, 1)); + r.addComponent(e, new Position(-2, -2)); + r.addComponent(e2, new Velocity(-1, -1)); + r.addComponent(e2, new Position(2, 2)); + r.addComponent(e3, new Position(0, 0)); + + r.addSystem(() => { + const velocities = r.getComponents(Velocity); + const positions = r.getComponents(Position); + for (let i = 0; i < velocities.size() && i < positions.size(); i++) { + if (velocities.get(i) === undefined || positions.get(i) === undefined) continue; + positions.get(i).x += velocities.get(i).x; + positions.get(i).y += velocities.get(i).y; + } + }); - const entityComp = { name: "entity" }; + expect(r.getComponents(Position).size()).toEqual(3); - const e = r.spawnEntity(); - expect(e.getId()).toBe(0); + r.runSystems(getRunSystemsParams(r)); + expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(-1, -1)); + expect(r.getComponents(Position).get(e2.getId())).toStrictEqual(new Position(1, 1)); + expect(r.getComponents(Position).get(e3.getId())).toStrictEqual(new Position(0, 0)); - try { - r.addComponent(e, entityComp); - assert.fail("Should have thrown an error"); - } catch (e) { - //@ts-ignore - expect(m.getExceptionMessage(e)[1].toString()).toBeDefined(); - } + r.runSystems(getRunSystemsParams(r)); + expect(r.getComponents(Position).get(e.getId())).toStrictEqual(new Position(0, 0)); + expect(r.getComponents(Position).get(e2.getId())).toStrictEqual(new Position(0, 0)); + expect(r.getComponents(Position).get(e3.getId())).toStrictEqual(new Position(0, 0)); + }); }); }); diff --git a/packages/ecs-lib/test/SparseArray.spec.ts b/packages/ecs-lib/test/SparseArray.spec.ts index e1a44620..60f669a6 100644 --- a/packages/ecs-lib/test/SparseArray.spec.ts +++ b/packages/ecs-lib/test/SparseArray.spec.ts @@ -3,40 +3,92 @@ import { describe, expect, it } from "vitest"; import Module from "../lib/libecs"; describe("SparseArray", () => { - it("basic instantation", async () => { - const m = await Module(); - const sa = new m.SparseArray(); - expect(sa).toBeDefined(); - expect(sa.size()).toBe(0); + describe("instantiation", () => { + it("should create an empty sparse array with size 0", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + expect(sa).toBeDefined(); + expect(sa.size()).toBe(0); + }); }); - it("basic insert", async () => { - const m = await Module(); - const sa = new m.SparseArray(); - sa.set(0, 1); - expect(sa.size()).toBe(1); - expect(sa.get(0)).toBe(1); + describe("set and get", () => { + it("should insert a value at index 0 and retrieve it", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 1); + expect(sa.size()).toBe(1); + expect(sa.get(0)).toBe(1); + }); + + it("should override an existing value at the same index", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 42); + sa.set(0, 99); + expect(sa.get(0)).toBe(99); + }); + + it("should store multiple values at different indices", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 10); + sa.set(2, 20); + sa.set(4, 30); + expect(sa.get(0)).toBe(10); + expect(sa.get(2)).toBe(20); + expect(sa.get(4)).toBe(30); + }); }); - it("basic remove", async () => { - const m = await Module(); - const sa = new m.SparseArray(); - sa.set(0, 1); - sa.erase(0); - expect(sa.size()).toBe(1); - expect(sa.get(0)).toBe(undefined); + describe("erase", () => { + it("should erase a value and return undefined at that index", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 1); + sa.erase(0); + expect(sa.size()).toBe(1); + expect(sa.get(0)).toBeUndefined(); + }); + + it("should only erase the targeted index", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 1); + sa.set(1, 2); + sa.erase(0); + expect(sa.get(0)).toBeUndefined(); + expect(sa.get(1)).toBe(2); + }); }); - it("basic iteration with get and size", async () => { - const m = await Module(); - const sa = new m.SparseArray(); - sa.set(0, 1); - sa.set(1, 2); - sa.set(2, 3); - let sum = 0; - for (let i = 0; i < sa.size(); i++) { - sum += sa.get(i); - } - expect(sum).toBe(6); + describe("iteration", () => { + it("should sum all values via get and size", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 1); + sa.set(1, 2); + sa.set(2, 3); + let sum = 0; + for (let i = 0; i < sa.size(); i++) { + sum += sa.get(i); + } + expect(sum).toBe(6); + }); + + it("should skip erased entries during iteration", async () => { + const m = await Module(); + const sa = new m.SparseArray(); + sa.set(0, 10); + sa.set(1, 20); + sa.set(2, 30); + sa.erase(1); + let sum = 0; + for (let i = 0; i < sa.size(); i++) { + const val = sa.get(i); + if (val !== undefined) sum += val; + } + expect(sum).toBe(40); + }); }); }); diff --git a/packages/ecs-lib/test/Zipper.spec.ts b/packages/ecs-lib/test/Zipper.spec.ts index f21e0f5f..204229be 100644 --- a/packages/ecs-lib/test/Zipper.spec.ts +++ b/packages/ecs-lib/test/Zipper.spec.ts @@ -4,123 +4,111 @@ import Module from "../lib/libecs"; class Velocity { name: string = "Velocity"; - x: number; - y: number; - - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + constructor( + public x: number, + public y: number, + ) {} } class Position { name: string = "Position"; - x: number; - y: number; - - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + constructor( + public x: number, + public y: number, + ) {} } describe("Zipper", () => { - it("single simple sparse array instantation", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); - - for (let i = 0; i < 5; i++) { - const e = r.spawnEntity(); - r.addComponent(e, new Velocity(i, i)); - } - - const zip = r.getZipper([Velocity]); - - expect(zip).toBeDefined(); - expect(zip).toStrictEqual([ - { Velocity: new Velocity(0, 0) }, - { Velocity: new Velocity(1, 1) }, - { Velocity: new Velocity(2, 2) }, - { Velocity: new Velocity(3, 3) }, - { Velocity: new Velocity(4, 4) }, - ]); - }); + describe("single component", () => { + it("should zip sequentially spawned entities with one component type", async () => { + const m = await Module(); + const r = new m.Registry(); + + for (let i = 0; i < 5; i++) { + const e = r.spawnEntity(); + r.addComponent(e, new Velocity(i, i)); + } + + const zip = r.getZipper([Velocity]); + expect(zip).toBeDefined(); + expect(zip).toStrictEqual([ + { Velocity: new Velocity(0, 0) }, + { Velocity: new Velocity(1, 1) }, + { Velocity: new Velocity(2, 2) }, + { Velocity: new Velocity(3, 3) }, + { Velocity: new Velocity(4, 4) }, + ]); + }); + + it("should zip non-sequential entity ids with one component type", async () => { + const m = await Module(); + const r = new m.Registry(); + + for (let i = 0; i < 5; i++) { + const e = new m.Entity(i * 5); + r.addComponent(e, new Velocity(i, i)); + } - it("single complex sparse array instantation", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); + const zip = r.getZipper([Velocity]); + expect(zip).toBeDefined(); + expect(zip[0]).toStrictEqual({ Velocity: new Velocity(0, 0) }); + expect(zip[1]).toStrictEqual({ Velocity: new Velocity(1, 1) }); + expect(zip[2]).toStrictEqual({ Velocity: new Velocity(2, 2) }); + expect(zip[3]).toStrictEqual({ Velocity: new Velocity(3, 3) }); + expect(zip[4]).toStrictEqual({ Velocity: new Velocity(4, 4) }); + }); + }); - for (let i = 0; i < 5; i++) { - const e = new m.Entity(i * 5); - r.addComponent(e, new Velocity(i, i)); - } + describe("multiple components", () => { + it("should only include entities that have all requested components", async () => { + const m = await Module(); + const r = new m.Registry(); - const zip = r.getZipper([Velocity]); - expect(zip).toBeDefined(); + for (let i = 0; i < 20; i++) { + const e = r.spawnEntity(); + if (i % 5 === 0) r.addComponent(e, new Velocity(0, 1)); + if (i % 3 === 0) r.addComponent(e, new Position(1, 0)); + } - expect(zip[0]).toStrictEqual({ Velocity: new Velocity(0, 0) }); - expect(zip[1]).toStrictEqual({ Velocity: new Velocity(1, 1) }); - expect(zip[2]).toStrictEqual({ Velocity: new Velocity(2, 2) }); - expect(zip[3]).toStrictEqual({ Velocity: new Velocity(3, 3) }); - expect(zip[4]).toStrictEqual({ Velocity: new Velocity(4, 4) }); - }); + const zip = r.getZipper([Velocity, Position]); + expect(zip).toBeDefined(); - it("multiple complex sparse array instantation", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); - - for (let i = 0; i < 20; i++) { - const e = r.spawnEntity(); - if (i % 5 === 0) r.addComponent(e, new Velocity(0, 1)); - if (i % 3 === 0) r.addComponent(e, new Position(1, 0)); - } - - const zip = r.getZipper([Velocity, Position]); - expect(zip).toBeDefined(); - - console.log(zip); - for (let i = 0; i < 20; i++) { - if (zip[i]) { - expect(zip[i]).toStrictEqual({ - Velocity: new Velocity(0, 1), - Position: new Position(1, 0), - }); + for (let i = 0; i < 20; i++) { + if (zip[i]) { + expect(zip[i]).toStrictEqual({ + Velocity: new Velocity(0, 1), + Position: new Position(1, 0), + }); + } } - } + }); }); - it("simple indexed zipper modification", async () => { - const m = await Module(); - const r = new m.Registry(); - expect(r).toBeDefined(); + describe("mutations through zipper", () => { + it("should reflect component mutations when zipper is re-fetched", async () => { + const m = await Module(); + const r = new m.Registry(); - for (let i = 0; i < 20; i++) { - const e = r.spawnEntity(); - if (i % 5 === 0) { - r.addComponent(e, new Velocity(0, 1)); + for (let i = 0; i < 20; i++) { + const e = r.spawnEntity(); + if (i % 5 === 0) r.addComponent(e, new Velocity(0, 1)); } - } - let zip = r.getZipper([Velocity]); - expect(zip).toBeDefined(); + let zip = r.getZipper([Velocity]); + expect(zip).toBeDefined(); - for (let i = 0; i < 20; i++) { - if (zip[i]) { - const vel = zip[i]["Velocity"]; - vel.y *= 2; + for (let i = 0; i < 20; i++) { + if (zip[i]) { + zip[i]["Velocity"].y *= 2; + } } - } - - zip = r.getZipper([Velocity]); - for (let i = 0; i < 20; i++) { - if (zip[i]) { - expect(zip[i]).toStrictEqual({ - Velocity: new Velocity(0, 2), - }); + + zip = r.getZipper([Velocity]); + for (let i = 0; i < 20; i++) { + if (zip[i]) { + expect(zip[i]).toStrictEqual({ Velocity: new Velocity(0, 2) }); + } } - } + }); }); }); diff --git a/packages/ecs-server/test/ecs-server-library.spec.ts b/packages/ecs-server/test/ecs-server-library.spec.ts index a7cb20c5..e3c6d8fe 100644 --- a/packages/ecs-server/test/ecs-server-library.spec.ts +++ b/packages/ecs-server/test/ecs-server-library.spec.ts @@ -12,27 +12,36 @@ class Position { constructor( public x: number, public y: number, - ) { - this.x = x; - this.y = y; - } + ) {} } +class Velocity { + name: string = "Velocity"; + constructor( + public x: number, + public y: number, + ) {} +} + +const getRunSystemsParams = (registry: Registry) => ({ + libs: { getComponentSystem: () => ({ registry }) }, +}); + +const assetManager = new AssetManagerLibrary(); +const libraryManager = new EditableLibraryManager(); +const appContext = new EditableApplicationContext(libraryManager); +const configRegistry = {} as IConfigRegistry; +const initContext = new InitContext(appContext, libraryManager, configRegistry, { + // @ts-ignore + canvas: null, + files: new Map([["/libecs.wasm", "./lib/libecs.wasm"]]), +}); +const clearContext = new ClearContext(appContext, libraryManager); +libraryManager.setAssetManager(assetManager); + describe("ECSServerLibrary", () => { let ecs: ECSServerLibrary; let registry: Registry; - const assetManager = new AssetManagerLibrary(); - const libraryManager = new EditableLibraryManager(); - const appContext = new EditableApplicationContext(libraryManager); - const configRegistry = {} as IConfigRegistry; - const initContext = new InitContext(appContext, libraryManager, configRegistry, { - // @ts-ignore - canvas: null, - files: new Map([["/libecs.wasm", "./lib/libecs.wasm"]]), - }); - - const clearContext = new ClearContext(appContext, libraryManager); - libraryManager.setAssetManager(assetManager); beforeAll(async () => { await assetManager.__init(initContext); @@ -44,22 +53,124 @@ describe("ECSServerLibrary", () => { registry = ecs.registry; }); - it("init and spawn entity", async () => { - const entity = registry.spawnEntity(); - expect(entity).toBeDefined(); - expect(entity.getId()).toBe(0); + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(ecs.__name).toBe("ECSLibrary"); + }); + }); + + describe("entity management", () => { + it("should spawn an entity with id 0", () => { + const entity = registry.spawnEntity(); + expect(entity).toBeDefined(); + expect(entity.getId()).toBe(0); + }); + + it("should assign incrementing ids to spawned entities", () => { + const e0 = registry.spawnEntity(); + const e1 = registry.spawnEntity(); + const e2 = registry.spawnEntity(); + expect(e0.getId()).toBe(0); + expect(e1.getId()).toBe(1); + expect(e2.getId()).toBe(2); + }); + + it("should kill an entity and remove its components", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.killEntity(entity); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + }); + + it("should kill an entity with multiple components", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.addComponent(entity, new Velocity(3, 4)); + registry.killEntity(entity); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + expect(registry.getComponents(Velocity).get(entity.getId())).toBeUndefined(); + }); + }); + + describe("component management", () => { + it("should add a component to an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(1, 2), + ); + }); + + it("should override a component on an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.addComponent(entity, new Position(9, 9)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(9, 9), + ); + }); + + it("should add multiple component types to the same entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(3, 4)); + registry.addComponent(entity, new Velocity(1, 0)); + expect(registry.getComponents(Position).get(entity.getId())).toStrictEqual( + new Position(3, 4), + ); + expect(registry.getComponents(Velocity).get(entity.getId())).toStrictEqual( + new Velocity(1, 0), + ); + }); + + it("should remove a component from an entity", () => { + const entity = registry.spawnEntity(); + registry.addComponent(entity, new Position(1, 2)); + registry.removeComponent(entity, Position); + expect(registry.getComponents(Position).get(entity.getId())).toBeUndefined(); + }); + + it("should track component count across multiple entities", () => { + const e0 = registry.spawnEntity(); + const e1 = registry.spawnEntity(); + registry.addComponent(e0, new Position(0, 0)); + registry.addComponent(e1, new Position(1, 1)); + expect(registry.getComponents(Position).size()).toBe(2); + }); }); - it("add component to entity", async () => { - const entity = registry.spawnEntity(); - const pos = new Position(1, 2); - registry.addComponent(entity, pos); - const components = registry.getComponents(Position); - expect(components.get(entity.getId())).toStrictEqual(new Position(1, 2)); - expect(components.size()).toBe(1); + describe("system management", () => { + it("should run a registered system once per call", () => { + let counter = 0; + registry.addSystem(() => { + counter += 1; + }); + registry.runSystems(getRunSystemsParams(registry)); + expect(counter).toBe(1); + }); + + it("should run all registered systems in order", () => { + const order: number[] = []; + registry.addSystem(() => order.push(1)); + registry.addSystem(() => order.push(2)); + registry.runSystems(getRunSystemsParams(registry)); + expect(order).toStrictEqual([1, 2]); + }); + + it("should run systems multiple times across multiple calls", () => { + let counter = 0; + registry.addSystem(() => { + counter += 1; + }); + for (let i = 0; i < 5; i++) { + registry.runSystems(getRunSystemsParams(registry)); + } + expect(counter).toBe(5); + }); }); - it("clear", async () => { - await ecs.__clear(clearContext); + describe("lifecycle", () => { + it("should clear without error", async () => { + await expect(ecs.__clear(clearContext)).resolves.not.toThrow(); + }); }); }); diff --git a/packages/graphics-2d/test/graphics-2d.library.spec.ts b/packages/graphics-2d/test/graphics-2d.library.spec.ts index ad665041..a5cc7e1d 100644 --- a/packages/graphics-2d/test/graphics-2d.library.spec.ts +++ b/packages/graphics-2d/test/graphics-2d.library.spec.ts @@ -1,22 +1,41 @@ import { type IConfigRegistry, InitContext } from "@nanoforge-dev/common"; -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import { EditableApplicationContext } from "../../core/src/common/context/contexts/application.editable-context"; import { EditableLibraryManager } from "../../core/src/common/library/manager/library.manager"; -import { Graphics2DLibrary } from "../src/graphics-2d.library"; +import { Graphics2DLibrary } from "../src"; -describe("Graphics 2D Library", () => { - const library = new Graphics2DLibrary(); +const makeContext = (canvas: HTMLCanvasElement | null) => { const libraryManager = new EditableLibraryManager(); const appContext = new EditableApplicationContext(libraryManager); const configRegistry = {} as IConfigRegistry; - const context = new InitContext(appContext, libraryManager, configRegistry, { + return new InitContext(appContext, libraryManager, configRegistry, { // @ts-ignore - canvas: null, + canvas, files: new Map(), }); +}; - it("Should throw if canvas is undefined", async () => { - await expect(library.__init(context)).rejects.toThrow(); +describe("Graphics2DLibrary", () => { + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new Graphics2DLibrary().__name).toBe("Graphics2DLibrary"); + }); + }); + + describe("before initialization", () => { + let library: Graphics2DLibrary; + + beforeEach(() => { + library = new Graphics2DLibrary(); + }); + + it("should throw when stage is accessed before __init", () => { + expect(() => library.stage).toThrow(); + }); + + it("should throw when __init is called with a null canvas", async () => { + await expect(library.__init(makeContext(null))).rejects.toThrow(); + }); }); }); diff --git a/packages/input/package.json b/packages/input/package.json index 8fe27775..47f96907 100644 --- a/packages/input/package.json +++ b/packages/input/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/input/*'", "release": "cliff-jumper" @@ -65,7 +66,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/input/test/input.library.spec.ts b/packages/input/test/input.library.spec.ts new file mode 100644 index 00000000..f126f0d5 --- /dev/null +++ b/packages/input/test/input.library.spec.ts @@ -0,0 +1,100 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { InputEnum, InputLibrary } from "../src"; + +const makeWindowMock = () => { + const listeners: Record void)[]> = {}; + return { + addEventListener: vi.fn((event: string, handler: (e: KeyboardEvent) => void) => { + if (!listeners[event]) listeners[event] = []; + listeners[event].push(handler); + }), + dispatch: (event: string, e: Partial) => { + listeners[event]?.forEach((h) => h(e as KeyboardEvent)); + }, + }; +}; + +describe("InputLibrary", () => { + let windowMock: ReturnType; + + beforeEach(() => { + windowMock = makeWindowMock(); + vi.stubGlobal("window", windowMock); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new InputLibrary().__name).toBe("InputLibrary"); + }); + }); + + describe("before initialization", () => { + it("should throw when isKeyPressed is called before __init", () => { + const library = new InputLibrary(); + expect(() => library.isKeyPressed(InputEnum.KeyA)).toThrow(); + }); + + it("should throw when getPressedKeys is called before __init", () => { + const library = new InputLibrary(); + expect(() => library.getPressedKeys()).toThrow(); + }); + }); + + describe("after initialization", () => { + let library: InputLibrary; + + beforeEach(async () => { + library = new InputLibrary(); + await library.__init(); + }); + + it("should register keydown and keyup event listeners", () => { + expect(windowMock.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function)); + expect(windowMock.addEventListener).toHaveBeenCalledWith("keyup", expect.any(Function)); + }); + + it("should return false for any key before any key event", () => { + expect(library.isKeyPressed(InputEnum.KeyA)).toBe(false); + expect(library.isKeyPressed(InputEnum.Space)).toBe(false); + expect(library.isKeyPressed(InputEnum.Enter)).toBe(false); + }); + + it("should return an empty pressed keys list when no key is down", () => { + expect(library.getPressedKeys()).toStrictEqual([]); + }); + + it("should return true for a key after a keydown event", () => { + windowMock.dispatch("keydown", { code: InputEnum.KeyA }); + expect(library.isKeyPressed(InputEnum.KeyA)).toBe(true); + }); + + it("should return false for a key after keydown then keyup", () => { + windowMock.dispatch("keydown", { code: InputEnum.KeyA }); + windowMock.dispatch("keyup", { code: InputEnum.KeyA }); + expect(library.isKeyPressed(InputEnum.KeyA)).toBe(false); + }); + + it("should list all currently pressed keys", () => { + windowMock.dispatch("keydown", { code: InputEnum.KeyA }); + windowMock.dispatch("keydown", { code: InputEnum.Space }); + const pressed = library.getPressedKeys(); + expect(pressed).toContain(InputEnum.KeyA); + expect(pressed).toContain(InputEnum.Space); + expect(pressed.length).toBe(2); + }); + + it("should remove a released key from pressed keys", () => { + windowMock.dispatch("keydown", { code: InputEnum.KeyA }); + windowMock.dispatch("keydown", { code: InputEnum.Space }); + windowMock.dispatch("keyup", { code: InputEnum.KeyA }); + const pressed = library.getPressedKeys(); + expect(pressed).not.toContain(InputEnum.KeyA); + expect(pressed).toContain(InputEnum.Space); + }); + }); +}); diff --git a/packages/music/package.json b/packages/music/package.json index aef17c62..e5582356 100644 --- a/packages/music/package.json +++ b/packages/music/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/music/*'", "release": "cliff-jumper" @@ -65,7 +66,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/music/test/music.library.spec.ts b/packages/music/test/music.library.spec.ts new file mode 100644 index 00000000..56f2b4cd --- /dev/null +++ b/packages/music/test/music.library.spec.ts @@ -0,0 +1,106 @@ +import { NfNotFound } from "@nanoforge-dev/common"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { MusicLibrary } from "../src"; + +const makeMockAudio = () => { + const play = vi.fn(() => Promise.resolve()); + const pause = vi.fn(); + return { + AudioClass: class { + muted = false; + play = play; + pause = pause; + src: string; + constructor(src: string) { + this.src = src; + } + }, + play, + pause, + }; +}; + +describe("MusicLibrary", () => { + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new MusicLibrary().__name).toBe("NfMusic"); + }); + }); + + describe("before initialization", () => { + it("should throw when play is called before __init", () => { + const library = new MusicLibrary(); + expect(() => library.play("theme")).toThrow(); + }); + + it("should throw when mute is called before __init", () => { + const library = new MusicLibrary(); + expect(() => library.mute()).toThrow(); + }); + + it("should throw when load is called before __init", () => { + const library = new MusicLibrary(); + expect(() => library.load("theme", "theme.mp3")).toThrow(); + }); + }); + + describe("after initialization", () => { + let library: MusicLibrary; + let mock: ReturnType; + + beforeEach(async () => { + mock = makeMockAudio(); + vi.stubGlobal("Audio", mock.AudioClass); + library = new MusicLibrary(); + await library.__init(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("should throw NfNotFound when playing an unknown track", () => { + expect(() => library.play("unknown")).toThrow(NfNotFound); + }); + + it("should play a loaded track without error", () => { + library.load("theme", "theme.mp3"); + expect(() => library.play("theme")).not.toThrow(); + }); + + it("should call play on the audio element", () => { + library.load("theme", "theme.mp3"); + library.play("theme"); + expect(mock.play).toHaveBeenCalled(); + }); + + it("should pause the previous track when playing a new one", () => { + library.load("theme", "theme.mp3"); + library.load("battle", "battle.mp3"); + library.play("theme"); + library.play("battle"); + expect(mock.pause).toHaveBeenCalled(); + }); + + it("should toggle mute state for all loaded tracks", async () => { + library.load("theme", "theme.mp3"); + library.load("battle", "battle.mp3"); + library.mute(); + library.load("credits", "credits.mp3"); + expect(() => library.play("credits")).not.toThrow(); + }); + + it("should load multiple tracks independently", () => { + library.load("theme", "theme.mp3"); + library.load("battle", "battle.mp3"); + expect(() => library.play("theme")).not.toThrow(); + expect(() => library.play("battle")).not.toThrow(); + }); + + it("should throw NfNotFound after loading a different track", () => { + library.load("theme", "theme.mp3"); + expect(() => library.play("credits")).toThrow(NfNotFound); + }); + }); +}); diff --git a/packages/network-client/package.json b/packages/network-client/package.json index c119f4e0..4c829077 100644 --- a/packages/network-client/package.json +++ b/packages/network-client/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/network-client/*'", "release": "cliff-jumper" @@ -66,7 +67,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/network-client/test/client.network.library.spec.ts b/packages/network-client/test/client.network.library.spec.ts new file mode 100644 index 00000000..57bbf971 --- /dev/null +++ b/packages/network-client/test/client.network.library.spec.ts @@ -0,0 +1,106 @@ +import { type InitContext, NfConfigException } from "@nanoforge-dev/common"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { NetworkClientLibrary } from "../src"; + +const makeContext = (config: Record) => + ({ + config: { + registerConfig: vi.fn().mockResolvedValue(config), + }, + }) as unknown as InitContext; + +describe("NetworkClientLibrary", () => { + beforeEach(() => { + vi.stubGlobal( + "WebSocket", + Object.assign( + vi.fn(function (this: any) { + this.readyState = 0; + this.binaryType = ""; + this.send = vi.fn(); + this.onerror = null; + this.onopen = null; + this.onmessage = null; + this.onclose = null; + }), + { OPEN: 1 }, + ), + ); + + vi.stubGlobal( + "RTCPeerConnection", + vi.fn(function (this: any) { + this.onicecandidate = null; + this.createDataChannel = vi.fn(function (this: any) { + return { + readyState: "closed", + send: vi.fn(), + onopen: null, + onmessage: null, + onerror: null, + onclose: null, + }; + }); + this.createOffer = vi.fn().mockResolvedValue({}); + this.setLocalDescription = vi.fn().mockResolvedValue(undefined); + }), + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new NetworkClientLibrary().__name).toBe("NetworkClientLibrary"); + }); + }); + + describe("config validation", () => { + it("should throw NfConfigException when neither TCP nor UDP port is provided", async () => { + const ctx = makeContext({ serverAddress: "127.0.0.1", magicValue: "END" }); + await expect(new NetworkClientLibrary().__init(ctx)).rejects.toThrow(NfConfigException); + }); + }); + + describe("initialization", () => { + it("should initialize a TCP client when only serverTcpPort is provided", async () => { + const ctx = makeContext({ + serverTcpPort: "8080", + serverAddress: "127.0.0.1", + magicValue: "END", + }); + const lib = new NetworkClientLibrary(); + await lib.__init(ctx); + expect(lib.tcp).toBeDefined(); + expect(lib.udp).toBeUndefined(); + }); + + it("should initialize a UDP client when only serverUdpPort is provided", async () => { + const ctx = makeContext({ + serverUdpPort: "8081", + serverAddress: "127.0.0.1", + magicValue: "END", + }); + const lib = new NetworkClientLibrary(); + await lib.__init(ctx); + expect(lib.udp).toBeDefined(); + expect(lib.tcp).toBeUndefined(); + }); + + it("should initialize both TCP and UDP clients when both ports are provided", async () => { + const ctx = makeContext({ + serverTcpPort: "8080", + serverUdpPort: "8081", + serverAddress: "127.0.0.1", + magicValue: "END", + }); + const lib = new NetworkClientLibrary(); + await lib.__init(ctx); + expect(lib.tcp).toBeDefined(); + expect(lib.udp).toBeDefined(); + }); + }); +}); diff --git a/packages/network-client/test/tcp.client.spec.ts b/packages/network-client/test/tcp.client.spec.ts new file mode 100644 index 00000000..7790c2cf --- /dev/null +++ b/packages/network-client/test/tcp.client.spec.ts @@ -0,0 +1,82 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { TCPClient } from "../src/tcp.client.network"; + +const makeWsMock = (readyState = 0) => ({ + readyState, + binaryType: "", + send: vi.fn(), + onerror: null as any, + onopen: null as any, + onmessage: null as any, + onclose: null as any, +}); + +describe("TCPClient", () => { + let ws: ReturnType; + + beforeEach(() => { + ws = makeWsMock(); + vi.stubGlobal( + "WebSocket", + Object.assign( + vi.fn(function () { + return ws; + }), + { OPEN: 1 }, + ), + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + describe("before connect", () => { + it("should report not connected before connect is called", () => { + const client = new TCPClient(8080, "127.0.0.1", "END", false); + expect(client.isConnected()).toBe(false); + }); + + it("should return no received packets before any data arrives", () => { + const client = new TCPClient(8080, "127.0.0.1", "END", false); + expect(client.getReceivedPackets()).toStrictEqual([]); + }); + + it("should not throw when sendData is called before connect", () => { + const client = new TCPClient(8080, "127.0.0.1", "END", false); + expect(() => client.sendData(new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + }); + + describe("after connect", () => { + it("should create a WebSocket to the correct url on connect", async () => { + const client = new TCPClient(9090, "192.168.1.1", "MAGIC", false); + await client.connect(); + expect(vi.mocked(WebSocket)).toHaveBeenCalledWith("ws://192.168.1.1:9090"); + }); + + it("should report connected when the WebSocket readyState is OPEN", async () => { + ws.readyState = 1; + const client = new TCPClient(8080, "127.0.0.1", "END", false); + await client.connect(); + expect(client.isConnected()).toBe(true); + }); + + it("should accumulate and parse packets from received message chunks", async () => { + const magic = new TextEncoder().encode("END"); + const payload = new Uint8Array([10, 20, 30]); + const packet = new Uint8Array([...payload, ...magic]); + + ws.readyState = 1; + const client = new TCPClient(8080, "127.0.0.1", "END", false); + await client.connect(); + + ws.onmessage({ data: packet.buffer }); + + const received = client.getReceivedPackets(); + expect(received).toHaveLength(1); + expect(received[0]).toStrictEqual(payload); + }); + }); +}); diff --git a/packages/network-client/test/udp.client.spec.ts b/packages/network-client/test/udp.client.spec.ts new file mode 100644 index 00000000..ae76a8ca --- /dev/null +++ b/packages/network-client/test/udp.client.spec.ts @@ -0,0 +1,63 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { UDPClient } from "../src/udp.client.network"; + +describe("UDPClient", () => { + beforeEach(() => { + vi.stubGlobal( + "WebSocket", + Object.assign( + vi.fn(() => ({ + readyState: 0, + binaryType: "", + send: vi.fn(), + onerror: null, + onopen: null, + onmessage: null, + onclose: null, + })), + { OPEN: 1 }, + ), + ); + + vi.stubGlobal( + "RTCPeerConnection", + vi.fn(() => ({ + createDataChannel: vi.fn(() => ({ + readyState: "closed", + send: vi.fn(), + onopen: null, + onmessage: null, + onerror: null, + onclose: null, + })), + onicecandidate: null, + createOffer: vi.fn().mockResolvedValue({}), + setLocalDescription: vi.fn().mockResolvedValue(undefined), + setRemoteDescription: vi.fn().mockResolvedValue(undefined), + addIceCandidate: vi.fn().mockResolvedValue(undefined), + })), + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + describe("before connect", () => { + it("should report not connected before connect is called", () => { + const client = new UDPClient(8081, "127.0.0.1", "END", false); + expect(client.isConnected()).toBe(false); + }); + + it("should return no received packets before any data arrives", () => { + const client = new UDPClient(8081, "127.0.0.1", "END", false); + expect(client.getReceivedPackets()).toStrictEqual([]); + }); + + it("should not throw when sendData is called before connect", () => { + const client = new UDPClient(8081, "127.0.0.1", "END", false); + expect(() => client.sendData(new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + }); +}); diff --git a/packages/network-client/test/utils.spec.ts b/packages/network-client/test/utils.spec.ts new file mode 100644 index 00000000..fb846764 --- /dev/null +++ b/packages/network-client/test/utils.spec.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; + +import { buildMagicPacket, parsePacketsFromChunks } from "../src/utils"; + +const magic = new TextEncoder().encode("END"); + +describe("buildMagicPacket", () => { + it("should append the magic bytes after the payload", () => { + const data = new Uint8Array([1, 2, 3]); + const result = buildMagicPacket(data, magic); + expect(result).toStrictEqual(new Uint8Array([1, 2, 3, ...magic])); + }); + + it("should produce a buffer of length data + magic", () => { + const data = new Uint8Array([10, 20]); + const result = buildMagicPacket(data, magic); + expect(result.length).toBe(data.length + magic.length); + }); + + it("should work with an empty payload", () => { + const result = buildMagicPacket(new Uint8Array(), magic); + expect(result).toStrictEqual(magic); + }); +}); + +describe("parsePacketsFromChunks", () => { + it("should parse a single complete packet from a flat buffer", () => { + const payload = new Uint8Array([10, 20, 30]); + const stream = buildMagicPacket(payload, magic); + const { packets, data, chunkedData } = parsePacketsFromChunks(stream, [], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + expect(data.length).toBe(0); + expect(chunkedData).toStrictEqual([]); + }); + + it("should parse two consecutive packets from a flat buffer", () => { + const p1 = new Uint8Array([1]); + const p2 = new Uint8Array([2]); + const stream = new Uint8Array([...buildMagicPacket(p1, magic), ...buildMagicPacket(p2, magic)]); + const { packets } = parsePacketsFromChunks(stream, [], magic); + expect(packets).toHaveLength(2); + expect(packets[0]).toStrictEqual(p1); + expect(packets[1]).toStrictEqual(p2); + }); + + it("should retain an incomplete packet as leftover data", () => { + const incomplete = new Uint8Array([5, 6, 7]); + const { packets, data } = parsePacketsFromChunks(incomplete, [], magic); + expect(packets).toHaveLength(0); + expect(data).toStrictEqual(incomplete); + }); + + it("should reassemble a packet split across multiple chunks", () => { + const payload = new Uint8Array([1, 2, 3]); + const full = buildMagicPacket(payload, magic); + const chunk1 = full.slice(0, 2); + const chunk2 = full.slice(2); + const { packets } = parsePacketsFromChunks(new Uint8Array(), [chunk1, chunk2], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + }); + + it("should combine leftover data with new chunks to complete a packet", () => { + const payload = new Uint8Array([10, 20]); + const full = buildMagicPacket(payload, magic); + const leftover = full.slice(0, 1); + const rest = full.slice(1); + const { packets } = parsePacketsFromChunks(leftover, [rest], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + }); + + it("should return empty packets and an empty chunkedData on an empty input", () => { + const { packets, data, chunkedData } = parsePacketsFromChunks(new Uint8Array(), [], magic); + expect(packets).toHaveLength(0); + expect(data.length).toBe(0); + expect(chunkedData).toStrictEqual([]); + }); +}); diff --git a/packages/network-server/package.json b/packages/network-server/package.json index e727f8cb..a5de4e57 100644 --- a/packages/network-server/package.json +++ b/packages/network-server/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/network-server/*'", "release": "cliff-jumper" @@ -70,7 +71,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/network-server/test/server.network.library.spec.ts b/packages/network-server/test/server.network.library.spec.ts new file mode 100644 index 00000000..77d1848a --- /dev/null +++ b/packages/network-server/test/server.network.library.spec.ts @@ -0,0 +1,89 @@ +import { type InitContext, NfConfigException } from "@nanoforge-dev/common"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { NetworkServerLibrary } from "../src"; + +vi.mock("ws", () => ({ + WebSocketServer: vi.fn(function (this: any) { + this.on = vi.fn(); + }), +})); + +vi.mock("wrtc", () => ({ + RTCPeerConnection: vi.fn(function (this: any) { + this.onconnectionstatechange = null; + this.onicecandidate = null; + this.ondatachannel = null; + this.close = vi.fn(); + }), +})); + +const makeContext = (config: Record) => + ({ + config: { + registerConfig: vi.fn().mockResolvedValue(config), + }, + }) as unknown as InitContext; + +describe("NetworkServerLibrary", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new NetworkServerLibrary().__name).toBe("NetworkServerLibrary"); + }); + }); + + describe("config validation", () => { + it("should throw NfConfigException when listeningInterface is not provided", async () => { + const ctx = makeContext({ listeningTcpPort: "9000", magicValue: "END" }); + await expect(new NetworkServerLibrary().__init(ctx)).rejects.toThrow(NfConfigException); + }); + + it("should throw NfConfigException when neither TCP nor UDP port is provided", async () => { + const ctx = makeContext({ listeningInterface: "0.0.0.0", magicValue: "END" }); + await expect(new NetworkServerLibrary().__init(ctx)).rejects.toThrow(NfConfigException); + }); + }); + + describe("initialization", () => { + it("should initialize a TCP server when only listeningTcpPort is provided", async () => { + const ctx = makeContext({ + listeningTcpPort: "9000", + listeningInterface: "0.0.0.0", + magicValue: "END", + }); + const lib = new NetworkServerLibrary(); + await lib.__init(ctx); + expect(lib.tcp).toBeDefined(); + expect(lib.udp).toBeUndefined(); + }); + + it("should initialize a UDP server when only listeningUdpPort is provided", async () => { + const ctx = makeContext({ + listeningUdpPort: "9001", + listeningInterface: "0.0.0.0", + magicValue: "END", + }); + const lib = new NetworkServerLibrary(); + await lib.__init(ctx); + expect(lib.udp).toBeDefined(); + expect(lib.tcp).toBeUndefined(); + }); + + it("should initialize both TCP and UDP servers when both ports are provided", async () => { + const ctx = makeContext({ + listeningTcpPort: "9000", + listeningUdpPort: "9001", + listeningInterface: "0.0.0.0", + magicValue: "END", + }); + const lib = new NetworkServerLibrary(); + await lib.__init(ctx); + expect(lib.tcp).toBeDefined(); + expect(lib.udp).toBeDefined(); + }); + }); +}); diff --git a/packages/network-server/test/tcp.server.spec.ts b/packages/network-server/test/tcp.server.spec.ts new file mode 100644 index 00000000..64a4a292 --- /dev/null +++ b/packages/network-server/test/tcp.server.spec.ts @@ -0,0 +1,121 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { TCPServer } from "../src/tcp.server.network"; + +vi.mock("ws", () => ({ + WebSocketServer: vi.fn(function (this: any) { + this.on = vi.fn(); + }), +})); + +const getWssInstance = async () => { + const { WebSocketServer } = await import("ws"); + return vi.mocked(WebSocketServer).mock.instances[0] as any; +}; + +describe("TCPServer", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("before listen", () => { + it("should have no connected clients initially", () => { + const server = new TCPServer(9000, "127.0.0.1", "END"); + expect(server.getConnectedClients()).toStrictEqual([]); + }); + + it("should return an empty packets map when no clients are connected", () => { + const server = new TCPServer(9000, "127.0.0.1", "END"); + expect(server.getReceivedPackets()).toStrictEqual(new Map()); + }); + + it("should not throw when sendToClient is called with an unknown clientId", () => { + const server = new TCPServer(9000, "127.0.0.1", "END"); + expect(() => server.sendToClient(99, new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + + it("should not throw when sendToEverybody is called with no clients", () => { + const server = new TCPServer(9000, "127.0.0.1", "END"); + expect(() => server.sendToEverybody(new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + }); + + describe("after listen", () => { + it("should start a WebSocketServer on the configured host and port", async () => { + const { WebSocketServer } = await import("ws"); + const server = new TCPServer(9001, "0.0.0.0", "MAGIC"); + server.listen(); + expect(WebSocketServer).toHaveBeenCalledWith({ port: 9001, host: "0.0.0.0" }); + }); + + it("should register a connection handler on the WebSocketServer", async () => { + const server = new TCPServer(9002, "0.0.0.0", "END"); + server.listen(); + const wss = await getWssInstance(); + expect(wss.on).toHaveBeenCalledWith("connection", expect.any(Function)); + }); + }); + + describe("client lifecycle", () => { + const setupServerWithHandler = async (port: number) => { + const server = new TCPServer(port, "0.0.0.0", "END"); + server.listen(); + const wss = await getWssInstance(); + const connectionHandler = wss.on.mock.calls[0][1] as (ws: any, req: any) => void; + return { server, connectionHandler }; + }; + + const makeWs = () => { + const handlers: Record void> = {}; + const ws = { + binaryType: "", + on: vi.fn((event: string, cb: (...args: any[]) => void) => { + handlers[event] = cb; + }), + send: vi.fn(), + }; + return { ws, handlers }; + }; + + it("should register a new client on connection", async () => { + const { server, connectionHandler } = await setupServerWithHandler(9003); + const { ws } = makeWs(); + connectionHandler(ws, { socket: { remoteAddress: "127.0.0.1" } }); + expect(server.getConnectedClients()).toStrictEqual([0]); + }); + + it("should remove a client on disconnect", async () => { + const { server, connectionHandler } = await setupServerWithHandler(9004); + const { ws, handlers } = makeWs(); + connectionHandler(ws, { socket: { remoteAddress: "127.0.0.1" } }); + expect(server.getConnectedClients()).toStrictEqual([0]); + handlers["close"]?.(); + expect(server.getConnectedClients()).toStrictEqual([]); + }); + + it("should assign incrementing ids to connected clients", async () => { + const { server, connectionHandler } = await setupServerWithHandler(9005); + const { ws: ws0 } = makeWs(); + const { ws: ws1 } = makeWs(); + connectionHandler(ws0, { socket: { remoteAddress: "127.0.0.1" } }); + connectionHandler(ws1, { socket: { remoteAddress: "127.0.0.2" } }); + expect(server.getConnectedClients()).toStrictEqual([0, 1]); + }); + + it("should parse a received packet from a connected client", async () => { + const magicBytes = new TextEncoder().encode("END"); + const payload = new Uint8Array([42, 43]); + const chunk = new Uint8Array([...payload, ...magicBytes]); + + const { server, connectionHandler } = await setupServerWithHandler(9006); + const { ws, handlers } = makeWs(); + connectionHandler(ws, { socket: { remoteAddress: "127.0.0.1" } }); + + handlers["message"]?.(Buffer.from(chunk)); + + const packets = server.getReceivedPackets(); + expect(packets.get(0)).toHaveLength(1); + expect(packets.get(0)?.[0]).toStrictEqual(payload); + }); + }); +}); diff --git a/packages/network-server/test/udp.server.spec.ts b/packages/network-server/test/udp.server.spec.ts new file mode 100644 index 00000000..7dd4ccf6 --- /dev/null +++ b/packages/network-server/test/udp.server.spec.ts @@ -0,0 +1,67 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { UDPServer } from "../src/udp.server.network"; + +vi.mock("ws", () => ({ + WebSocketServer: vi.fn(function (this: any) { + this.on = vi.fn(); + }), +})); + +vi.mock("wrtc", () => ({ + RTCPeerConnection: vi.fn(function (this: any) { + this.onconnectionstatechange = null; + this.onicecandidate = null; + this.ondatachannel = null; + this.setRemoteDescription = vi.fn().mockResolvedValue(undefined); + this.addIceCandidate = vi.fn().mockResolvedValue(undefined); + this.createAnswer = vi.fn().mockResolvedValue({}); + this.setLocalDescription = vi.fn().mockResolvedValue(undefined); + this.close = vi.fn(); + }), +})); + +describe("UDPServer", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("before listen", () => { + it("should have no connected clients initially", () => { + const server = new UDPServer(9100, "127.0.0.1", "END"); + expect(server.getConnectedClients()).toStrictEqual([]); + }); + + it("should return an empty packets map when no clients are connected", () => { + const server = new UDPServer(9100, "127.0.0.1", "END"); + expect(server.getReceivedPackets()).toStrictEqual(new Map()); + }); + + it("should not throw when sendToClient is called with an unknown clientId", () => { + const server = new UDPServer(9100, "127.0.0.1", "END"); + expect(() => server.sendToClient(99, new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + + it("should not throw when sendToEverybody is called with no clients", () => { + const server = new UDPServer(9100, "127.0.0.1", "END"); + expect(() => server.sendToEverybody(new Uint8Array([1, 2, 3]))).not.toThrow(); + }); + }); + + describe("after listen", () => { + it("should start a WebSocketServer on the configured host and port", async () => { + const { WebSocketServer } = await import("ws"); + const server = new UDPServer(9101, "0.0.0.0", "MAGIC"); + server.listen(); + expect(WebSocketServer).toHaveBeenCalledWith({ port: 9101, host: "0.0.0.0" }); + }); + + it("should register a connection handler on the WebSocketServer", async () => { + const { WebSocketServer } = await import("ws"); + const server = new UDPServer(9102, "0.0.0.0", "END"); + server.listen(); + const wss = vi.mocked(WebSocketServer).mock.instances[0] as any; + expect(wss.on).toHaveBeenCalledWith("connection", expect.any(Function)); + }); + }); +}); diff --git a/packages/network-server/test/utils.spec.ts b/packages/network-server/test/utils.spec.ts new file mode 100644 index 00000000..eb08205d --- /dev/null +++ b/packages/network-server/test/utils.spec.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; + +import { buildMagicPacket, parsePacketsFromChunks, rawDataToUint8Array } from "../src/utils"; + +const magic = new TextEncoder().encode("END"); + +describe("buildMagicPacket", () => { + it("should append the magic bytes after the payload", () => { + const data = new Uint8Array([1, 2, 3]); + const result = buildMagicPacket(data, magic); + expect(result).toStrictEqual(new Uint8Array([1, 2, 3, ...magic])); + }); + + it("should produce a buffer of length data + magic", () => { + const data = new Uint8Array([10, 20]); + const result = buildMagicPacket(data, magic); + expect(result.length).toBe(data.length + magic.length); + }); + + it("should work with an empty payload", () => { + const result = buildMagicPacket(new Uint8Array(), magic); + expect(result).toStrictEqual(magic); + }); +}); + +describe("parsePacketsFromChunks", () => { + it("should parse a single complete packet from a flat buffer", () => { + const payload = new Uint8Array([10, 20, 30]); + const stream = buildMagicPacket(payload, magic); + const { packets, data, chunkedData } = parsePacketsFromChunks(stream, [], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + expect(data.length).toBe(0); + expect(chunkedData).toStrictEqual([]); + }); + + it("should parse two consecutive packets from a flat buffer", () => { + const p1 = new Uint8Array([1]); + const p2 = new Uint8Array([2]); + const stream = new Uint8Array([...buildMagicPacket(p1, magic), ...buildMagicPacket(p2, magic)]); + const { packets } = parsePacketsFromChunks(stream, [], magic); + expect(packets).toHaveLength(2); + expect(packets[0]).toStrictEqual(p1); + expect(packets[1]).toStrictEqual(p2); + }); + + it("should retain an incomplete packet as leftover data", () => { + const incomplete = new Uint8Array([5, 6, 7]); + const { packets, data } = parsePacketsFromChunks(incomplete, [], magic); + expect(packets).toHaveLength(0); + expect(data).toStrictEqual(incomplete); + }); + + it("should reassemble a packet split across multiple chunks", () => { + const payload = new Uint8Array([1, 2, 3]); + const full = buildMagicPacket(payload, magic); + const chunk1 = full.slice(0, 2); + const chunk2 = full.slice(2); + const { packets } = parsePacketsFromChunks(new Uint8Array(), [chunk1, chunk2], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + }); + + it("should combine leftover data with new chunks to complete a packet", () => { + const payload = new Uint8Array([10, 20]); + const full = buildMagicPacket(payload, magic); + const leftover = full.slice(0, 1); + const rest = full.slice(1); + const { packets } = parsePacketsFromChunks(leftover, [rest], magic); + expect(packets).toHaveLength(1); + expect(packets[0]).toStrictEqual(payload); + }); + + it("should return empty packets and empty chunkedData on empty input", () => { + const { packets, data, chunkedData } = parsePacketsFromChunks(new Uint8Array(), [], magic); + expect(packets).toHaveLength(0); + expect(data.length).toBe(0); + expect(chunkedData).toStrictEqual([]); + }); +}); + +describe("rawDataToUint8Array", () => { + it("should convert a Node.js Buffer to Uint8Array", () => { + const buf = Buffer.from([1, 2, 3]); + const result = rawDataToUint8Array(buf); + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toStrictEqual(new Uint8Array([1, 2, 3])); + }); + + it("should convert an ArrayBuffer to Uint8Array", () => { + const buf = new Uint8Array([4, 5, 6]).buffer; + const result = rawDataToUint8Array(buf); + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toStrictEqual(new Uint8Array([4, 5, 6])); + }); + + it("should concatenate an array of Buffers into a single Uint8Array", () => { + const bufs = [Buffer.from([1, 2]), Buffer.from([3, 4])]; + const result = rawDataToUint8Array(bufs); + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toStrictEqual(new Uint8Array([1, 2, 3, 4])); + }); + + it("should throw on an unsupported RawData type", () => { + expect(() => rawDataToUint8Array("bad" as any)).toThrow("Unsupported WebSocket RawData type"); + }); +}); diff --git a/packages/sound/package.json b/packages/sound/package.json index 4dba34c5..f610f296 100644 --- a/packages/sound/package.json +++ b/packages/sound/package.json @@ -50,6 +50,7 @@ "build": "tsc --noEmit && tsup", "lint": "prettier --check . && eslint --format=pretty src", "format": "prettier --write . && eslint --fix --format=pretty src", + "test:unit": "vitest run --config ../../vitest.config.ts", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/sound/*'", "release": "cliff-jumper" @@ -65,7 +66,8 @@ "eslint": "catalog:lint", "prettier": "catalog:lint", "tsup": "catalog:build", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "catalog:test" }, "packageManager": "pnpm@10.29.3", "engines": { diff --git a/packages/sound/test/sound.library.spec.ts b/packages/sound/test/sound.library.spec.ts new file mode 100644 index 00000000..da5725c2 --- /dev/null +++ b/packages/sound/test/sound.library.spec.ts @@ -0,0 +1,97 @@ +import { NfNotFound } from "@nanoforge-dev/common"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { SoundLibrary } from "../src"; + +const makeMockAudio = () => { + const play = vi.fn(() => Promise.resolve()); + const pause = vi.fn(); + return { + AudioClass: class { + muted = false; + play = play; + pause = pause; + src: string; + constructor(src: string) { + this.src = src; + } + }, + play, + pause, + }; +}; + +describe("SoundLibrary", () => { + describe("metadata", () => { + it("should expose the correct library name", () => { + expect(new SoundLibrary().__name).toBe("NfSound"); + }); + }); + + describe("before initialization", () => { + it("should throw when play is called before __init", () => { + const library = new SoundLibrary(); + expect(() => library.play("click")).toThrow(); + }); + + it("should throw when mute is called before __init", () => { + const library = new SoundLibrary(); + expect(() => library.mute()).toThrow(); + }); + + it("should throw when load is called before __init", () => { + const library = new SoundLibrary(); + expect(() => library.load("click", "click.mp3")).toThrow(); + }); + }); + + describe("after initialization", () => { + let library: SoundLibrary; + let mock: ReturnType; + + beforeEach(async () => { + mock = makeMockAudio(); + vi.stubGlobal("Audio", mock.AudioClass); + library = new SoundLibrary(); + await library.__init(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("should throw NfNotFound when playing an unknown sound", () => { + expect(() => library.play("unknown")).toThrow(NfNotFound); + }); + + it("should play a loaded sound without error", () => { + library.load("click", "click.mp3"); + expect(() => library.play("click")).not.toThrow(); + }); + + it("should call play on the audio element", () => { + library.load("click", "click.mp3"); + library.play("click"); + expect(mock.play).toHaveBeenCalled(); + }); + + it("should start muted and toggle mute state", async () => { + library.load("click", "click.mp3"); + library.mute(); + library.load("beep", "beep.mp3"); + expect(() => library.play("beep")).not.toThrow(); + }); + + it("should load multiple sounds independently", () => { + library.load("click", "click.mp3"); + library.load("boom", "boom.mp3"); + expect(() => library.play("click")).not.toThrow(); + expect(() => library.play("boom")).not.toThrow(); + }); + + it("should throw NfNotFound after loading a different sound", () => { + library.load("click", "click.mp3"); + expect(() => library.play("boom")).toThrow(NfNotFound); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb8fcd48..8b367ef0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,11 +11,11 @@ catalogs: version: 8.5.1 ci: '@commitlint/cli': - specifier: ^20.4.2 - version: 20.4.2 + specifier: ^20.4.3 + version: 20.4.3 '@commitlint/config-conventional': - specifier: ^20.4.2 - version: 20.4.2 + specifier: ^20.4.3 + version: 20.4.3 '@favware/cliff-jumper': specifier: ^6.0.0 version: 6.0.0 @@ -26,22 +26,22 @@ catalogs: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^16.2.7 - version: 16.2.7 + specifier: ^16.3.2 + version: 16.3.2 config: class-transformer: specifier: ^0.5.1 version: 0.5.1 class-validator: specifier: ^0.14.3 - version: 0.14.3 + version: 0.14.4 core: '@types/node': - specifier: ^25.3.0 - version: 25.3.0 + specifier: ^25.3.3 + version: 25.3.3 turbo: - specifier: ^2.8.10 - version: 2.8.10 + specifier: ^2.8.13 + version: 2.8.13 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -74,13 +74,13 @@ catalogs: version: 7.0.0 eslint-plugin-format: specifier: ^1.4.0 - version: 1.4.0 + version: 1.5.0 eslint-plugin-prettier: specifier: ^5.5.5 version: 5.5.5 globals: specifier: ^17.3.0 - version: 17.3.0 + version: 17.4.0 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -118,10 +118,10 @@ importers: devDependencies: '@commitlint/cli': specifier: catalog:ci - version: 20.4.2(@types/node@25.3.0)(typescript@5.9.3) + version: 20.4.3(@types/node@25.3.3)(typescript@5.9.3) '@commitlint/config-conventional': specifier: catalog:ci - version: 20.4.2 + version: 20.4.3 '@nanoforge-dev/actions': specifier: catalog:ci version: 1.1.0 @@ -136,10 +136,10 @@ importers: version: 6.0.2(prettier@3.8.1) '@types/node': specifier: catalog:core - version: 25.3.0 + version: 25.3.3 '@vitest/coverage-v8': specifier: catalog:test - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + version: 4.0.18(vitest@4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2)) eslint: specifier: catalog:lint version: 10.0.2(jiti@2.6.1) @@ -148,16 +148,16 @@ importers: version: 9.1.7 lint-staged: specifier: catalog:ci - version: 16.2.7 + version: 16.3.2 prettier: specifier: catalog:lint version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) turbo: specifier: catalog:core - version: 2.8.10 + version: 2.8.13 typedoc: specifier: catalog:docs version: 0.28.17(typescript@5.9.3) @@ -169,7 +169,49 @@ importers: version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) + + e2e/game: + devDependencies: + '@nanoforge-dev/asset-manager': + specifier: workspace:* + version: link:../../packages/asset-manager + '@nanoforge-dev/common': + specifier: workspace:* + version: link:../../packages/common + '@nanoforge-dev/core': + specifier: workspace:* + version: link:../../packages/core + '@nanoforge-dev/ecs-client': + specifier: workspace:* + version: link:../../packages/ecs-client + '@nanoforge-dev/ecs-server': + specifier: workspace:* + version: link:../../packages/ecs-server + '@nanoforge-dev/graphics-2d': + specifier: workspace:* + version: link:../../packages/graphics-2d + '@nanoforge-dev/input': + specifier: workspace:* + version: link:../../packages/input + '@nanoforge-dev/music': + specifier: workspace:* + version: link:../../packages/music + '@nanoforge-dev/network-client': + specifier: workspace:* + version: link:../../packages/network-client + '@nanoforge-dev/network-server': + specifier: workspace:* + version: link:../../packages/network-server + '@nanoforge-dev/sound': + specifier: workspace:* + version: link:../../packages/sound + typescript: + specifier: catalog:core + version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/asset-manager: dependencies: @@ -197,13 +239,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/common: devDependencies: @@ -227,10 +269,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/config: dependencies: @@ -239,7 +284,7 @@ importers: version: 0.5.1 class-validator: specifier: catalog:config - version: 0.14.3 + version: 0.14.4 devDependencies: '@favware/cliff-jumper': specifier: catalog:ci @@ -261,7 +306,7 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 @@ -282,7 +327,7 @@ importers: version: 0.5.1 class-validator: specifier: catalog:config - version: 0.14.3 + version: 0.14.4 devDependencies: '@favware/cliff-jumper': specifier: catalog:ci @@ -304,10 +349,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/ecs-client: dependencies: @@ -338,7 +386,7 @@ importers: version: 6.0.2(prettier@3.8.1) '@types/node': specifier: catalog:core - version: 25.3.0 + version: 25.3.3 eslint: specifier: catalog:lint version: 10.0.2(jiti@2.6.1) @@ -347,13 +395,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/ecs-lib: dependencies: @@ -375,7 +423,7 @@ importers: version: 6.0.2(prettier@3.8.1) '@types/node': specifier: catalog:core - version: 25.3.0 + version: 25.3.3 eslint: specifier: catalog:lint version: 10.0.2(jiti@2.6.1) @@ -384,13 +432,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/ecs-server: dependencies: @@ -421,7 +469,7 @@ importers: version: 6.0.2(prettier@3.8.1) '@types/node': specifier: catalog:core - version: 25.3.0 + version: 25.3.3 eslint: specifier: catalog:lint version: 10.0.2(jiti@2.6.1) @@ -430,13 +478,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/graphics-2d: dependencies: @@ -467,13 +515,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 vitest: specifier: catalog:test - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/input: dependencies: @@ -501,10 +549,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/music: dependencies: @@ -532,10 +583,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/network-client: dependencies: @@ -566,10 +620,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/network-server: dependencies: @@ -612,10 +669,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) packages/sound: dependencies: @@ -643,10 +703,13 @@ importers: version: 3.8.1 tsup: specifier: catalog:build - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: catalog:test + version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) utils/eslint-config: dependencies: @@ -664,13 +727,13 @@ importers: version: 7.0.0 eslint-plugin-format: specifier: catalog:lint - version: 1.4.0(eslint@10.0.2(jiti@2.6.1)) + version: 1.5.0(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-prettier: specifier: catalog:lint version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1))(prettier@3.8.1) globals: specifier: catalog:lint - version: 17.3.0 + version: 17.4.0 typescript-eslint: specifier: catalog:lint version: 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) @@ -758,73 +821,73 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@commitlint/cli@20.4.2': - resolution: {integrity: sha512-YjYSX2yj/WsVoxh9mNiymfFS2ADbg2EK4+1WAsMuckwKMCqJ5PDG0CJU/8GvmHWcv4VRB2V02KqSiecRksWqZQ==} + '@commitlint/cli@20.4.3': + resolution: {integrity: sha512-Z37EMoDT7+Upg500vlr/vZrgRsb6Xc5JAA3Tv7BYbobnN/ZpqUeZnSLggBg2+1O+NptRDtyujr2DD1CPV2qwhA==} engines: {node: '>=v18'} hasBin: true - '@commitlint/config-conventional@20.4.2': - resolution: {integrity: sha512-rwkTF55q7Q+6dpSKUmJoScV0f3EpDlWKw2UPzklkLS4o5krMN1tPWAVOgHRtyUTMneIapLeQwaCjn44Td6OzBQ==} + '@commitlint/config-conventional@20.4.3': + resolution: {integrity: sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==} engines: {node: '>=v18'} - '@commitlint/config-validator@20.4.0': - resolution: {integrity: sha512-zShmKTF+sqyNOfAE0vKcqnpvVpG0YX8F9G/ZIQHI2CoKyK+PSdladXMSns400aZ5/QZs+0fN75B//3Q5CHw++w==} + '@commitlint/config-validator@20.4.3': + resolution: {integrity: sha512-jCZpZFkcSL3ZEdL5zgUzFRdytv3xPo8iukTe9VA+QGus/BGhpp1xXSVu2B006GLLb2gYUAEGEqv64kTlpZNgmA==} engines: {node: '>=v18'} - '@commitlint/ensure@20.4.1': - resolution: {integrity: sha512-WLQqaFx1pBooiVvBrA1YfJNFqZF8wS/YGOtr5RzApDbV9tQ52qT5VkTsY65hFTnXhW8PcDfZLaknfJTmPejmlw==} + '@commitlint/ensure@20.4.3': + resolution: {integrity: sha512-WcXGKBNn0wBKpX8VlXgxqedyrLxedIlLBCMvdamLnJFEbUGJ9JZmBVx4vhLV3ZyA8uONGOb+CzW0Y9HDbQ+ONQ==} engines: {node: '>=v18'} '@commitlint/execute-rule@20.0.0': resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} engines: {node: '>=v18'} - '@commitlint/format@20.4.0': - resolution: {integrity: sha512-i3ki3WR0rgolFVX6r64poBHXM1t8qlFel1G1eCBvVgntE3fCJitmzSvH5JD/KVJN/snz6TfaX2CLdON7+s4WVQ==} + '@commitlint/format@20.4.3': + resolution: {integrity: sha512-UDJVErjLbNghop6j111rsHJYGw6MjCKAi95K0GT2yf4eeiDHy3JDRLWYWEjIaFgO+r+dQSkuqgJ1CdMTtrvHsA==} engines: {node: '>=v18'} - '@commitlint/is-ignored@20.4.1': - resolution: {integrity: sha512-In5EO4JR1lNsAv1oOBBO24V9ND1IqdAJDKZiEpdfjDl2HMasAcT7oA+5BKONv1pRoLG380DGPE2W2RIcUwdgLA==} + '@commitlint/is-ignored@20.4.3': + resolution: {integrity: sha512-W5VQKZ7fdJ1X3Tko+h87YZaqRMGN1KvQKXyCM8xFdxzMIf1KCZgN4uLz3osLB1zsFcVS4ZswHY64LI26/9ACag==} engines: {node: '>=v18'} - '@commitlint/lint@20.4.2': - resolution: {integrity: sha512-buquzNRtFng6xjXvBU1abY/WPEEjCgUipNQrNmIWe8QuJ6LWLtei/LDBAzEe5ASm45+Q9L2Xi3/GVvlj50GAug==} + '@commitlint/lint@20.4.3': + resolution: {integrity: sha512-CYOXL23e+nRKij81+d0+dymtIi7Owl9QzvblJYbEfInON/4MaETNSLFDI74LDu+YJ0ML5HZyw9Vhp9QpckwQ0A==} engines: {node: '>=v18'} - '@commitlint/load@20.4.0': - resolution: {integrity: sha512-Dauup/GfjwffBXRJUdlX/YRKfSVXsXZLnINXKz0VZkXdKDcaEILAi9oflHGbfydonJnJAbXEbF3nXPm9rm3G6A==} + '@commitlint/load@20.4.3': + resolution: {integrity: sha512-3cdJOUVP+VcgHa7bhJoWS+Z8mBNXB5aLWMBu7Q7uX8PSeWDzdbrBlR33J1MGGf7r1PZDp+mPPiFktk031PgdRw==} engines: {node: '>=v18'} - '@commitlint/message@20.4.0': - resolution: {integrity: sha512-B5lGtvHgiLAIsK5nLINzVW0bN5hXv+EW35sKhYHE8F7V9Uz1fR4tx3wt7mobA5UNhZKUNgB/+ldVMQE6IHZRyA==} + '@commitlint/message@20.4.3': + resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==} engines: {node: '>=v18'} - '@commitlint/parse@20.4.1': - resolution: {integrity: sha512-XNtZjeRcFuAfUnhYrCY02+mpxwY4OmnvD3ETbVPs25xJFFz1nRo/25nHj+5eM+zTeRFvWFwD4GXWU2JEtoK1/w==} + '@commitlint/parse@20.4.3': + resolution: {integrity: sha512-hzC3JCo3zs3VkQ833KnGVuWjWIzR72BWZWjQM7tY/7dfKreKAm7fEsy71tIFCRtxf2RtMP2d3RLF1U9yhFSccA==} engines: {node: '>=v18'} - '@commitlint/read@20.4.0': - resolution: {integrity: sha512-QfpFn6/I240ySEGv7YWqho4vxqtPpx40FS7kZZDjUJ+eHxu3azfhy7fFb5XzfTqVNp1hNoI3tEmiEPbDB44+cg==} + '@commitlint/read@20.4.3': + resolution: {integrity: sha512-j42OWv3L31WfnP8WquVjHZRt03w50Y/gEE8FAyih7GQTrIv2+pZ6VZ6pWLD/ml/3PO+RV2SPtRtTp/MvlTb8rQ==} engines: {node: '>=v18'} - '@commitlint/resolve-extends@20.4.0': - resolution: {integrity: sha512-ay1KM8q0t+/OnlpqXJ+7gEFQNlUtSU5Gxr8GEwnVf2TPN3+ywc5DzL3JCxmpucqxfHBTFwfRMXxPRRnR5Ki20g==} + '@commitlint/resolve-extends@20.4.3': + resolution: {integrity: sha512-QucxcOy+00FhS9s4Uy0OyS5HeUV+hbC6OLqkTSIm6fwMdKva+OEavaCDuLtgd9akZZlsUo//XzSmPP3sLKBPog==} engines: {node: '>=v18'} - '@commitlint/rules@20.4.2': - resolution: {integrity: sha512-oz83pnp5Yq6uwwTAabuVQPNlPfeD2Y5ZjMb7Wx8FSUlu4sLYJjbBWt8031Z0osCFPfHzAwSYrjnfDFKtuSMdKg==} + '@commitlint/rules@20.4.3': + resolution: {integrity: sha512-Yuosd7Grn5qiT7FovngXLyRXTMUbj9PYiSkvUgWK1B5a7+ZvrbWDS7epeUapYNYatCy/KTpPFPbgLUdE+MUrBg==} engines: {node: '>=v18'} '@commitlint/to-lines@20.0.0': resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} engines: {node: '>=v18'} - '@commitlint/top-level@20.4.0': - resolution: {integrity: sha512-NDzq8Q6jmFaIIBC/GG6n1OQEaHdmaAAYdrZRlMgW6glYWGZ+IeuXmiymDvQNXPc82mVxq2KiE3RVpcs+1OeDeA==} + '@commitlint/top-level@20.4.3': + resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==} engines: {node: '>=v18'} - '@commitlint/types@20.4.0': - resolution: {integrity: sha512-aO5l99BQJ0X34ft8b0h7QFkQlqxC6e7ZPVmBKz13xM9O8obDaM1Cld4sQlJDXXU/VFuUzQ30mVtHjVz74TuStw==} + '@commitlint/types@20.4.3': + resolution: {integrity: sha512-51OWa1Gi6ODOasPmfJPq6js4pZoomima4XLZZCrkldaH2V5Nb3bVhNXPeT6XV0gubbainSpTw4zi68NqAeCNCg==} engines: {node: '>=v18'} '@conventional-changelog/git-client@1.0.1': @@ -842,8 +905,8 @@ packages: '@dprint/formatter@0.5.1': resolution: {integrity: sha512-cdZUrm0iv/FnnY3CKE2dEcVhNEzrC551aE2h2mTFwQCRBrqyARLDnb7D+3PlXTUVp3s34ftlnGOVCmhLT9DeKA==} - '@dprint/markdown@0.20.0': - resolution: {integrity: sha512-qvynFdQZwul4Y+hoMP02QerEhM5VItb4cO8/qpQrSuQuYvDU+bIseiheVAetSpWlNPBU1JK8bQKloiCSp9lXnA==} + '@dprint/markdown@0.21.1': + resolution: {integrity: sha512-XbZ/R7vRrBaZFYXG6vAvLvtaMVXHu8XB+xwie7OYrG+dPoGDP8UADGirIbzUyX8TmrAZcl6QBmalipTGlpzRmQ==} '@dprint/toml@0.7.0': resolution: {integrity: sha512-eFaQTcfxKHB+YyTh83x7GEv+gDPuj9q5NFOTaoj5rZmQTbj6OgjjMxUicmS1R8zYcx8YAq5oA9J3YFa5U6x2gA==} @@ -1056,8 +1119,8 @@ packages: resolution: {integrity: sha512-PPYtcLzhSafdylp8NBOxMCYIcLqTUMNiQc7ciBoAIvxNG2egM+P7e2nNPui5+Svyk89Q+Tnbrp139ZRIIBw3IA==} engines: {node: '>=v16'} - '@gerrit0/mini-shiki@3.22.0': - resolution: {integrity: sha512-jMpciqEVUBKE1QwU64S4saNMzpsSza6diNCk4MWAeCxO2+LFi2FIFmL2S0VDLzEJCxuvCbU783xi8Hp/gkM5CQ==} + '@gerrit0/mini-shiki@3.23.0': + resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -1075,10 +1138,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1189,145 +1248,267 @@ packages: '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@oxfmt/binding-android-arm-eabi@0.35.0': + resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.35.0': + resolution: {integrity: sha512-/O+EbuAJYs6nde/anv+aID6uHsGQApyE9JtYBo/79KyU8e6RBN3DMbT0ix97y1SOnCglurmL2iZ+hlohjP2PnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.35.0': + resolution: {integrity: sha512-pGqRtqlNdn9d4VrmGUWVyQjkw79ryhI6je9y2jfqNUIZCfqceob+R97YYAoG7C5TFyt8ILdLVoN+L2vw/hSFyA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.35.0': + resolution: {integrity: sha512-8GmsDcSozTPjrCJeGpp+sCmS9+9V5yRrdEZ1p/sTWxPG5nYeAfSLuS0nuEYjXSO+CtdSbStIW6dxa+4NM58yRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.35.0': + resolution: {integrity: sha512-QyfKfTe0ytHpFKHAcHCGQEzN45QSqq1AHJOYYxQMgLM3KY4xu8OsXHpCnINjDsV4XGnQzczJDU9e04Zmd8XqIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.35.0': + resolution: {integrity: sha512-u+kv3JD6P3J38oOyUaiCqgY5TNESzBRZJ5lyZQ6c2czUW2v5SIN9E/KWWa9vxoc+P8AFXQFUVrdzGy1tK+nbPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.35.0': + resolution: {integrity: sha512-1NiZroCiV57I7Pf8kOH4XGR366kW5zir3VfSMBU2D0V14GpYjiYmPYFAoJboZvp8ACnZKUReWyMkNKSa5ad58A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.35.0': + resolution: {integrity: sha512-7Q0Xeg7ZnW2nxnZ4R7aF6DEbCFls4skgHZg+I63XitpNvJCbVIU8MFOTZlvZGRsY9+rPgWPQGeUpLHlyx0wvMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.35.0': + resolution: {integrity: sha512-5Okqi+uhYFxwKz8hcnUftNNwdm8BCkf6GSCbcz9xJxYMm87k1E4p7PEmAAbhLTk7cjSdDre6TDL0pDzNX+Y22Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.35.0': + resolution: {integrity: sha512-9k66pbZQXM/lBJWys3Xbc5yhl4JexyfqkEf/tvtq8976VIJnLAAL3M127xHA3ifYSqxdVHfVGTg84eiBHCGcNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.35.0': + resolution: {integrity: sha512-aUcY9ofKPtjO52idT6t0SAQvEF6ctjzUQa1lLp7GDsRpSBvuTrBQGeq0rYKz3gN8dMIQ7mtMdGD9tT4LhR8jAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.35.0': + resolution: {integrity: sha512-C6yhY5Hvc2sGM+mCPek9ZLe5xRUOC/BvhAt2qIWFAeXMn4il04EYIjl3DsWiJr0xDMTJhvMOmD55xTRPlNp39w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.35.0': + resolution: {integrity: sha512-RG2hlvOMK4OMZpO3mt8MpxLQ0AAezlFqhn5mI/g5YrVbPFyoCv9a34AAvbSJS501ocOxlFIRcKEuw5hFvddf9g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.35.0': + resolution: {integrity: sha512-wzmh90Pwvqj9xOKHJjkQYBpydRkaXG77ZvDz+iFDRRQpnqIEqGm5gmim2s6vnZIkDGsvKCuTdtxm0GFmBjM1+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.35.0': + resolution: {integrity: sha512-+HCqYCJPCUy5I+b2cf+gUVaApfgtoQT3HdnSg/l7NIcLHOhKstlYaGyrFZLmUpQt4WkFbpGKZZayG6zjRU0KFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.35.0': + resolution: {integrity: sha512-kFYmWfR9YL78XyO5ws+1dsxNvZoD973qfVMNFOS4e9bcHXGF7DvGC2tY5UDFwyMCeB33t3sDIuGONKggnVNSJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.35.0': + resolution: {integrity: sha512-uD/NGdM65eKNCDGyTGdO8e9n3IHX+wwuorBvEYrPJXhDXL9qz6gzddmXH8EN04ejUXUujlq4FsoSeCfbg0Y+Jg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.35.0': + resolution: {integrity: sha512-oSRD2k8J2uxYDEKR2nAE/YTY9PobOEnhZgCmspHu0+yBQ665yH8lFErQVSTE7fcGJmJp/cC6322/gc8VFuQf7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.35.0': + resolution: {integrity: sha512-WCDJjlS95NboR0ugI2BEwzt1tYvRDorDRM9Lvctls1SLyKYuNRCyrPwp1urUPFBnwgBNn9p2/gnmo7gFMySRoQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -1342,21 +1523,25 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@shikijs/engine-oniguruma@3.22.0': - resolution: {integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==} + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - '@shikijs/langs@3.22.0': - resolution: {integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==} + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} - '@shikijs/themes@3.22.0': - resolution: {integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==} + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} - '@shikijs/types@3.22.0': - resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==} + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -1404,11 +1589,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - - '@types/node@25.3.0': - resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==} + '@types/node@25.3.3': + resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -1528,11 +1710,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -1581,15 +1758,15 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-v8-to-istanbul@0.3.11: - resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.2: - resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} - engines: {node: 20 || >=22} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} @@ -1603,9 +1780,9 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.2: - resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} - engines: {node: 20 || >=22} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1644,15 +1821,15 @@ packages: class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - class-validator@0.14.3: - resolution: {integrity: sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==} + class-validator@0.14.4: + resolution: {integrity: sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==} cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-truncate@5.1.1: - resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} cliui@8.0.1: @@ -1691,12 +1868,12 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - conventional-changelog-angular@8.1.0: - resolution: {integrity: sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==} + conventional-changelog-angular@8.3.0: + resolution: {integrity: sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==} engines: {node: '>=18'} - conventional-changelog-conventionalcommits@9.1.0: - resolution: {integrity: sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==} + conventional-changelog-conventionalcommits@9.3.0: + resolution: {integrity: sha512-kYFx6gAyjSIMwNtASkI3ZE99U1fuVDJr0yTYgVy+I2QG46zNZfl2her+0+eoviG82c5WQvW1jMt1eOQTeJLodA==} engines: {node: '>=18'} conventional-changelog-preset-loader@5.0.0: @@ -1707,8 +1884,8 @@ packages: resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} - conventional-commits-parser@6.2.1: - resolution: {integrity: sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==} + conventional-commits-parser@6.3.0: + resolution: {integrity: sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==} engines: {node: '>=18'} hasBin: true @@ -1725,8 +1902,8 @@ packages: cosmiconfig: '>=9' typescript: '>=5' - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -1824,10 +2001,10 @@ packages: eslint-parser-plain@0.1.1: resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==} - eslint-plugin-format@1.4.0: - resolution: {integrity: sha512-6o3fBJENUZPXlg01ab0vTldr6YThw0dxb49QMVp1V9bI7k22dtXYuWWMm3mitAsntJOt8V4pa7BWHUalTrSBPA==} + eslint-plugin-format@1.5.0: + resolution: {integrity: sha512-jaeOKrxs79Nn6rMkLycPkLHvBVKcgsFG+RqNXb6W9iS9y2Q0NYGhFTLcDUdp5mf01X99wEkjtX2O8cumM7lNMQ==} peerDependencies: - eslint: ^8.40.0 || ^9.0.0 + eslint: ^8.40.0 || ^9.0.0 || ^10.0.0 eslint-plugin-prettier@5.5.5: resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} @@ -1952,8 +2129,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.3.4: + resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1964,8 +2141,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} get-stream@9.0.1: @@ -2010,6 +2187,7 @@ packages: git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true glob-parent@6.0.2: @@ -2020,8 +2198,8 @@ packages: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} - globals@17.3.0: - resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} has-flag@4.0.0: @@ -2129,10 +2307,6 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - javascript-natural-sort@0.7.1: resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} @@ -2184,8 +2358,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libphonenumber-js@1.12.36: - resolution: {integrity: sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ==} + libphonenumber-js@1.12.38: + resolution: {integrity: sha512-vwzxmasAy9hZigxtqTbFEwp8ZdZ975TiqVDwj5bKx5sR+zi5ucUQy9mbVTkKM9GzqdLdxux/hTw2nmN5J7POMA==} lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} @@ -2197,8 +2371,8 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - lint-staged@16.2.7: - resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==} + lint-staged@16.3.2: + resolution: {integrity: sha512-xKqhC2AeXLwiAHXguxBjuChoTTWFC6Pees0SHPwOpwlvI3BH7ZADFPddAdN3pgo3aiKgPUx/bxE78JfUnxQnlg==} engines: {node: '>=20.17'} hasBin: true @@ -2279,27 +2453,27 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@10.2.3: - resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.1: + resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2307,10 +2481,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nano-spawn@2.0.0: - resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} - engines: {node: '>=20.17'} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2358,6 +2528,11 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + oxfmt@0.35.0: + resolution: {integrity: sha512-QYeXWkP+aLt7utt5SLivNIk09glWx9QE235ODjgcEZ3sd1VMaUBSpLymh6ZRCA76gD2rMP4bXanUz/fx+nLM9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2410,11 +2585,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -2444,8 +2614,8 @@ packages: yaml: optional: true - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2500,8 +2670,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2529,6 +2699,10 @@ packages: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + smol-toml@1.6.0: resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} engines: {node: '>= 18'} @@ -2563,16 +2737,16 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.1: - resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} engines: {node: '>=20'} strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-final-newline@4.0.0: @@ -2600,10 +2774,9 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tar@7.5.7: - resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + tar@7.5.10: + resolution: {integrity: sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -2626,6 +2799,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -2676,38 +2853,38 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - turbo-darwin-64@2.8.10: - resolution: {integrity: sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g==} + turbo-darwin-64@2.8.13: + resolution: {integrity: sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.8.10: - resolution: {integrity: sha512-sidzowgWL3s5xCHLeqwC9M3s9M0i16W1nuQF3Mc7fPHpZ+YPohvcbVFBB2uoRRHYZg6yBnwD4gyUHKTeXfwtXA==} + turbo-darwin-arm64@2.8.13: + resolution: {integrity: sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.8.10: - resolution: {integrity: sha512-YK9vcpL3TVtqonB021XwgaQhY9hJJbKKUhLv16osxV0HkcQASQWUqR56yMge7puh6nxU67rQlTq1b7ksR1T3KA==} + turbo-linux-64@2.8.13: + resolution: {integrity: sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.8.10: - resolution: {integrity: sha512-3+j2tL0sG95iBJTm+6J8/45JsETQABPqtFyYjVjBbi6eVGdtNTiBmHNKrbvXRlQ3ZbUG75bKLaSSDHSEEN+btQ==} + turbo-linux-arm64@2.8.13: + resolution: {integrity: sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg==} cpu: [arm64] os: [linux] - turbo-windows-64@2.8.10: - resolution: {integrity: sha512-hdeF5qmVY/NFgiucf8FW0CWJWtyT2QPm5mIsX0W1DXAVzqKVXGq+Zf+dg4EUngAFKjDzoBeN6ec2Fhajwfztkw==} + turbo-windows-64@2.8.13: + resolution: {integrity: sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.8.10: - resolution: {integrity: sha512-QGdr/Q8LWmj+ITMkSvfiz2glf0d7JG0oXVzGL3jxkGqiBI1zXFj20oqVY0qWi+112LO9SVrYdpHS0E/oGFrMbQ==} + turbo-windows-arm64@2.8.13: + resolution: {integrity: sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA==} cpu: [arm64] os: [win32] - turbo@2.8.10: - resolution: {integrity: sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ==} + turbo@2.8.13: + resolution: {integrity: sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A==} hasBin: true type-check@0.4.0: @@ -2745,9 +2922,6 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -3008,32 +3182,32 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@commitlint/cli@20.4.2(@types/node@25.3.0)(typescript@5.9.3)': + '@commitlint/cli@20.4.3(@types/node@25.3.3)(typescript@5.9.3)': dependencies: - '@commitlint/format': 20.4.0 - '@commitlint/lint': 20.4.2 - '@commitlint/load': 20.4.0(@types/node@25.3.0)(typescript@5.9.3) - '@commitlint/read': 20.4.0 - '@commitlint/types': 20.4.0 + '@commitlint/format': 20.4.3 + '@commitlint/lint': 20.4.3 + '@commitlint/load': 20.4.3(@types/node@25.3.3)(typescript@5.9.3) + '@commitlint/read': 20.4.3 + '@commitlint/types': 20.4.3 tinyexec: 1.0.2 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/config-conventional@20.4.2': + '@commitlint/config-conventional@20.4.3': dependencies: - '@commitlint/types': 20.4.0 - conventional-changelog-conventionalcommits: 9.1.0 + '@commitlint/types': 20.4.3 + conventional-changelog-conventionalcommits: 9.3.0 - '@commitlint/config-validator@20.4.0': + '@commitlint/config-validator@20.4.3': dependencies: - '@commitlint/types': 20.4.0 + '@commitlint/types': 20.4.3 ajv: 8.18.0 - '@commitlint/ensure@20.4.1': + '@commitlint/ensure@20.4.3': dependencies: - '@commitlint/types': 20.4.0 + '@commitlint/types': 20.4.3 lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 @@ -3042,31 +3216,31 @@ snapshots: '@commitlint/execute-rule@20.0.0': {} - '@commitlint/format@20.4.0': + '@commitlint/format@20.4.3': dependencies: - '@commitlint/types': 20.4.0 + '@commitlint/types': 20.4.3 picocolors: 1.1.1 - '@commitlint/is-ignored@20.4.1': + '@commitlint/is-ignored@20.4.3': dependencies: - '@commitlint/types': 20.4.0 + '@commitlint/types': 20.4.3 semver: 7.7.4 - '@commitlint/lint@20.4.2': + '@commitlint/lint@20.4.3': dependencies: - '@commitlint/is-ignored': 20.4.1 - '@commitlint/parse': 20.4.1 - '@commitlint/rules': 20.4.2 - '@commitlint/types': 20.4.0 + '@commitlint/is-ignored': 20.4.3 + '@commitlint/parse': 20.4.3 + '@commitlint/rules': 20.4.3 + '@commitlint/types': 20.4.3 - '@commitlint/load@20.4.0(@types/node@25.3.0)(typescript@5.9.3)': + '@commitlint/load@20.4.3(@types/node@25.3.3)(typescript@5.9.3)': dependencies: - '@commitlint/config-validator': 20.4.0 + '@commitlint/config-validator': 20.4.3 '@commitlint/execute-rule': 20.0.0 - '@commitlint/resolve-extends': 20.4.0 - '@commitlint/types': 20.4.0 - cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@25.3.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + '@commitlint/resolve-extends': 20.4.3 + '@commitlint/types': 20.4.3 + cosmiconfig: 9.0.1(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@25.3.3)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3) is-plain-obj: 4.1.0 lodash.mergewith: 4.6.2 picocolors: 1.1.1 @@ -3074,60 +3248,60 @@ snapshots: - '@types/node' - typescript - '@commitlint/message@20.4.0': {} + '@commitlint/message@20.4.3': {} - '@commitlint/parse@20.4.1': + '@commitlint/parse@20.4.3': dependencies: - '@commitlint/types': 20.4.0 - conventional-changelog-angular: 8.1.0 - conventional-commits-parser: 6.2.1 + '@commitlint/types': 20.4.3 + conventional-changelog-angular: 8.3.0 + conventional-commits-parser: 6.3.0 - '@commitlint/read@20.4.0': + '@commitlint/read@20.4.3': dependencies: - '@commitlint/top-level': 20.4.0 - '@commitlint/types': 20.4.0 + '@commitlint/top-level': 20.4.3 + '@commitlint/types': 20.4.3 git-raw-commits: 4.0.0 minimist: 1.2.8 tinyexec: 1.0.2 - '@commitlint/resolve-extends@20.4.0': + '@commitlint/resolve-extends@20.4.3': dependencies: - '@commitlint/config-validator': 20.4.0 - '@commitlint/types': 20.4.0 + '@commitlint/config-validator': 20.4.3 + '@commitlint/types': 20.4.3 global-directory: 4.0.1 import-meta-resolve: 4.2.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 - '@commitlint/rules@20.4.2': + '@commitlint/rules@20.4.3': dependencies: - '@commitlint/ensure': 20.4.1 - '@commitlint/message': 20.4.0 + '@commitlint/ensure': 20.4.3 + '@commitlint/message': 20.4.3 '@commitlint/to-lines': 20.0.0 - '@commitlint/types': 20.4.0 + '@commitlint/types': 20.4.3 '@commitlint/to-lines@20.0.0': {} - '@commitlint/top-level@20.4.0': + '@commitlint/top-level@20.4.3': dependencies: escalade: 3.2.0 - '@commitlint/types@20.4.0': + '@commitlint/types@20.4.3': dependencies: - conventional-commits-parser: 6.2.1 + conventional-commits-parser: 6.3.0 picocolors: 1.1.1 - '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1)': + '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.3.0)': dependencies: '@types/semver': 7.7.1 semver: 7.7.4 optionalDependencies: conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.1 + conventional-commits-parser: 6.3.0 '@dprint/formatter@0.5.1': {} - '@dprint/markdown@0.20.0': {} + '@dprint/markdown@0.21.1': {} '@dprint/toml@0.7.0': {} @@ -3220,7 +3394,7 @@ snapshots: dependencies: '@eslint/object-schema': 3.0.2 debug: 4.4.3 - minimatch: 10.2.3 + minimatch: 10.2.4 transitivePeerDependencies: - supports-color @@ -3266,12 +3440,12 @@ snapshots: dependencies: colorette: 2.0.20 - '@gerrit0/mini-shiki@3.22.0': + '@gerrit0/mini-shiki@3.23.0': dependencies: - '@shikijs/engine-oniguruma': 3.22.0 - '@shikijs/langs': 3.22.0 - '@shikijs/themes': 3.22.0 - '@shikijs/types': 3.22.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 '@humanfs/core@0.19.1': {} @@ -3285,11 +3459,9 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@isaacs/cliui@9.0.0': {} - '@isaacs/fs-minipass@4.0.1': dependencies: - minipass: 7.1.2 + minipass: 7.1.3 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -3313,7 +3485,7 @@ snapshots: node-fetch: 2.7.0 nopt: 8.1.0 semver: 7.7.4 - tar: 7.5.7 + tar: 7.5.10 transitivePeerDependencies: - encoding - supports-color @@ -3430,81 +3602,138 @@ snapshots: dependencies: '@octokit/openapi-types': 25.1.0 + '@oxfmt/binding-android-arm-eabi@0.35.0': + optional: true + + '@oxfmt/binding-android-arm64@0.35.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.35.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.35.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.35.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.35.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.35.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.35.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.35.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.35.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.35.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.35.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.35.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.35.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.35.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.35.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.35.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.35.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.35.0': + optional: true + '@pkgr/core@0.2.9': {} - '@rollup/rollup-android-arm-eabi@4.57.1': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.57.1': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.57.1': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.57.1': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.57.1': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.57.1': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.57.1': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.57.1': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.57.1': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.57.1': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.57.1': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.57.1': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.57.1': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.57.1': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.57.1': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.57.1': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.57.1': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.57.1': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.57.1': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.57.1': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.57.1': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.57.1': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.57.1': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.57.1': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@sapphire/result@2.8.0': {} @@ -3513,26 +3742,28 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@shikijs/engine-oniguruma@3.22.0': + '@shikijs/engine-oniguruma@3.23.0': dependencies: - '@shikijs/types': 3.22.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.22.0': + '@shikijs/langs@3.23.0': dependencies: - '@shikijs/types': 3.22.0 + '@shikijs/types': 3.23.0 - '@shikijs/themes@3.22.0': + '@shikijs/themes@3.23.0': dependencies: - '@shikijs/types': 3.22.0 + '@shikijs/types': 3.23.0 - '@shikijs/types@3.22.0': + '@shikijs/types@3.23.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 '@shikijs/vscode-textmate@10.0.2': {} + '@simple-libs/stream-utils@1.2.0': {} + '@sindresorhus/merge-streams@4.0.0': {} '@standard-schema/spec@1.1.0': {} @@ -3545,7 +3776,7 @@ snapshots: '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 lodash-es: 4.17.23 - minimatch: 9.0.5 + minimatch: 9.0.9 parse-imports-exports: 0.2.4 prettier: 3.8.1 transitivePeerDependencies: @@ -3573,11 +3804,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@25.2.3': - dependencies: - undici-types: 7.16.0 - - '@types/node@25.3.0': + '@types/node@25.3.3': dependencies: undici-types: 7.18.2 @@ -3589,7 +3816,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.3.3 '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -3658,7 +3885,7 @@ snapshots: '@typescript-eslint/types': 8.56.1 '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - minimatch: 10.2.3 + minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -3682,11 +3909,11 @@ snapshots: '@typescript-eslint/types': 8.56.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.18 - ast-v8-to-istanbul: 0.3.11 + ast-v8-to-istanbul: 0.3.12 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 @@ -3694,7 +3921,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + vitest: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) '@vitest/expect@4.0.18': dependencies: @@ -3705,13 +3932,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) '@vitest/pretty-format@4.0.18': dependencies: @@ -3741,8 +3968,6 @@ snapshots: dependencies: acorn: 8.16.0 - acorn@8.15.0: {} - acorn@8.16.0: {} agent-base@7.1.4: {} @@ -3783,7 +4008,7 @@ snapshots: assertion-error@2.0.1: {} - ast-v8-to-istanbul@0.3.11: + ast-v8-to-istanbul@0.3.12: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -3791,9 +4016,7 @@ snapshots: balanced-match@1.0.2: {} - balanced-match@4.0.2: - dependencies: - jackspeak: 4.2.3 + balanced-match@4.0.4: {} before-after-hook@2.2.3: {} @@ -3805,9 +4028,9 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.2: + brace-expansion@5.0.4: dependencies: - balanced-match: 4.0.2 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -3834,20 +4057,20 @@ snapshots: class-transformer@0.5.1: {} - class-validator@0.14.3: + class-validator@0.14.4: dependencies: '@types/validator': 13.15.10 - libphonenumber-js: 1.12.36 + libphonenumber-js: 1.12.38 validator: 13.15.26 cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 - cli-truncate@5.1.1: + cli-truncate@5.2.0: dependencies: - slice-ansi: 7.1.2 - string-width: 8.1.1 + slice-ansi: 8.0.0 + string-width: 8.2.0 cliui@8.0.1: dependencies: @@ -3878,11 +4101,11 @@ snapshots: consola@3.4.2: {} - conventional-changelog-angular@8.1.0: + conventional-changelog-angular@8.3.0: dependencies: compare-func: 2.0.0 - conventional-changelog-conventionalcommits@9.1.0: + conventional-changelog-conventionalcommits@9.3.0: dependencies: compare-func: 2.0.0 @@ -3890,26 +4113,27 @@ snapshots: conventional-commits-filter@5.0.0: {} - conventional-commits-parser@6.2.1: + conventional-commits-parser@6.3.0: dependencies: + '@simple-libs/stream-utils': 1.2.0 meow: 13.2.0 conventional-recommended-bump@10.0.0: dependencies: - '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) + '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.3.0) conventional-changelog-preset-loader: 5.0.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.1 + conventional-commits-parser: 6.3.0 meow: 13.2.0 - cosmiconfig-typescript-loader@6.2.0(@types/node@25.3.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@25.3.3)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 25.3.0 - cosmiconfig: 9.0.0(typescript@5.9.3) + '@types/node': 25.3.3 + cosmiconfig: 9.0.1(typescript@5.9.3) jiti: 2.6.1 typescript: 5.9.3 - cosmiconfig@9.0.0(typescript@5.9.3): + cosmiconfig@9.0.1(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 @@ -4006,7 +4230,7 @@ snapshots: eslint-rule-docs: 1.1.235 log-symbols: 7.0.1 plur: 5.1.0 - string-width: 8.1.1 + string-width: 8.2.0 supports-hyperlinks: 4.4.0 eslint-formatting-reporter@0.0.0(eslint@10.0.2(jiti@2.6.1)): @@ -4016,15 +4240,16 @@ snapshots: eslint-parser-plain@0.1.1: {} - eslint-plugin-format@1.4.0(eslint@10.0.2(jiti@2.6.1)): + eslint-plugin-format@1.5.0(eslint@10.0.2(jiti@2.6.1)): dependencies: '@dprint/formatter': 0.5.1 - '@dprint/markdown': 0.20.0 + '@dprint/markdown': 0.21.1 '@dprint/toml': 0.7.0 eslint: 10.0.2(jiti@2.6.1) eslint-formatting-reporter: 0.0.0(eslint@10.0.2(jiti@2.6.1)) eslint-parser-plain: 0.1.1 ohash: 2.0.11 + oxfmt: 0.35.0 prettier: 3.8.1 synckit: 0.11.12 @@ -4080,7 +4305,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.3 + minimatch: 10.2.4 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -4165,22 +4390,22 @@ snapshots: fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 - mlly: 1.8.0 - rollup: 4.57.1 + mlly: 1.8.1 + rollup: 4.59.0 flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.3.4 keyv: 4.5.4 - flatted@3.3.3: {} + flatted@3.3.4: {} fsevents@2.3.3: optional: true get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.5.0: {} get-stream@9.0.1: dependencies: @@ -4230,7 +4455,7 @@ snapshots: dependencies: ini: 4.1.1 - globals@17.3.0: {} + globals@17.4.0: {} has-flag@4.0.0: {} @@ -4274,7 +4499,7 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 is-glob@4.0.3: dependencies: @@ -4305,10 +4530,6 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - javascript-natural-sort@0.7.1: {} jiti@2.6.1: {} @@ -4346,7 +4567,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.12.36: {} + libphonenumber-js@1.12.38: {} lilconfig@3.1.3: {} @@ -4356,19 +4577,18 @@ snapshots: dependencies: uc.micro: 2.1.0 - lint-staged@16.2.7: + lint-staged@16.3.2: dependencies: commander: 14.0.3 listr2: 9.0.5 micromatch: 4.0.8 - nano-spawn: 2.0.0 - pidtree: 0.6.0 string-argv: 0.3.2 + tinyexec: 1.0.2 yaml: 2.8.2 listr2@9.0.5: dependencies: - cli-truncate: 5.1.1 + cli-truncate: 5.2.0 colorette: 2.0.20 eventemitter3: 5.0.4 log-update: 6.1.0 @@ -4405,7 +4625,7 @@ snapshots: ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi: 9.0.2 lunr@2.3.9: {} @@ -4446,25 +4666,25 @@ snapshots: mimic-function@5.0.1: {} - minimatch@10.2.3: + minimatch@10.2.4: dependencies: - brace-expansion: 5.0.2 + brace-expansion: 5.0.4 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 minimist@1.2.8: {} - minipass@7.1.2: {} + minipass@7.1.3: {} minizlib@3.1.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 - mlly@1.8.0: + mlly@1.8.1: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.3 @@ -4477,8 +4697,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nano-spawn@2.0.0: {} - nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -4519,6 +4737,30 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + oxfmt@0.35.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.35.0 + '@oxfmt/binding-android-arm64': 0.35.0 + '@oxfmt/binding-darwin-arm64': 0.35.0 + '@oxfmt/binding-darwin-x64': 0.35.0 + '@oxfmt/binding-freebsd-x64': 0.35.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.35.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.35.0 + '@oxfmt/binding-linux-arm64-gnu': 0.35.0 + '@oxfmt/binding-linux-arm64-musl': 0.35.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.35.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.35.0 + '@oxfmt/binding-linux-riscv64-musl': 0.35.0 + '@oxfmt/binding-linux-s390x-gnu': 0.35.0 + '@oxfmt/binding-linux-x64-gnu': 0.35.0 + '@oxfmt/binding-linux-x64-musl': 0.35.0 + '@oxfmt/binding-openharmony-arm64': 0.35.0 + '@oxfmt/binding-win32-arm64-msvc': 0.35.0 + '@oxfmt/binding-win32-ia32-msvc': 0.35.0 + '@oxfmt/binding-win32-x64-msvc': 0.35.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4560,29 +4802,27 @@ snapshots: picomatch@4.0.3: {} - pidtree@0.6.0: {} - pirates@4.0.7: {} pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.1 pathe: 2.0.3 plur@5.1.0: dependencies: irregular-plurals: 3.5.0 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.8.2): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 - postcss: 8.5.6 + postcss: 8.5.8 yaml: 2.8.2 - postcss@8.5.6: + postcss@8.5.8: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4621,35 +4861,35 @@ snapshots: rfdc@1.4.1: {} - rollup@4.57.1: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 semver@7.7.4: {} @@ -4669,6 +4909,11 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + smol-toml@1.6.0: {} source-map-js@1.2.1: {} @@ -4692,19 +4937,19 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 - string-width@8.1.1: + string-width@8.2.0: dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -4735,11 +4980,11 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - tar@7.5.7: + tar@7.5.10: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 - minipass: 7.1.2 + minipass: 7.1.3 minizlib: 3.1.0 yallist: 5.0.0 @@ -4762,6 +5007,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@2.1.0: {} + tinyrainbow@3.0.3: {} to-regex-range@5.0.1: @@ -4780,7 +5027,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 @@ -4791,16 +5038,16 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.8.2) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.2) resolve-from: 5.0.0 - rollup: 4.57.1 + rollup: 4.59.0 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 0.3.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.6 + postcss: 8.5.8 typescript: 5.9.3 transitivePeerDependencies: - jiti @@ -4810,32 +5057,32 @@ snapshots: tunnel@0.0.6: {} - turbo-darwin-64@2.8.10: + turbo-darwin-64@2.8.13: optional: true - turbo-darwin-arm64@2.8.10: + turbo-darwin-arm64@2.8.13: optional: true - turbo-linux-64@2.8.10: + turbo-linux-64@2.8.13: optional: true - turbo-linux-arm64@2.8.10: + turbo-linux-arm64@2.8.13: optional: true - turbo-windows-64@2.8.10: + turbo-windows-64@2.8.13: optional: true - turbo-windows-arm64@2.8.10: + turbo-windows-arm64@2.8.13: optional: true - turbo@2.8.10: + turbo@2.8.13: optionalDependencies: - turbo-darwin-64: 2.8.10 - turbo-darwin-arm64: 2.8.10 - turbo-linux-64: 2.8.10 - turbo-linux-arm64: 2.8.10 - turbo-windows-64: 2.8.10 - turbo-windows-arm64: 2.8.10 + turbo-darwin-64: 2.8.13 + turbo-darwin-arm64: 2.8.13 + turbo-linux-64: 2.8.13 + turbo-linux-arm64: 2.8.13 + turbo-windows-64: 2.8.13 + turbo-windows-arm64: 2.8.13 type-check@0.4.0: dependencies: @@ -4847,10 +5094,10 @@ snapshots: typedoc@0.28.17(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.22.0 + '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 markdown-it: 14.1.1 - minimatch: 9.0.5 + minimatch: 9.0.9 typescript: 5.9.3 yaml: 2.8.2 @@ -4871,8 +5118,6 @@ snapshots: ufo@1.6.3: {} - undici-types@7.16.0: {} - undici-types@7.18.2: {} undici@5.29.0: @@ -4893,24 +5138,24 @@ snapshots: validator@13.15.26: {} - vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2): + vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 + postcss: 8.5.8 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.3.0 + '@types/node': 25.3.3 fsevents: 2.3.3 jiti: 2.6.1 yaml: 2.8.2 - vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2): + vitest@4.0.18(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -4927,10 +5172,10 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.3.0 + '@types/node': 25.3.3 transitivePeerDependencies: - jiti - less @@ -4975,7 +5220,7 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index aa3151c6..f4b2129a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,23 +1,24 @@ packages: - packages/* - utils/* + - e2e/game catalogs: build: tsup: ^8.5.1 ci: - "@commitlint/cli": ^20.4.2 - "@commitlint/config-conventional": ^20.4.2 - "@favware/cliff-jumper": ^6.0.0 - "@nanoforge-dev/actions": ^1.1.0 + '@commitlint/cli': ^20.4.3 + '@commitlint/config-conventional': ^20.4.3 + '@favware/cliff-jumper': ^6.0.0 + '@nanoforge-dev/actions': ^1.1.0 husky: ^9.1.7 - lint-staged: ^16.2.7 + lint-staged: ^16.3.2 config: class-transformer: ^0.5.1 class-validator: ^0.14.3 core: - "@types/node": ^25.3.0 - turbo: ^2.8.10 + '@types/node': ^25.3.3 + turbo: ^2.8.13 typescript: ^5.9.3 docs: typedoc: ^0.28.17 @@ -25,8 +26,8 @@ catalogs: graphics: konva: ^10.2.0 lint: - "@eslint/js": ^10.0.1 - "@trivago/prettier-plugin-sort-imports": ^6.0.2 + '@eslint/js': ^10.0.1 + '@trivago/prettier-plugin-sort-imports': ^6.0.2 eslint: ^10.0.2 eslint-config-prettier: ^10.1.8 eslint-formatter-pretty: ^7.0.0 @@ -36,12 +37,12 @@ catalogs: prettier: ^3.8.1 typescript-eslint: ^8.56.1 network: - "@mapbox/node-pre-gyp": ^2.0.3 - "@types/ws": ^8.18.1 + '@mapbox/node-pre-gyp': ^2.0.3 + '@types/ws': ^8.18.1 wrtc: ^0.4.7 ws: ^8.19.0 test: - "@vitest/coverage-v8": ^4.0.18 + '@vitest/coverage-v8': ^4.0.18 vitest: ^4.0.18 onlyBuiltDependencies: diff --git a/turbo.json b/turbo.json index 5c848463..378baa59 100644 --- a/turbo.json +++ b/turbo.json @@ -8,7 +8,7 @@ "build": { "dependsOn": ["^build"], "inputs": ["src/**", "wasm/**", "test/**", "package.json", "tsconfig.json", "tsup.config.ts"], - "outputs": ["dist/**"], + "outputs": ["dist/**", ".nanoforge/client/**", ".nanoforge/server/**"], "outputLogs": "errors-only" }, "format": { @@ -28,12 +28,6 @@ "inputs": ["src/**", "wasm/**", "test/**", "package.json", "tsconfig.json", "tsup.config.ts"], "outputs": [], "outputLogs": "errors-only" - }, - "test:e2e": { - "dependsOn": ["^test:e2e"], - "inputs": ["src/**", "wasm/**", "test/**", "package.json", "tsconfig.json", "tsup.config.ts"], - "outputs": [], - "outputLogs": "errors-only" } } } diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 00000000..d11d59a8 --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["e2e/**/*.test.ts"], + passWithNoTests: true, + hookTimeout: 100000, + testTimeout: 100000, + }, +}); From 9975eaaff52de7ba8ea8c4e47980178a1ad4db12 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 5 Mar 2026 10:42:01 +0100 Subject: [PATCH 2/3] test: add cli to deps --- e2e/game/package.json | 1 + pnpm-lock.yaml | 932 ++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 1 + 3 files changed, 934 insertions(+) diff --git a/e2e/game/package.json b/e2e/game/package.json index ef1cfb25..4c849dd3 100644 --- a/e2e/game/package.json +++ b/e2e/game/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "@nanoforge-dev/asset-manager": "workspace:*", + "@nanoforge-dev/cli": "latest", "@nanoforge-dev/common": "workspace:*", "@nanoforge-dev/core": "workspace:*", "@nanoforge-dev/ecs-client": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b367ef0..950d8aa6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: '@nanoforge-dev/asset-manager': specifier: workspace:* version: link:../../packages/asset-manager + '@nanoforge-dev/cli': + specifier: latest + version: 1.2.0(@types/node@25.3.3) '@nanoforge-dev/common': specifier: workspace:* version: link:../../packages/common @@ -780,6 +783,24 @@ packages: '@actions/io@2.0.0': resolution: {integrity: sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==} + '@angular-devkit/core@21.2.0': + resolution: {integrity: sha512-HZdTn46Ca6qbb9Zef8R/+TWsk6mNKRm4rJyL3PxHP6HnVCwSPNZ0LNN9BjVREBs+UlRdXqBGFBZh5D1nBgu5GQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^5.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@21.2.0': + resolution: {integrity: sha512-thB201KLPu2rg0XGb4BNHRk2m4cRczr5brOtGt5qr0yCfYu17hkKrkrfbSh9ebVAwXRW2+WNwAFde08K28vpQA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@21.2.0': + resolution: {integrity: sha512-3kn3FI5v7BQ7Zct6raek+WgvyDwOJ8wElbyC903GxMQCDBRGGcevhHvTAIHhknihEsrgplzPhTlWeMbk1JfdFg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1138,6 +1159,274 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/ansi@2.0.3': + resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/checkbox@5.1.0': + resolution: {integrity: sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.0.8': + resolution: {integrity: sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.1.5': + resolution: {integrity: sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.0.8': + resolution: {integrity: sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.0.8': + resolution: {integrity: sha512-QieW3F1prNw3j+hxO7/NKkG1pk3oz7pOB6+5Upwu3OIwADfPX0oZVppsqlL+Vl/uBHHDSOBY0BirLctLnXwGGg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@2.0.3': + resolution: {integrity: sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/figures@2.0.3': + resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/input@5.0.8': + resolution: {integrity: sha512-p0IJslw0AmedLEkOU+yrEX3Aj2RTpQq7ZOf8nc1DIhjzaxRWrrgeuE5Kyh39fVRgtcACaMXx/9WNo8+GjgBOfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.0.8': + resolution: {integrity: sha512-uGLiQah9A0F9UIvJBX52m0CnqtLaym0WpT9V4YZrjZ+YRDKZdwwoEPz06N6w8ChE2lrnsdyhY9sL+Y690Kh9gQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.0.8': + resolution: {integrity: sha512-zt1sF4lYLdvPqvmvHdmjOzuUUjuCQ897pdUCO8RbXMUDKXJTTyOQgtn23le+jwcb+MpHl3VAFvzIdxRAf6aPlA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.3.0': + resolution: {integrity: sha512-JAj66kjdH/F1+B7LCigjARbwstt3SNUOSzMdjpsvwJmzunK88gJeXmcm95L9nw1KynvFVuY4SzXh/3Y0lvtgSg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.2.4': + resolution: {integrity: sha512-fTuJ5Cq9W286isLxwj6GGyfTjx1Zdk4qppVEPexFuA6yioCCXS4V1zfKroQqw7QdbDPN73xs2DiIAlo55+kBqg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.1.4': + resolution: {integrity: sha512-9yPTxq7LPmYjrGn3DRuaPuPbmC6u3fiWcsE9ggfLcdgO/ICHYgxq7mEy1yJ39brVvgXhtOtvDVjDh9slJxE4LQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.1.0': + resolution: {integrity: sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.3': + resolution: {integrity: sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1164,6 +1453,27 @@ packages: resolution: {integrity: sha512-BTZyJ69Ax5nvMYqFRzj4WGvFTUW4W9JSDSaln4DwJmDtS3davkwphuXS85WEghQ1lpbp+gHuOUXu6Xj56gwW2w==} engines: {node: '25'} + '@nanoforge-dev/cli@1.2.0': + resolution: {integrity: sha512-toHxsI8Eoh7/hsVIttgZg2JVxcNY/rx2AtYt6oWrH6CEYaEJqXfHuSe8LGiG2Ybtc/a74tkRScvmk5I50QKgdQ==} + engines: {node: '25'} + hasBin: true + + '@nanoforge-dev/loader-client@1.2.0': + resolution: {integrity: sha512-iEzxcqfDpKJ2Y02sGTNcMsGqB7tXjLAYx2lW+mME0VD5uJT45Pt89mE9biYqBlFHSBNr5UdG+DmyiNFiZEXQcw==} + engines: {node: '25'} + + '@nanoforge-dev/loader-server@1.1.0': + resolution: {integrity: sha512-U+AOZIzzCb3VPPkGR3OpUpAm97xEyndDXLHH/XA+XZMlPHx6h3n8hA7sJnN4szPqx5CQR8ssgnpwdgnI6lwNWQ==} + engines: {node: 24.11.0} + + '@nanoforge-dev/loader-website@1.1.0': + resolution: {integrity: sha512-6taExH65vAfUpIVrWSzPzLsGkdXcdREjOjvgY4vOwXet3JXRtA6+yHjqp2kVg9Yrp+OA3iUPNXOsLRXOrfNVJw==} + engines: {node: 24.11.0} + + '@nanoforge-dev/schematics@1.2.0': + resolution: {integrity: sha512-ObxdyAi8D0Waj3gfuyzGuwEv7M9DTsYe81kv+2rduR6go/Wxbbt3FWykEZ7pWxXX9VgxmMxlptYRMrNEk7bLaA==} + engines: {node: '25'} + '@octokit/auth-token@4.0.0': resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} engines: {node: '>= 18'} @@ -1248,6 +1558,66 @@ packages: '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@oven/bun-darwin-aarch64@1.3.10': + resolution: {integrity: sha512-PXgg5gqcS/rHwa1hF0JdM1y5TiyejVrMHoBmWY/DjtfYZoFTXie1RCFOkoG0b5diOOmUcuYarMpH7CSNTqwj+w==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.3.10': + resolution: {integrity: sha512-w1gaTlqU0IJCmJ1X+PGHkdNU1n8Gemx5YKkjhkJIguvFINXEBB5U1KG82QsT65Tk4KyNMfbLTlmy4giAvUoKfA==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.3.10': + resolution: {integrity: sha512-Nhssuh7GBpP5PiDSOl3+qnoIG7PJo+ec2oomDevnl9pRY6x6aD2gRt0JE+uf+A8Om2D6gjeHCxjEdrw5ZHE8mA==} + cpu: [x64] + os: [darwin] + + '@oven/bun-linux-aarch64-musl@1.3.10': + resolution: {integrity: sha512-Ui5pAgM7JE9MzHokF0VglRMkbak3lTisY4Mf1AZutPACXWgKJC5aGrgnHBfkl7QS6fEeYb0juy1q4eRznRHOsw==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-aarch64@1.3.10': + resolution: {integrity: sha512-OUgPHfL6+PM2Q+tFZjcaycN3D7gdQdYlWnwMI31DXZKY1r4HINWk9aEz9t/rNaHg65edwNrt7dsv9TF7xK8xIA==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-baseline@1.3.10': + resolution: {integrity: sha512-oqvMDYpX6dGJO03HgO5bXuccEsH3qbdO3MaAiAlO4CfkBPLUXz3N0DDElg5hz0L6ktdDVKbQVE5lfe+LAUISQg==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl-baseline@1.3.10': + resolution: {integrity: sha512-/hOZ6S1VsTX6vtbhWVL9aAnOrdpuO54mAGUWpTdMz7dFG5UBZ/VUEiK0pBkq9A1rlBk0GeD/6Y4NBFl8Ha7cRA==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl@1.3.10': + resolution: {integrity: sha512-poVXvOShekbexHq45b4MH/mRjQKwACAC8lHp3Tz/hEDuz0/20oncqScnmKwzhBPEpqJvydXficXfBYuSim8opw==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.3.10': + resolution: {integrity: sha512-bzUgYj/PIZziB/ZesIP9HUyfvh6Vlf3od+TrbTTyVEuCSMKzDPQVW/yEbRp0tcHO3alwiEXwJDrWrHAguXlgiQ==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-aarch64@1.3.10': + resolution: {integrity: sha512-GXbz2swvN2DLw2dXZFeedMxSJtI64xQ9xp9Eg7Hjejg6mS2E4dP1xoQ2yAo2aZPi/2OBPAVaGzppI2q20XumHA==} + cpu: [arm64] + os: [win32] + + '@oven/bun-windows-x64-baseline@1.3.10': + resolution: {integrity: sha512-gh3UAHbUdDUG6fhLc1Csa4IGdtghue6U8oAIXWnUqawp6lwb3gOCRvp25IUnLF5vUHtgfMxuEUYV7YA2WxVutw==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.3.10': + resolution: {integrity: sha512-qaS1In3yfC/Z/IGQriVmF8GWwKuNqiw7feTSJWaQhH5IbL6ENR+4wGNPniZSJFaM/SKUO0e/YCRdoVBvgU4C1g==} + cpu: [x64] + os: [win32] + '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1542,6 +1912,10 @@ packages: resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} engines: {node: '>=18'} + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -1719,6 +2093,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} @@ -1745,6 +2127,10 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1788,6 +2174,12 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bun@1.3.10: + resolution: {integrity: sha512-S/CXaXXIyA4CMjdMkYQ4T2YMqnAn4s0ysD3mlsY4bUiOCqGlv28zck4Wd4H4kpvbekx15S9mUeLQ7Uxd0tYTLA==} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1810,10 +2202,21 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1828,10 +2231,18 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-truncate@5.2.0: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1952,6 +2363,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -2094,9 +2508,18 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2226,6 +2649,10 @@ packages: engines: {node: '>=18'} hasBin: true + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2272,6 +2699,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2348,6 +2779,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2478,6 +2912,14 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -2489,6 +2931,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2528,6 +2974,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@9.3.0: + resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} + engines: {node: '>=20'} + oxfmt@0.35.0: resolution: {integrity: sha512-QYeXWkP+aLt7utt5SLivNIk09glWx9QE235ODjgcEZ3sd1VMaUBSpLymh6ZRCA76gD2rMP4bXanUz/fx+nLM9Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2647,6 +3097,13 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2675,6 +3132,12 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -2695,6 +3158,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -2725,6 +3192,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.3.1: + resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} + engines: {node: '>=18'} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2933,6 +3404,10 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -3047,6 +3522,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3101,6 +3580,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -3133,6 +3616,36 @@ snapshots: '@actions/io@2.0.0': {} + '@angular-devkit/core@21.2.0(chokidar@5.0.0)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + jsonc-parser: 3.3.1 + picomatch: 4.0.3 + rxjs: 7.8.2 + source-map: 0.7.6 + optionalDependencies: + chokidar: 5.0.0 + + '@angular-devkit/schematics-cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.0(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.0(chokidar@5.0.0) + '@inquirer/prompts': 7.10.1(@types/node@25.3.3) + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@21.2.0(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.0(chokidar@5.0.0) + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + ora: 9.3.0 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -3459,6 +3972,250 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@1.0.2': {} + + '@inquirer/ansi@2.0.3': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.3.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/checkbox@5.1.0(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/confirm@5.1.21(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/confirm@6.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/core@10.3.2(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.3.3) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/core@11.1.5(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.3) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/editor@4.2.23(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/editor@5.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/external-editor': 2.0.3(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/expand@4.0.23(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/expand@5.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/external-editor@1.0.3(@types/node@25.3.3)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/external-editor@2.0.3(@types/node@25.3.3)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/figures@2.0.3': {} + + '@inquirer/input@4.3.1(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/input@5.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/number@3.0.23(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/number@4.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/password@4.0.23(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/password@5.0.8(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/prompts@7.10.1(@types/node@25.3.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.3.3) + '@inquirer/confirm': 5.1.21(@types/node@25.3.3) + '@inquirer/editor': 4.2.23(@types/node@25.3.3) + '@inquirer/expand': 4.0.23(@types/node@25.3.3) + '@inquirer/input': 4.3.1(@types/node@25.3.3) + '@inquirer/number': 3.0.23(@types/node@25.3.3) + '@inquirer/password': 4.0.23(@types/node@25.3.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.3.3) + '@inquirer/search': 3.2.2(@types/node@25.3.3) + '@inquirer/select': 4.4.2(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/prompts@8.3.0(@types/node@25.3.3)': + dependencies: + '@inquirer/checkbox': 5.1.0(@types/node@25.3.3) + '@inquirer/confirm': 6.0.8(@types/node@25.3.3) + '@inquirer/editor': 5.0.8(@types/node@25.3.3) + '@inquirer/expand': 5.0.8(@types/node@25.3.3) + '@inquirer/input': 5.0.8(@types/node@25.3.3) + '@inquirer/number': 4.0.8(@types/node@25.3.3) + '@inquirer/password': 5.0.8(@types/node@25.3.3) + '@inquirer/rawlist': 5.2.4(@types/node@25.3.3) + '@inquirer/search': 4.1.4(@types/node@25.3.3) + '@inquirer/select': 5.1.0(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/rawlist@4.1.11(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/type': 3.0.10(@types/node@25.3.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/rawlist@5.2.4(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/search@3.2.2(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.3.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/search@4.1.4(@types/node@25.3.3)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/select@4.4.2(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.3.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.3.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/select@5.1.0(@types/node@25.3.3)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@25.3.3) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.3) + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/type@3.0.10(@types/node@25.3.3)': + optionalDependencies: + '@types/node': 25.3.3 + + '@inquirer/type@4.0.3(@types/node@25.3.3)': + optionalDependencies: + '@types/node': 25.3.3 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -3496,6 +4253,42 @@ snapshots: '@actions/github': 7.0.0 commander: 14.0.3 + '@nanoforge-dev/cli@1.2.0(@types/node@25.3.3)': + dependencies: + '@angular-devkit/schematics': 21.2.0(chokidar@5.0.0) + '@angular-devkit/schematics-cli': 21.2.0(@types/node@25.3.3)(chokidar@5.0.0) + '@inquirer/prompts': 8.3.0(@types/node@25.3.3) + '@nanoforge-dev/loader-client': 1.2.0 + '@nanoforge-dev/loader-server': 1.1.0 + '@nanoforge-dev/schematics': 1.2.0(chokidar@5.0.0) + ansis: 4.2.0 + bun: 1.3.10 + chokidar: 5.0.0 + class-transformer: 0.5.1 + class-validator: 0.14.4 + commander: 14.0.3 + node-emoji: 2.2.0 + ora: 9.3.0 + reflect-metadata: 0.2.2 + transitivePeerDependencies: + - '@types/node' + + '@nanoforge-dev/loader-client@1.2.0': + dependencies: + '@nanoforge-dev/loader-website': 1.1.0 + bun: 1.3.10 + + '@nanoforge-dev/loader-server@1.1.0': {} + + '@nanoforge-dev/loader-website@1.1.0': {} + + '@nanoforge-dev/schematics@1.2.0(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.0(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.0(chokidar@5.0.0) + transitivePeerDependencies: + - chokidar + '@octokit/auth-token@4.0.0': {} '@octokit/auth-token@5.1.2': {} @@ -3602,6 +4395,42 @@ snapshots: dependencies: '@octokit/openapi-types': 25.1.0 + '@oven/bun-darwin-aarch64@1.3.10': + optional: true + + '@oven/bun-darwin-x64-baseline@1.3.10': + optional: true + + '@oven/bun-darwin-x64@1.3.10': + optional: true + + '@oven/bun-linux-aarch64-musl@1.3.10': + optional: true + + '@oven/bun-linux-aarch64@1.3.10': + optional: true + + '@oven/bun-linux-x64-baseline@1.3.10': + optional: true + + '@oven/bun-linux-x64-musl-baseline@1.3.10': + optional: true + + '@oven/bun-linux-x64-musl@1.3.10': + optional: true + + '@oven/bun-linux-x64@1.3.10': + optional: true + + '@oven/bun-windows-aarch64@1.3.10': + optional: true + + '@oven/bun-windows-x64-baseline@1.3.10': + optional: true + + '@oven/bun-windows-x64@1.3.10': + optional: true + '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -3764,6 +4593,8 @@ snapshots: '@simple-libs/stream-utils@1.2.0': {} + '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} '@standard-schema/spec@1.1.0': {} @@ -3972,6 +4803,10 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -4000,6 +4835,8 @@ snapshots: ansi-styles@6.2.3: {} + ansis@4.2.0: {} + any-promise@1.3.0: {} argparse@2.0.1: {} @@ -4036,6 +4873,21 @@ snapshots: dependencies: fill-range: 7.1.1 + bun@1.3.10: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.3.10 + '@oven/bun-darwin-x64': 1.3.10 + '@oven/bun-darwin-x64-baseline': 1.3.10 + '@oven/bun-linux-aarch64': 1.3.10 + '@oven/bun-linux-aarch64-musl': 1.3.10 + '@oven/bun-linux-x64': 1.3.10 + '@oven/bun-linux-x64-baseline': 1.3.10 + '@oven/bun-linux-x64-musl': 1.3.10 + '@oven/bun-linux-x64-musl-baseline': 1.3.10 + '@oven/bun-windows-aarch64': 1.3.10 + '@oven/bun-windows-x64': 1.3.10 + '@oven/bun-windows-x64-baseline': 1.3.10 + bundle-require@5.1.0(esbuild@0.27.3): dependencies: esbuild: 0.27.3 @@ -4049,10 +4901,18 @@ snapshots: chalk@5.6.2: {} + char-regex@1.0.2: {} + + chardet@2.1.1: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + chownr@3.0.0: {} class-transformer@0.5.1: {} @@ -4067,11 +4927,15 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-spinners@3.4.0: {} + cli-truncate@5.2.0: dependencies: slice-ansi: 8.0.0 string-width: 8.2.0 + cli-width@4.1.0: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -4173,6 +5037,8 @@ snapshots: emoji-regex@8.0.0: {} + emojilib@2.4.0: {} + entities@4.5.0: {} env-paths@2.2.1: {} @@ -4364,8 +5230,18 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} + fast-wrap-ansi@0.2.0: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -4474,6 +5350,10 @@ snapshots: husky@9.1.7: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4505,6 +5385,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-interactive@2.0.0: {} + is-number@7.0.0: {} is-obj@2.0.0: {} @@ -4556,6 +5438,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonc-parser@3.3.1: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -4691,6 +5575,10 @@ snapshots: ms@2.1.3: {} + mute-stream@2.0.0: {} + + mute-stream@3.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -4701,6 +5589,13 @@ snapshots: natural-compare@1.4.0: {} + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -4737,6 +5632,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@9.3.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.1 + string-width: 8.2.0 + oxfmt@0.35.0: dependencies: tinypool: 2.1.0 @@ -4846,6 +5752,10 @@ snapshots: readdirp@4.1.2: {} + readdirp@5.0.0: {} + + reflect-metadata@0.2.2: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -4892,6 +5802,12 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safer-buffer@2.1.2: {} + semver@7.7.4: {} shebang-command@2.0.0: @@ -4904,6 +5820,10 @@ snapshots: signal-exit@4.1.0: {} + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -4926,6 +5846,8 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.3.1: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -5126,6 +6048,8 @@ snapshots: undici@6.23.0: {} + unicode-emoji-modifier-base@1.0.0: {} + unicorn-magic@0.3.0: {} universal-user-agent@6.0.1: {} @@ -5210,6 +6134,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -5250,4 +6180,6 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f4b2129a..52910ac0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -46,6 +46,7 @@ catalogs: vitest: ^4.0.18 onlyBuiltDependencies: + - bun - esbuild - unrs-resolver - wrtc From 5e8f0bc2f24983874c2f0d4a58a73feda23cbd64 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 5 Mar 2026 10:51:28 +0100 Subject: [PATCH 3/3] test: remove start from test as not stable --- e2e/game.test.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/e2e/game.test.ts b/e2e/game.test.ts index 7f0784a6..5a5202d5 100644 --- a/e2e/game.test.ts +++ b/e2e/game.test.ts @@ -9,7 +9,7 @@ const PROJECT_DIR = join(dirname(fileURLToPath(import.meta.url)), "./game"); describe("E2E Game", () => { beforeAll(() => { rmSync(join(PROJECT_DIR, ".nanoforge"), { recursive: true, force: true }); - execSync("nf build", { + execSync("pnpm run build", { cwd: PROJECT_DIR, stdio: "pipe", timeout: 120_000, @@ -33,14 +33,4 @@ describe("E2E Game", () => { expect(existsSync(join(PROJECT_DIR, ".nanoforge/server/libecs.wasm"))).toBe(true); }); }); - - describe("Server", () => { - it("should start, run for 5 ticks, and exit cleanly", () => { - execSync("bun run ../run-server.mjs", { - cwd: PROJECT_DIR, - stdio: "pipe", - timeout: 15_000, - }); - }, 20_000); - }); });