@directivegames/genesys.sdk 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +60 -0
  2. package/dist/src/asset-pack/eslint.config.js +43 -0
  3. package/dist/src/asset-pack/scripts/postinstall.js +64 -0
  4. package/dist/src/asset-pack/src/index.js +1 -0
  5. package/dist/src/core/cli.js +306 -0
  6. package/dist/src/core/common.js +324 -0
  7. package/dist/src/core/index.js +6 -0
  8. package/dist/src/core/tools/build-project.js +450 -0
  9. package/dist/src/core/tools/index.js +2 -0
  10. package/dist/src/core/tools/new-asset-pack.js +150 -0
  11. package/dist/src/core/tools/new-project.js +292 -0
  12. package/dist/src/core/types.js +1 -0
  13. package/dist/src/dependencies.js +82 -0
  14. package/dist/src/electron/IpcSerializableError.js +38 -0
  15. package/dist/src/electron/api.js +7 -0
  16. package/dist/src/electron/backend/actions.js +56 -0
  17. package/dist/src/electron/backend/handler.js +441 -0
  18. package/dist/src/electron/backend/logging.js +41 -0
  19. package/dist/src/electron/backend/main.js +315 -0
  20. package/dist/src/electron/backend/menu.js +208 -0
  21. package/dist/src/electron/backend/state.js +201 -0
  22. package/dist/src/electron/backend/tools/const.js +9 -0
  23. package/dist/src/electron/backend/tools/file-server.js +383 -0
  24. package/dist/src/electron/backend/tools/open-project.js +261 -0
  25. package/dist/src/electron/backend/window.js +161 -0
  26. package/dist/src/templates/eslint.config.js +43 -0
  27. package/dist/src/templates/scripts/genesys/build-project.js +42 -0
  28. package/dist/src/templates/scripts/genesys/calc-bounding-box.js +205 -0
  29. package/dist/src/templates/scripts/genesys/common.js +36 -0
  30. package/dist/src/templates/scripts/genesys/const.js +9 -0
  31. package/dist/src/templates/scripts/genesys/dev/dump-default-scene.js +8 -0
  32. package/dist/src/templates/scripts/genesys/dev/generate-manifest.js +116 -0
  33. package/dist/src/templates/scripts/genesys/dev/launcher.js +39 -0
  34. package/dist/src/templates/scripts/genesys/dev/storage-provider.js +188 -0
  35. package/dist/src/templates/scripts/genesys/dev/update-template-scenes.js +67 -0
  36. package/dist/src/templates/scripts/genesys/doc-server.js +12 -0
  37. package/dist/src/templates/scripts/genesys/genesys-mcp.js +413 -0
  38. package/dist/src/templates/scripts/genesys/mcp/doc-tools.js +70 -0
  39. package/dist/src/templates/scripts/genesys/mcp/editor-functions.js +123 -0
  40. package/dist/src/templates/scripts/genesys/mcp/editor-tools.js +51 -0
  41. package/dist/src/templates/scripts/genesys/mcp/get-scene-state.js +26 -0
  42. package/dist/src/templates/scripts/genesys/mcp/run-subprocess.js +23 -0
  43. package/dist/src/templates/scripts/genesys/mcp/search-actors.js +703 -0
  44. package/dist/src/templates/scripts/genesys/mcp/search-assets.js +296 -0
  45. package/dist/src/templates/scripts/genesys/mcp/utils.js +234 -0
  46. package/dist/src/templates/scripts/genesys/misc.js +32 -0
  47. package/dist/src/templates/scripts/genesys/mock.js +5 -0
  48. package/dist/src/templates/scripts/genesys/place-actors.js +112 -0
  49. package/dist/src/templates/scripts/genesys/post-install.js +25 -0
  50. package/dist/src/templates/scripts/genesys/remove-engine-comments.js +113 -0
  51. package/dist/src/templates/scripts/genesys/storageProvider.js +146 -0
  52. package/dist/src/templates/scripts/genesys/validate-prefabs.js +115 -0
  53. package/dist/src/templates/src/index.js +20 -0
  54. package/dist/src/templates/src/templates/firstPerson/src/auto-imports.js +1 -0
  55. package/dist/src/templates/src/templates/firstPerson/src/game.js +30 -0
  56. package/dist/src/templates/src/templates/firstPerson/src/player.js +60 -0
  57. package/dist/src/templates/src/templates/fps/src/auto-imports.js +1 -0
  58. package/dist/src/templates/src/templates/fps/src/game.js +30 -0
  59. package/dist/src/templates/src/templates/fps/src/player.js +64 -0
  60. package/dist/src/templates/src/templates/fps/src/weapon.js +62 -0
  61. package/dist/src/templates/src/templates/freeCamera/src/auto-imports.js +1 -0
  62. package/dist/src/templates/src/templates/freeCamera/src/game.js +30 -0
  63. package/dist/src/templates/src/templates/freeCamera/src/player.js +43 -0
  64. package/dist/src/templates/src/templates/sideScroller/src/auto-imports.js +1 -0
  65. package/dist/src/templates/src/templates/sideScroller/src/const.js +43 -0
  66. package/dist/src/templates/src/templates/sideScroller/src/game.js +103 -0
  67. package/dist/src/templates/src/templates/sideScroller/src/level-generator.js +249 -0
  68. package/dist/src/templates/src/templates/sideScroller/src/player.js +105 -0
  69. package/dist/src/templates/src/templates/thirdPerson/src/auto-imports.js +1 -0
  70. package/dist/src/templates/src/templates/thirdPerson/src/game.js +30 -0
  71. package/dist/src/templates/src/templates/thirdPerson/src/player.js +63 -0
  72. package/dist/src/templates/src/templates/vehicle/src/auto-imports.js +1 -0
  73. package/dist/src/templates/src/templates/vehicle/src/base-vehicle.js +122 -0
  74. package/dist/src/templates/src/templates/vehicle/src/game.js +33 -0
  75. package/dist/src/templates/src/templates/vehicle/src/mesh-vehicle.js +189 -0
  76. package/dist/src/templates/src/templates/vehicle/src/player.js +102 -0
  77. package/dist/src/templates/src/templates/vehicle/src/primitive-vehicle.js +259 -0
  78. package/dist/src/templates/src/templates/vehicle/src/ui-hints.js +100 -0
  79. package/dist/src/templates/src/templates/vr-game/src/auto-imports.js +1 -0
  80. package/dist/src/templates/src/templates/vr-game/src/game.js +55 -0
  81. package/dist/src/templates/src/templates/vr-game/src/sample-vr-actor.js +29 -0
  82. package/dist/src/templates/vite.config.js +46 -0
  83. package/package.json +176 -0
  84. package/scripts/post-install.ts +143 -0
  85. package/src/asset-pack/.gitattributes +89 -0
  86. package/src/asset-pack/eslint.config.js +45 -0
  87. package/src/asset-pack/gitignore +11 -0
  88. package/src/asset-pack/scripts/postinstall.ts +81 -0
  89. package/src/asset-pack/src/index.ts +0 -0
  90. package/src/asset-pack/tsconfig.json +34 -0
  91. package/src/templates/.cursor/mcp.json +20 -0
  92. package/src/templates/.cursorignore +2 -0
  93. package/src/templates/.gitattributes +89 -0
  94. package/src/templates/.vscode/settings.json +6 -0
  95. package/src/templates/AGENTS.md +86 -0
  96. package/src/templates/CLAUDE.md +1 -0
  97. package/src/templates/README.md +24 -0
  98. package/src/templates/eslint.config.js +45 -0
  99. package/src/templates/gitignore +11 -0
  100. package/src/templates/index.html +34 -0
  101. package/src/templates/pnpm-lock.yaml +3676 -0
  102. package/src/templates/scripts/genesys/build-project.ts +51 -0
  103. package/src/templates/scripts/genesys/calc-bounding-box.ts +272 -0
  104. package/src/templates/scripts/genesys/common.ts +46 -0
  105. package/src/templates/scripts/genesys/const.ts +9 -0
  106. package/src/templates/scripts/genesys/dev/dump-default-scene.ts +11 -0
  107. package/src/templates/scripts/genesys/dev/generate-manifest.ts +146 -0
  108. package/src/templates/scripts/genesys/dev/launcher.ts +46 -0
  109. package/src/templates/scripts/genesys/dev/storage-provider.ts +229 -0
  110. package/src/templates/scripts/genesys/dev/update-template-scenes.ts +84 -0
  111. package/src/templates/scripts/genesys/doc-server.ts +16 -0
  112. package/src/templates/scripts/genesys/genesys-mcp.ts +526 -0
  113. package/src/templates/scripts/genesys/mcp/doc-tools.ts +86 -0
  114. package/src/templates/scripts/genesys/mcp/editor-functions.ts +151 -0
  115. package/src/templates/scripts/genesys/mcp/editor-tools.ts +73 -0
  116. package/src/templates/scripts/genesys/mcp/get-scene-state.ts +35 -0
  117. package/src/templates/scripts/genesys/mcp/run-subprocess.ts +30 -0
  118. package/src/templates/scripts/genesys/mcp/search-actors.ts +858 -0
  119. package/src/templates/scripts/genesys/mcp/search-assets.ts +380 -0
  120. package/src/templates/scripts/genesys/mcp/utils.ts +281 -0
  121. package/src/templates/scripts/genesys/misc.ts +42 -0
  122. package/src/templates/scripts/genesys/mock.ts +6 -0
  123. package/src/templates/scripts/genesys/place-actors.ts +179 -0
  124. package/src/templates/scripts/genesys/post-install.ts +30 -0
  125. package/src/templates/scripts/genesys/prefab.schema.json +85 -0
  126. package/src/templates/scripts/genesys/remove-engine-comments.ts +135 -0
  127. package/src/templates/scripts/genesys/run-mcp-inspector.bat +5 -0
  128. package/src/templates/scripts/genesys/storageProvider.ts +182 -0
  129. package/src/templates/scripts/genesys/validate-prefabs.ts +138 -0
  130. package/src/templates/src/index.ts +22 -0
  131. package/src/templates/src/templates/firstPerson/assets/default.genesys-scene +166 -0
  132. package/src/templates/src/templates/firstPerson/src/auto-imports.ts +0 -0
  133. package/src/templates/src/templates/firstPerson/src/game.ts +39 -0
  134. package/src/templates/src/templates/firstPerson/src/player.ts +63 -0
  135. package/src/templates/src/templates/fps/assets/default.genesys-scene +9460 -0
  136. package/src/templates/src/templates/fps/assets/models/SM_Beam_400.glb +0 -0
  137. package/src/templates/src/templates/fps/assets/models/SM_ChamferCube.glb +0 -0
  138. package/src/templates/src/templates/fps/assets/models/SM_Floor_Thick_400x400.glb +0 -0
  139. package/src/templates/src/templates/fps/assets/models/SM_Floor_Thick_400x400_Orange.glb +0 -0
  140. package/src/templates/src/templates/fps/assets/models/SM_Floor_Thin_400x400.glb +0 -0
  141. package/src/templates/src/templates/fps/assets/models/SM_Floor_Thin_400x400_Orange.glb +0 -0
  142. package/src/templates/src/templates/fps/assets/models/SM_Ramp_400x400.glb +0 -0
  143. package/src/templates/src/templates/fps/assets/models/SM_Rifle.glb +0 -0
  144. package/src/templates/src/templates/fps/assets/models/SM_Wall_Thin_400x200.glb +0 -0
  145. package/src/templates/src/templates/fps/assets/models/SM_Wall_Thin_400x200_Orange.glb +0 -0
  146. package/src/templates/src/templates/fps/assets/models/SM_Wall_Thin_400x400.glb +0 -0
  147. package/src/templates/src/templates/fps/assets/models/SM_Wall_Thin_400x400_Orange.glb +0 -0
  148. package/src/templates/src/templates/fps/src/auto-imports.ts +0 -0
  149. package/src/templates/src/templates/fps/src/game.ts +39 -0
  150. package/src/templates/src/templates/fps/src/player.ts +69 -0
  151. package/src/templates/src/templates/fps/src/weapon.ts +54 -0
  152. package/src/templates/src/templates/freeCamera/assets/default.genesys-scene +166 -0
  153. package/src/templates/src/templates/freeCamera/src/auto-imports.ts +0 -0
  154. package/src/templates/src/templates/freeCamera/src/game.ts +39 -0
  155. package/src/templates/src/templates/freeCamera/src/player.ts +45 -0
  156. package/src/templates/src/templates/sideScroller/assets/default.genesys-scene +122 -0
  157. package/src/templates/src/templates/sideScroller/src/auto-imports.ts +0 -0
  158. package/src/templates/src/templates/sideScroller/src/const.ts +46 -0
  159. package/src/templates/src/templates/sideScroller/src/game.ts +122 -0
  160. package/src/templates/src/templates/sideScroller/src/level-generator.ts +361 -0
  161. package/src/templates/src/templates/sideScroller/src/player.ts +125 -0
  162. package/src/templates/src/templates/thirdPerson/assets/default.genesys-scene +166 -0
  163. package/src/templates/src/templates/thirdPerson/src/auto-imports.ts +0 -0
  164. package/src/templates/src/templates/thirdPerson/src/game.ts +39 -0
  165. package/src/templates/src/templates/thirdPerson/src/player.ts +61 -0
  166. package/src/templates/src/templates/vehicle/assets/default.genesys-scene +226 -0
  167. package/src/templates/src/templates/vehicle/assets/models/cyberTruck/chassis.glb +0 -0
  168. package/src/templates/src/templates/vehicle/assets/models/cyberTruck/wheel.glb +0 -0
  169. package/src/templates/src/templates/vehicle/src/auto-imports.ts +0 -0
  170. package/src/templates/src/templates/vehicle/src/base-vehicle.ts +145 -0
  171. package/src/templates/src/templates/vehicle/src/game.ts +43 -0
  172. package/src/templates/src/templates/vehicle/src/mesh-vehicle.ts +191 -0
  173. package/src/templates/src/templates/vehicle/src/player.ts +109 -0
  174. package/src/templates/src/templates/vehicle/src/primitive-vehicle.ts +266 -0
  175. package/src/templates/src/templates/vehicle/src/ui-hints.ts +101 -0
  176. package/src/templates/src/templates/vr-game/assets/default.genesys-scene +247 -0
  177. package/src/templates/src/templates/vr-game/src/auto-imports.ts +1 -0
  178. package/src/templates/src/templates/vr-game/src/game.ts +66 -0
  179. package/src/templates/src/templates/vr-game/src/sample-vr-actor.ts +26 -0
  180. package/src/templates/tsconfig.json +35 -0
  181. package/src/templates/vite.config.ts +52 -0
