@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,450 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as ENGINE from 'genesys.js';
4
+ import { findFilesByPredicate, getProjectFolderAndFile, runCommandAsync } from '../index.js';
5
+ function getTscBinaryPath(projectFolder, logger) {
6
+ // Look in the project's node_modules for tsc
7
+ const projectBinaryPath = path.join(projectFolder, 'node_modules', '.bin', process.platform === 'win32' ? 'tsc.cmd' : 'tsc');
8
+ if (fs.existsSync(projectBinaryPath)) {
9
+ logger.log(`Found tsc binary at: ${projectBinaryPath}`);
10
+ return projectBinaryPath;
11
+ }
12
+ // Fallback to pnpm exec
13
+ logger.log('tsc not found in project node_modules, using pnpm exec');
14
+ return 'pnpm exec tsc';
15
+ }
16
+ function getEsbuildBinaryPath(projectFolder, logger, isPackaged, resourcesPath) {
17
+ // In packaged app, esbuild binary is in app.asar.unpacked
18
+ if (isPackaged) {
19
+ const platform = process.platform;
20
+ const arch = process.arch;
21
+ // Construct the platform-specific binary path
22
+ let binaryName = 'esbuild';
23
+ let platformFolder = '';
24
+ if (platform === 'win32') {
25
+ binaryName = 'esbuild.exe';
26
+ platformFolder = `win32-${arch}`;
27
+ }
28
+ else if (platform === 'darwin') {
29
+ platformFolder = `darwin-${arch}`;
30
+ }
31
+ else if (platform === 'linux') {
32
+ platformFolder = `linux-${arch}`;
33
+ }
34
+ // Try to find the binary in app.asar.unpacked
35
+ const unpackedPath = path.join(resourcesPath, 'app.asar.unpacked', 'node_modules', '@esbuild', platformFolder, binaryName);
36
+ if (fs.existsSync(unpackedPath)) {
37
+ logger.log(`Found esbuild binary at: ${unpackedPath}`);
38
+ return unpackedPath;
39
+ }
40
+ logger.warn('esbuild binary not found in app.asar.unpacked, trying SDK\'s node_modules');
41
+ }
42
+ // In dev mode or as fallback, look in the project's node_modules
43
+ const projectBinaryPath = path.join(projectFolder, 'node_modules', '.bin', process.platform === 'win32' ? 'esbuild.cmd' : 'esbuild');
44
+ if (fs.existsSync(projectBinaryPath)) {
45
+ logger.log(`Found esbuild binary in project at: ${projectBinaryPath}`);
46
+ return projectBinaryPath;
47
+ }
48
+ // When running as CLI (not packaged), also check the SDK's own node_modules
49
+ // resourcesPath points to the SDK root in CLI mode
50
+ const sdkBinaryPath = path.join(resourcesPath, 'node_modules', '.bin', process.platform === 'win32' ? 'esbuild.cmd' : 'esbuild');
51
+ if (fs.existsSync(sdkBinaryPath)) {
52
+ logger.log(`Found esbuild binary in SDK at: ${sdkBinaryPath}`);
53
+ return sdkBinaryPath;
54
+ }
55
+ // Last resort - use pnpm dlx
56
+ return 'pnpm dlx esbuild';
57
+ }
58
+ function gatherAssets(data) {
59
+ const assets = [];
60
+ for (const [k, v] of Object.entries(data)) {
61
+ if (typeof v === 'string' && v.startsWith(ENGINE.PROJECT_PATH_PREFIX)) {
62
+ assets.push(v);
63
+ }
64
+ if (typeof v === 'object' && v !== null) {
65
+ assets.push(...gatherAssets(v));
66
+ }
67
+ }
68
+ return assets;
69
+ }
70
+ export function findFilesWithExtension(dir, extension) {
71
+ // Normalize extension (ensure it starts with dot)
72
+ const ext = extension.startsWith('.') ? extension : `.${extension}`;
73
+ let results = [];
74
+ const files = fs.readdirSync(dir);
75
+ for (const file of files) {
76
+ const filePath = path.join(dir, file);
77
+ const stat = fs.statSync(filePath);
78
+ if (stat.isDirectory()) {
79
+ results = results.concat(findFilesWithExtension(filePath, ext));
80
+ }
81
+ else if (filePath.endsWith(ext)) {
82
+ results.push(filePath);
83
+ }
84
+ }
85
+ return results;
86
+ }
87
+ async function copyAssets(projectPath, distFolder, alwaysCopyAssets, logger) {
88
+ try {
89
+ // Find all files that can contain asset paths in distFolder
90
+ const beginTime = Date.now();
91
+ const jsonFiles = findFilesWithExtension(distFolder, 'json');
92
+ const jsFiles = findFilesWithExtension(distFolder, 'js');
93
+ const htmlFiles = findFilesWithExtension(distFolder, 'html');
94
+ const cssFiles = findFilesWithExtension(distFolder, 'css');
95
+ const sceneFiles = findFilesWithExtension(distFolder, ENGINE.SCENE_NAME_EXT);
96
+ const durationSec = (Date.now() - beginTime) / 1000;
97
+ logger.log(`Stats: findFilesWithExtension done in ${durationSec}s`);
98
+ const assets = new Set();
99
+ const processedAssets = new Set();
100
+ // include the scene files in the json files
101
+ jsonFiles.push(...sceneFiles);
102
+ // Add always copy assets
103
+ for (const asset of alwaysCopyAssets) {
104
+ let sourceAssetPath = asset;
105
+ const withProjectPrefix = asset.startsWith(ENGINE.PROJECT_PATH_PREFIX);
106
+ if (withProjectPrefix) {
107
+ sourceAssetPath = asset.replace(ENGINE.PROJECT_PATH_PREFIX, projectPath);
108
+ }
109
+ else if (!path.isAbsolute(asset)) {
110
+ sourceAssetPath = path.join(projectPath, asset);
111
+ }
112
+ logger.log('Handling alwaysCopyAssets entry:', asset, sourceAssetPath);
113
+ if (fs.statSync(sourceAssetPath).isDirectory()) {
114
+ // If it's a directory, add all files in it
115
+ const files = findFilesByPredicate(sourceAssetPath, () => true);
116
+ for (const file of files) {
117
+ const relativePath = path.relative(projectPath, file).replace(/\\/g, '/');
118
+ assets.add(`${ENGINE.PROJECT_PATH_PREFIX}/${relativePath}`);
119
+ }
120
+ logger.log(`Added ${files.length} files from alwaysCopyAssets directory ${sourceAssetPath}`);
121
+ }
122
+ else {
123
+ if (!withProjectPrefix) {
124
+ assets.add(`${ENGINE.PROJECT_PATH_PREFIX}/${path.relative(projectPath, sourceAssetPath).replace(/\\/g, '/')}`);
125
+ }
126
+ else {
127
+ assets.add(asset);
128
+ }
129
+ }
130
+ }
131
+ // Helper function to discover assets from a file
132
+ async function discoverAssetsFromFile(filePath) {
133
+ const discoveredAssets = [];
134
+ try {
135
+ if (filePath.endsWith('.json') || filePath.includes(ENGINE.SCENE_NAME_EXT)) {
136
+ const jsonData = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
137
+ discoveredAssets.push(...gatherAssets(jsonData));
138
+ }
139
+ else if (filePath.endsWith('.js')) {
140
+ const regex = new RegExp(String.raw `(["'])${ENGINE.PROJECT_PATH_PREFIX}\/.*?\1`, 'g');
141
+ const jsData = await fs.promises.readFile(filePath, 'utf8');
142
+ const matches = jsData.matchAll(regex);
143
+ for (const match of matches) {
144
+ discoveredAssets.push(match[0].replace(/["']/g, ''));
145
+ }
146
+ }
147
+ else if (filePath.endsWith('.html') || filePath.endsWith('.css')) {
148
+ // For HTML and CSS files, look for asset paths in various contexts
149
+ const regex = new RegExp(String.raw `${ENGINE.PROJECT_PATH_PREFIX}\/[^"'\s)]+`, 'g');
150
+ const fileData = await fs.promises.readFile(filePath, 'utf8');
151
+ const matches = fileData.matchAll(regex);
152
+ for (const match of matches) {
153
+ discoveredAssets.push(match[0]);
154
+ }
155
+ }
156
+ }
157
+ catch (error) {
158
+ logger.error(`Failed to read file ${filePath}: ${error}`);
159
+ }
160
+ return discoveredAssets;
161
+ }
162
+ // Recursively discover all assets
163
+ async function discoverAllAssets(initialFiles) {
164
+ const filesToProcess = [...initialFiles];
165
+ while (filesToProcess.length > 0) {
166
+ const currentFile = filesToProcess.shift();
167
+ if (processedAssets.has(currentFile)) {
168
+ continue;
169
+ }
170
+ processedAssets.add(currentFile);
171
+ const discoveredAssets = await discoverAssetsFromFile(currentFile);
172
+ for (const asset of discoveredAssets) {
173
+ if (!assets.has(asset)) {
174
+ assets.add(asset);
175
+ // Check if this asset is a file that might contain references to other assets
176
+ const sourceAssetPath = asset.replace(ENGINE.PROJECT_PATH_PREFIX, projectPath);
177
+ try {
178
+ await fs.promises.access(sourceAssetPath);
179
+ // Only process JSON, JS, HTML, CSS, and scene files that might contain asset references
180
+ if (sourceAssetPath.endsWith('.json') ||
181
+ sourceAssetPath.endsWith('.js') ||
182
+ sourceAssetPath.endsWith('.html') ||
183
+ sourceAssetPath.endsWith('.css') ||
184
+ sourceAssetPath.includes(ENGINE.SCENE_NAME_EXT)) {
185
+ filesToProcess.push(sourceAssetPath);
186
+ }
187
+ }
188
+ catch {
189
+ logger.error(`copyAssets: source asset ${asset} does not exist, referenced from ${currentFile}`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ // Start discovery from initial files
196
+ {
197
+ const beginTime = Date.now();
198
+ await discoverAllAssets([...jsonFiles, ...jsFiles, ...htmlFiles, ...cssFiles]);
199
+ const durationSec = (Date.now() - beginTime) / 1000;
200
+ logger.log(`Stats: Assets discovered in ${durationSec}s`);
201
+ }
202
+ /*
203
+ // Find and add lightmap files and navmesh files that may not be directly referenced
204
+ // Disabled for now, see https://directive.slack.com/archives/C08M839GVBP/p1755865316511099
205
+ try {
206
+ const beginTime = Date.now();
207
+ const engineFiles = [
208
+ ...findFilesWithExtension(projectPath, ENGINE.LIGHTMAP_JSON_EXT),
209
+ ...findFilesWithExtension(projectPath, ENGINE.LIGHTMAP_BIN_EXT),
210
+ ...findFilesWithExtension(projectPath, ENGINE.NAVMESH_EXT),
211
+ ];
212
+ for (const file of engineFiles) {
213
+ // Convert absolute path to @project relative path
214
+ const relativePath = path.relative(projectPath, file);
215
+ const assetPath = `${ENGINE.PROJECT_PATH_PREFIX}/${relativePath.replace(/\\/g, '/')}`;
216
+ assets.add(assetPath);
217
+ logger.log(`Found engine file: ${assetPath}`);
218
+ }
219
+ const durationSec = (Date.now() - beginTime) / 1000;
220
+ logger.log(`Stats: lightmap and navmesh files discovered in ${durationSec}s`);
221
+ } catch (error) {
222
+ logger.error(`Failed to scan for engine files: ${error}`);
223
+ }
224
+ */
225
+ // Copy all assets in parallel
226
+ const copyPromises = Array.from(assets).map(async (asset) => {
227
+ try {
228
+ const sourceAssetPath = asset.replace(ENGINE.PROJECT_PATH_PREFIX, projectPath);
229
+ // Check if source exists
230
+ try {
231
+ await fs.promises.access(sourceAssetPath);
232
+ }
233
+ catch {
234
+ // No need to log again here, it's already logged in the discoverAllAssets function
235
+ return;
236
+ }
237
+ if (asset.includes(ENGINE.SCENE_NAME_EXT)) {
238
+ // Do not copy the scene files since they have been minified and copied in an early step (see the buildProject function)
239
+ // If we copy them again the minification will be undone
240
+ // Also even if the copy is skipped the path map still needs to be set in order to have the correct scene file path in their referencers (for instance game.js)
241
+ logger.verbose(`Skip copying ${sourceAssetPath} as it's been minified and copied in an early step`);
242
+ }
243
+ else if (fs.statSync(sourceAssetPath).isDirectory()) {
244
+ // directory, skip it
245
+ }
246
+ else {
247
+ const destinationAssetPath = asset.replace(ENGINE.PROJECT_PATH_PREFIX, distFolder);
248
+ logger.verbose(`Copying ${sourceAssetPath} to ${destinationAssetPath}`);
249
+ await fs.promises.mkdir(path.dirname(destinationAssetPath), { recursive: true });
250
+ await fs.promises.copyFile(sourceAssetPath, destinationAssetPath);
251
+ }
252
+ }
253
+ catch (error) {
254
+ logger.error(`Failed to copy asset ${asset}: ${error}`);
255
+ }
256
+ });
257
+ {
258
+ const beginTime = Date.now();
259
+ await Promise.all(copyPromises);
260
+ const durationSec = (Date.now() - beginTime) / 1000;
261
+ logger.verbose(`Stats: Assets copied in ${durationSec}s`);
262
+ }
263
+ // Collect all files that need path updates (initial dist files + copied asset files that can contain references)
264
+ const filesToUpdate = [...jsonFiles, ...jsFiles, ...htmlFiles, ...cssFiles];
265
+ // Add copied asset files that might contain asset references
266
+ for (const asset of assets) {
267
+ const destinationAssetPath = asset.replace(ENGINE.PROJECT_PATH_PREFIX, distFolder);
268
+ if (destinationAssetPath.endsWith('.json') ||
269
+ destinationAssetPath.endsWith('.js') ||
270
+ destinationAssetPath.endsWith('.html') ||
271
+ destinationAssetPath.endsWith('.css') ||
272
+ destinationAssetPath.includes(ENGINE.SCENE_NAME_EXT)) {
273
+ filesToUpdate.push(destinationAssetPath);
274
+ }
275
+ }
276
+ }
277
+ catch (error) {
278
+ logger.error(`Failed to copy assets: ${error}`);
279
+ throw error;
280
+ }
281
+ }
282
+ function buildSceneData(sceneData) {
283
+ function removeEditorData(data) {
284
+ if (typeof data === 'object' && data !== null) {
285
+ delete data.editorData;
286
+ for (const v of Object.values(data)) {
287
+ removeEditorData(v);
288
+ }
289
+ }
290
+ }
291
+ removeEditorData(sceneData);
292
+ return sceneData;
293
+ }
294
+ export async function buildProject(projectPath, runTsc, logger, handler) {
295
+ const { folder: projectFolder, file: projectFile } = getProjectFolderAndFile(projectPath, logger);
296
+ try {
297
+ logger.log(`Building project at ${projectPath}`);
298
+ if (!fs.existsSync(projectFolder)) {
299
+ return {
300
+ success: false,
301
+ message: `Project does not exist: ${projectPath}`,
302
+ path: projectPath
303
+ };
304
+ }
305
+ const sourceFolder = path.join(projectFolder, 'src');
306
+ const excludedFolders = ['node_modules', '.git'];
307
+ const gameFiles = findFilesByPredicate(sourceFolder, (filePath) => path.basename(filePath) === ENGINE.DEFAULT_GAME_NAME && !filePath.includes(ENGINE.BUILT_PROJECT_FOLDER), excludedFolders);
308
+ const sceneFiles = findFilesByPredicate(projectFolder, (filePath) => path.basename(filePath).includes(ENGINE.SCENE_NAME_EXT) && !filePath.includes(ENGINE.BUILT_PROJECT_FOLDER), excludedFolders);
309
+ if (!gameFiles.length) {
310
+ return {
311
+ success: false,
312
+ message: `Game file ${ENGINE.DEFAULT_GAME_NAME} does not exist`,
313
+ path: projectPath
314
+ };
315
+ }
316
+ if (gameFiles.length > 1) {
317
+ return {
318
+ success: false,
319
+ message: `Multiple game files found: ${gameFiles.join(', ')}`,
320
+ path: projectPath
321
+ };
322
+ }
323
+ if (!projectFile) {
324
+ return {
325
+ success: false,
326
+ message: `Project file ${ENGINE.GAME_PROJECT_FILE_EXT} does not exist`,
327
+ path: projectPath
328
+ };
329
+ }
330
+ const autoImportPath = path.join(sourceFolder, 'auto-imports.ts');
331
+ if (fs.existsSync(autoImportPath)) {
332
+ let importText = '';
333
+ const sourceFiles = findFilesWithExtension(sourceFolder, 'ts');
334
+ for (const sourceFile of sourceFiles) {
335
+ if (sourceFile.includes('auto-imports.ts')) {
336
+ continue;
337
+ }
338
+ if (sourceFile.includes(ENGINE.DEFAULT_GAME_NAME)) {
339
+ continue;
340
+ }
341
+ const importPath = path.relative(sourceFolder, sourceFile).replaceAll('\\', '/');
342
+ importText += `import './${importPath}';\n`;
343
+ }
344
+ fs.writeFileSync(autoImportPath, importText);
345
+ }
346
+ else {
347
+ logger.warn(`Auto-imports file ${autoImportPath} does not exist`);
348
+ }
349
+ const distFolder = path.join(projectFolder, ENGINE.BUILT_PROJECT_FOLDER);
350
+ if (fs.existsSync(distFolder)) {
351
+ const beginTime = Date.now();
352
+ fs.rmSync(distFolder, { recursive: true });
353
+ const durationSec = (Date.now() - beginTime) / 1000;
354
+ logger.log(`Stats: Dist folder removed in ${durationSec}s`);
355
+ }
356
+ fs.mkdirSync(distFolder, { recursive: true });
357
+ if (runTsc) {
358
+ logger.log('Running TypeScript type checking');
359
+ const beginTime = Date.now();
360
+ // Get the direct path to tsc binary (avoids pnpm exec overhead)
361
+ const tscBinary = getTscBinaryPath(projectFolder, logger);
362
+ // Build the command - quote the binary path only if it's not pnpm exec
363
+ const tscParameters = '--noEmit --incremental';
364
+ const tscCommand = tscBinary.startsWith('pnpm')
365
+ ? `${tscBinary} ${tscParameters}`
366
+ : `"${tscBinary}" ${tscParameters}`;
367
+ logger.log(`Using tsc: ${tscBinary}`);
368
+ // Only type check, don't emit files - esbuild will handle transpilation
369
+ // --incremental speeds up subsequent builds by caching results
370
+ await runCommandAsync(tscCommand, projectFolder, logger);
371
+ const durationSec = (Date.now() - beginTime) / 1000;
372
+ logger.log(`Stats: TypeScript type checking completed in ${durationSec}s`);
373
+ }
374
+ {
375
+ const gameBundleFilePath = path.join(distFolder, ENGINE.DEFAULT_GAME_BUNDLE_NAME);
376
+ logger.log(`Building game bundle from ${gameFiles[0]}`);
377
+ const beginTime = Date.now();
378
+ // Get the direct path to esbuild binary (avoids pnpm exec overhead)
379
+ const esbuildBinary = getEsbuildBinaryPath(projectFolder, logger, handler.app.isPackaged, handler.app.resourcesPath);
380
+ // Build the command - quote the binary path only if it's not pnpm exec
381
+ const buildParameters = `--bundle --platform=browser --minify --keep-names --outfile="${gameBundleFilePath}" --external:genesys.js --external:three --format=cjs`;
382
+ const buildCommand = esbuildBinary.startsWith('pnpm')
383
+ ? `${esbuildBinary} "${gameFiles[0]}" ${buildParameters}`
384
+ : `"${esbuildBinary}" "${gameFiles[0]}" ${buildParameters}`;
385
+ logger.log(`Using esbuild: ${esbuildBinary}`);
386
+ const { error, stdout, stderr } = await runCommandAsync(buildCommand, projectFolder, logger);
387
+ const durationSec = (Date.now() - beginTime) / 1000;
388
+ logger.log(`Stats: Game bundle built in ${durationSec}s`);
389
+ if (error && error.code !== 0) {
390
+ await handler.ui.showErrorDialog('Failed to build project', stderr);
391
+ throw new Error(`Building game bundle failed: ${stderr || error.message}`);
392
+ }
393
+ }
394
+ // inject the prefabs into the scene
395
+ for (const sceneFile of sceneFiles) {
396
+ const sceneData = JSON.parse(fs.readFileSync(sceneFile, 'utf8'));
397
+ if (!sceneData.$version || sceneData.$version < ENGINE.SerializationVersion.V1) {
398
+ sceneData.prefabs = {};
399
+ for (const actor of sceneData.actors) {
400
+ const prefabName = actor.prefabName;
401
+ if (prefabName) {
402
+ const prefabPath = prefabName.replace(ENGINE.PROJECT_PATH_PREFIX, projectFolder);
403
+ if (!fs.existsSync(prefabPath)) {
404
+ throw new Error(`Prefab ${prefabPath} does not exist`);
405
+ }
406
+ const prefabData = JSON.parse(fs.readFileSync(prefabPath, 'utf8'));
407
+ sceneData.prefabs[prefabName] = prefabData;
408
+ }
409
+ }
410
+ }
411
+ const relativePath = path.relative(projectFolder, sceneFile);
412
+ const sceneDestinationPath = path.join(distFolder, relativePath);
413
+ fs.mkdirSync(path.dirname(sceneDestinationPath), { recursive: true });
414
+ fs.writeFileSync(sceneDestinationPath, JSON.stringify(buildSceneData(sceneData)));
415
+ }
416
+ // copy the project file to the dist folder
417
+ fs.copyFileSync(path.join(projectFolder, projectFile), path.join(distFolder, projectFile));
418
+ const projectConfig = JSON.parse(fs.readFileSync(path.join(projectFolder, projectFile), 'utf8'));
419
+ {
420
+ const beginTime = Date.now();
421
+ const alwaysCopyAssets = [];
422
+ if (Array.isArray(projectConfig.alwaysCopyAssets)) {
423
+ alwaysCopyAssets.push(...projectConfig.alwaysCopyAssets);
424
+ }
425
+ else if (typeof projectConfig.alwaysCopyAssets === 'string') {
426
+ alwaysCopyAssets.push(projectConfig.alwaysCopyAssets);
427
+ }
428
+ else {
429
+ logger.warn('project.json alwaysCopyAssets is not an array or string');
430
+ }
431
+ await copyAssets(projectFolder, distFolder, alwaysCopyAssets, logger);
432
+ const durationSec = (Date.now() - beginTime) / 1000;
433
+ logger.log(`Stats: copyAssets done in ${durationSec}s`);
434
+ }
435
+ logger.log(`Project built successfully at ${distFolder}`);
436
+ return {
437
+ success: true,
438
+ message: `Project built successfully at ${distFolder}`,
439
+ path: projectPath
440
+ };
441
+ }
442
+ catch (error) {
443
+ return {
444
+ success: false,
445
+ message: `Failed to build project: ${error}`,
446
+ path: projectPath,
447
+ error: error,
448
+ };
449
+ }
450
+ }
@@ -0,0 +1,2 @@
1
+ export * from './new-project.js';
2
+ export * from './new-asset-pack.js';
@@ -0,0 +1,150 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as ENGINE from 'genesys.js';
4
+ import { ALL_DEPENDENCIES } from '../../dependencies.js';
5
+ import { copyAsync, existsAsync, getEngineVersion, getProjectRoot, mkdirAsync, readdirAsync, runCommandAsync, writeFileAsync } from '../common.js';
6
+ import { IgnoredFiles } from '../index.js';
7
+ export const packProjectFiles = {
8
+ packageJson: {
9
+ name: '',
10
+ version: '0.0.1',
11
+ scripts: {
12
+ 'build': 'tsc',
13
+ 'pack': 'pnpm build && pnpm pack',
14
+ 'lint': 'eslint . --fix --ext .ts,.tsx',
15
+ 'postinstall': 'tsx scripts/postinstall.ts'
16
+ },
17
+ keywords: [],
18
+ type: 'module',
19
+ files: [
20
+ 'dist',
21
+ 'src',
22
+ 'assets',
23
+ 'scripts'
24
+ ],
25
+ main: 'dist/src/index.js',
26
+ peerDependencies: {
27
+ 'genesys.js': `${getEngineVersion()}`,
28
+ 'three': ALL_DEPENDENCIES['three'],
29
+ },
30
+ devDependencies: {
31
+ '@types/three': ALL_DEPENDENCIES['@types/three'],
32
+ 'nanoid': ALL_DEPENDENCIES['nanoid'],
33
+ 'ajv': ALL_DEPENDENCIES['ajv'],
34
+ 'esbuild': ALL_DEPENDENCIES['esbuild'],
35
+ 'canvas': '3.1.0',
36
+ 'get-port': '7.1.0',
37
+ 'zod-to-json-schema': '3.24.6',
38
+ 'ws': '8.18.2',
39
+ '@types/node': '22.15.21',
40
+ '@types/jsdom': '21.1.7',
41
+ '@modelcontextprotocol/sdk': '1.22.0',
42
+ 'tsx': '4.20.3', // pin the tsx version to avoid import issues of the @webxr-input-profiles/motion-controllers module in game projects
43
+ 'zod': '3.24.4',
44
+ 'jsdom': '26.1.0',
45
+ 'typescript': '5.8.3',
46
+ '@typescript-eslint/parser': '8.29.1',
47
+ '@typescript-eslint/utils': '8.38.0',
48
+ 'eslint': '9.24.0',
49
+ '@types/ws': '8.18.1',
50
+ 'ts-morph': '26.0.0',
51
+ }
52
+ },
53
+ codeWorkspace: {
54
+ folders: [
55
+ {
56
+ path: '.'
57
+ }
58
+ ],
59
+ settings: {
60
+ ['editor.tabSize']: 2
61
+ }
62
+ }
63
+ };
64
+ export function getAssetPackTemplatePath(isPackaged, resourcesPath, projectRoot) {
65
+ const packageName = 'src/asset-pack';
66
+ if (isPackaged) {
67
+ return path.join(resourcesPath, 'vendor', packageName);
68
+ }
69
+ else {
70
+ return path.join(projectRoot, packageName);
71
+ }
72
+ }
73
+ export async function newAssetPack(parentPath, packName, logger, handler) {
74
+ const packPath = path.join(parentPath, packName);
75
+ // Validate pack name - only allow English characters, numbers, hyphens, underscores, and dots
76
+ const validNameRegex = /^[a-zA-Z0-9_.-]+$/;
77
+ if (!validNameRegex.test(packName)) {
78
+ return {
79
+ success: false,
80
+ message: `Invalid pack name "${packName}". Pack name can only contain English characters (a-z, A-Z), numbers (0-9), hyphens (-), underscores (_), and dots (.).`,
81
+ path: packPath
82
+ };
83
+ }
84
+ const createAssetPackMessage = `Creating asset pack at ${packPath}`;
85
+ logger.log(createAssetPackMessage);
86
+ handler?.ui.showLoadingOverlay(`Creating asset pack...\n${createAssetPackMessage}`);
87
+ try {
88
+ if (await existsAsync(packPath)) {
89
+ // Check if directory is empty
90
+ const files = (await readdirAsync(packPath)).filter(file => !IgnoredFiles.includes(file));
91
+ if (files.length > 0) {
92
+ return {
93
+ success: false,
94
+ message: `The directory ${packPath} already contains files. Please choose an empty directory.`,
95
+ path: packPath
96
+ };
97
+ }
98
+ }
99
+ else {
100
+ await mkdirAsync(packPath, { recursive: true });
101
+ }
102
+ // create asset pack directories
103
+ logger.log('Creating asset pack directories...');
104
+ await Promise.all([
105
+ mkdirAsync(path.join(packPath, `${ENGINE.ASSETS_FOLDER}/models`), { recursive: true }),
106
+ mkdirAsync(path.join(packPath, `${ENGINE.ASSETS_FOLDER}/textures`), { recursive: true }),
107
+ mkdirAsync(path.join(packPath, `${ENGINE.ASSETS_FOLDER}/sounds`), { recursive: true })
108
+ ]);
109
+ const packageJson = { ...packProjectFiles.packageJson }; // Create a copy to avoid mutation
110
+ packageJson.name = packName;
111
+ // generate package.json and code-workspace file
112
+ logger.log('Generating package.json and code-workspace file...');
113
+ await Promise.all([
114
+ writeFileAsync(path.join(packPath, 'package.json'), JSON.stringify(packageJson, null, 2)),
115
+ writeFileAsync(path.join(packPath, `${packName}.code-workspace`), JSON.stringify(packProjectFiles.codeWorkspace, null, 2)),
116
+ ]);
117
+ // Copy template files
118
+ logger.log('Copying template files...');
119
+ const templatePath = getAssetPackTemplatePath(handler.app.isPackaged, handler.app.resourcesPath, getProjectRoot());
120
+ await copyAsync(templatePath, packPath, { recursive: true });
121
+ // Rename gitignore to .gitignore (npm excludes .gitignore files from packages)
122
+ const gitignoreSrc = path.join(packPath, 'gitignore');
123
+ const gitignoreDst = path.join(packPath, '.gitignore');
124
+ if (await existsAsync(gitignoreSrc)) {
125
+ await fs.promises.rename(gitignoreSrc, gitignoreDst);
126
+ }
127
+ // install dependencies
128
+ logger.log('Running pnpm install...');
129
+ await runCommandAsync('pnpm install', packPath, logger);
130
+ // build asset pack
131
+ logger.log('Running pnpm build...');
132
+ await runCommandAsync('pnpm build', packPath, logger);
133
+ // open the asset pack in the file explorer
134
+ handler?.os.openPath(packPath);
135
+ logger.log('Asset pack created successfully');
136
+ return {
137
+ success: true,
138
+ message: `Asset pack created successfully at ${packPath}`,
139
+ path: packPath
140
+ };
141
+ }
142
+ catch (error) {
143
+ return {
144
+ success: false,
145
+ message: `Failed to create asset pack: ${error}`,
146
+ path: packPath,
147
+ error: error,
148
+ };
149
+ }
150
+ }