Repository: decentraland/js-sdk-toolchain
Pull request #1107: feat: add type safe entity names file
Author: login: gonpombo8
Created: 2025-04-22T18:20:03Z | Merged: 2025-04-22T20:29:51Z

# Entity Names Type Generation

## Description
This PR introduces a new feature to automatically generate TypeScript definitions for entity names in the scene. It simplifies referencing scene entities by name with full type safety and IDE autocompletion support.

## Implementation Details
- Added a new function `generateEntityNamesType` to create a TypeScript file with string literal types for entity names
- Automatically generates this file whenever the scene composite is saved
- Creates both a string literal union type and a constant object for better developer experience
- Entity names are properly sanitized for valid TypeScript identifiers

## Benefits
- Type safety when referencing entities by name in code
- Autocomplete support in IDE for all named entities in the scene
- Prevents runtime errors from typos in entity name strings
- Makes refactoring entity names easier as TypeScript will catch all references

## Example Usage
```typescript
import { EntityNames } from '../assets/scene/entity-names'

// Auto-completion support via constant object
const GroundBoedoOrNull = engine.getEntityOrNullByName(EntityNames.Ground_Boedo1) 
const GroundBoedo = engine.getEntityByName<EntityNames>(EntityNames.Ground_Boedo1)
console.log(GroundBoedo)

```

This implementation helps reduce runtime errors and improves the developer experience when working with named entities in Decentraland scenes.



Diff:
diff --git a/packages/@dcl/ecs/src/engine/index.ts b/packages/@dcl/ecs/src/engine/index.ts
index c9890f124..2a2a38419 100644
--- a/packages/@dcl/ecs/src/engine/index.ts
+++ b/packages/@dcl/ecs/src/engine/index.ts
@@ -189,7 +189,7 @@ function preEngine(options?: IEngineOptions): PreEngine {
     }
   }
 
