From 935330673226bd271b435c208806198a60b59ce6 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Sat, 12 Oct 2024 11:29:54 +1100 Subject: [PATCH] fix: tests, improvements --- .../src/dojo/setup.ts | 12 +- packages/state/package.json | 2 +- packages/state/src/__tests__/utils.test.ts | 237 +++++++++++++++++- packages/state/src/recs/index.ts | 45 +++- packages/state/src/utils/index.ts | 118 +++++---- pnpm-lock.yaml | 34 ++- 6 files changed, 371 insertions(+), 77 deletions(-) diff --git a/examples/example-vite-react-app-recs/src/dojo/setup.ts b/examples/example-vite-react-app-recs/src/dojo/setup.ts index 263b89c9..bc8e818b 100644 --- a/examples/example-vite-react-app-recs/src/dojo/setup.ts +++ b/examples/example-vite-react-app-recs/src/dojo/setup.ts @@ -1,6 +1,6 @@ import { DojoConfig, DojoProvider } from "@dojoengine/core"; import { BurnerManager } from "@dojoengine/create-burner"; -import { getSyncEvents } from "@dojoengine/state"; +import { getSyncEntities, getSyncEvents } from "@dojoengine/state"; import * as torii from "@dojoengine/torii-client"; import { Account, ArraySignatureType } from "starknet"; @@ -24,6 +24,15 @@ export async function setup({ ...config }: DojoConfig) { // Define contract components based on the world configuration const contractComponents = defineContractComponents(world); + const getSync = await getSyncEntities( + toriiClient, + contractComponents as any, + undefined, + [], + 3000, + true + ); + // Create client-side components that mirror the contract components const clientComponents = createClientComponents({ contractComponents }); @@ -79,5 +88,6 @@ export async function setup({ ...config }: DojoConfig) { burnerManager, toriiClient, eventSync, + getSync, }; } diff --git a/packages/state/package.json b/packages/state/package.json index 4e1457bc..03259ddb 100644 --- a/packages/state/package.json +++ b/packages/state/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@dojoengine/recs": "2.0.13", - "@dojoengine/torii-client": "workspace:*", + "@dojoengine/torii-client": "1.0.0-alpha.20", "vitest": "^1.6.0" } } diff --git a/packages/state/src/__tests__/utils.test.ts b/packages/state/src/__tests__/utils.test.ts index ba5ff9b6..518fbcc8 100644 --- a/packages/state/src/__tests__/utils.test.ts +++ b/packages/state/src/__tests__/utils.test.ts @@ -1,5 +1,5 @@ -import { Type as RecsType } from "@dojoengine/recs"; -import { describe, expect, it } from "vitest"; +import { Type as RecsType, Schema } from "@dojoengine/recs"; +import { describe, expect, it, vi } from "vitest"; import { convertValues } from "../utils"; @@ -108,4 +108,237 @@ describe("convertValues", () => { expect(result.nested).toEqual({ innerField: { value: "42" } }); }); }); + + it("should handle null and undefined values", () => { + const schema: Schema = { + name: RecsType.String, + age: RecsType.Number, + }; + const values = { + name: { value: "Alice", type: "string" }, + age: undefined, + }; + const expected = { + name: "Alice", + age: undefined, + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should convert enum types correctly", () => { + const schema: Schema = { + status: RecsType.String, + }; + const values = { + status: { value: { option: "ACTIVE" }, type: "enum" }, + }; + const expected = { + status: "ACTIVE", + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.StringArray with empty array", () => { + const schema: Schema = { + tags: RecsType.StringArray, + }; + const values = { + tags: { value: [], type: "array" }, + }; + const expected = { + tags: [], + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.StringArray with enum items", () => { + const schema: Schema = { + tags: RecsType.StringArray, + }; + const values = { + tags: { + value: [ + { value: { option: "TAG1" }, type: "enum" }, + { value: { option: "TAG2" }, type: "enum" }, + ], + type: "array", + }, + }; + const expected = { + tags: ["TAG1", "TAG2"], + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.StringArray with BigInt conversion", () => { + const schema: Schema = { + ids: RecsType.StringArray, + }; + const values = { + ids: { + value: [ + { value: "12345678901234567890", type: "string" }, + { value: "98765432109876543210", type: "string" }, + ], + type: "array", + }, + }; + const expected = { + ids: [ + BigInt("12345678901234567890"), + BigInt("98765432109876543210"), + ], + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should fallback to string if BigInt conversion fails", () => { + vi.spyOn(console, "warn").mockImplementation(() => {}); + + const schema: Schema = { + ids: RecsType.StringArray, + }; + const values = { + ids: { + value: [{ value: "invalid_bigint", type: "string" }], + type: "array", + }, + }; + const expected = { + ids: ["invalid_bigint"], + }; + expect(convertValues(schema, values)).toEqual(expected); + expect(console.warn).toHaveBeenCalledWith( + "Failed to convert invalid_bigint to BigInt. Using string value instead." + ); + }); + + it("should handle RecsType.String", () => { + const schema: Schema = { + name: RecsType.String, + }; + const values = { + name: { value: "Bob", type: "string" }, + }; + const expected = { + name: "Bob", + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.BigInt with valid BigInt", () => { + const schema: Schema = { + balance: RecsType.BigInt, + }; + const values = { + balance: { value: "1000000000000000000", type: "string" }, + }; + const expected = { + balance: BigInt("1000000000000000000"), + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.Boolean", () => { + const schema: Schema = { + isActive: RecsType.Boolean, + }; + const values = { + isActive: { value: true, type: "boolean" }, + }; + const expected = { + isActive: true, + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle RecsType.Number", () => { + const schema: Schema = { + score: RecsType.Number, + }; + const values = { + score: { value: "42", type: "string" }, + }; + const expected = { + score: 42, + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle nested structs", () => { + const nestedSchema: Schema = { + street: RecsType.String, + zip: RecsType.Number, + }; + const schema: Schema = { + name: RecsType.String, + address: nestedSchema, + }; + const values = { + name: { value: "Charlie", type: "string" }, + address: { + value: { + street: { value: "123 Main St", type: "string" }, + zip: { value: "12345", type: "string" }, + }, + type: "struct", + }, + }; + const expected = { + name: "Charlie", + address: { + street: "123 Main St", + zip: 12345, + }, + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle map structures", () => { + const nestedSchema: Schema = { + key1: RecsType.String, + key2: RecsType.Number, + }; + const schema: Schema = { + config: nestedSchema, + }; + const values = { + config: { + value: new Map([ + ["key1", { value: "value1", type: "string" }], + ["key2", { value: "100", type: "string" }], + ]), + type: "struct", + }, + }; + const expected = { + config: { + key1: "value1", + key2: 100, + }, + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle primitive fallback in default case", () => { + const schema: Schema = { + miscellaneous: RecsType.String, + }; + const values = { + miscellaneous: { value: "some value", type: "unknown" }, + }; + const expected = { + miscellaneous: "some value", + }; + expect(convertValues(schema, values)).toEqual(expected); + }); + + it("should handle empty schema", () => { + const schema: Schema = {}; + const values = { + anyKey: { value: "any value", type: "string" }, + }; + const expected = {}; + expect(convertValues(schema, values)).toEqual(expected); + }); }); diff --git a/packages/state/src/recs/index.ts b/packages/state/src/recs/index.ts index c4228de8..6794e24e 100644 --- a/packages/state/src/recs/index.ts +++ b/packages/state/src/recs/index.ts @@ -49,7 +49,7 @@ export const getSyncEntities = async ( clause: Clause | undefined, entityKeyClause: EntityKeysClause[], limit: number = 100, - logging: boolean = true + logging: boolean = false ) => { if (logging) console.log("Starting getSyncEntities"); await getEntities(client, clause, components, limit, logging); @@ -199,7 +199,7 @@ export const getEntitiesQuery = async ( entityKeyClause: EntityKeysClause, patternMatching: PatternMatching = "FixedLen", limit: number = 1000, - logging: boolean = true + logging: boolean = false ) => { if (logging) console.log("Starting getEntitiesQuery"); let cursor = 0; @@ -310,7 +310,8 @@ export const setEntities = async ( components: Component[], logging: boolean = false ) => { - if (logging) console.log(entities); + if (logging) console.log("Entities to set:", entities); + for (let key in entities) { if (!Object.hasOwn(entities, key)) { continue; @@ -330,14 +331,32 @@ export const setEntities = async ( if (recsComponent) { try { - setComponent( - recsComponent, - key as Entity, - convertValues( - recsComponent.schema, - entities[key][componentName] - ) as ComponentValue - ); + const rawValue = entities[key][componentName]; + if (logging) + console.log( + `Raw value for ${componentName} on ${key}:`, + rawValue + ); + + const convertedValue = convertValues( + recsComponent.schema, + rawValue + ) as ComponentValue; + + if (logging) + console.log( + `Converted value for ${componentName} on ${key}:`, + convertedValue + ); + + if (!convertedValue) { + console.error( + `convertValues returned undefined or invalid for ${componentName} on ${key}` + ); + } + + setComponent(recsComponent, key as Entity, convertedValue); + if (logging) console.log( `Set component ${recsComponent.metadata?.name} on ${key}` @@ -348,6 +367,10 @@ export const setEntities = async ( error ); } + } else { + console.warn( + `Component ${componentName} not found in provided components.` + ); } } } diff --git a/packages/state/src/utils/index.ts b/packages/state/src/utils/index.ts index a52bb0fe..90c38014 100644 --- a/packages/state/src/utils/index.ts +++ b/packages/state/src/utils/index.ts @@ -2,13 +2,10 @@ import { Type as RecsType, Schema } from "@dojoengine/recs"; export function convertValues(schema: Schema, values: any) { return Object.keys(schema).reduce((acc, key) => { - if (!acc) { - acc = {}; - } const schemaType = schema[key]; const value = values[key]; - if (value === null || value === undefined) { + if (value == null) { acc[key] = value; return acc; } @@ -20,27 +17,7 @@ export function convertValues(schema: Schema, values: any) { switch (schemaType) { case RecsType.StringArray: - if (value.type === "array" && value.value.length === 0) { - acc[key] = []; - } else if ( - value.type === "array" && - value.value[0].type === "enum" - ) { - acc[key] = value.value.map( - (item: any) => item.value.option - ); - } else { - acc[key] = value.value.map((a: any) => { - try { - return BigInt(a.value); - } catch (error) { - console.warn( - `Failed to convert ${a.value} to BigInt. Using string value instead.` - ); - return a.value; - } - }); - } + acc[key] = handleStringArray(value); break; case RecsType.String: @@ -48,15 +25,7 @@ export function convertValues(schema: Schema, values: any) { break; case RecsType.BigInt: - try { - acc[key] = BigInt(value.value); - } catch (error) { - console.warn( - `Failed to convert ${value.value} to BigInt. Using string value instead.` - ); - - acc[key] = BigInt(`0x${value.value}`); - } + acc[key] = handleBigInt(value.value); break; case RecsType.Boolean: @@ -68,26 +37,73 @@ export function convertValues(schema: Schema, values: any) { break; default: - if (typeof schemaType === "object" && value.type === "struct") { - if (value.value instanceof Map) { - const structValues = Object.fromEntries(value.value); - acc[key] = convertValues(schemaType, structValues); - } else { - acc[key] = convertValues(schemaType, value.value); - } - } else if ( - Array.isArray(schemaType) && - value.type === "array" - ) { - acc[key] = value.value.map((item: any) => - convertValues(schemaType[0], item) - ); - } else { - acc[key] = value.value; - } + acc[key] = handleDefault(schemaType, value); break; } return acc; }, {}); } + +function handleStringArray(value: any) { + if (value.type === "array" && value.value.length === 0) { + return []; + } + if (value.type === "array" && value.value[0]?.type === "enum") { + return value.value.map((item: any) => item.value.option); + } + return value.value.map((a: any) => { + try { + return BigInt(a.value); + } catch (error) { + console.warn( + `Failed to convert ${a.value} to BigInt. Using string value instead.` + ); + return a.value; + } + }); +} + +function handleBigInt(value: string | bigint) { + if (typeof value === "bigint") { + return value; + } + try { + return BigInt(value); + } catch (error) { + console.warn( + `Failed to convert ${value} to BigInt. Attempting hexadecimal conversion.` + ); + try { + return BigInt(`0x${value}`); + } catch (hexError) { + console.warn( + `Failed to convert 0x${value} to BigInt. Using string value instead.` + ); + return value; + } + } +} + +function handleDefault(schemaType: any, value: any) { + if (typeof schemaType === "object" && value.type === "struct") { + if (value.value instanceof Map) { + const structValues = Object.fromEntries(value.value); + return convertValues(schemaType, structValues); + } else if (typeof value.value === "object") { + // Handle cases where value.value might already be a plain object + return convertValues(schemaType, value.value); + } else { + console.warn( + `Expected value.value to be a Map or object for struct type, got ${typeof value.value}.` + ); + return value.value; + } + } + if (Array.isArray(schemaType) && value.type === "array") { + return value.value.map((item: any) => + convertValues(schemaType[0], item) + ); + } + return value.value; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c081745..56aefe55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1265,8 +1265,8 @@ importers: specifier: 2.0.13 version: 2.0.13(typescript@5.6.2)(zod@3.23.8) '@dojoengine/torii-client': - specifier: workspace:* - version: link:../torii-client + specifier: 1.0.0-alpha.20 + version: 1.0.0-alpha.20 starknet: specifier: 6.11.0 version: 6.11.0(encoding@0.1.13) @@ -2287,6 +2287,12 @@ packages: '@dojoengine/recs@2.0.13': resolution: {integrity: sha512-Cgz4Unlnk2FSDoFTYKrJexX/KiSYPMFMxftxQkC+9LUKS5yNGkgFQM7xu4/L1HvpDAenL7NjUmH6ynRAS7Iifw==} + '@dojoengine/torii-client@1.0.0-alpha.20': + resolution: {integrity: sha512-c5GhkvFoIEt5YhZ6DUL0oliqLO1VlRKO7vqpNxS8FoPMVUBrvLw+GyohtaLwTnmNKNNoiDIdgQZqIzGZS7grow==} + + '@dojoengine/torii-wasm@1.0.0-alpha.20': + resolution: {integrity: sha512-Ry/uyzyZ+8+BiSGniNzw49s8ksPRfWhd5gyWAhJH50vzNOmApjddN8XWII9+NTm6R/1/yEb7G5U2D8W7vAcGvQ==} + '@emnapi/core@1.2.0': resolution: {integrity: sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==} @@ -13818,6 +13824,12 @@ snapshots: - utf-8-validate - zod + '@dojoengine/torii-client@1.0.0-alpha.20': + dependencies: + '@dojoengine/torii-wasm': 1.0.0-alpha.20 + + '@dojoengine/torii-wasm@1.0.0-alpha.20': {} + '@emnapi/core@1.2.0': dependencies: '@emnapi/wasi-threads': 1.0.1 @@ -20404,8 +20416,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -20424,37 +20436,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -20465,7 +20477,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3