@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.
Files changed (55) hide show
  1. package/.gitignore +29 -0
  2. package/LICENSE +13 -0
  3. package/README.md +103 -0
  4. package/bin/viji.js +75 -0
  5. package/eslint.config.js +37 -0
  6. package/index.html +20 -0
  7. package/package.json +82 -0
  8. package/postcss.config.js +6 -0
  9. package/public/favicon.png +0 -0
  10. package/scenes/audio-visualizer/main.js +287 -0
  11. package/scenes/core-demo/main.js +532 -0
  12. package/scenes/demo-scene/main.js +619 -0
  13. package/scenes/global.d.ts +15 -0
  14. package/scenes/particle-system/main.js +349 -0
  15. package/scenes/tsconfig.json +12 -0
  16. package/scenes/video-mirror/main.ts +436 -0
  17. package/src/App.css +42 -0
  18. package/src/App.tsx +279 -0
  19. package/src/cli/commands/build.js +147 -0
  20. package/src/cli/commands/create.js +71 -0
  21. package/src/cli/commands/dev.js +108 -0
  22. package/src/cli/commands/init.js +262 -0
  23. package/src/cli/utils/cli-utils.js +208 -0
  24. package/src/cli/utils/scene-compiler.js +432 -0
  25. package/src/components/SDKPage.tsx +337 -0
  26. package/src/components/core/CoreContainer.tsx +126 -0
  27. package/src/components/ui/DeviceSelectionList.tsx +137 -0
  28. package/src/components/ui/FPSCounter.tsx +78 -0
  29. package/src/components/ui/FileDropzonePanel.tsx +120 -0
  30. package/src/components/ui/FileListPanel.tsx +285 -0
  31. package/src/components/ui/InputExpansionPanel.tsx +31 -0
  32. package/src/components/ui/MediaPlayerControls.tsx +191 -0
  33. package/src/components/ui/MenuContainer.tsx +71 -0
  34. package/src/components/ui/ParametersMenu.tsx +797 -0
  35. package/src/components/ui/ProjectSwitcherMenu.tsx +192 -0
  36. package/src/components/ui/QuickInputControls.tsx +542 -0
  37. package/src/components/ui/SDKMenuSystem.tsx +96 -0
  38. package/src/components/ui/SettingsMenu.tsx +346 -0
  39. package/src/components/ui/SimpleInputControls.tsx +137 -0
  40. package/src/index.css +68 -0
  41. package/src/main.tsx +10 -0
  42. package/src/scenes-hmr.ts +158 -0
  43. package/src/services/project-filesystem.ts +436 -0
  44. package/src/stores/scene-player/index.ts +3 -0
  45. package/src/stores/scene-player/input-manager.store.ts +1045 -0
  46. package/src/stores/scene-player/scene-session.store.ts +659 -0
  47. package/src/styles/globals.css +111 -0
  48. package/src/templates/minimal-template.js +11 -0
  49. package/src/utils/debounce.js +34 -0
  50. package/src/vite-env.d.ts +1 -0
  51. package/tailwind.config.js +18 -0
  52. package/tsconfig.app.json +27 -0
  53. package/tsconfig.json +27 -0
  54. package/tsconfig.node.json +27 -0
  55. 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
+ }