@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
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Scene Compiler - Advanced compilation and bundling for Viji scenes
3
+ */
4
+
5
+ import { readFile, writeFile, readdir, stat } from 'fs/promises';
6
+ import { join, dirname, extname, relative, resolve } from 'path';
7
+ import { existsSync } from 'fs';
8
+
9
+ export class SceneCompiler {
10
+ constructor(options = {}) {
11
+ this.options = {
12
+ minify: false, // forced off by CLI contract
13
+ sourceMap: false, // optional, not used now
14
+ inlineAssets: false, // keep simple, no inlining
15
+ validateScene: false, // minimal validation
16
+ ...options
17
+ };
18
+ this.dependencies = new Map();
19
+ this.assets = new Map();
20
+ this.warnings = [];
21
+ this.errors = [];
22
+ }
23
+
24
+ /**
25
+ * Compile a project folder-based scene
26
+ * @param {string} projectPath - Path to the project folder
27
+ * @param {Object} buildOptions - Build configuration options
28
+ */
29
+ async compileProject(projectPath) {
30
+ console.log('🔄 Starting project compilation (simple bundle)...');
31
+ const startTime = Date.now();
32
+
33
+ try {
34
+ // Find main scene file (main.js or main.ts)
35
+ const mainFile = await this.findMainFile(projectPath);
36
+ if (!mainFile) {
37
+ throw new Error('No main.js or main.ts file found in project');
38
+ }
39
+
40
+ // Create dist directory
41
+ const distPath = join(projectPath, 'dist');
42
+ await this.ensureDirectory(distPath);
43
+
44
+ // Set output file path
45
+ const outputFile = join(distPath, 'scene.js');
46
+
47
+ // Read and analyze main scene file
48
+ const sceneCode = await readFile(mainFile, 'utf8');
49
+ const analysis = await this.analyzeScene(sceneCode, mainFile);
50
+
51
+ if (this.errors.length > 0) {
52
+ throw new Error(`Compilation failed: ${this.errors.join(', ')}`);
53
+ }
54
+
55
+ // Bundle local dependencies (very simple: concatenate in order)
56
+ const bundle = await this.createBundle(sceneCode, analysis, projectPath);
57
+ const output = await this.generateOutput(bundle, analysis);
58
+ await writeFile(outputFile, output, 'utf8');
59
+
60
+ // Create metadata
61
+ const metadata = this.generateMetadata(analysis, {
62
+ compilationTime: Date.now() - startTime,
63
+ outputSize: output.length,
64
+ sourceFile: mainFile,
65
+ outputFile,
66
+ projectPath
67
+ });
68
+
69
+ // Write metadata file
70
+ await writeFile(join(distPath, 'scene.json'), JSON.stringify(metadata, null, 2), 'utf8');
71
+
72
+ console.log(`✅ Project compiled successfully: ${outputFile}`);
73
+
74
+ return {
75
+ success: true,
76
+ outputFile,
77
+ metadata,
78
+ warnings: this.warnings
79
+ };
80
+
81
+ } catch (error) {
82
+ console.error('💥 Compilation failed:', error.message);
83
+ return {
84
+ success: false,
85
+ error: error.message,
86
+ warnings: this.warnings,
87
+ errors: this.errors
88
+ };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Legacy compile method for backward compatibility
94
+ */
95
+ async compile(sceneFile, outputFile) {
96
+ console.log('🔄 Starting scene compilation...');
97
+ const startTime = Date.now();
98
+
99
+ try {
100
+ // Read and analyze main scene file
101
+ const sceneCode = await readFile(sceneFile, 'utf8');
102
+ const analysis = await this.analyzeScene(sceneCode, sceneFile);
103
+
104
+ if (this.errors.length > 0) {
105
+ throw new Error(`Compilation failed: ${this.errors.join(', ')}`);
106
+ }
107
+
108
+ const bundle = await this.createBundle(sceneCode, analysis, dirname(sceneFile));
109
+ const output = await this.generateOutput(bundle, analysis);
110
+
111
+ // Create metadata
112
+ const metadata = this.generateMetadata(analysis, {
113
+ compilationTime: Date.now() - startTime,
114
+ outputSize: output.length,
115
+ sourceFile: sceneFile,
116
+ outputFile
117
+ });
118
+
119
+ return {
120
+ code: output,
121
+ metadata,
122
+ warnings: this.warnings,
123
+ sourceMap: this.options.sourceMap ? this.generateSourceMap(bundle) : null
124
+ };
125
+
126
+ } catch (error) {
127
+ console.error('💥 Compilation failed:', error.message);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Find the main scene file (main.js or main.ts) in a project directory
134
+ */
135
+ async findMainFile(projectPath) {
136
+ const possibleFiles = ['main.ts', 'main.js', 'main.tsx', 'main.jsx'];
137
+
138
+ for (const filename of possibleFiles) {
139
+ const filepath = join(projectPath, filename);
140
+ if (existsSync(filepath)) {
141
+ return filepath;
142
+ }
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * Ensure a directory exists, create it if it doesn't
150
+ */
151
+ async ensureDirectory(dirPath) {
152
+ try {
153
+ // In browser environment, this would need to be simulated
154
+ // For now, we'll use a simple check
155
+ if (!existsSync(dirPath)) {
156
+ // In a real implementation: await mkdir(dirPath, { recursive: true });
157
+ console.log(`📁 Directory would be created: ${dirPath}`);
158
+ }
159
+ } catch (error) {
160
+ console.warn(`Could not create directory: ${dirPath}`, error);
161
+ }
162
+ }
163
+
164
+ async analyzeScene(code, filePath) {
165
+ const analysis = {
166
+ parameters: [],
167
+ functions: [],
168
+ imports: [],
169
+ exports: [],
170
+ dependencies: [],
171
+ assets: [],
172
+ shaders: [],
173
+ validation: {
174
+ hasRender: false,
175
+ hasInit: false,
176
+ hasParameters: false,
177
+ isValid: true,
178
+ issues: []
179
+ }
180
+ };
181
+
182
+ // Extract exports
183
+ const exportMatches = [...code.matchAll(/export\s+(?:const|function|class|let|var)\s+(\w+)/g)];
184
+ analysis.exports = exportMatches.map(match => match[1]);
185
+
186
+ // Validate required exports
187
+ analysis.validation.hasRender = analysis.exports.includes('render');
188
+ analysis.validation.hasInit = analysis.exports.includes('init');
189
+
190
+ if (!analysis.validation.hasRender) {
191
+ analysis.validation.issues.push('Missing required export: render');
192
+ analysis.validation.isValid = false;
193
+ }
194
+
195
+ // Extract parameters
196
+ const parameterMatch = code.match(/export\s+const\s+parameters\s*=\s*(\[[\s\S]*?\]);/);
197
+ if (parameterMatch) {
198
+ try {
199
+ analysis.parameters = this.parseParameters(parameterMatch[1]);
200
+ analysis.validation.hasParameters = true;
201
+ } catch (error) {
202
+ this.warnings.push(`Could not parse parameters: ${error.message}`);
203
+ }
204
+ }
205
+
206
+ // Extract imports and resolve dependencies
207
+ const importMatches = [...code.matchAll(/import\s+.*?from\s+['"]([^'"]+)['"]/g)];
208
+ for (const match of importMatches) {
209
+ const importPath = match[1];
210
+ analysis.imports.push(importPath);
211
+
212
+ if (!importPath.startsWith('.')) {
213
+ // External dependency
214
+ analysis.dependencies.push(importPath);
215
+ } else {
216
+ // Local file - resolve and analyze
217
+ const resolvedPath = resolve(dirname(filePath), importPath);
218
+ if (existsSync(resolvedPath) || existsSync(resolvedPath + '.js')) {
219
+ const localDep = existsSync(resolvedPath) ? resolvedPath : resolvedPath + '.js';
220
+ analysis.dependencies.push(localDep);
221
+ } else {
222
+ this.warnings.push(`Could not resolve local import: ${importPath}`);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Find shader files
228
+ const shaderExtensions = ['.glsl', '.vert', '.frag', '.vs', '.fs'];
229
+ const projectDir = dirname(filePath);
230
+ analysis.shaders = await this.findFiles(projectDir, shaderExtensions);
231
+
232
+ // Find assets
233
+ const assetExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp3', '.wav', '.ogg'];
234
+ analysis.assets = await this.findFiles(projectDir, assetExtensions);
235
+
236
+ return analysis;
237
+ }
238
+
239
+ async findFiles(directory, extensions) {
240
+ const files = [];
241
+
242
+ try {
243
+ const entries = await readdir(directory, { withFileTypes: true });
244
+
245
+ for (const entry of entries) {
246
+ const fullPath = join(directory, entry.name);
247
+
248
+ if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
249
+ files.push(...await this.findFiles(fullPath, extensions));
250
+ } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
251
+ files.push(fullPath);
252
+ }
253
+ }
254
+ } catch (error) {
255
+ // Directory doesn't exist or can't be read
256
+ }
257
+
258
+ return files;
259
+ }
260
+
261
+ parseParameters(parameterString) {
262
+ // Safe parameter parsing with error handling
263
+ try {
264
+ // Replace template literals and clean up for evaluation
265
+ const cleanedString = parameterString
266
+ .replace(/`([^`]*)`/g, '"$1"') // Convert template literals to strings
267
+ .replace(/\$\{[^}]*\}/g, '""'); // Remove template expressions
268
+
269
+ return eval(cleanedString);
270
+ } catch (error) {
271
+ throw new Error(`Invalid parameters syntax: ${error.message}`);
272
+ }
273
+ }
274
+
275
+ async createBundle(sceneCode, analysis, projectDir) {
276
+ const bundle = {
277
+ main: sceneCode,
278
+ dependencies: new Map(),
279
+ assets: new Map(),
280
+ shaders: new Map()
281
+ };
282
+
283
+ // Bundle local dependencies (shallow: only direct relative imports that exist)
284
+ for (const dep of analysis.dependencies) {
285
+ if (dep.startsWith('/') || dep.includes(':\\')) {
286
+ // Absolute path - local file
287
+ try {
288
+ const content = await readFile(dep, 'utf8');
289
+ const relativePath = relative(projectDir, dep);
290
+ bundle.dependencies.set(relativePath, content);
291
+ } catch (error) {
292
+ this.warnings.push(`Could not read dependency: ${dep}`);
293
+ }
294
+ }
295
+ }
296
+
297
+ // Skip asset inlining for simple build
298
+
299
+ // Bundle shaders
300
+ for (const shaderPath of analysis.shaders) {
301
+ try {
302
+ const content = await readFile(shaderPath, 'utf8');
303
+ const relativePath = relative(projectDir, shaderPath);
304
+ bundle.shaders.set(relativePath, content);
305
+ } catch (error) {
306
+ this.warnings.push(`Could not read shader: ${shaderPath}`);
307
+ }
308
+ }
309
+
310
+ return bundle;
311
+ }
312
+
313
+ async generateOutput(bundle, analysis) {
314
+ const timestamp = new Date().toISOString();
315
+
316
+ let output = `/**
317
+ * Viji Scene Bundle
318
+ * Generated: ${timestamp}
319
+ * Minified: ${this.options.minify}
320
+ * Platform: Universal
321
+ */
322
+
323
+ `;
324
+
325
+ // Add bundled dependencies
326
+ if (bundle.dependencies.size > 0) {
327
+ output += `// === Bundled Dependencies ===\n`;
328
+ for (const [path, content] of bundle.dependencies) {
329
+ output += `// File: ${path}\n`;
330
+ output += content + '\n\n';
331
+ }
332
+ }
333
+
334
+ // No assets bundling in simple build
335
+
336
+ // No shader bundling in simple build
337
+
338
+ // Scene parameters (if present in code via export const parameters) are not transformed
339
+
340
+ // Add main scene code
341
+ output += `// === Main Scene Code ===\n`;
342
+ output += bundle.main + '\n\n';
343
+
344
+ // No compatibility wrapper; emit plain concatenated code
345
+
346
+ // Add bundle metadata
347
+ output += `\n// === Bundle Metadata ===\n`;
348
+ output += `const BUNDLE_METADATA = {\n`;
349
+ output += ` version: "1.0.0",\n`;
350
+ output += ` generatedAt: "${timestamp}",\n`;
351
+ output += ` parameters: SCENE_PARAMETERS,\n`;
352
+ output += ` exports: ${JSON.stringify(analysis.exports)},\n`;
353
+ output += ` hasAssets: ${bundle.assets.size > 0},\n`;
354
+ output += ` hasShaders: ${bundle.shaders.size > 0},\n`;
355
+ output += ` minified: ${this.options.minify}\n`;
356
+ output += `};\n`;
357
+
358
+ // Never minify per current contract
359
+
360
+ return output;
361
+ }
362
+
363
+ // Removed compatibility wrapper
364
+
365
+ // Removed minifier
366
+
367
+ // Removed source map generation
368
+
369
+ generateMetadata(analysis, buildInfo) {
370
+ return {
371
+ version: '1.0.0',
372
+ buildInfo,
373
+ scene: {
374
+ parameters: analysis.parameters,
375
+ exports: analysis.exports,
376
+ validation: analysis.validation,
377
+ dependencies: analysis.dependencies.length,
378
+ assets: analysis.assets.length,
379
+ shaders: analysis.shaders.length
380
+ },
381
+ compatibility: {
382
+ vijiCore: '>=0.2.0',
383
+ platform: 'universal'
384
+ },
385
+ performance: {
386
+ complexity: this.assessComplexity(analysis),
387
+ estimatedMemory: this.estimateMemoryUsage(analysis),
388
+ features: this.detectFeatures(analysis)
389
+ }
390
+ };
391
+ }
392
+
393
+ assessComplexity(analysis) {
394
+ let score = 0;
395
+
396
+ if (analysis.exports.includes('onMouseMove')) score += 10;
397
+ if (analysis.exports.includes('onKeyPress')) score += 5;
398
+ if (analysis.parameters.length > 10) score += 15;
399
+ if (analysis.assets.length > 5) score += 10;
400
+ if (analysis.shaders.length > 0) score += 20;
401
+ if (analysis.dependencies.length > 3) score += 10;
402
+
403
+ if (score < 20) return 'low';
404
+ if (score < 50) return 'medium';
405
+ return 'high';
406
+ }
407
+
408
+ estimateMemoryUsage(analysis) {
409
+ let size = 100; // Base KB
410
+
411
+ size += analysis.parameters.length * 2;
412
+ size += analysis.assets.length * 50; // Assume average 50KB per asset
413
+ size += analysis.shaders.length * 10;
414
+ size += analysis.dependencies.length * 20;
415
+
416
+ return `~${Math.round(size)}KB`;
417
+ }
418
+
419
+ detectFeatures(analysis) {
420
+ const features = [];
421
+
422
+ if (analysis.exports.includes('onMouseMove')) features.push('mouse-interaction');
423
+ if (analysis.exports.includes('onKeyPress')) features.push('keyboard-input');
424
+ if (analysis.exports.includes('onResize')) features.push('responsive');
425
+ if (analysis.assets.length > 0) features.push('assets');
426
+ if (analysis.shaders.length > 0) features.push('shaders');
427
+ if (analysis.parameters.some(group => group.category === 'audio')) features.push('audio-reactive');
428
+
429
+ return features;
430
+ }
431
+ }
432
+