@@ -0,0 +1,703 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as ENGINE from 'genesys.js';
4
+ import { ModuleKind, ModuleResolutionKind, Project, ScriptTarget } from 'ts-morph';
5
+ import { z } from 'zod';
6
+ import { zodToJsonSchema } from 'zod-to-json-schema';
7
+ import { StorageProvider } from '../storageProvider.js';
8
+ import { isSubclass } from './utils.js';
9
+ const PropertyInfoSchema = z.object({
10
+ type: z.string().describe('Type of the property, e.g. "string", "number", "THREE.Vector3", "object"'),
11
+ description: z.string().optional().describe('Description of the property, extracted from JSDoc if available'),
12
+ canPopulateFromJson: z.boolean().optional().describe('Whether this property can be populated from JSON values'),
13
+ optional: z.boolean().describe('Whether the property is optional or not'),
14
+ properties: z.lazy(() => z.record(PropertyInfoSchema)).optional().describe('If the property is an object, this contains its properties recursively'),
15
+ }).describe('Represents information about a property within a parameter');
16
+ const ParameterInfoSchema = z.object({
17
+ paramName: z.string().describe('Name of the constructor parameter'),
18
+ type: z.string().describe('Type of the parameter, e.g. "string", "number", "THREE.Vector3", "object"'),
19
+ description: z.string().optional().describe('Description of the parameter, extracted from JSDoc if available'),
20
+ canPopulateFromJson: z.boolean().optional().describe('Whether this parameter can be populated from JSON values'),
21
+ properties: z.record(PropertyInfoSchema).optional().describe('If the parameter is an object, this contains its properties recursively'),
22
+ optional: z.boolean().describe('Whether the parameter is optional or not'),
23
+ }).describe('Represents information about a constructor parameter');
24
+ const ConstructorParametersSchema = z.array(ParameterInfoSchema).describe('Constructor parameters as an array preserving order');
25
+ const ActorInfoSchema = z.object({
26
+ className: z.string().describe('Fully qualified name of the class including ENGINE. or GAME. prefix'),
27
+ filePath: z.string().describe('Path to the source file containing this class'),
28
+ baseClasses: z.array(z.string()).describe('Array of parent class names this actor extends from'),
29
+ constructorParams: ConstructorParametersSchema.optional().describe('Array of constructor parameters in declaration order'),
30
+ canPopulateFromJson: z.boolean().optional().describe('Whether this actor can be instantiated purely from JSON data (all required parameters are JSON-serializable)'),
31
+ description: z.string().optional().describe('Human-readable description of the actor class and its purpose')
32
+ }).describe('Complete information about an actor class');
33
+ export const ThreeVector3Schema = z.object({
34
+ type: z.literal('THREE.Vector3').describe('The type of the object, must be "THREE.Vector3"'),
35
+ x: z.number().describe('The x component of the vector'),
36
+ y: z.number().describe('The y component of the vector'),
37
+ z: z.number().describe('The z component of the vector'),
38
+ }).describe('Represents a THREE.Vector3 object');
39
+ export const ThreeEulerSchema = z.object({
40
+ type: z.literal('THREE.Euler').describe('The type of the object, must be "THREE.Euler"'),
41
+ x: z.number().describe('The x component of the euler, pitch, in radians'),
42
+ y: z.number().describe('The y component of the euler, yaw, in radians'),
43
+ z: z.number().describe('The z component of the euler, roll, in radians'),
44
+ }).describe('Represents a THREE.Euler object');
45
+ /**
46
+ * Creates metadata description for the search results by recursively extracting descriptions from Zod schemas
47
+ */
48
+ function createMetadataDescription() {
49
+ function extractSchemaDescriptions(schema) {
50
+ // Handle missing or invalid schema
51
+ if (!schema)
52
+ return {};
53
+ const result = {};
54
+ // Add schema's own description if present
55
+ if (schema.description) {
56
+ result.description = schema.description;
57
+ }
58
+ // Handle object with properties
59
+ if (schema.type === 'object' && schema.properties) {
60
+ result.fields = {};
61
+ for (const [key, prop] of Object.entries(schema.properties)) {
62
+ result.fields[key] = extractSchemaDescriptions(prop);
63
+ }
64
+ }
65
+ // Handle arrays
66
+ if (schema.type === 'array' && schema.items) {
67
+ const items = extractSchemaDescriptions(schema.items);
68
+ if (items && Object.keys(items).length > 0) {
69
+ result.items = items;
70
+ }
71
+ }
72
+ // Handle references
73
+ if (schema.$ref && schema.$ref.startsWith('#/definitions/')) {
74
+ const refName = schema.$ref.replace('#/definitions/', '');
75
+ const refSchema = definitions[refName];
76
+ if (refSchema) {
77
+ Object.assign(result, extractSchemaDescriptions(refSchema));
78
+ }
79
+ }
80
+ // Handle anyOf/oneOf (union types)
81
+ if (schema.anyOf ?? schema.oneOf) {
82
+ const variants = schema.anyOf ?? schema.oneOf;
83
+ result.variants = variants.map((variant) => extractSchemaDescriptions(variant));
84
+ }
85
+ return result;
86
+ }
87
+ // Convert Zod schema to JSON schema
88
+ const actorInfoSchema = zodToJsonSchema(ActorInfoSchema, 'ActorInfoSchema');
89
+ // Get all definitions from the schema
90
+ const definitions = actorInfoSchema.definitions ?? {};
91
+ const result = extractSchemaDescriptions(actorInfoSchema).fields;
92
+ const vector3Schema = zodToJsonSchema(ThreeVector3Schema, 'ThreeVector3Schema');
93
+ const eulerSchema = zodToJsonSchema(ThreeEulerSchema, 'ThreeEulerSchema');
94
+ const vector3Example = {
95
+ type: 'THREE.Vector3',
96
+ x: 1,
97
+ y: 2,
98
+ z: 3,
99
+ };
100
+ const eulerExample = {
101
+ type: 'THREE.Euler',
102
+ x: 0,
103
+ y: 1.57,
104
+ z: -3.14,
105
+ };
106
+ result.specialTypes = {
107
+ description: 'Here shows how some special types should be populated from JSON values',
108
+ 'THREE.Vector3': { ...extractSchemaDescriptions(vector3Schema.definitions?.ThreeVector3Schema ?? {}), example: vector3Example },
109
+ 'THREE.Euler': { ...extractSchemaDescriptions(eulerSchema.definitions?.ThreeEulerSchema ?? {}), example: eulerExample },
110
+ };
111
+ return result;
112
+ }
113
+ /**
114
+ * Creates a ts-morph project with proper TypeScript configuration
115
+ */
116
+ function createTsMorphProject(storageProvider) {
117
+ try {
118
+ // Try to use the project's tsconfig.json
119
+ const tsconfigPath = storageProvider.getFullPath('@project/tsconfig.json');
120
+ const project = new Project({
121
+ tsConfigFilePath: fs.existsSync(tsconfigPath) ? tsconfigPath : undefined,
122
+ useInMemoryFileSystem: false,
123
+ skipAddingFilesFromTsConfig: true,
124
+ });
125
+ console.log('✅ ts-morph project created successfully');
126
+ return project;
127
+ }
128
+ catch (error) {
129
+ console.warn('⚠️ Failed to create ts-morph project with tsconfig, using default configuration');
130
+ console.warn('Error:', error instanceof Error ? error.message : String(error));
131
+ // Fallback to basic configuration
132
+ return new Project({
133
+ useInMemoryFileSystem: false,
134
+ compilerOptions: {
135
+ target: ScriptTarget.ES2020,
136
+ module: ModuleKind.ESNext,
137
+ moduleResolution: ModuleResolutionKind.NodeJs,
138
+ strict: true,
139
+ esModuleInterop: true,
140
+ skipLibCheck: true,
141
+ forceConsistentCasingInFileNames: true,
142
+ }
143
+ });
144
+ }
145
+ }
146
+ /**
147
+ * Extracts enum values from a union type
148
+ */
149
+ function extractEnumValues(type) {
150
+ try {
151
+ if (type.isUnion()) {
152
+ const enumValues = [];
153
+ const unionTypes = type.getUnionTypes();
154
+ for (const unionType of unionTypes) {
155
+ if (unionType.isStringLiteral()) {
156
+ const literalValue = unionType.getLiteralValue();
157
+ if (typeof literalValue === 'string') {
158
+ enumValues.push(literalValue);
159
+ }
160
+ }
161
+ else if (unionType.isNumberLiteral()) {
162
+ const literalValue = unionType.getLiteralValue();
163
+ if (typeof literalValue === 'number') {
164
+ enumValues.push(literalValue.toString());
165
+ }
166
+ }
167
+ }
168
+ return enumValues;
169
+ }
170
+ return [];
171
+ }
172
+ catch (error) {
173
+ return [];
174
+ }
175
+ }
176
+ /**
177
+ * Extracts properties from an object type using ts-morph Type analysis
178
+ */
179
+ function extractObjectProperties(type, visited = new Set()) {
180
+ try {
181
+ const properties = {};
182
+ const typeString = type.getText();
183
+ // Prevent infinite recursion
184
+ if (visited.has(typeString)) {
185
+ return {};
186
+ }
187
+ visited.add(typeString);
188
+ // Handle union types - extract properties from the non-undefined part
189
+ if (type.isUnion()) {
190
+ const unionTypes = type.getUnionTypes();
191
+ for (const unionType of unionTypes) {
192
+ // Skip undefined types
193
+ if (unionType.isUndefined()) {
194
+ continue;
195
+ }
196
+ // If we find an object type in the union, extract its properties
197
+ const unionProperties = unionType.getApparentProperties();
198
+ if (unionProperties && unionProperties.length > 0) {
199
+ return extractObjectProperties(unionType, visited);
200
+ }
201
+ }
202
+ }
203
+ // Get all properties of the type (includes inherited properties)
204
+ const typeProperties = type.getApparentProperties();
205
+ for (const prop of typeProperties) {
206
+ const propName = prop.getName();
207
+ // Skip internal/private properties
208
+ if (propName.startsWith('_') || propName.startsWith('__')) {
209
+ continue;
210
+ }
211
+ const propDeclaration = prop.getValueDeclaration();
212
+ if (!propDeclaration)
213
+ continue;
214
+ // Get property type
215
+ const propType = prop.getTypeAtLocation(propDeclaration);
216
+ const propTypeText = propType.getText(propDeclaration);
217
+ // Try to extract JSDoc description
218
+ let description;
219
+ if (propDeclaration && 'getJsDocs' in propDeclaration) {
220
+ const jsDocs = propDeclaration.getJsDocs();
221
+ if (jsDocs.length > 0) {
222
+ description = jsDocs[0].getDescription();
223
+ }
224
+ }
225
+ // Check if this is an enum/union type
226
+ const enumValues = extractEnumValues(propType);
227
+ let finalType = normalizeTypeText(propTypeText);
228
+ // Add enum values to the cleaned description
229
+ let finalDescription = description;
230
+ if (enumValues.length > 0) {
231
+ const enumDescription = `Possible values: ${enumValues.join(', ')}`;
232
+ finalDescription = finalDescription ? `${finalDescription}. ${enumDescription}` : enumDescription;
233
+ }
234
+ // Determine if this type can be populated from JSON
235
+ const jsonSerializable = canPopulateFromJson(propTypeText, propType);
236
+ // Determine if this property has nested properties
237
+ const paramInfo = {
238
+ type: shouldTreatAsObject(propTypeText, propType) ? 'object' : finalType,
239
+ description: finalDescription,
240
+ canPopulateFromJson: jsonSerializable,
241
+ optional: prop.isOptional()
242
+ };
243
+ // If it's an object type, recursively extract properties
244
+ if (shouldTreatAsObject(propTypeText, propType)) {
245
+ paramInfo.properties = extractObjectProperties(propType, visited);
246
+ }
247
+ properties[propName] = paramInfo;
248
+ }
249
+ return properties;
250
+ }
251
+ catch (error) {
252
+ console.warn('Failed to extract object properties:', error instanceof Error ? error.message : String(error));
253
+ return {};
254
+ }
255
+ }
256
+ /**
257
+ * Extracts properties from an options object type using ts-morph Type analysis
258
+ */
259
+ function extractOptionsProperties(param) {
260
+ try {
261
+ const type = param.getType();
262
+ return extractObjectProperties(type);
263
+ }
264
+ catch (error) {
265
+ console.warn(`Failed to extract properties for parameter ${param.getName()}:`, error instanceof Error ? error.message : String(error));
266
+ return {};
267
+ }
268
+ }
269
+ /**
270
+ * Determines if a type should be treated as an object with properties
271
+ */
272
+ function shouldTreatAsObject(typeText, tsType) {
273
+ // Skip primitive types (but not when they appear in object literals)
274
+ if (!typeText.includes('{') && ['string', 'number', 'boolean'].some(t => typeText === t || typeText === `${t} | undefined`)) {
275
+ return false;
276
+ }
277
+ // Skip THREE.Vector3 and THREE.Euler - these are handled specially
278
+ if (typeText.includes('Vector3') || typeText.includes('Euler'))
279
+ return false;
280
+ // Skip ENGINE.* and GAME.* classes - these are complex types
281
+ if (typeText.includes('ENGINE.') || typeText.includes('GAME.'))
282
+ return false;
283
+ // Skip function types
284
+ if (typeText.includes('=>') || typeText.includes('Function') || typeText.includes('()'))
285
+ return false;
286
+ // If we have TypeScript type information, use it
287
+ if (tsType) {
288
+ try {
289
+ // Handle union types - check if any union member is an interface-like type
290
+ if (tsType.isUnion()) {
291
+ const unionTypes = tsType.getUnionTypes();
292
+ for (const unionType of unionTypes) {
293
+ // Skip undefined types
294
+ if (unionType.isUndefined()) {
295
+ continue;
296
+ }
297
+ // Check if this union member is an interface-like type
298
+ if (isInterfaceLikeType(unionType)) {
299
+ return true;
300
+ }
301
+ }
302
+ }
303
+ // Check if the type is an interface-like type
304
+ if (isInterfaceLikeType(tsType)) {
305
+ return true;
306
+ }
307
+ }
308
+ catch (error) {
309
+ // If we can't get properties, fall back to string matching
310
+ }
311
+ }
312
+ // Treat object literals as objects (inline interfaces)
313
+ if (typeText.includes('{')) {
314
+ return true;
315
+ }
316
+ return false;
317
+ }
318
+ /**
319
+ * Checks if a type is interface-like (has properties but is not a function or complex class)
320
+ */
321
+ function isInterfaceLikeType(type) {
322
+ try {
323
+ const properties = type.getApparentProperties();
324
+ if (!properties || properties.length === 0) {
325
+ return false;
326
+ }
327
+ // Check if any property is a function - if so, this might be a class or complex type
328
+ for (const prop of properties) {
329
+ try {
330
+ const propType = prop.getTypeAtLocation(prop.getValueDeclaration());
331
+ const propTypeText = propType.getText();
332
+ // If we find function properties, this is likely a class or complex type
333
+ if (propTypeText.includes('=>') || propTypeText.includes('Function') || propTypeText.includes('()')) {
334
+ // Allow if it's clearly a callback/event handler (common in options)
335
+ const propName = prop.getName();
336
+ if (propName.toLowerCase().includes('callback') ||
337
+ propName.toLowerCase().includes('handler') ||
338
+ propName.toLowerCase().includes('listener') ||
339
+ propName.toLowerCase().startsWith('on')) {
340
+ continue; // Allow these function properties
341
+ }
342
+ return false; // Skip types with non-callback function properties
343
+ }
344
+ }
345
+ catch (error) {
346
+ // If we can't analyze a property, continue
347
+ continue;
348
+ }
349
+ }
350
+ return true; // It has properties and they're not complex functions
351
+ }
352
+ catch (error) {
353
+ return false;
354
+ }
355
+ }
356
+ /**
357
+ * Determines if a type can be populated from JSON values
358
+ */
359
+ function canPopulateFromJson(typeText, tsType, visited = new Set()) {
360
+ // Handle undefined types - they can be omitted in JSON
361
+ if (typeText.includes('undefined')) {
362
+ // Extract the non-undefined part
363
+ const nonUndefPart = typeText.replace(/\s*\|\s*undefined/g, '').trim();
364
+ if (nonUndefPart) {
365
+ // Create a new visited set for the non-undefined part to avoid false recursion detection
366
+ const newVisited = new Set(visited);
367
+ newVisited.delete(typeText); // Remove the original union type
368
+ return canPopulateFromJson(nonUndefPart, tsType, newVisited);
369
+ }
370
+ return true; // Pure undefined can be omitted
371
+ }
372
+ // Prevent infinite recursion
373
+ if (visited.has(typeText)) {
374
+ return false;
375
+ }
376
+ visited.add(typeText);
377
+ // Primitive types can be populated
378
+ if (['string', 'number', 'boolean'].some(t => typeText === t || typeText.startsWith(t))) {
379
+ return true;
380
+ }
381
+ // THREE.Vector3 and THREE.Euler can be populated as [x,y,z] arrays
382
+ if (typeText.includes('Vector3') || typeText.includes('Euler')) {
383
+ return true;
384
+ }
385
+ // ENGINE.* and GAME.* classes cannot be populated (complex runtime objects)
386
+ if (typeText.includes('ENGINE.') || typeText.includes('GAME.')) {
387
+ return false;
388
+ }
389
+ // Function types cannot be populated
390
+ if (typeText.includes('=>') || typeText.includes('Function') || typeText.includes('()')) {
391
+ return false;
392
+ }
393
+ // Object literals can be populated (inline object types)
394
+ if (typeText.includes('{')) {
395
+ return true; // Inline object types can be populated
396
+ }
397
+ // Array types can be populated if their element type can be populated
398
+ if (typeText.includes('[]')) {
399
+ const elementType = typeText.replace('[]', '').trim();
400
+ return canPopulateFromJson(elementType, undefined, visited);
401
+ }
402
+ // Check TypeScript type information
403
+ if (tsType) {
404
+ try {
405
+ const enumValues = extractEnumValues(tsType);
406
+ if (enumValues.length > 0) {
407
+ return true; // Enums can be populated
408
+ }
409
+ // Handle union types
410
+ if (tsType.isUnion()) {
411
+ const unionTypes = tsType.getUnionTypes();
412
+ // Check if any non-undefined union member can be populated
413
+ for (const unionType of unionTypes) {
414
+ if (unionType.isUndefined()) {
415
+ continue;
416
+ }
417
+ const unionTypeText = unionType.getText();
418
+ if (canPopulateFromJson(unionTypeText, unionType, visited)) {
419
+ return true;
420
+ }
421
+ }
422
+ return false;
423
+ }
424
+ // Check if this is an object type with properties
425
+ if (isInterfaceLikeType(tsType)) {
426
+ return canObjectBePopulatedFromJson(tsType, visited);
427
+ }
428
+ }
429
+ catch (error) {
430
+ // If type analysis fails, fall back to string matching
431
+ }
432
+ }
433
+ // Default to false for unknown types
434
+ return false;
435
+ }
436
+ /**
437
+ * Checks if an object type can be populated from JSON by checking if all required properties can be populated
438
+ */
439
+ function canObjectBePopulatedFromJson(tsType, visited = new Set()) {
440
+ try {
441
+ const properties = tsType.getApparentProperties();
442
+ if (!properties || properties.length === 0) {
443
+ return true; // Empty object can be populated
444
+ }
445
+ // Check all properties
446
+ for (const prop of properties) {
447
+ const propName = prop.getName();
448
+ // Skip internal/private properties
449
+ if (propName.startsWith('_') || propName.startsWith('__')) {
450
+ continue;
451
+ }
452
+ const propDeclaration = prop.getValueDeclaration();
453
+ if (!propDeclaration)
454
+ continue;
455
+ // Check if property is required
456
+ const isOptional = prop.isOptional();
457
+ // If it's required, it must be populatable
458
+ if (!isOptional) {
459
+ const propType = prop.getTypeAtLocation(propDeclaration);
460
+ const propTypeText = propType.getText(propDeclaration);
461
+ if (!canPopulateFromJson(propTypeText, propType, visited)) {
462
+ return false; // Required property cannot be populated
463
+ }
464
+ }
465
+ // Note: We don't need to check optional properties - they can be omitted
466
+ }
467
+ return true; // All required properties can be populated (or all properties are optional)
468
+ }
469
+ catch (error) {
470
+ return false; // If analysis fails, assume it cannot be populated
471
+ }
472
+ }
473
+ /**
474
+ * Normalizes type text for display
475
+ */
476
+ function normalizeTypeText(typeText) {
477
+ if (typeText.includes('Vector3'))
478
+ return 'THREE.Vector3';
479
+ if (typeText.includes('Euler'))
480
+ return 'THREE.Euler';
481
+ return typeText;
482
+ }
483
+ /**
484
+ * Extracts JSDoc description for a parameter
485
+ */
486
+ function extractParamJSDoc(param) {
487
+ // Get JSDoc tags from the parent constructor
488
+ const constructor = param.getParent();
489
+ if (!constructor || !('getJsDocs' in constructor))
490
+ return undefined;
491
+ try {
492
+ const jsDocs = constructor.getJsDocs();
493
+ if (!jsDocs || jsDocs.length === 0)
494
+ return undefined;
495
+ const paramName = param.getName();
496
+ for (const jsDoc of jsDocs) {
497
+ const tags = jsDoc.getTags();
498
+ for (const tag of tags) {
499
+ if (tag.getTagName() === 'param' && tag.getComment()?.includes(paramName)) {
500
+ return tag.getComment();
501
+ }
502
+ }
503
+ }
504
+ }
505
+ catch (error) {
506
+ // If JSDoc extraction fails, silently continue
507
+ return undefined;
508
+ }
509
+ return undefined;
510
+ }
511
+ /**
512
+ * Analyzes a constructor parameter to extract detailed information
513
+ */
514
+ function analyzeParameter(param) {
515
+ const tsType = param.getType();
516
+ const type = tsType.getText(param);
517
+ const normalizedType = normalizeTypeText(type);
518
+ const paramName = param.getName();
519
+ // Extract JSDoc description
520
+ let description = extractParamJSDoc(param);
521
+ // Check if this is an enum/union type
522
+ const enumValues = extractEnumValues(tsType);
523
+ // Add enum values to the cleaned description
524
+ let finalDescription = description;
525
+ if (enumValues.length > 0) {
526
+ const enumDescription = `Possible values: ${enumValues.join(', ')}`;
527
+ finalDescription = finalDescription ? `${finalDescription}. ${enumDescription}` : enumDescription;
528
+ }
529
+ // Determine if this type can be populated from JSON
530
+ const jsonSerializable = canPopulateFromJson(type, tsType);
531
+ const paramInfo = {
532
+ paramName,
533
+ type: shouldTreatAsObject(type, tsType) ? 'object' : normalizedType,
534
+ description: finalDescription,
535
+ canPopulateFromJson: jsonSerializable,
536
+ optional: param.isOptional()
537
+ };
538
+ // Extract properties if this is an options object
539
+ if (shouldTreatAsObject(type, tsType)) {
540
+ paramInfo.properties = extractOptionsProperties(param);
541
+ }
542
+ return paramInfo;
543
+ }
544
+ /**
545
+ * Determines if an actor class can be populated from JSON based on its constructor parameters
546
+ */
547
+ function canActorBePopulatedFromJson(constructorParams) {
548
+ // If there are no constructor parameters, it can be populated
549
+ if (!constructorParams || constructorParams.length === 0) {
550
+ return true;
551
+ }
552
+ // Check each parameter
553
+ for (const paramInfo of constructorParams) {
554
+ // If any required parameter cannot be populated from JSON, the actor cannot be populated
555
+ if (paramInfo.canPopulateFromJson === false) {
556
+ // Check if this parameter is optional (has undefined in type)
557
+ const isOptional = paramInfo.type.includes('undefined');
558
+ if (!isOptional) {
559
+ return false; // Required parameter cannot be populated from JSON
560
+ }
561
+ }
562
+ }
563
+ return true; // All required parameters can be populated from JSON
564
+ }
565
+ /**
566
+ * Extracts constructor parameters from a class declaration
567
+ */
568
+ function extractConstructorParams(classDecl) {
569
+ const constructors = classDecl.getConstructors();
570
+ if (constructors.length === 0)
571
+ return [];
572
+ // Take the first constructor (could be enhanced to handle overloads)
573
+ const constructor = constructors[0];
574
+ return constructor.getParameters().map(param => analyzeParameter(param));
575
+ }
576
+ /**
577
+ * Analyzes a class declaration to extract actor information
578
+ */
579
+ function analyzeActorClassBasics(sourceFile, classDeclaration, classPrefix) {
580
+ const className = classDeclaration.getName();
581
+ if (!className)
582
+ return null;
583
+ const prefixedClassName = classPrefix + className;
584
+ const classConstructor = ENGINE.ClassRegistry.getRegistry().get(prefixedClassName);
585
+ if (!isSubclass(classConstructor, ENGINE.Actor))
586
+ return null;
587
+ // Extract basic information
588
+ const filePath = sourceFile.getFilePath();
589
+ const heritage = classDeclaration.getExtends();
590
+ const parentClassName = heritage?.getExpression().getText() ?? '';
591
+ // For now, create a simple base classes array
592
+ const baseClasses = [];
593
+ if (parentClassName) {
594
+ baseClasses.push(parentClassName);
595
+ }
596
+ // Extract JSDoc description if available
597
+ const jsDocs = classDeclaration.getJsDocs();
598
+ const description = jsDocs.length > 0 ? jsDocs[0].getDescription() : undefined;
599
+ // Extract constructor parameters
600
+ // const constructorParams = extractConstructorParams(classDeclaration);
601
+ return {
602
+ className: prefixedClassName,
603
+ filePath,
604
+ baseClasses,
605
+ // constructorParams,
606
+ description,
607
+ };
608
+ }
609
+ /**
610
+ * Recursively searches a directory for TypeScript files
611
+ */
612
+ function collectTypeScriptFiles(dir, storageProvider) {
613
+ let results = [];
614
+ const actualDir = storageProvider.getFullPath(dir);
615
+ if (!fs.existsSync(actualDir)) {
616
+ console.warn(`Directory does not exist: ${actualDir}`);
617
+ return results;
618
+ }
619
+ const list = fs.readdirSync(actualDir);
620
+ list.forEach((file) => {
621
+ const filePath = path.join(dir, file);
622
+ const actualFilePath = storageProvider.getFullPath(filePath);
623
+ const stat = fs.statSync(actualFilePath);
624
+ if (stat && stat.isDirectory() && file !== 'node_modules' && !file.startsWith('.')) {
625
+ // Recursively search subdirectories
626
+ results = results.concat(collectTypeScriptFiles(filePath, storageProvider));
627
+ }
628
+ else if (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts') && !file.endsWith('.spec.ts')) {
629
+ // Include TypeScript files but exclude declaration files
630
+ results.push(filePath);
631
+ }
632
+ });
633
+ return results;
634
+ }
635
+ /**
636
+ * Main function to search for actor classes
637
+ */
638
+ export async function populateClassesInfo(options = {}) {
639
+ const { classesToSearch = [], includeConstructorParams = false } = options;
640
+ const dirs = [];
641
+ if (Object.values(classesToSearch).some(className => className.startsWith(ENGINE.Prefix.ENGINE))) {
642
+ dirs.push(ENGINE.ENGINE_PATH_PREFIX);
643
+ }
644
+ if (Object.values(classesToSearch).some(className => className.startsWith(ENGINE.Prefix.GAME))) {
645
+ dirs.push(ENGINE.PROJECT_PATH_PREFIX);
646
+ }
647
+ const storageProvider = new StorageProvider();
648
+ // Collect TypeScript files
649
+ const files = [];
650
+ for (const dir of dirs) {
651
+ const dirFiles = collectTypeScriptFiles(dir, storageProvider);
652
+ files.push(...dirFiles);
653
+ console.log(`Found ${dirFiles.length} TypeScript files in ${dir}`);
654
+ }
655
+ console.log(`Total TypeScript files found: ${files.length}`);
656
+ // Create ts-morph project
657
+ const project = createTsMorphProject(storageProvider);
658
+ // Add files to ts-morph project (limit for performance)
659
+ const filesToAnalyze = files;
660
+ console.log(`Analyzing ${filesToAnalyze.length} files with ts-morph...`);
661
+ for (const filePath of filesToAnalyze) {
662
+ try {
663
+ const actualPath = storageProvider.getFullPath(filePath);
664
+ if (fs.existsSync(actualPath)) {
665
+ project.addSourceFileAtPath(actualPath);
666
+ }
667
+ }
668
+ catch (error) {
669
+ console.warn(`Failed to add file ${filePath}:`, error instanceof Error ? error.message : String(error));
670
+ }
671
+ }
672
+ const result = {
673
+ metadataDescription: includeConstructorParams ? createMetadataDescription() : {},
674
+ actors: {}
675
+ };
676
+ // Analyze each source file for actor classes
677
+ let totalClasses = 0;
678
+ let actorClasses = 0;
679
+ const engineFullPath = storageProvider.getFullPath(ENGINE.ENGINE_PATH_PREFIX);
680
+ for (const sourceFile of project.getSourceFiles()) {
681
+ const isEngineClass = sourceFile.getFilePath().startsWith(engineFullPath);
682
+ const classPrefix = isEngineClass ? ENGINE.Prefix.ENGINE : ENGINE.Prefix.GAME;
683
+ const classes = sourceFile.getClasses();
684
+ totalClasses += classes.length;
685
+ for (const classDecl of classes) {
686
+ const actorInfo = analyzeActorClassBasics(sourceFile, classDecl, classPrefix);
687
+ if (actorInfo) {
688
+ actorClasses++;
689
+ console.log(`Found Actor-derived class: ${actorInfo.className} in ${path.basename(actorInfo.filePath)}`);
690
+ if (includeConstructorParams) {
691
+ actorInfo.constructorParams = extractConstructorParams(classDecl);
692
+ // Determine if the entire actor can be populated from JSON
693
+ actorInfo.canPopulateFromJson = canActorBePopulatedFromJson(actorInfo.constructorParams);
694
+ }
695
+ if (!classesToSearch || classesToSearch.length === 0 || classesToSearch.includes(actorInfo.className)) {
696
+ result.actors[actorInfo.className] = actorInfo;
697
+ }
698
+ }
699
+ }
700
+ }
701
+ console.log(`✅ Analysis complete: ${totalClasses} total classes, ${actorClasses} Actor-derived classes found`);
702
+ return result;
703
+ }