@viji-dev/sdk 1.0.0 → 1.0.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 (77) hide show
  1. package/README.md +70 -63
  2. package/bin/viji.js +9 -29
  3. package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
  4. package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
  5. package/dist/assets/core-CiQx3w0t.js +12 -0
  6. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  7. package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
  8. package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
  9. package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
  10. package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
  11. package/dist/assets/glsl-DMyvO4G4.js +1 -0
  12. package/dist/assets/index-BhFxsauQ.js +215 -0
  13. package/dist/assets/index-BqhVeA7U.css +1 -0
  14. package/dist/assets/index-T4TOjvD0.js +1 -0
  15. package/dist/assets/index-Wz9WqGqz.js +52 -0
  16. package/dist/assets/index-t24aGwla.js +1 -0
  17. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  18. package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
  19. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  20. package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
  21. package/{index.html → dist/index.html} +2 -1
  22. package/package.json +31 -35
  23. package/src/cli/commands/build.js +50 -99
  24. package/src/cli/commands/create.js +49 -46
  25. package/src/cli/commands/dev.js +30 -97
  26. package/src/cli/server/dev-server.js +233 -0
  27. package/src/cli/server/scene-scanner.js +93 -0
  28. package/src/cli/server/vite-scene-plugin.d.ts +2 -0
  29. package/src/cli/server/vite-scene-plugin.js +134 -0
  30. package/src/cli/utils/cli-utils.js +29 -139
  31. package/src/cli/utils/scene-compiler.js +10 -17
  32. package/src/templates/scene-templates.js +85 -0
  33. package/.gitignore +0 -29
  34. package/eslint.config.js +0 -37
  35. package/postcss.config.js +0 -6
  36. package/scenes/audio-visualizer/main.js +0 -287
  37. package/scenes/core-demo/main.js +0 -532
  38. package/scenes/demo-scene/main.js +0 -619
  39. package/scenes/global.d.ts +0 -15
  40. package/scenes/particle-system/main.js +0 -349
  41. package/scenes/tsconfig.json +0 -12
  42. package/scenes/video-mirror/main.ts +0 -436
  43. package/src/App.css +0 -42
  44. package/src/App.tsx +0 -279
  45. package/src/cli/commands/init.js +0 -262
  46. package/src/components/SDKPage.tsx +0 -337
  47. package/src/components/core/CoreContainer.tsx +0 -126
  48. package/src/components/ui/DeviceSelectionList.tsx +0 -137
  49. package/src/components/ui/FPSCounter.tsx +0 -78
  50. package/src/components/ui/FileDropzonePanel.tsx +0 -120
  51. package/src/components/ui/FileListPanel.tsx +0 -285
  52. package/src/components/ui/InputExpansionPanel.tsx +0 -31
  53. package/src/components/ui/MediaPlayerControls.tsx +0 -191
  54. package/src/components/ui/MenuContainer.tsx +0 -71
  55. package/src/components/ui/ParametersMenu.tsx +0 -797
  56. package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
  57. package/src/components/ui/QuickInputControls.tsx +0 -542
  58. package/src/components/ui/SDKMenuSystem.tsx +0 -96
  59. package/src/components/ui/SettingsMenu.tsx +0 -346
  60. package/src/components/ui/SimpleInputControls.tsx +0 -137
  61. package/src/index.css +0 -68
  62. package/src/main.tsx +0 -10
  63. package/src/scenes-hmr.ts +0 -158
  64. package/src/services/project-filesystem.ts +0 -436
  65. package/src/stores/scene-player/index.ts +0 -3
  66. package/src/stores/scene-player/input-manager.store.ts +0 -1045
  67. package/src/stores/scene-player/scene-session.store.ts +0 -659
  68. package/src/styles/globals.css +0 -111
  69. package/src/templates/minimal-template.js +0 -11
  70. package/src/utils/debounce.js +0 -34
  71. package/src/vite-env.d.ts +0 -1
  72. package/tailwind.config.js +0 -18
  73. package/tsconfig.app.json +0 -27
  74. package/tsconfig.json +0 -27
  75. package/tsconfig.node.json +0 -27
  76. package/vite.config.ts +0 -54
  77. /package/{public → dist}/favicon.png +0 -0
