@viji-dev/sdk 1.0.0
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/.gitignore +29 -0
- package/LICENSE +13 -0
- package/README.md +103 -0
- package/bin/viji.js +75 -0
- package/eslint.config.js +37 -0
- package/index.html +20 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/public/favicon.png +0 -0
- package/scenes/audio-visualizer/main.js +287 -0
- package/scenes/core-demo/main.js +532 -0
- package/scenes/demo-scene/main.js +619 -0
- package/scenes/global.d.ts +15 -0
- package/scenes/particle-system/main.js +349 -0
- package/scenes/tsconfig.json +12 -0
- package/scenes/video-mirror/main.ts +436 -0
- package/src/App.css +42 -0
- package/src/App.tsx +279 -0
- package/src/cli/commands/build.js +147 -0
- package/src/cli/commands/create.js +71 -0
- package/src/cli/commands/dev.js +108 -0
- package/src/cli/commands/init.js +262 -0
- package/src/cli/utils/cli-utils.js +208 -0
- package/src/cli/utils/scene-compiler.js +432 -0
- package/src/components/SDKPage.tsx +337 -0
- package/src/components/core/CoreContainer.tsx +126 -0
- package/src/components/ui/DeviceSelectionList.tsx +137 -0
- package/src/components/ui/FPSCounter.tsx +78 -0
- package/src/components/ui/FileDropzonePanel.tsx +120 -0
- package/src/components/ui/FileListPanel.tsx +285 -0
- package/src/components/ui/InputExpansionPanel.tsx +31 -0
- package/src/components/ui/MediaPlayerControls.tsx +191 -0
- package/src/components/ui/MenuContainer.tsx +71 -0
- package/src/components/ui/ParametersMenu.tsx +797 -0
- package/src/components/ui/ProjectSwitcherMenu.tsx +192 -0
- package/src/components/ui/QuickInputControls.tsx +542 -0
- package/src/components/ui/SDKMenuSystem.tsx +96 -0
- package/src/components/ui/SettingsMenu.tsx +346 -0
- package/src/components/ui/SimpleInputControls.tsx +137 -0
- package/src/index.css +68 -0
- package/src/main.tsx +10 -0
- package/src/scenes-hmr.ts +158 -0
- package/src/services/project-filesystem.ts +436 -0
- package/src/stores/scene-player/index.ts +3 -0
- package/src/stores/scene-player/input-manager.store.ts +1045 -0
- package/src/stores/scene-player/scene-session.store.ts +659 -0
- package/src/styles/globals.css +111 -0
- package/src/templates/minimal-template.js +11 -0
- package/src/utils/debounce.js +34 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +18 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +27 -0
- package/vite.config.ts +54 -0
package/src/App.tsx
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { HeroUIProvider } from '@heroui/react';
|
|
3
|
+
import SDKPage from './components/SDKPage';
|
|
4
|
+
import { projectFileSystem } from './services/project-filesystem';
|
|
5
|
+
// Force Vite to include /scenes files in module graph for HMR
|
|
6
|
+
import { sceneHmrCount } from './scenes-hmr';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
interface Project {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
path: string;
|
|
13
|
+
mainFile: string;
|
|
14
|
+
lastModified: Date;
|
|
15
|
+
sceneType: 'javascript' | 'typescript' | 'shader';
|
|
16
|
+
isBuilt: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const App: React.FC = () => {
|
|
20
|
+
// Transform ESM exports to runtime-friendly code for worker eval (initial load)
|
|
21
|
+
// No longer needed - parameter object API scenes are passed directly as strings to VijiCore
|
|
22
|
+
|
|
23
|
+
const [sceneError, setSceneError] = useState<string>('');
|
|
24
|
+
const [currentSceneCode, setCurrentSceneCode] = useState<string>('');
|
|
25
|
+
const [currentProject, setCurrentProject] = useState<Project | undefined>();
|
|
26
|
+
const hmrCooldownUntilRef = useRef<number>(0);
|
|
27
|
+
const lastCodeRef = useRef<string>('');
|
|
28
|
+
useEffect(() => { lastCodeRef.current = currentSceneCode; }, [currentSceneCode]);
|
|
29
|
+
|
|
30
|
+
const hashString = (str: string): string => {
|
|
31
|
+
let hash = 2166136261; // FNV offset basis
|
|
32
|
+
for (let i = 0; i < str.length; i++) {
|
|
33
|
+
hash ^= str.charCodeAt(i);
|
|
34
|
+
hash = (hash * 16777619) >>> 0; // FNV prime, convert to unsigned 32-bit
|
|
35
|
+
}
|
|
36
|
+
return hash.toString(16);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Load first available project on initial load
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
console.log('๐งท scenes HMR files attached:', sceneHmrCount);
|
|
42
|
+
const loadInitialProject = async () => {
|
|
43
|
+
if (!currentProject) {
|
|
44
|
+
try {
|
|
45
|
+
const projects = await projectFileSystem.getProjects();
|
|
46
|
+
if (projects.length > 0) {
|
|
47
|
+
// Convert ProjectInfo to Project format
|
|
48
|
+
const firstProject: Project = {
|
|
49
|
+
id: projects[0].id,
|
|
50
|
+
name: projects[0].name,
|
|
51
|
+
path: projects[0].path,
|
|
52
|
+
mainFile: projects[0].mainFile,
|
|
53
|
+
lastModified: projects[0].lastModified,
|
|
54
|
+
sceneType: projects[0].projectType,
|
|
55
|
+
isBuilt: projects[0].isBuilt || false,
|
|
56
|
+
};
|
|
57
|
+
await handleProjectSwitch(firstProject);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('โ Failed to load initial project:', error);
|
|
61
|
+
setSceneError('No projects found. Create a project to get started.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
loadInitialProject();
|
|
66
|
+
}, [currentProject]);
|
|
67
|
+
// Vite HMR: watch current project's files and reload scene code on save
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const hot: any = (import.meta as any).hot;
|
|
70
|
+
if (!currentProject || !hot) return;
|
|
71
|
+
|
|
72
|
+
const stripQuery = (p: string) => (p || '').replace(/\?.*$/, '');
|
|
73
|
+
const normalize = (p: string) => stripQuery(p).replace(/^\/@fs\//, '/');
|
|
74
|
+
|
|
75
|
+
const onAfterUpdate = async (payload: any) => {
|
|
76
|
+
try {
|
|
77
|
+
if (Date.now() < hmrCooldownUntilRef.current) return;
|
|
78
|
+
const updates = payload?.updates || [];
|
|
79
|
+
console.log('๐ฐ๏ธ Vite HMR payload:', updates);
|
|
80
|
+
// Ignore HMR updates that come from virtual modules or outside /scenes
|
|
81
|
+
const changed = updates.some((u: any) => {
|
|
82
|
+
const p = normalize(u.path || u.acceptedPath || '');
|
|
83
|
+
// Any file change under the active project's folder triggers reload (multi-file support)
|
|
84
|
+
const hit = p.includes(`/scenes/${currentProject.id}/`);
|
|
85
|
+
if (hit) console.log('๐ Matched HMR path:', p);
|
|
86
|
+
return hit;
|
|
87
|
+
});
|
|
88
|
+
if (!changed) return;
|
|
89
|
+
|
|
90
|
+
console.log(`๐ฅ HMR detected change in ${currentProject.name}, reloading scene code...`);
|
|
91
|
+
// For multi-file projects, bundle local relative imports too
|
|
92
|
+
const code = await projectFileSystem.loadBundledProjectCode(currentProject.id);
|
|
93
|
+
const prevHash = hashString(lastCodeRef.current || '');
|
|
94
|
+
const nextHash = hashString(code || '');
|
|
95
|
+
console.log('โป๏ธ [HMR] reloading active project code', {
|
|
96
|
+
project: currentProject.id,
|
|
97
|
+
prevLen: lastCodeRef.current?.length || 0,
|
|
98
|
+
nextLen: code.length,
|
|
99
|
+
prevHash,
|
|
100
|
+
nextHash
|
|
101
|
+
});
|
|
102
|
+
setSceneError('');
|
|
103
|
+
setCurrentSceneCode(code);
|
|
104
|
+
} catch (e: any) {
|
|
105
|
+
console.error('โ HMR reload failed:', e);
|
|
106
|
+
setSceneError(e?.message || 'Hot reload failed');
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const onBeforeUpdate = (payload: any) => {
|
|
111
|
+
try {
|
|
112
|
+
console.log('๐ฐ๏ธ Vite HMR beforeUpdate:', payload?.updates);
|
|
113
|
+
} catch {}
|
|
114
|
+
};
|
|
115
|
+
const onInvalidate = (payload: any) => {
|
|
116
|
+
try {
|
|
117
|
+
console.log('๐ฐ๏ธ Vite HMR invalidate:', payload?.path || payload);
|
|
118
|
+
} catch {}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
hot.on('vite:beforeUpdate', onBeforeUpdate);
|
|
122
|
+
hot.on('vite:afterUpdate', onAfterUpdate);
|
|
123
|
+
hot.on('vite:invalidate', onInvalidate);
|
|
124
|
+
return () => {
|
|
125
|
+
try {
|
|
126
|
+
hot.off?.('vite:beforeUpdate', onBeforeUpdate);
|
|
127
|
+
hot.off?.('vite:afterUpdate', onAfterUpdate);
|
|
128
|
+
hot.off?.('vite:invalidate', onInvalidate);
|
|
129
|
+
} catch {}
|
|
130
|
+
};
|
|
131
|
+
}, [currentProject?.id]);
|
|
132
|
+
|
|
133
|
+
// Also listen to custom scene file update events and reload code
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!currentProject) return;
|
|
136
|
+
const handler = async (e: any) => {
|
|
137
|
+
try {
|
|
138
|
+
const path = e?.detail?.path || '';
|
|
139
|
+
if (!path.includes(`/scenes/${currentProject.id}/`)) return;
|
|
140
|
+
|
|
141
|
+
console.log('๐งจ [Scenes HMR] event for active project:', path);
|
|
142
|
+
const code = await projectFileSystem.loadBundledProjectCode(currentProject.id);
|
|
143
|
+
// Debug logs disabled - HMR working correctly
|
|
144
|
+
|
|
145
|
+
console.log('๐งจ [Scenes HMR] applying bundle for active project', {
|
|
146
|
+
path,
|
|
147
|
+
project: currentProject.id,
|
|
148
|
+
prevLen: lastCodeRef.current?.length || 0,
|
|
149
|
+
nextLen: code.length,
|
|
150
|
+
codeChanged: lastCodeRef.current !== code
|
|
151
|
+
});
|
|
152
|
+
setSceneError('');
|
|
153
|
+
// Always force update when file is saved, regardless of content
|
|
154
|
+
// Add a timestamp comment to ensure React always sees it as a new value
|
|
155
|
+
const timestamp = Date.now();
|
|
156
|
+
const timestampedCode = `// File modified: ${timestamp}\n${code}`;
|
|
157
|
+
|
|
158
|
+
console.log('๐ [App] Setting new scene code with timestamp:', {
|
|
159
|
+
oldLen: currentSceneCode.length,
|
|
160
|
+
newLen: timestampedCode.length,
|
|
161
|
+
timestamp: timestamp
|
|
162
|
+
});
|
|
163
|
+
setCurrentSceneCode(timestampedCode);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error('โ Scenes HMR event reload failed:', err);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
window.addEventListener('viji-scene-file-updated', handler as any);
|
|
169
|
+
return () => {
|
|
170
|
+
window.removeEventListener('viji-scene-file-updated', handler as any);
|
|
171
|
+
};
|
|
172
|
+
}, [currentProject?.id]);
|
|
173
|
+
|
|
174
|
+
// Fallback dev polling: periodically re-read active project's code and update if changed
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!currentProject) return;
|
|
177
|
+
let cancelled = false;
|
|
178
|
+
const interval = window.setInterval(async () => {
|
|
179
|
+
if (cancelled) return;
|
|
180
|
+
if (Date.now() < hmrCooldownUntilRef.current) return;
|
|
181
|
+
try {
|
|
182
|
+
const code = await projectFileSystem.loadBundledProjectCode(currentProject.id);
|
|
183
|
+
if (code && code !== lastCodeRef.current) {
|
|
184
|
+
setSceneError('');
|
|
185
|
+
setCurrentSceneCode(code);
|
|
186
|
+
}
|
|
187
|
+
} catch {}
|
|
188
|
+
}, 900);
|
|
189
|
+
return () => {
|
|
190
|
+
cancelled = true;
|
|
191
|
+
try { window.clearInterval(interval); } catch {}
|
|
192
|
+
};
|
|
193
|
+
}, [currentProject?.id]);
|
|
194
|
+
|
|
195
|
+
const handleSceneError = (error: string) => {
|
|
196
|
+
setSceneError(error);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
// Project management functions
|
|
201
|
+
const handleProjectSwitch = async (project: Project) => {
|
|
202
|
+
console.log('๐ฏ USER CLICKED PROJECT:', {
|
|
203
|
+
id: project.id,
|
|
204
|
+
name: project.name,
|
|
205
|
+
path: project.path,
|
|
206
|
+
mainFile: project.mainFile
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const requestId = Symbol(project.id);
|
|
210
|
+
const latestRequestRef = (handleProjectSwitch as any).latestRef || { current: requestId };
|
|
211
|
+
latestRequestRef.current = requestId;
|
|
212
|
+
(handleProjectSwitch as any).latestRef = latestRequestRef;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Load scene code dynamically from the filesystem
|
|
216
|
+
// Scene code is now in parameter object API format - passed directly to VijiCore
|
|
217
|
+
console.log(`๐ Loading project with ID: "${project.id}"`);
|
|
218
|
+
const sceneCode = await projectFileSystem.loadBundledProjectCode(project.id);
|
|
219
|
+
// Guard against out-of-order async resolutions
|
|
220
|
+
if (latestRequestRef.current !== requestId) return;
|
|
221
|
+
// Update both the selected project and its code together so effects see a consistent pair
|
|
222
|
+
setCurrentProject(project);
|
|
223
|
+
setCurrentSceneCode(sceneCode);
|
|
224
|
+
setSceneError(''); // Clear any previous errors
|
|
225
|
+
// avoid immediate HMR loop right after loading
|
|
226
|
+
hmrCooldownUntilRef.current = Date.now() + 1000;
|
|
227
|
+
console.log('โ
Successfully loaded scene for:', project.name, `(${sceneCode.length} chars)`);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
230
|
+
setSceneError(`Failed to load scene: ${errorMessage}`);
|
|
231
|
+
console.error('โ Failed to load scene code for project ID:', project.id, 'Project name:', project.name, 'Error:', error);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Note: UI CRUD handlers removed - project management now done manually/CLI
|
|
236
|
+
|
|
237
|
+
// Error display component
|
|
238
|
+
if (sceneError) {
|
|
239
|
+
return (
|
|
240
|
+
<HeroUIProvider>
|
|
241
|
+
<div className="fixed inset-0 bg-black flex items-center justify-center">
|
|
242
|
+
<div className="text-center text-white max-w-lg p-8">
|
|
243
|
+
<h2 className="text-xl font-bold mb-4 text-red-400">Scene Error</h2>
|
|
244
|
+
<pre className="text-sm bg-red-900/20 p-4 rounded border border-red-500/50 overflow-auto whitespace-pre-wrap">
|
|
245
|
+
{sceneError}
|
|
246
|
+
</pre>
|
|
247
|
+
<div className="flex space-x-2 mt-4">
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => setSceneError('')}
|
|
250
|
+
className="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded"
|
|
251
|
+
>
|
|
252
|
+
Dismiss Error
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</HeroUIProvider>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<HeroUIProvider>
|
|
265
|
+
<div className="relative">
|
|
266
|
+
<SDKPage
|
|
267
|
+
sceneCode={currentSceneCode}
|
|
268
|
+
onSceneError={handleSceneError}
|
|
269
|
+
currentProject={currentProject}
|
|
270
|
+
onProjectSwitch={handleProjectSwitch}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
</div>
|
|
274
|
+
</HeroUIProvider>
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export default App;
|
|
279
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Command - Compile scenes for platform deployment
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
6
|
+
import { dirname, resolve, join } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { SceneCompiler } from '../utils/scene-compiler.js';
|
|
9
|
+
import { validateProject, createSpinner, formatFileSize, formatDuration, isVijiWorkspace, findWorkspaceRoot } from '../utils/cli-utils.js';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
export async function buildCommand(sceneName, options) {
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
const spinner = createSpinner('Building Viji scene...');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Check if we're in a Viji workspace
|
|
20
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
21
|
+
if (!workspaceRoot) {
|
|
22
|
+
console.error('โ Not in a Viji workspace.');
|
|
23
|
+
console.log('๐ก Run "viji init my-workspace" to create a workspace first');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('๐ Workspace:', workspaceRoot);
|
|
28
|
+
|
|
29
|
+
// If no scene name provided, show available scenes
|
|
30
|
+
if (!sceneName) {
|
|
31
|
+
console.error('โ Please specify a scene name to build');
|
|
32
|
+
console.log('๐ก Usage: viji build <scene-name>');
|
|
33
|
+
// TODO: List available scenes
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validate scene exists
|
|
38
|
+
const scenePath = join(workspaceRoot, 'scenes', sceneName);
|
|
39
|
+
const validation = validateProject(scenePath);
|
|
40
|
+
if (!validation.valid) {
|
|
41
|
+
console.error(`โ Invalid scene "${sceneName}":`);
|
|
42
|
+
validation.issues.forEach(issue => console.error(` โข ${issue}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { projectDir, mainScene } = validation.paths;
|
|
47
|
+
|
|
48
|
+
// Parse build options
|
|
49
|
+
const outputFile = resolve(projectDir, options.output || 'dist/scene.js');
|
|
50
|
+
const outputDir = dirname(outputFile);
|
|
51
|
+
|
|
52
|
+
console.log('๐ฆ Building Viji scene for deployment...');
|
|
53
|
+
console.log('๐ Project:', projectDir);
|
|
54
|
+
console.log('๐ Scene file:', mainScene);
|
|
55
|
+
console.log('๐ค Output:', outputFile);
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
// Create output directory
|
|
59
|
+
await mkdir(outputDir, { recursive: true });
|
|
60
|
+
|
|
61
|
+
// Initialize compiler with options
|
|
62
|
+
const compiler = new SceneCompiler({ minify: false });
|
|
63
|
+
|
|
64
|
+
spinner.start();
|
|
65
|
+
spinner.update('Analyzing scene structure...');
|
|
66
|
+
|
|
67
|
+
// Compile scene
|
|
68
|
+
const buildResult = await compiler.compile(mainScene, outputFile);
|
|
69
|
+
|
|
70
|
+
spinner.update('Writing output files...');
|
|
71
|
+
|
|
72
|
+
// Write main bundle
|
|
73
|
+
await writeFile(outputFile, buildResult.code);
|
|
74
|
+
|
|
75
|
+
// No source maps/minify in current simple build
|
|
76
|
+
|
|
77
|
+
// Write metadata
|
|
78
|
+
const metadataFile = outputFile.replace(/\.js$/i, '.meta.json');
|
|
79
|
+
await writeFile(metadataFile, JSON.stringify(buildResult.metadata, null, 2));
|
|
80
|
+
|
|
81
|
+
const buildTime = Date.now() - startTime;
|
|
82
|
+
spinner.stop('โ
Build completed successfully!');
|
|
83
|
+
|
|
84
|
+
// Display results
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log('๐ Build Results:');
|
|
87
|
+
console.log(` ๐ฆ Bundle size: ${formatFileSize(buildResult.code.length)}`);
|
|
88
|
+
console.log(` โฑ๏ธ Build time: ${formatDuration(buildTime)}`);
|
|
89
|
+
console.log(` ๐ฏ Scene file: ${outputFile}`);
|
|
90
|
+
console.log(` ๐ Metadata: ${metadataFile}`);
|
|
91
|
+
|
|
92
|
+
if (options.sourceMap && buildResult.sourceMap) {
|
|
93
|
+
console.log(` ๐บ๏ธ Source map: ${outputFile}.map`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Display scene info
|
|
97
|
+
const scene = buildResult.metadata.scene;
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log('๐จ Scene Info:');
|
|
100
|
+
console.log(` ๐๏ธ Parameters: ${scene.parameters.length} groups`);
|
|
101
|
+
console.log(` ๐ค Exports: ${scene.exports.join(', ')}`);
|
|
102
|
+
console.log(` ๐ Dependencies: ${scene.dependencies}`);
|
|
103
|
+
console.log(` ๐ผ๏ธ Assets: ${scene.assets}`);
|
|
104
|
+
console.log(` ๐จ Shaders: ${scene.shaders}`);
|
|
105
|
+
console.log(` ๐ Complexity: ${buildResult.metadata.performance.complexity}`);
|
|
106
|
+
console.log(` ๐พ Est. Memory: ${buildResult.metadata.performance.estimatedMemory}`);
|
|
107
|
+
|
|
108
|
+
if (buildResult.metadata.performance.features.length > 0) {
|
|
109
|
+
console.log(` โจ Features: ${buildResult.metadata.performance.features.join(', ')}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Display warnings
|
|
113
|
+
if (buildResult.warnings.length > 0) {
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log('โ ๏ธ Warnings:');
|
|
116
|
+
buildResult.warnings.forEach(warning => {
|
|
117
|
+
console.log(` โข ${warning}`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Display validation issues
|
|
122
|
+
if (!scene.validation.isValid) {
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('โ Validation Issues:');
|
|
125
|
+
scene.validation.issues.forEach(issue => {
|
|
126
|
+
console.log(` โข ${issue}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log('๐ Your scene is ready for deployment!');
|
|
132
|
+
console.log('๐ Upload the bundle to the Viji platform to share your creation.');
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
spinner.stop('๐ฅ Build failed');
|
|
136
|
+
console.error('');
|
|
137
|
+
console.error('๐ฅ Build failed:', error.message);
|
|
138
|
+
|
|
139
|
+
if (error.stack && process.env.DEBUG) {
|
|
140
|
+
console.error('');
|
|
141
|
+
console.error('Stack trace:');
|
|
142
|
+
console.error(error.stack);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Command - Generate new Viji scene projects (minimal)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { join, resolve } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import { isVijiWorkspace, findWorkspaceRoot } from '../utils/cli-utils.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Shared minimal template (single source of truth)
|
|
16
|
+
import { getMinimalSceneTemplate } from '../../templates/minimal-template.js';
|
|
17
|
+
|
|
18
|
+
export async function createCommand(sceneName, options) {
|
|
19
|
+
try {
|
|
20
|
+
console.log('๐จ Creating new Viji scene...');
|
|
21
|
+
console.log(`๐ Scene: ${sceneName}`);
|
|
22
|
+
console.log(`๐ฏ Language: ${options.lang || 'js'}`);
|
|
23
|
+
|
|
24
|
+
// Check if we're in a Viji workspace
|
|
25
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
26
|
+
if (!workspaceRoot) {
|
|
27
|
+
console.error('โ Not in a Viji workspace.');
|
|
28
|
+
console.log('๐ก Run "viji init my-workspace" to create a workspace first');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('๐ Workspace:', workspaceRoot);
|
|
33
|
+
|
|
34
|
+
// Create scene inside workspace scenes/ directory
|
|
35
|
+
const scenesDir = join(workspaceRoot, 'scenes');
|
|
36
|
+
const scenePath = join(scenesDir, sceneName);
|
|
37
|
+
|
|
38
|
+
// Ensure scene doesn't already exist
|
|
39
|
+
if (existsSync(scenePath)) {
|
|
40
|
+
console.error(`โ Scene already exists: ${scenePath}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create scene folder
|
|
45
|
+
await mkdir(scenePath, { recursive: true });
|
|
46
|
+
console.log(`โ
Created scene directory: ${scenePath}`);
|
|
47
|
+
|
|
48
|
+
// Write minimal scene
|
|
49
|
+
const lang = options.lang === 'ts' ? 'ts' : 'js';
|
|
50
|
+
const mainName = lang === 'ts' ? 'main.ts' : 'main.js';
|
|
51
|
+
const mainPath = join(scenePath, mainName);
|
|
52
|
+
|
|
53
|
+
const contents = getMinimalSceneTemplate(lang)
|
|
54
|
+
.replace('New TypeScript Scene', `${sceneName} - minimal scene`)
|
|
55
|
+
.replace('New JavaScript Scene', `${sceneName} - minimal scene`);
|
|
56
|
+
|
|
57
|
+
await writeFile(mainPath, contents);
|
|
58
|
+
console.log(`๐ Created scene file: ${join(scenePath, mainName)}`);
|
|
59
|
+
|
|
60
|
+
console.log('๐ Scene created successfully!');
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('๐ Next steps:');
|
|
63
|
+
console.log(' viji dev # Start development server');
|
|
64
|
+
console.log(` # Edit scenes/${sceneName}/${mainName} in your IDE`);
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log('๐ Happy coding!');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('๐ฅ Failed to create scene:', error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Command - Start development server with hot reload
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { join, resolve } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname } from 'path';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
import { isVijiWorkspace, findWorkspaceRoot } from '../utils/cli-utils.js';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
export async function devCommand(options) {
|
|
17
|
+
try {
|
|
18
|
+
console.log('๐ฅ Starting Viji development server...');
|
|
19
|
+
|
|
20
|
+
// Check if we're in a Viji workspace
|
|
21
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
22
|
+
if (!workspaceRoot) {
|
|
23
|
+
console.error('โ Not in a Viji workspace.');
|
|
24
|
+
console.log('๐ก Run "viji init my-workspace" to create a workspace first');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('๐ Workspace:', workspaceRoot);
|
|
29
|
+
await startDevServer(workspaceRoot, options);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('๐ฅ Failed to start development server:', error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function startDevServer(workspaceRoot, options) {
|
|
37
|
+
const { port, host, open } = options;
|
|
38
|
+
|
|
39
|
+
console.log(`๐ Development server will start on: http://${host}:${port}`);
|
|
40
|
+
|
|
41
|
+
// Build Vite args and run in workspace directory
|
|
42
|
+
const viteArgs = ['run', 'dev', '--', '--port', String(port), '--host', host];
|
|
43
|
+
if (open) viteArgs.push('--open');
|
|
44
|
+
|
|
45
|
+
const viteProcess = spawn('npm', viteArgs, {
|
|
46
|
+
cwd: workspaceRoot,
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
env: {
|
|
49
|
+
...process.env
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Handle process termination
|
|
54
|
+
process.on('SIGINT', () => {
|
|
55
|
+
console.log('\n๐ Stopping development server...');
|
|
56
|
+
viteProcess.kill('SIGINT');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
process.on('SIGTERM', () => {
|
|
61
|
+
viteProcess.kill('SIGTERM');
|
|
62
|
+
process.exit(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
viteProcess.on('exit', (code) => {
|
|
66
|
+
if (code !== 0) {
|
|
67
|
+
console.error(`โ Development server exited with code ${code}`);
|
|
68
|
+
process.exit(code);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
console.log('โ
Development server started!');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function findSDKDirectory() {
|
|
76
|
+
// Try to find SDK directory from current working directory upwards
|
|
77
|
+
let currentPath = process.cwd();
|
|
78
|
+
|
|
79
|
+
while (currentPath !== '/') {
|
|
80
|
+
const potentialSDK = join(currentPath, 'node_modules', 'viji-sdk');
|
|
81
|
+
if (existsSync(potentialSDK)) {
|
|
82
|
+
return potentialSDK;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parentPath = dirname(currentPath);
|
|
86
|
+
if (parentPath === currentPath) break;
|
|
87
|
+
currentPath = parentPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Try global installation
|
|
91
|
+
try {
|
|
92
|
+
const require = createRequire(import.meta.url);
|
|
93
|
+
const globalSDK = require.resolve('viji-sdk');
|
|
94
|
+
if (globalSDK) {
|
|
95
|
+
return dirname(dirname(globalSDK)); // Go up from lib/index.js to root
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Not found globally
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Try relative to this CLI file
|
|
102
|
+
const relativeSDK = resolve(__dirname, '..', '..', '..');
|
|
103
|
+
if (existsSync(join(relativeSDK, 'package.json'))) {
|
|
104
|
+
return relativeSDK;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null;
|
|
108
|
+
}
|