@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.
- package/README.md +70 -63
- package/bin/viji.js +9 -29
- package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
- package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
- package/dist/assets/core-CiQx3w0t.js +12 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
- package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
- package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
- package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
- package/dist/assets/glsl-DMyvO4G4.js +1 -0
- package/dist/assets/index-BhFxsauQ.js +215 -0
- package/dist/assets/index-BqhVeA7U.css +1 -0
- package/dist/assets/index-T4TOjvD0.js +1 -0
- package/dist/assets/index-Wz9WqGqz.js +52 -0
- package/dist/assets/index-t24aGwla.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +31 -35
- package/src/cli/commands/build.js +50 -99
- package/src/cli/commands/create.js +49 -46
- package/src/cli/commands/dev.js +30 -97
- package/src/cli/server/dev-server.js +233 -0
- package/src/cli/server/scene-scanner.js +93 -0
- package/src/cli/server/vite-scene-plugin.d.ts +2 -0
- package/src/cli/server/vite-scene-plugin.js +134 -0
- package/src/cli/utils/cli-utils.js +29 -139
- package/src/cli/utils/scene-compiler.js +10 -17
- package/src/templates/scene-templates.js +85 -0
- package/.gitignore +0 -29
- package/eslint.config.js +0 -37
- package/postcss.config.js +0 -6
- package/scenes/audio-visualizer/main.js +0 -287
- package/scenes/core-demo/main.js +0 -532
- package/scenes/demo-scene/main.js +0 -619
- package/scenes/global.d.ts +0 -15
- package/scenes/particle-system/main.js +0 -349
- package/scenes/tsconfig.json +0 -12
- package/scenes/video-mirror/main.ts +0 -436
- package/src/App.css +0 -42
- package/src/App.tsx +0 -279
- package/src/cli/commands/init.js +0 -262
- package/src/components/SDKPage.tsx +0 -337
- package/src/components/core/CoreContainer.tsx +0 -126
- package/src/components/ui/DeviceSelectionList.tsx +0 -137
- package/src/components/ui/FPSCounter.tsx +0 -78
- package/src/components/ui/FileDropzonePanel.tsx +0 -120
- package/src/components/ui/FileListPanel.tsx +0 -285
- package/src/components/ui/InputExpansionPanel.tsx +0 -31
- package/src/components/ui/MediaPlayerControls.tsx +0 -191
- package/src/components/ui/MenuContainer.tsx +0 -71
- package/src/components/ui/ParametersMenu.tsx +0 -797
- package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
- package/src/components/ui/QuickInputControls.tsx +0 -542
- package/src/components/ui/SDKMenuSystem.tsx +0 -96
- package/src/components/ui/SettingsMenu.tsx +0 -346
- package/src/components/ui/SimpleInputControls.tsx +0 -137
- package/src/index.css +0 -68
- package/src/main.tsx +0 -10
- package/src/scenes-hmr.ts +0 -158
- package/src/services/project-filesystem.ts +0 -436
- package/src/stores/scene-player/index.ts +0 -3
- package/src/stores/scene-player/input-manager.store.ts +0 -1045
- package/src/stores/scene-player/scene-session.store.ts +0 -659
- package/src/styles/globals.css +0 -111
- package/src/templates/minimal-template.js +0 -11
- package/src/utils/debounce.js +0 -34
- package/src/vite-env.d.ts +0 -1
- package/tailwind.config.js +0 -18
- package/tsconfig.app.json +0 -27
- package/tsconfig.json +0 -27
- package/tsconfig.node.json +0 -27
- package/vite.config.ts +0 -54
- /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();
|