package/src/scenes-hmr.ts DELETED
@@ -1,158 +0,0 @@
1
- // Load all scene files eagerly as raw; seed a live map we can update on HMR
2
- const initialMapRaw = import.meta.glob('/scenes/**/*.{js,ts,jsx,tsx}', { query: '?raw', import: 'default', eager: true });
3
-
4
- // Import project filesystem to mark source changes
5
- import { projectFileSystem } from './services/project-filesystem';
6
-
7
- // Use global window storage to persist the map across HMR reloads
8
- if (!(window as any).vijiSceneHmrLive) {
9
- (window as any).vijiSceneHmrLive = new Map<string, string>(
10
- Object.entries(initialMapRaw).map(([k, v]) => [k, v as unknown as string])
11
- );
12
- }
13
- export const sceneHmrLive = (window as any).vijiSceneHmrLive as Map<string, string>;
14
-
15
- export const sceneHmrCount = sceneHmrLive.size;
16
-
17
- // Debug: log what files were actually discovered
18
- console.log('🔍 [Scenes HMR] Discovered files:', Array.from(sceneHmrLive.keys()));
19
-
20
- // Import scene files as modules (not raw) for HMR tracking
21
- const sceneModules = import.meta.glob('/scenes/**/*.{js,ts,jsx,tsx}', { eager: false });
22
- // Also prepare lazy loaders for targeted refreshes
23
- const sceneLoaders = import.meta.glob('/scenes/**/*.{js,ts,jsx,tsx}', { query: '?raw', import: 'default' });
24
-
25
- // File change polling mechanism (since Vite HMR doesn't work well with external files)
26
- // Use global window storage to persist across HMR reloads
27
- if (!(window as any).vijiSceneHashes) {
28
- (window as any).vijiSceneHashes = new Map<string, string>();
29
- }
30
- const fileHashes = (window as any).vijiSceneHashes as Map<string, string>;
31
-
32
- // Initialize file hashes only if not already set
33
- async function initializeHashes() {
34
- console.log('🔧 [Scenes HMR] Initializing file hashes for', sceneHmrLive.size, 'files');
35
-
36
- // Update the global map with fresh initial content
37
- for (const [path, content] of Object.entries(initialMapRaw)) {
38
- sceneHmrLive.set(path, content as unknown as string);
39
- }
40
-
41
- for (const [path, content] of sceneHmrLive.entries()) {
42
- const hash = hashContent(content);
43
- if (!fileHashes.has(path)) {
44
- fileHashes.set(path, hash);
45
- console.log('🔧 [Scenes HMR] Initial hash for', path, ':', hash, `(${content.length} chars)`);
46
- } else {
47
- const oldHash = fileHashes.get(path);
48
- console.log('🔧 [Scenes HMR] Hash already exists for', path, ':', oldHash, 'vs current:', hash);
49
-
50
- // If hash changed during initialization, reload fresh content and trigger event
51
- if (oldHash !== hash) {
52
- console.log('🎯 [Scenes HMR] File change detected during init:', path, `(${content.length} chars)`, 'oldHash:', oldHash, 'newHash:', hash);
53
- // Reload fresh content instead of using stale initial content
54
- try {
55
- const loader = sceneLoaders[path];
56
- if (loader) {
57
- const freshContent = await (loader as any)();
58
- const freshHash = hashContent(freshContent);
59
- console.log('🔄 [Scenes HMR] Reloaded fresh content:', path, `(${freshContent.length} chars)`);
60
- fileHashes.set(path, freshHash);
61
- sceneHmrLive.set(path, freshContent);
62
- projectFileSystem.markSourceChanged(path); // Mark source as changed for cache invalidation
63
- window.dispatchEvent(new CustomEvent('viji-scene-file-updated', { detail: { path } }));
64
- } else {
65
- // Fallback to stale content if no loader
66
- fileHashes.set(path, hash);
67
- sceneHmrLive.set(path, content);
68
- window.dispatchEvent(new CustomEvent('viji-scene-file-updated', { detail: { path } }));
69
- }
70
- } catch (err) {
71
- console.warn('⚠️ [Scenes HMR] Failed to reload fresh content for:', path, err);
72
- // Fallback to stale content
73
- fileHashes.set(path, hash);
74
- sceneHmrLive.set(path, content);
75
- window.dispatchEvent(new CustomEvent('viji-scene-file-updated', { detail: { path } }));
76
- }
77
- }
78
- }
79
- }
80
- }
81
-
82
- // Run initialization
83
- initializeHashes();
84
-
85
- function hashContent(content: string): string {
86
- let hash = 2166136261; // FNV offset basis
87
- for (let i = 0; i < content.length; i++) {
88
- hash ^= content.charCodeAt(i);
89
- hash = (hash * 16777619) >>> 0; // FNV prime, convert to unsigned 32-bit
90
- }
91
- return hash.toString(16);
92
- }
93
-
94
- async function checkForFileChanges() {
95
- for (const [path, loader] of Object.entries(sceneLoaders)) {
96
- try {
97
- const currentContent = await (loader as any)();
98
- const currentHash = hashContent(currentContent);
99
- const lastHash = fileHashes.get(path);
100
-
101
- // Debug: show hash comparison for core-demo (only when different)
102
- if (path.includes('core-demo') && lastHash !== currentHash) {
103
- console.log('🔍 [Scenes HMR] Change detected in', path, '- lastHash:', lastHash, 'currentHash:', currentHash);
104
- }
105
-
106
- if (lastHash && lastHash !== currentHash) {
107
- console.log('🎯 [Scenes HMR] file change detected:', path, `(${currentContent.length} chars)`);
108
-
109
- sceneHmrLive.set(path, currentContent);
110
- fileHashes.set(path, currentHash);
111
- projectFileSystem.markSourceChanged(path); // Mark source as changed for cache invalidation
112
- window.dispatchEvent(new CustomEvent('viji-scene-file-updated', { detail: { path } }));
113
- } else if (!lastHash) {
114
- // First time seeing this file
115
- fileHashes.set(path, currentHash);
116
- console.log('🔧 [Scenes HMR] First time hash set for:', path, currentHash);
117
- }
118
- } catch (err) {
119
- console.warn('⚠️ [Scenes HMR] failed to check file:', path, err);
120
- }
121
- }
122
- }
123
-
124
- // Polling disabled - HMR system is working correctly and polling conflicts with it
125
- console.log('🔧 [Scenes HMR] Polling disabled - HMR events are sufficient');
126
-
127
- // Also try HMR as fallback (in case it works for some files)
128
- if (import.meta.hot) {
129
- // Accept updates for each scene module explicitly and refresh live map
130
- // Use regular module imports for HMR tracking, then fetch raw content
131
- for (const p of Object.keys(sceneModules)) {
132
- try {
133
- console.log('🔧 [Scenes HMR] registering hot.accept for:', p);
134
- import.meta.hot.accept(p, async (newModule) => {
135
- try {
136
- console.log('🎯 [Scenes HMR] hot.accept triggered for:', p, newModule);
137
- const loader = sceneLoaders[p];
138
- if (!loader) {
139
- console.warn('⚠️ [Scenes HMR] no loader for:', p);
140
- return;
141
- }
142
- const content = await (loader as any)();
143
- const contentHash = hashContent(content);
144
- sceneHmrLive.set(p, content);
145
- fileHashes.set(p, contentHash);
146
- console.log('🎯 [Scenes HMR] accept handler updated:', p, `(${content.length} chars)`);
147
- window.dispatchEvent(new CustomEvent('viji-scene-file-updated', { detail: { path: p } }));
148
- } catch (err) {
149
- console.warn('⚠️ [Scenes HMR] accept reload failed:', p, err);
150
- }
151
- });
152
- } catch (err) {
153
- console.warn('⚠️ [Scenes HMR] failed to register hot.accept for:', p, err);
154
- }
155
- }
156
- }
157
-
158
-
@@ -1,436 +0,0 @@
1
- /**
2
- * Project File System Service
3
- * Manages folder-based scene projects in the root scenes/ directory
4
- * Each project is a folder containing main.js/main.ts and any additional files
5
- */
6
-
7
- // Browser-compatible path utilities
8
- // Bring in eagerly-tracked scene files (raw strings) so Vite HMR updates contents live
9
- import { sceneHmrLive } from '../scenes-hmr';
10
- const joinPaths = (...paths: string[]): string => {
11
- return paths
12
- .filter(Boolean)
13
- .join('/')
14
- .replace(/\/+/g, '/') // Remove duplicate slashes
15
- .replace(/\/$/, ''); // Remove trailing slash
16
- };
17
-
18
- export interface ProjectInfo {
19
- id: string;
20
- name: string;
21
- path: string;
22
- mainFile: string;
23
- projectType: 'javascript' | 'typescript' | 'shader';
24
- lastModified: Date;
25
- isBuilt: boolean;
26
- distPath?: string;
27
- }
28
-
29
- // Note: CreateProjectOptions and BuildProjectOptions removed - project management moved to CLI
30
-
31
- export class ProjectFileSystemService {
32
- private scenesRoot: string;
33
- private projectsCache = new Map<string, ProjectInfo>();
34
- private changeCallbacks = new Set<(projects: ProjectInfo[]) => void>();
35
- private bundleCache = new Map<string, { content: string; hash: string; timestamp: number; sourceTimestamp: number }>();
36
- // Use global window storage for source change counter to persist across HMR reloads
37
- private get sourceChangeCounter(): Map<string, number> {
38
- if (!(window as any).vijiSourceChangeCounter) {
39
- (window as any).vijiSourceChangeCounter = new Map<string, number>();
40
- }
41
- return (window as any).vijiSourceChangeCounter;
42
- }
43
-
44
- // Note: hashString removed - not needed anymore
45
-
46
- // Increment change counter for a source file (called by HMR system)
47
- public markSourceChanged(filePath: string): void {
48
- const currentCount = this.sourceChangeCounter.get(filePath) || 0;
49
- const newCount = currentCount + 1;
50
- this.sourceChangeCounter.set(filePath, newCount);
51
- console.log('🔄 [Bundler] Source change marked for:', filePath, 'count:', newCount, '(was:', currentCount + ')');
52
- }
53
-
54
- constructor(workspaceRoot: string = '') {
55
- this.scenesRoot = joinPaths(workspaceRoot, 'scenes');
56
- this.initializeDirectory();
57
- // Wire Vite HMR to refresh project list when files in /scenes change
58
- try {
59
- const hot: any = (import.meta as any).hot;
60
- if (hot) {
61
- const normalize = (p: string) => p.replace(/\?.*$/, '').replace(/^\/@fs\//, '/');
62
- const onChange = () => {
63
- // Debounced notify
64
- try { this.notifyChange(); } catch {}
65
- };
66
- hot.on('vite:afterUpdate', (payload: any) => {
67
- const updates = payload?.updates || [];
68
- if (updates.some((u: any) => (normalize(u.path || u.acceptedPath || '')).includes('/scenes/'))) {
69
- onChange();
70
- }
71
- });
72
- hot.on?.('vite:invalidate', (payload: any) => {
73
- const p = normalize(payload?.path || '');
74
- if (p.startsWith('/scenes/')) onChange();
75
- });
76
- }
77
- } catch {}
78
- }
79
-
80
- /**
81
- * Initialize scenes directory if it doesn't exist
82
- */
83
- private async initializeDirectory(): Promise<void> {
84
- try {
85
- console.log(`📁 Initialized scenes directory: ${this.scenesRoot}`);
86
- } catch (error) {
87
- console.error('Failed to initialize scenes directory:', error);
88
- }
89
- }
90
-
91
- /**
92
- * Get all scene projects from the scenes/ folder
93
- */
94
- async getProjects(): Promise<ProjectInfo[]> {
95
- try {
96
- // Use Vite's import.meta.glob to dynamically discover scene projects
97
- const projects = await this.scanProjectsFromFilesystem();
98
-
99
- // Update cache
100
- this.projectsCache.clear();
101
- projects.forEach(project => {
102
- this.projectsCache.set(project.id, project);
103
- });
104
-
105
- return projects;
106
- } catch (error) {
107
- console.error('Failed to scan projects:', error);
108
- return [];
109
- }
110
- }
111
-
112
- /**
113
- * Dynamically scan the scenes/ folder for projects using Vite's import.meta.glob
114
- */
115
- private async scanProjectsFromFilesystem(): Promise<ProjectInfo[]> {
116
- const projects: ProjectInfo[] = [];
117
-
118
- try {
119
- // Use Vite's glob import to discover all main files in scene projects
120
- const sceneModules = import.meta.glob('/scenes/*/main.{js,ts,jsx,tsx}', {
121
- eager: false,
122
- import: 'default'
123
- });
124
-
125
-
126
- console.log('🔍 Discovered scene modules:', Object.keys(sceneModules));
127
-
128
- for (const modulePath of Object.keys(sceneModules)) {
129
- // Extract project information from the path
130
- // Path format: /scenes/project-name/main.{js|ts|jsx|tsx}
131
- const pathMatch = modulePath.match(/^\/scenes\/([^\/]+)\/main\.(js|ts|jsx|tsx)$/);
132
-
133
- if (pathMatch) {
134
- const [, projectId, extension] = pathMatch;
135
- const projectName = this.formatProjectName(projectId);
136
- const projectType = this.getProjectTypeFromExtension(extension);
137
-
138
- console.log(`🔍 Found project: ID="${projectId}", Name="${projectName}", Path="${modulePath}"`);
139
-
140
- // Built status will be determined differently since dynamic globs aren't supported
141
- const isBuilt = false; // TODO: Check built status via different method
142
-
143
- const project: ProjectInfo = {
144
- id: projectId,
145
- name: projectName,
146
- path: `./scenes/${projectId}`,
147
- mainFile: `./scenes/${projectId}/main.${extension}`,
148
- projectType,
149
- lastModified: new Date(), // In real implementation, this would come from file stats
150
- isBuilt,
151
- distPath: isBuilt ? `./scenes/${projectId}/dist` : undefined
152
- };
153
-
154
- // Add to cache for consistent lookup
155
- this.projectsCache.set(projectId, project);
156
-
157
- projects.push(project);
158
- console.log('📁 Discovered project:', project.name, `(${project.projectType}) - ID: ${project.id}`);
159
- }
160
- }
161
-
162
- // Sort projects by name
163
- projects.sort((a, b) => a.name.localeCompare(b.name));
164
-
165
- console.log(`✅ Found ${projects.length} scene projects`);
166
- return projects;
167
-
168
- } catch (error) {
169
- console.error('❌ Failed to scan filesystem for projects:', error);
170
- // Fallback: return empty array so UI still works
171
- return [];
172
- }
173
- }
174
-
175
- /**
176
- * Convert kebab-case project folder names to display names
177
- */
178
- private formatProjectName(projectId: string): string {
179
- // Return folder name as-is (no formatting)
180
- return projectId;
181
- }
182
-
183
- /**
184
- * Determine project type from file extension
185
- */
186
- private getProjectTypeFromExtension(extension: string): 'javascript' | 'typescript' | 'shader' {
187
- switch (extension) {
188
- case 'ts':
189
- case 'tsx':
190
- return 'typescript';
191
- case 'js':
192
- case 'jsx':
193
- default:
194
- return 'javascript';
195
- }
196
- }
197
-
198
- /**
199
- * Convert ESM exports to runtime-friendly declarations for worker eval
200
- */
201
- private transformESMToRuntime(code: string): string {
202
- try {
203
- let out = code;
204
- out = out.replace(/^(\s*)export\s+default\s+/gm, '$1const __default__ = ');
205
- out = out.replace(/^(\s*)export\s+const\s+/gm, '$1const ');
206
- out = out.replace(/^(\s*)export\s+(let|var)\s+/gm, '$1$2 ');
207
- out = out.replace(/^(\s*)export\s+function\s+/gm, '$1function ');
208
- out = out.replace(/^(\s*)export\s+class\s+/gm, '$1class ');
209
- // Remove bare export statements like `export {}` or `export { name }`
210
- out = out.replace(/^\s*export\s*\{[^}]*\}\s*;?\s*$/gm, '');
211
- // Remove type-only exports left after TS stripping (e.g., `export type X = ...;`)
212
- out = out.replace(/^\s*export\s+type\s+[^;]+;?\s*$/gm, '');
213
- // Clean up any dangling `export` tokens that may remain alone on a line
214
- out = out.replace(/^\s*export\s*;?\s*$/gm, '');
215
- return out;
216
- } catch {
217
- return code;
218
- }
219
- }
220
-
221
- /**
222
- * Strip minimal TypeScript syntax to plain JS
223
- */
224
- private stripTypescript(code: string): string {
225
- try {
226
- let out = code;
227
- // 1) Strip types from function parameter lists
228
- out = out.replace(/function\s+([\w$]+)\s*\(([^)]*)\)/g, (_m, name, params) => {
229
- const cleaned = String(params)
230
- .replace(/\?\s*:\s*[^,\)]+/g, '')
231
- .replace(/:\s*[^,\)]+/g, '');
232
- return `function ${name}(${cleaned})`;
233
- });
234
- // 2) Remove return types on functions
235
- out = out.replace(/\)\s*:\s*[A-Za-z_$][A-Za-z0-9_$<>,\[\]\|\s]*\s*\{/g, '){');
236
- // 3) Remove variable type annotations
237
- out = out.replace(/\b(let|const|var)\s+([\w$]+)\s*:\s*[^=;\n]+/g, '$1 $2');
238
- // 4) Remove interfaces and type aliases
239
- out = out.replace(/interface\s+[A-Za-z_$][A-Za-z0-9_$]*\s*\{[\s\S]*?\}\s*/g, '');
240
- out = out.replace(/type\s+[A-Za-z_$][A-Za-z0-9_$]*\s*=\s*[\s\S]*?;\s*/g, '');
241
- // 5) Remove `as Type` assertions
242
- out = out.replace(/\s+as\s+[A-Za-z_$][A-Za-z0-9_$<>,\[\]\|\s]*/g, '');
243
- return out;
244
- } catch {
245
- return code;
246
- }
247
- }
248
-
249
-
250
- // Note: Project creation removed - manage projects manually/CLI
251
-
252
- // Note: Project building removed - use CLI to build projects
253
-
254
- // Note: Project deletion removed - manage projects manually/CLI
255
-
256
- // Note: Project renaming removed - manage projects manually/CLI
257
-
258
- /**
259
- * Get default scene template for new projects
260
- */
261
- private getDefaultSceneTemplate(projectType: 'javascript' | 'typescript' | 'shader'): string {
262
- // Delegate to shared minimal template to avoid duplicates
263
- const lang = projectType === 'typescript' ? 'ts' : 'js';
264
- try {
265
- // Dynamic import to avoid bundling issues in some environments
266
- // Note: For type safety we keep a simple fallback below.
267
- // @ts-ignore
268
- const { getMinimalSceneTemplate } = require('../templates/minimal-template.js');
269
- return getMinimalSceneTemplate(lang);
270
- } catch {
271
- // Fallback minimal template
272
- return lang === 'ts'
273
- ? `// New TypeScript Scene\n\n// Parameter declarations\n// const speed = viji.slider(1.0, { min: 0.1, max: 3.0, label: 'Speed' });\n// const color = viji.color('#ff0000', { label: 'Color' });\n\nfunction render(viji: any) {\n // Your scene code here\n}\n`
274
- : `// New JavaScript Scene\n\n// Parameter declarations\n// const speed = viji.slider(1.0, { min: 0.1, max: 3.0, label: 'Speed' });\n// const color = viji.color('#ff0000', { label: 'Color' });\n\nfunction render(viji) {\n // Your scene code here\n}\n`;
275
- }
276
- }
277
-
278
- // Note: loadProjectCode removed - use loadBundledProjectCode instead
279
-
280
- /**
281
- * Load and bundle a project's main file plus its local relative imports into a single script
282
- */
283
- async loadBundledProjectCode(projectId: string): Promise<string> {
284
- let project = this.projectsCache.get(projectId);
285
- if (!project) {
286
- // Self-heal cache (HMR may recreate the service)
287
- const refreshed = await this.getProjects();
288
- project = refreshed.find(p => p.id === projectId);
289
- if (!project) {
290
- throw new Error(`Project not found: ${projectId}`);
291
- }
292
- }
293
-
294
- // Simple time-based cache to prevent rapid duplicate calls (no content checking)
295
- const cacheKey = projectId;
296
- const cached = this.bundleCache.get(cacheKey);
297
- if (cached && Date.now() - cached.timestamp < 50) { // Very short 50ms cache
298
- console.log('📦 [Bundler] Using recent cached bundle for:', projectId, '(rapid call prevention)');
299
- return cached.content;
300
- }
301
- // discover all scene files as raw loaders
302
- // Prefer eager HMR map for freshest contents
303
- const loaders = import.meta.glob('/scenes/**/*.{js,ts,jsx,tsx}', { eager: false, query: '?raw', import: 'default' });
304
- const tryPaths = [
305
- `/scenes/${projectId}/main.js`,
306
- `/scenes/${projectId}/main.ts`,
307
- `/scenes/${projectId}/main.jsx`,
308
- `/scenes/${projectId}/main.tsx`
309
- ];
310
- const mainPath = tryPaths.find(p => loaders[p] || sceneHmrLive.has(p));
311
- if (!mainPath) {
312
- // Fallback to default template
313
- return this.getDefaultSceneTemplate(project.projectType);
314
- }
315
-
316
- const visited = new Set<string>();
317
- const order: string[] = [];
318
- const read = async (path: string): Promise<string> => {
319
- const eager = sceneHmrLive.get(path);
320
- if (eager) {
321
- // console.log('📚 [Bundler] reading from HMR live map:', path, `(${eager.length} chars)`);
322
- return eager;
323
- }
324
- // console.log('📚 [Bundler] reading from Vite loader:', path);
325
- const content = await (loaders[path] as any)();
326
- // console.log('📚 [Bundler] loaded from Vite:', path, `(${content.length} chars)`);
327
- return content;
328
- };
329
- const resolveRel = (from: string, rel: string): string | null => {
330
- if (!rel.startsWith('.')) return null;
331
- const fromDir = from.replace(/\/[^\/]*$/, '');
332
- const norm = (p: string) => p.replace(/\\+/g, '/').replace(/\/\./g, '/');
333
- const base = norm(joinPaths(fromDir, rel));
334
- const candidates = [
335
- base,
336
- `${base}.js`, `${base}.ts`, `${base}.jsx`, `${base}.tsx`,
337
- `${base}/index.js`, `${base}/index.ts`, `${base}/index.jsx`, `${base}/index.tsx`
338
- ];
339
- for (const c of candidates) if (loaders[c]) return c;
340
- return null;
341
- };
342
- const importRegex = /import\s+[^'"`]+from\s+['"]([^'"`]+)['"]/g;
343
-
344
- const dfs = async (path: string) => {
345
- if (visited.has(path)) return;
346
- visited.add(path);
347
- let code = await read(path);
348
- // console.log('📚 [Bundler] reading:', path, `(${code.length} chars)`);
349
- // follow local relative imports
350
- const rels: string[] = [];
351
- for (const m of code.matchAll(importRegex) as any) {
352
- const rel = m[1];
353
- const resolved = resolveRel(path, rel);
354
- if (resolved) rels.push(resolved);
355
- }
356
- for (const dep of rels) await dfs(dep);
357
- order.push(path);
358
- };
359
- await dfs(mainPath);
360
-
361
- // concatenate in order (deps first, main last)
362
- let bundled = '';
363
- for (const p of order) {
364
- let source = await read(p);
365
-
366
- // Debug: Show line 230 content for core-demo and source info (disabled - HMR working)
367
- // if (p.includes('core-demo')) {
368
- // const lines = source.split('\n');
369
- // const isFromHmr = sceneHmrLive.has(p);
370
- // if (lines.length > 230) {
371
- // console.log('🎨 [Bundler] Line 230 in', p, ':', JSON.stringify(lines[229]), `(source: ${isFromHmr ? 'HMR-live' : 'Vite-loader'})`);
372
- // }
373
- // }
374
-
375
- if (p.endsWith('.ts') || p.endsWith('.tsx')) source = this.stripTypescript(source);
376
- source = this.transformESMToRuntime(source);
377
- bundled += `\n// File: ${p}\n` + source + `\n`;
378
- }
379
-
380
- // Update cache with new bundle (simple time-based cache)
381
- this.bundleCache.set(cacheKey, {
382
- content: bundled,
383
- hash: '', // Not used anymore
384
- timestamp: Date.now(),
385
- sourceTimestamp: 0 // Not used anymore
386
- });
387
-
388
- // console.log('🧩 [Bundler] final bundle length:', bundled.length);
389
- return bundled;
390
- }
391
-
392
- /**
393
- * Watch for changes in the scenes directory
394
- */
395
- onProjectsChange(callback: (projects: ProjectInfo[]) => void): () => void {
396
- this.changeCallbacks.add(callback);
397
-
398
- // Return unsubscribe function
399
- return () => {
400
- this.changeCallbacks.delete(callback);
401
- };
402
- }
403
-
404
- /**
405
- * Refresh projects list from filesystem
406
- */
407
- async refreshProjects(): Promise<ProjectInfo[]> {
408
- return await this.getProjects();
409
- }
410
-
411
- // Private helper methods
412
-
413
- // Note: normalizeProjectName removed - not needed for read-only operations
414
-
415
- private notifyChange(): void {
416
- // Notify all listeners asynchronously
417
- setTimeout(async () => {
418
- const projects = await this.getProjects();
419
- this.changeCallbacks.forEach(callback => {
420
- try {
421
- callback(projects);
422
- } catch (error) {
423
- console.error('Error in project change callback:', error);
424
- }
425
- });
426
- }, 0);
427
- }
428
-
429
-
430
- // Note: buildProjectInternal removed - building moved to CLI
431
-
432
-
433
- }
434
-
435
- // Default singleton instance
436
- export const projectFileSystem = new ProjectFileSystemService();
@@ -1,3 +0,0 @@
1
- // Re-export all store types and hooks
2
- export * from './scene-session.store';
3
- export * from './input-manager.store';