-  function getEntityOrNullByName(value: string) {
+  function getEntityOrNullByName<T = string>(value: T) {
     const NameComponent = components.Name({ defineComponent })
     for (const [entity, name] of getEntitiesWith(NameComponent)) {
       if (name.value === value) return entity
@@ -197,6 +197,11 @@ function preEngine(options?: IEngineOptions): PreEngine {
     return null
   }
 
+  function getEntityByName<T = never, K = T>(value: K & (T extends never ? never : string)): Entity {
+    const entity = getEntityOrNullByName(value)
+    return entity!
+  }
+
   function* getComponentDefGroup<T extends ComponentDefinition<any>[]>(...args: T): Iterable<[Entity, ...T]> {
     const [firstComponentDef, ...componentDefinitions] = args
     for (const [entity] of firstComponentDef.iterator()) {
@@ -252,6 +257,7 @@ function preEngine(options?: IEngineOptions): PreEngine {
     getComponent,
     getComponentOrNull: getComponentOrNull as IEngine['getComponentOrNull'],
     getEntityOrNullByName,
+    getEntityByName,
     removeComponentDefinition,
     registerComponentDefinition,
     entityContainer,
@@ -313,6 +319,7 @@ export function Engine(options?: IEngineOptions): IEngine {
     componentsIter: partialEngine.componentsIter,
     seal: partialEngine.seal,
     getEntityOrNullByName: partialEngine.getEntityOrNullByName,
+    getEntityByName: partialEngine.getEntityByName,
 
     update,
 
diff --git a/packages/@dcl/ecs/src/engine/types.ts b/packages/@dcl/ecs/src/engine/types.ts
index a2ec40337..913738e07 100644
--- a/packages/@dcl/ecs/src/engine/types.ts
+++ b/packages/@dcl/ecs/src/engine/types.ts
@@ -62,6 +62,7 @@ export type PreEngine = Pick<
   | 'seal'
   | 'entityContainer'
   | 'getEntityOrNullByName'
+  | 'getEntityByName'
 > & {
   getSystems: () => SystemItem[]
 }
@@ -245,7 +246,15 @@ export interface IEngine {
    * Search for the entity that matches de label string defined in the editor.
    * @param value - Name value string
    */
-  getEntityOrNullByName(label: string): Entity | null
+  getEntityOrNullByName<T = string>(label: T): Entity | null
+
+  /**
+   * @public
+   * Search for the entity that matches de label string defined in the editor.
+   * @param value - Name value string
+   * @typeParam T - The type of the entity name value
+   */
+  getEntityByName<T = never, K = T>(value: K & (T extends never ? never : string)): Entity
 
   /**
    * @public
diff --git a/packages/@dcl/inspector/src/lib/data-layer/host/utils/composite-dirty.ts b/packages/@dcl/inspector/src/lib/data-layer/host/utils/composite-dirty.ts
index 130cd8dbe..fb4fa7ed0 100644
--- a/packages/@dcl/inspector/src/lib/data-layer/host/utils/composite-dirty.ts
+++ b/packages/@dcl/inspector/src/lib/data-layer/host/utils/composite-dirty.ts
@@ -9,7 +9,7 @@ import {
 } from '@dcl/ecs'
 import { initComponents } from '@dcl/asset-packs'
 import { EditorComponentNames, EditorComponents } from '../../../sdk/components'
-import { dumpEngineToComposite, dumpEngineToCrdtCommands } from './engine-to-composite'
+import { dumpEngineToComposite, dumpEngineToCrdtCommands, generateEntityNamesType } from './engine-to-composite'
 import { FileSystemInterface } from '../../types'
 import { CompositeManager, createFsCompositeProvider } from './fs-composite-provider'
 import { getMinimalComposite } from '../../client/feeded-local-fs'
@@ -21,6 +21,7 @@ import { toSceneComponent } from './component'
 import { addNodesComponentsToPlayerAndCamera } from './migrations/add-nodes-to-player-and-camera'
 import { fixNetworkEntityValues } from './migrations/fix-network-entity-values'
 import { selectSceneEntity } from './migrations/select-scene-entity'
+import { DIRECTORY, withAssetDir } from '../fs-utils'
 
 enum DirtyEnum {
   // No changes
@@ -108,6 +109,9 @@ export async function compositeAndDirty(
       await fs.writeFile('main.crdt', Buffer.from(mainCrdt))
       await compositeProvider.save({ src: compositePath, composite }, 'json')
 
+      // Generate entity names type file
+      await generateEntityNamesType(engine, withAssetDir(DIRECTORY.SCENE + '/entity-names.ts'), 'EntityNames', fs)
+
       return composite
     } catch (e) {
       console.log('Failed saving composite')
diff --git a/packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts b/packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
index 0e4a198ec..1db0b1afb 100644
--- a/packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
+++ b/packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
@@ -8,9 +8,11 @@ import {
   IEngine,
   LastWriteWinElementSetComponentDefinition,
   PutComponentOperation,
-  getCompositeRootComponent
+  getCompositeRootComponent,
+  Name
 } from '@dcl/ecs'
 import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
+import { FileSystemInterface } from '../../types'
 
 function componentToCompositeComponentData<T>(
   $case: 'json' | 'binary',
@@ -128,3 +130,87 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers and handle duplicates in a single pass
+    const validNameMap = new Map<string, string>()
+    const finalNames: Array<{ original: string; valid: string }> = []
+
+    for (const name of uniqueNames) {
+      // Create a valid TypeScript identifier
+      let validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Handle collision if this valid name already exists
+      if (validNameMap.has(validName)) {
+        // Add numeric suffix for uniqueness
+        let suffix = 1
+        while (validNameMap.has(`${validName}_${suffix}`)) {
+          suffix++
+        }
+        validName = `${validName}_${suffix}`
+      }
+
+      // Store the mapping and add to result
+      validNameMap.set(validName, name)
+      finalNames.push({ original: name, valid: validName })
+    }
+
+    // Generate the .d.ts file content
+    let fileContent = `// Auto-generated entity names from the scene\n\n`
+
+    // Add a constant object with name keys for IDE autocompletion
+    fileContent += `\n/**\n * Object containing all entity names in the scene for autocomplete support.\n */\n`
+    fileContent += `export enum ${typeName} {\n`
+
+    for (const { original, valid } of finalNames) {
+      fileContent += `  ${valid} = "${original}",\n`
+    }
+
+    fileContent += `} \n`
+
+    // Write to file
+    await fs.writeFile(outputPath, Buffer.from(fileContent, 'utf-8'))
+  } catch (e) {
+    console.error('Fail to generate entity names types', e)
+  }
+}
diff --git a/packages/@dcl/playground-assets/etc/playground-assets.api.md b/packages/@dcl/playground-assets/etc/playground-assets.api.md
index 71eb0f7c4..87608b1ae 100644
--- a/packages/@dcl/playground-assets/etc/playground-assets.api.md
+++ b/packages/@dcl/playground-assets/etc/playground-assets.api.md
@@ -1247,8 +1247,9 @@ export interface IEngine {
     getComponent<T>(componentId: number | string): ComponentDefinition<T>;
     getComponentOrNull<T>(componentId: number | string): ComponentDefinition<T> | null;
     getEntitiesWith<T extends [ComponentDefinition<any>, ...ComponentDefinition<any>[]]>(...components: T): Iterable<[Entity, ...ReadonlyComponentSchema<T>]>;
+    getEntityByName<T = never, K = T>(value: K & (T extends never ? never : string)): Entity;
     // @alpha
-    getEntityOrNullByName(label: string): Entity | null;
+    getEntityOrNullByName<T = string>(label: T): Entity | null;
     getEntityState(entity: Entity): EntityState;
     // (undocumented)
     _id: number;
diff --git a/packages/@dcl/sdk-commands/package-lock.json b/packages/@dcl/sdk-commands/package-lock.json
index b67b608a0..516c4ad8a 100644
--- a/packages/@dcl/sdk-commands/package-lock.json
+++ b/packages/@dcl/sdk-commands/package-lock.json
@@ -65,7 +65,7 @@
     "../inspector": {
       "version": "0.1.0",
       "dependencies": {
-        "@dcl/asset-packs": "2.2.1",
+        "@dcl/asset-packs": "2.3.1",
         "ts-deepmerge": "^7.0.0"
       },
       "devDependencies": {
@@ -3141,7 +3141,7 @@
         "@babylonjs/inspector": "~6.18.0",
         "@babylonjs/loaders": "~6.18.0",
         "@babylonjs/materials": "~6.18.0",
-        "@dcl/asset-packs": "2.2.1",
+        "@dcl/asset-packs": "2.3.1",
         "@dcl/ecs": "file:../ecs",
         "@dcl/ecs-math": "2.1.0",
         "@dcl/mini-rpc": "^1.0.7",
diff --git a/test/ecs/components/Name.spec.ts b/test/ecs/components/Name.spec.ts
index 9c2b51965..e15763cdd 100644
--- a/test/ecs/components/Name.spec.ts
+++ b/test/ecs/components/Name.spec.ts
@@ -12,5 +12,8 @@ describe('Generated Raycast ProtoBuf', () => {
 
     expect(newEngine.getEntityOrNullByName('CASLA')).toBeDefined()
     expect(newEngine.getEntityOrNullByName('Boedo')).toBe(null)
+
+    expect(newEngine.getEntityByName<string>('CASLA')).toBeDefined()
+    expect(newEngine.getEntityByName<string>('Boedo')).toBe(null)
   })
 })
diff --git a/test/snapshots/development-bundles/static-scene.test.ts.crdt b/test/snapshots/development-bundles/static-scene.test.ts.crdt
index 127009c86..b35fb9607 100644
--- a/test/snapshots/development-bundles/static-scene.test.ts.crdt
+++ b/test/snapshots/development-bundles/static-scene.test.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=462.7k bytes
+SCENE_COMPILED_JS_SIZE_PROD=462.9k bytes
 THE BUNDLE HAS SOURCEMAPS
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/static-scene.test.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 54k
-  MALLOC_COUNT = 13727
+  MALLOC_COUNT = 13735
   ALIVE_OBJS_DELTA ~= 2.70k
 CALL onStart()
   main.crdt: PUT_COMPONENT e=0x200 c=1 t=0 data={"position":{"x":5.880000114440918,"y":2.7916901111602783,"z":7.380000114440918},"rotation":{"x":0,"y":0,"z":0,"w":1},"scale":{"x":1,"y":1,"z":1},"parent":0}
@@ -56,4 +56,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 3k
   MALLOC_COUNT = -5
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 1209.35k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1209.94k bytes
\ No newline at end of file
diff --git a/test/snapshots/development-bundles/testing-fw.test.ts.crdt b/test/snapshots/development-bundles/testing-fw.test.ts.crdt
index f697eaa9a..7ca765937 100644
--- a/test/snapshots/development-bundles/testing-fw.test.ts.crdt
+++ b/test/snapshots/development-bundles/testing-fw.test.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=463.2k bytes
+SCENE_COMPILED_JS_SIZE_PROD=463.4k bytes
 THE BUNDLE HAS SOURCEMAPS
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/testing-fw.test.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 63k
-  MALLOC_COUNT = 14248
+  MALLOC_COUNT = 14260
   ALIVE_OBJS_DELTA ~= 2.85k
 CALL onStart()
   LOG: ["Adding one to position.y=0"]
@@ -64,4 +64,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 5k
   MALLOC_COUNT = -40
   ALIVE_OBJS_DELTA ~= -0.01k
-  MEMORY_USAGE_COUNT ~= 1217.97k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1218.62k bytes
\ No newline at end of file
diff --git a/test/snapshots/development-bundles/two-way-crdt.test.ts.crdt b/test/snapshots/development-bundles/two-way-crdt.test.ts.crdt
index a76f7d5d1..b94dbbc99 100644
--- a/test/snapshots/development-bundles/two-way-crdt.test.ts.crdt
+++ b/test/snapshots/development-bundles/two-way-crdt.test.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=463.3k bytes
+SCENE_COMPILED_JS_SIZE_PROD=463.4k bytes
 THE BUNDLE HAS SOURCEMAPS
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/two-way-crdt.test.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 63k
-  MALLOC_COUNT = 14248
+  MALLOC_COUNT = 14260
   ALIVE_OBJS_DELTA ~= 2.85k
 CALL onStart()
   LOG: ["Adding one to position.y=0"]
@@ -64,4 +64,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 5k
   MALLOC_COUNT = -40
   ALIVE_OBJS_DELTA ~= -0.01k
-  MEMORY_USAGE_COUNT ~= 1217.97k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1218.63k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/append-value-crdt.ts.crdt b/test/snapshots/production-bundles/append-value-crdt.ts.crdt
index e15ab81b3..432f592c9 100644
--- a/test/snapshots/production-bundles/append-value-crdt.ts.crdt
+++ b/test/snapshots/production-bundles/append-value-crdt.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=203k bytes
+SCENE_COMPILED_JS_SIZE_PROD=203.1k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/append-value-crdt.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 65k
-  MALLOC_COUNT = 12773
+  MALLOC_COUNT = 12784
   ALIVE_OBJS_DELTA ~= 2.85k
 CALL onStart()
   Renderer: APPEND_VALUE e=0x200 c=1063 t=0 data={"button":0,"hit":{"position":{"x":1,"y":2,"z":3},"globalOrigin":{"x":1,"y":2,"z":3},"direction":{"x":1,"y":2,"z":3},"normalHit":{"x":1,"y":2,"z":3},"length":10,"meshName":"mesh","entityId":512},"state":1,"timestamp":1,"analog":5,"tickNumber":0}
@@ -55,4 +55,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 14k
   MALLOC_COUNT = 31
   ALIVE_OBJS_DELTA ~= 0.01k
-  MEMORY_USAGE_COUNT ~= 909.46k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 909.93k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/billboard.ts.crdt b/test/snapshots/production-bundles/billboard.ts.crdt
index 32a262943..265cbda72 100644
--- a/test/snapshots/production-bundles/billboard.ts.crdt
+++ b/test/snapshots/production-bundles/billboard.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=236k bytes
+SCENE_COMPILED_JS_SIZE_PROD=236.1k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/billboard.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 66k
-  MALLOC_COUNT = 14885
+  MALLOC_COUNT = 14892
   ALIVE_OBJS_DELTA ~= 3.25k
 CALL onStart()
   OPCODES ~= 0k
@@ -77,4 +77,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 10k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 1051.90k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1052.22k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/cube-deleted.ts.crdt b/test/snapshots/production-bundles/cube-deleted.ts.crdt
index 36d2e269e..0094f3c93 100644
--- a/test/snapshots/production-bundles/cube-deleted.ts.crdt
+++ b/test/snapshots/production-bundles/cube-deleted.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=199.2k bytes
+SCENE_COMPILED_JS_SIZE_PROD=199.3k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/cube-deleted.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 55k
-  MALLOC_COUNT = 11927
+  MALLOC_COUNT = 11935
   ALIVE_OBJS_DELTA ~= 2.63k
 CALL onStart()
   OPCODES ~= 0k
@@ -42,4 +42,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 5k
   MALLOC_COUNT = 1
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 872.22k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 872.62k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/cube.ts.crdt b/test/snapshots/production-bundles/cube.ts.crdt
index fb31d4413..42182f779 100644
--- a/test/snapshots/production-bundles/cube.ts.crdt
+++ b/test/snapshots/production-bundles/cube.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=199.1k bytes
+SCENE_COMPILED_JS_SIZE_PROD=199.2k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/cube.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 55k
-  MALLOC_COUNT = 11900
+  MALLOC_COUNT = 11908
   ALIVE_OBJS_DELTA ~= 2.62k
 CALL onStart()
   OPCODES ~= 0k
@@ -32,4 +32,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 1k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 862.13k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 862.53k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/cubes.ts.crdt b/test/snapshots/production-bundles/cubes.ts.crdt
index d02779176..cb25a3f33 100644
--- a/test/snapshots/production-bundles/cubes.ts.crdt
+++ b/test/snapshots/production-bundles/cubes.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=236.4k bytes
+SCENE_COMPILED_JS_SIZE_PROD=236.5k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/cubes.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 105k
-  MALLOC_COUNT = 18228
+  MALLOC_COUNT = 18235
   ALIVE_OBJS_DELTA ~= 4.53k
 CALL onStart()
   OPCODES ~= 0k
@@ -1652,4 +1652,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 693k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 1187.85k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1188.17k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/pointer-events.ts.crdt b/test/snapshots/production-bundles/pointer-events.ts.crdt
index 64b8b102d..dd0c8fb7e 100644
--- a/test/snapshots/production-bundles/pointer-events.ts.crdt
+++ b/test/snapshots/production-bundles/pointer-events.ts.crdt
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/pointer-events.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 56k
-  MALLOC_COUNT = 12186
+  MALLOC_COUNT = 12195
   ALIVE_OBJS_DELTA ~= 2.70k
 CALL onStart()
   OPCODES ~= 0k
@@ -43,4 +43,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 1k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 879.86k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 880.28k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/schema-components.ts.crdt b/test/snapshots/production-bundles/schema-components.ts.crdt
index 0203ddf05..3089f3a98 100644
--- a/test/snapshots/production-bundles/schema-components.ts.crdt
+++ b/test/snapshots/production-bundles/schema-components.ts.crdt
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/schema-components.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 59k
-  MALLOC_COUNT = 12027
+  MALLOC_COUNT = 12035
   ALIVE_OBJS_DELTA ~= 2.65k
 CALL onStart()
   OPCODES ~= 0k
@@ -31,4 +31,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 1k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 864.55k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 864.95k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/ui.ts.crdt b/test/snapshots/production-bundles/ui.ts.crdt
index ad2f73481..4fa608968 100644
--- a/test/snapshots/production-bundles/ui.ts.crdt
+++ b/test/snapshots/production-bundles/ui.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=357.9k bytes
+SCENE_COMPILED_JS_SIZE_PROD=358k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/ui.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 65k
-  MALLOC_COUNT = 19552
+  MALLOC_COUNT = 19559
   ALIVE_OBJS_DELTA ~= 3.88k
 CALL onStart()
   OPCODES ~= 0k
@@ -65,4 +65,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 65k
   MALLOC_COUNT = 0
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 1708.08k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 1708.45k bytes
\ No newline at end of file
diff --git a/test/snapshots/production-bundles/with-main-function.ts.crdt b/test/snapshots/production-bundles/with-main-function.ts.crdt
index fa823a89a..265a7360f 100644
--- a/test/snapshots/production-bundles/with-main-function.ts.crdt
+++ b/test/snapshots/production-bundles/with-main-function.ts.crdt
@@ -1,4 +1,4 @@
-SCENE_COMPILED_JS_SIZE_PROD=199.8k bytes
+SCENE_COMPILED_JS_SIZE_PROD=199.9k bytes
 (start empty vm 0.21.0-3680274614.commit-1808aa1)
   OPCODES ~= 0k
   MALLOC_COUNT = 1005
@@ -9,8 +9,8 @@ EVAL test/snapshots/production-bundles/with-main-function.js
   REQUIRE: ~system/EngineApi
   REQUIRE: ~system/EngineApi
   OPCODES ~= 55k
-  MALLOC_COUNT = 12056
-  ALIVE_OBJS_DELTA ~= 2.65k
+  MALLOC_COUNT = 12064
+  ALIVE_OBJS_DELTA ~= 2.66k
 CALL onStart()
   OPCODES ~= 0k
   MALLOC_COUNT = 6
@@ -37,4 +37,4 @@ CALL onUpdate(0.1)
   OPCODES ~= 3k
   MALLOC_COUNT = 5
   ALIVE_OBJS_DELTA ~= 0.00k
-  MEMORY_USAGE_COUNT ~= 877.68k bytes
\ No newline at end of file
+  MEMORY_USAGE_COUNT ~= 878.07k bytes
\ No newline at end of file


Reviews:
user: login: nicoecheza
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-22T18:50:32Z
user: login: gonpombo8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T18:53:58Z
user: login: gonpombo8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T19:02:05Z
user: login: nicoecheza
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T19:04:01Z

Review comments:
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
+
+    // Generate the .d.ts file content
+    let fileContent = `// Auto-generated entity names from the scene\n\n`
+    fileContent += `/**\n * Union type containing all entity names in the scene.\n * Use for type-safe references to entities by name.\n */\n`
+    fileContent += `export type I${typeName} = `
+
+    if (uniqueNames.length === 0) {
+      fileContent += `never\n`
+    } else {
+      fileContent += `\n`
+      for (const key in uniqueNames) {
+        const isLast = Number(key) === uniqueNames.length - 1
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: nicoecheza
body: is this casting to `Number` ok? Because it's casting strings 🤔 
created_at: 2025-04-22T18:40:43Z
author_association: MEMBER
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
+
+    // Generate the .d.ts file content
+    let fileContent = `// Auto-generated entity names from the scene\n\n`
+    fileContent += `/**\n * Union type containing all entity names in the scene.\n * Use for type-safe references to entities by name.\n */\n`
+    fileContent += `export type I${typeName} = `
+
+    if (uniqueNames.length === 0) {
+      fileContent += `never\n`
+    } else {
+      fileContent += `\n`
+      for (const key in uniqueNames) {
+        const isLast = Number(key) === uniqueNames.length - 1
+        fileContent += `  | "${uniqueNames[key]}"${isLast ? '\n' : '\n'}`
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: nicoecheza
body: this one is the same for both cases: `${isLast ? '\n' : '\n'}`
created_at: 2025-04-22T18:41:07Z
author_association: MEMBER
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
+
+    // Generate the .d.ts file content
+    let fileContent = `// Auto-generated entity names from the scene\n\n`
+    fileContent += `/**\n * Union type containing all entity names in the scene.\n * Use for type-safe references to entities by name.\n */\n`
+    fileContent += `export type I${typeName} = `
+
+    if (uniqueNames.length === 0) {
+      fileContent += `never\n`
+    } else {
+      fileContent += `\n`
+      for (const key in uniqueNames) {
+        const isLast = Number(key) === uniqueNames.length - 1
+        fileContent += `  | "${uniqueNames[key]}"${isLast ? '\n' : '\n'}`
+      }
+    }
+
+    // Add a constant object with name keys for IDE autocompletion
+    fileContent += `\n/**\n * Object containing all entity names in the scene for autocomplete support.\n */\n`
+    fileContent += `export const ${typeName} = {\n`
+
+    for (const { original, valid } of validNames) {
+      fileContent += `  ${valid}: "${original}",\n`
+    }
+
+    fileContent += `} as const\n`
+
+    // Write to file
+    await fs.writeFile(outputPath, Buffer.from(fileContent, 'utf-8'))
+  } catch (e) {
+    console.error('Faield to generate entity names types', e)
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: nicoecheza
body: `Failed`
created_at: 2025-04-22T18:41:31Z
author_association: MEMBER
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: nicoecheza
body: if the user creates two entities with names like: 'test-1' and 'test_1', the latter will be used as valid for both cases below on line 203. I think this may produce a bug?

tested it here: https://tsplay.dev/w2yyxW

created_at: 2025-04-22T18:47:33Z
author_association: MEMBER
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
+
+    // Generate the .d.ts file content
+    let fileContent = `// Auto-generated entity names from the scene\n\n`
+    fileContent += `/**\n * Union type containing all entity names in the scene.\n * Use for type-safe references to entities by name.\n */\n`
+    fileContent += `export type I${typeName} = `
+
+    if (uniqueNames.length === 0) {
+      fileContent += `never\n`
+    } else {
+      fileContent += `\n`
+      for (const key in uniqueNames) {
+        const isLast = Number(key) === uniqueNames.length - 1
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: gonpombo8
body: its a ` for key in ` loop in an array, so its returning the index key
created_at: 2025-04-22T18:53:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,3 +130,85 @@ export function dumpEngineToCrdtCommands(engine: IEngine): Uint8Array {
 
   return crdtBuffer.toBinary()
 }
+
+/**
+ * Generate a TypeScript declaration file with a string literal union type containing all entity names in the scene.
+ * This allows for type-safe references to entities by name in scene scripts.
+ *
+ * @param engine The ECS engine instance
+ * @param outputPath The path where the .d.ts file should be written
+ * @param typeName The name for the generated type (default: "SceneEntityNames")
+ * @param fs FileSystem interface for writing the file
+ * @returns Promise that resolves when the file has been written
+ */
+export async function generateEntityNamesType(
+  engine: IEngine,
+  outputPath: string = 'scene-entity-names.d.ts',
+  typeName: string = 'SceneEntityNames',
+  fs: FileSystemInterface
+): Promise<void> {
+  try {
+    // Find the Name component definition
+
+    const NameComponent: typeof Name = engine.getComponentOrNull(Name.componentId) as typeof Name
+
+    if (!NameComponent) {
+      throw new Error('Name component not found in engine')
+    }
+
+    // Collect all names from entities
+    const names: string[] = []
+    for (const [_, nameValue] of engine.getEntitiesWith(NameComponent)) {
+      if (nameValue.value) {
+        names.push(nameValue.value)
+      }
+    }
+
+    // Sort names for consistency
+    names.sort()
+
+    // Remove duplicates
+    const uniqueNames = Array.from(new Set(names))
+
+    // Generate valid TypeScript identifiers
+    const validNames = uniqueNames.map((name) => {
+      // Create a valid TypeScript identifier
+      const validName = name
+        .replace(/[^a-zA-Z0-9_]/g, '_') // Replace non-alphanumeric chars with underscore
+        .replace(/^[0-9]/, '_$&') // Prepend underscore if starts with number
+
+      // Ensure no duplicate keys after validation
+      return { original: name, valid: validName }
+    })
path: packages/@dcl/inspector/src/lib/data-layer/host/utils/engine-to-composite.ts
user: login: gonpombo8
body: good catch, solved
created_at: 2025-04-22T19:02:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-04-22T18:23:48Z
body: ## Deploying js-sdk-toolchain with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>68542c1</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://db29fa42.js-sdk-toolchain.pages.dev'>https://db29fa42.js-sdk-toolchain.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://feat-generate-name-type-safe.js-sdk-toolchain.pages.dev'>https://feat-generate-name-type-safe.js-sdk-toolchain.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/38a078bb7e3b8a9fb61d6501f2a1be04/pages/view/js-sdk-toolchain/db29fa42-36ea-4393-aa24-cd691c821a05)

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-22T18:24:16Z
body: # Test this pull request
- The `@dcl/sdk` package can be tested in scenes by running
   ```bash
   npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/generate-name-type-safe/dcl-sdk-7.8.5-14603846583.commit-63e7a5a.tgz"
   ```

- To test with `npx init`
   ```bash
   export SDK_COMMANDS="https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/generate-name-type-safe/dcl-sdk-commands-7.8.5-14603846583.commit-63e7a5a.tgz"
   npx $SDK_COMMANDS init
   ```

- The `@dcl/inspector` package can be tested by visiting [this url](https://feat-generate-name-type-safe.js-sdk-toolchain.pages.dev/inspector)
  - Or by installing it via NPM
   ```bash
   npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/generate-name-type-safe/@dcl/inspector/dcl-inspector-7.8.5-14603846583.commit-63e7a5a.tgz"
   ```

- The `/changerealm` command to test [test in-world](https://play.decentraland.zone/?realm=https://sdk-team-cdn.decentraland.org/ipfs/feat/generate-name-type-safe-e2e)
   ```
   /changerealm https://sdk-team-cdn.decentraland.org/ipfs/feat/generate-name-type-safe-e2e
   ```

- You can preview this build entering:
  https://playground.decentraland.org/?sdk-branch=feat/generate-name-type-safe
author_association: CONTRIBUTOR