@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,262 @@
1
+ /**
2
+ * Init Command - Initialize a new Viji workspace
3
+ */
4
+
5
+ import { mkdir, writeFile, readFile, copyFile, readdir, stat } from 'fs/promises';
6
+ import { existsSync, readFileSync } from 'fs';
7
+ import { join, resolve, dirname, basename } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { spawn } from 'child_process';
10
+ import { createRequire } from 'module';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ export async function initCommand(workspaceName, options) {
16
+ try {
17
+ console.log('🎨 Initializing Viji workspace...');
18
+
19
+ // Determine workspace path
20
+ let workspacePath;
21
+ if (workspaceName === '.' || workspaceName === './') {
22
+ workspacePath = process.cwd();
23
+ workspaceName = basename(workspacePath);
24
+ console.log(`📁 Initializing in current directory: ${workspacePath}`);
25
+ console.log(`🏷️ Workspace name: ${workspaceName}`);
26
+ } else {
27
+ workspacePath = resolve(process.cwd(), workspaceName);
28
+ console.log(`📁 Creating workspace: ${workspacePath}`);
29
+ }
30
+
31
+ // Check if directory exists and is not empty
32
+ if (existsSync(workspacePath)) {
33
+ const files = await readdir(workspacePath);
34
+ if (files.length > 0) {
35
+ console.error(`❌ Directory ${workspacePath} is not empty`);
36
+ console.log('💡 Use "viji init ." to initialize in current directory');
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ // Find SDK directory (where this command is running from)
42
+ const sdkDir = findSDKDirectory();
43
+ if (!sdkDir) {
44
+ console.error('❌ Could not find Viji SDK installation');
45
+ process.exit(1);
46
+ }
47
+
48
+ console.log('🛠️ SDK directory:', sdkDir);
49
+ console.log('🚀 Copying workspace files...');
50
+
51
+ // Create workspace directory if it doesn't exist
52
+ await mkdir(workspacePath, { recursive: true });
53
+
54
+ // Copy SDK structure to workspace
55
+ await copyWorkspaceFiles(sdkDir, workspacePath, workspaceName);
56
+
57
+ console.log('📦 Installing dependencies...');
58
+
59
+ // Install dependencies in the new workspace
60
+ await installDependencies(workspacePath);
61
+
62
+ console.log('✅ Workspace initialized successfully!');
63
+ console.log('');
64
+ console.log('🎯 Next steps:');
65
+ if (workspaceName !== basename(process.cwd())) {
66
+ console.log(` cd ${workspaceName}`);
67
+ }
68
+ console.log(' viji create my-first-scene --lang=js');
69
+ console.log(' viji dev');
70
+ console.log('');
71
+ console.log('🚀 Happy creating!');
72
+
73
+ } catch (error) {
74
+ console.error('💥 Failed to initialize workspace:', error.message);
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ async function copyWorkspaceFiles(sdkDir, workspacePath, workspaceName) {
80
+ // Files and directories to copy from SDK to workspace
81
+ const filesToCopy = [
82
+ 'src',
83
+ 'scenes',
84
+ 'public',
85
+ 'index.html',
86
+ 'vite.config.ts',
87
+ 'tsconfig.json',
88
+ 'tsconfig.app.json',
89
+ 'tsconfig.node.json',
90
+ 'tailwind.config.js',
91
+ 'postcss.config.js',
92
+ 'eslint.config.js',
93
+ '.gitignore'
94
+ ];
95
+
96
+ // Files to exclude from copying
97
+ const excludePatterns = [
98
+ 'node_modules',
99
+ 'dist',
100
+ 'build',
101
+ '.git',
102
+ 'package-lock.json',
103
+ 'tsconfig.tsbuildinfo',
104
+ 'vite.config.d.ts'
105
+ ];
106
+
107
+ for (const item of filesToCopy) {
108
+ const sourcePath = join(sdkDir, item);
109
+ const targetPath = join(workspacePath, item);
110
+
111
+ if (existsSync(sourcePath)) {
112
+ await copyRecursive(sourcePath, targetPath, excludePatterns);
113
+ }
114
+ }
115
+
116
+ // Create workspace-specific package.json
117
+ await createWorkspacePackageJson(sdkDir, workspacePath, workspaceName);
118
+ }
119
+
120
+ async function copyRecursive(source, target, excludePatterns = []) {
121
+ const stats = await stat(source);
122
+
123
+ // Check if this path should be excluded
124
+ const sourceName = basename(source);
125
+ if (excludePatterns.some(pattern => sourceName.includes(pattern))) {
126
+ return;
127
+ }
128
+
129
+ if (stats.isDirectory()) {
130
+ await mkdir(target, { recursive: true });
131
+ const files = await readdir(source);
132
+
133
+ for (const file of files) {
134
+ await copyRecursive(
135
+ join(source, file),
136
+ join(target, file),
137
+ excludePatterns
138
+ );
139
+ }
140
+ } else {
141
+ await copyFile(source, target);
142
+ }
143
+ }
144
+
145
+ async function createWorkspacePackageJson(sdkDir, workspacePath, workspaceName) {
146
+ // Read the SDK package.json as template
147
+ const sdkPackageJson = JSON.parse(readFileSync(join(sdkDir, 'package.json'), 'utf8'));
148
+
149
+ // Create workspace package.json
150
+ const workspacePackageJson = {
151
+ name: workspaceName,
152
+ private: true,
153
+ version: "0.1.0",
154
+ type: "module",
155
+ description: `Viji workspace: ${workspaceName}`,
156
+ scripts: {
157
+ dev: "vite",
158
+ build: "tsc -b && vite build",
159
+ lint: "eslint .",
160
+ preview: "vite preview"
161
+ },
162
+ dependencies: {
163
+ // Copy runtime dependencies from SDK
164
+ "@heroicons/react": sdkPackageJson.dependencies["@heroicons/react"],
165
+ "@heroui/react": sdkPackageJson.dependencies["@heroui/react"],
166
+ "@viji-dev/core": sdkPackageJson.dependencies["@viji-dev/core"],
167
+ "autoprefixer": sdkPackageJson.dependencies["autoprefixer"],
168
+ "chokidar": sdkPackageJson.dependencies["chokidar"],
169
+ "clsx": sdkPackageJson.dependencies["clsx"],
170
+ "framer-motion": sdkPackageJson.dependencies["framer-motion"],
171
+ "react": sdkPackageJson.dependencies["react"],
172
+ "react-dom": sdkPackageJson.dependencies["react-dom"],
173
+ "tailwind-merge": sdkPackageJson.dependencies["tailwind-merge"],
174
+ "tailwindcss": sdkPackageJson.dependencies["tailwindcss"],
175
+ "zustand": sdkPackageJson.dependencies["zustand"]
176
+ },
177
+ devDependencies: {
178
+ // Copy dev dependencies from SDK
179
+ "@eslint/js": sdkPackageJson.devDependencies["@eslint/js"],
180
+ "@types/node": sdkPackageJson.devDependencies["@types/node"],
181
+ "@types/react": sdkPackageJson.devDependencies["@types/react"],
182
+ "@types/react-dom": sdkPackageJson.devDependencies["@types/react-dom"],
183
+ "@vitejs/plugin-react": sdkPackageJson.devDependencies["@vitejs/plugin-react"],
184
+ "eslint": sdkPackageJson.devDependencies["eslint"],
185
+ "eslint-plugin-react-hooks": sdkPackageJson.devDependencies["eslint-plugin-react-hooks"],
186
+ "eslint-plugin-react-refresh": sdkPackageJson.devDependencies["eslint-plugin-react-refresh"],
187
+ "globals": sdkPackageJson.devDependencies["globals"],
188
+ "typescript": sdkPackageJson.devDependencies["typescript"],
189
+ "typescript-eslint": sdkPackageJson.devDependencies["typescript-eslint"],
190
+ "vite": sdkPackageJson.devDependencies["vite"]
191
+ },
192
+ viji: {
193
+ sdkVersion: sdkPackageJson.version,
194
+ initDate: new Date().toISOString(),
195
+ workspaceVersion: "1.0.0"
196
+ }
197
+ };
198
+
199
+ await writeFile(
200
+ join(workspacePath, 'package.json'),
201
+ JSON.stringify(workspacePackageJson, null, 2)
202
+ );
203
+ }
204
+
205
+ async function installDependencies(workspacePath) {
206
+ return new Promise((resolve, reject) => {
207
+ // Use platform-specific npm command
208
+ const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
209
+
210
+ const installProcess = spawn(npmCommand, ['install'], {
211
+ cwd: workspacePath,
212
+ stdio: 'inherit',
213
+ shell: true
214
+ });
215
+
216
+ installProcess.on('exit', (code) => {
217
+ if (code === 0) {
218
+ resolve();
219
+ } else {
220
+ reject(new Error(`npm install failed with code ${code}`));
221
+ }
222
+ });
223
+
224
+ installProcess.on('error', (error) => {
225
+ reject(error);
226
+ });
227
+ });
228
+ }
229
+
230
+ function findSDKDirectory() {
231
+ // Try relative to this CLI file first (for development)
232
+ const relativeSDK = resolve(__dirname, '..', '..', '..');
233
+ if (existsSync(join(relativeSDK, 'package.json'))) {
234
+ const packageJson = JSON.parse(readFileSync(join(relativeSDK, 'package.json'), 'utf8'));
235
+ if (packageJson.name === 'viji-sdk') {
236
+ return relativeSDK;
237
+ }
238
+ }
239
+
240
+ // Try to find global installation
241
+ try {
242
+ const require = createRequire(import.meta.url);
243
+ const globalSDK = require.resolve('viji-sdk');
244
+ if (globalSDK) {
245
+ return dirname(dirname(globalSDK)); // Go up from lib/index.js to root
246
+ }
247
+ } catch (error) {
248
+ // Not found globally
249
+ }
250
+
251
+ // Try to find in node_modules
252
+ let currentPath = process.cwd();
253
+ while (currentPath !== dirname(currentPath)) {
254
+ const potentialSDK = join(currentPath, 'node_modules', 'viji-sdk');
255
+ if (existsSync(potentialSDK)) {
256
+ return potentialSDK;
257
+ }
258
+ currentPath = dirname(currentPath);
259
+ }
260
+
261
+ return null;
262
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * CLI Utilities - Common functions for CLI commands
3
+ */
4
+
5
+ import { existsSync, readFileSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+
8
+ export function validateProject(projectDir = process.cwd()) {
9
+ // Support two layouts:
10
+ // 1) SDK scene folder: scenes/<project>/main.(js|ts)
11
+ // 2) External project: src/main.(js|ts)
12
+ const candidates = [
13
+ join(projectDir, 'main.ts'),
14
+ join(projectDir, 'main.js'),
15
+ join(projectDir, 'main.tsx'),
16
+ join(projectDir, 'main.jsx'),
17
+ join(projectDir, 'src', 'main.ts'),
18
+ join(projectDir, 'src', 'main.js'),
19
+ join(projectDir, 'src', 'main.tsx'),
20
+ join(projectDir, 'src', 'main.jsx')
21
+ ];
22
+
23
+ const mainScenePath = candidates.find(p => existsSync(p));
24
+ const packageJsonPath = join(projectDir, 'package.json');
25
+
26
+ const issues = [];
27
+ if (!mainScenePath) {
28
+ issues.push('No main.(js|ts|jsx|tsx) found in current folder or src/');
29
+ }
30
+
31
+ return {
32
+ valid: issues.length === 0,
33
+ issues,
34
+ paths: {
35
+ packageJson: packageJsonPath,
36
+ mainScene: mainScenePath || join(projectDir, 'main.js'),
37
+ projectDir
38
+ }
39
+ };
40
+ }
41
+
42
+ export function getProjectInfo(projectDir = process.cwd()) {
43
+ const validation = validateProject(projectDir);
44
+
45
+ if (!validation.valid) {
46
+ return { valid: false, ...validation };
47
+ }
48
+
49
+ try {
50
+ const packageJson = JSON.parse(readFileSync(validation.paths.packageJson, 'utf8'));
51
+
52
+ return {
53
+ valid: true,
54
+ name: packageJson.name,
55
+ version: packageJson.version,
56
+ description: packageJson.description,
57
+ dependencies: packageJson.dependencies || {},
58
+ devDependencies: packageJson.devDependencies || {},
59
+ scripts: packageJson.scripts || {},
60
+ paths: validation.paths
61
+ };
62
+ } catch (error) {
63
+ return {
64
+ valid: false,
65
+ issues: ['Could not read project information'],
66
+ error: error.message
67
+ };
68
+ }
69
+ }
70
+
71
+ export function formatProjectPath(projectName, targetDir = process.cwd()) {
72
+ return join(targetDir, projectName);
73
+ }
74
+
75
+ export function generateProjectId() {
76
+ return Math.random().toString(36).substr(2, 9);
77
+ }
78
+
79
+ export function validateProjectName(name) {
80
+ const issues = [];
81
+
82
+ if (!name || name.trim().length === 0) {
83
+ issues.push('Project name cannot be empty');
84
+ }
85
+
86
+ if (name.length > 214) {
87
+ issues.push('Project name too long (max 214 characters)');
88
+ }
89
+
90
+ if (name.toLowerCase() !== name) {
91
+ issues.push('Project name should be lowercase');
92
+ }
93
+
94
+ if (!/^[a-z0-9-_]+$/.test(name)) {
95
+ issues.push('Project name can only contain lowercase letters, numbers, hyphens, and underscores');
96
+ }
97
+
98
+ if (name.startsWith('.') || name.startsWith('-') || name.startsWith('_')) {
99
+ issues.push('Project name cannot start with a dot, hyphen, or underscore');
100
+ }
101
+
102
+ const reservedNames = ['viji', 'viji-sdk', 'core', 'main', 'index', 'src', 'dist', 'build'];
103
+ if (reservedNames.includes(name.toLowerCase())) {
104
+ issues.push(`Project name "${name}" is reserved`);
105
+ }
106
+
107
+ return {
108
+ valid: issues.length === 0,
109
+ issues
110
+ };
111
+ }
112
+
113
+ export function logWithIcon(icon, message, color = 'white') {
114
+ const colors = {
115
+ red: '\x1b[31m',
116
+ green: '\x1b[32m',
117
+ yellow: '\x1b[33m',
118
+ blue: '\x1b[34m',
119
+ magenta: '\x1b[35m',
120
+ cyan: '\x1b[36m',
121
+ white: '\x1b[37m',
122
+ reset: '\x1b[0m'
123
+ };
124
+
125
+ console.log(`${colors[color]}${icon} ${message}${colors.reset}`);
126
+ }
127
+
128
+ export function formatDuration(ms) {
129
+ if (ms < 1000) return `${ms}ms`;
130
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
131
+ return `${(ms / 60000).toFixed(1)}m`;
132
+ }
133
+
134
+ export function formatFileSize(bytes) {
135
+ if (bytes === 0) return '0 B';
136
+
137
+ const k = 1024;
138
+ const sizes = ['B', 'KB', 'MB', 'GB'];
139
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
140
+
141
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
142
+ }
143
+
144
+ export function createSpinner(text = 'Loading...') {
145
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
146
+ let i = 0;
147
+ let intervalId;
148
+
149
+ const spinner = {
150
+ start() {
151
+ process.stdout.write(`${frames[0]} ${text}`);
152
+ intervalId = setInterval(() => {
153
+ process.stdout.write(`\r${frames[i]} ${text}`);
154
+ i = (i + 1) % frames.length;
155
+ }, 80);
156
+ },
157
+
158
+ stop(finalText) {
159
+ if (intervalId) {
160
+ clearInterval(intervalId);
161
+ process.stdout.write(`\r${finalText || text}\n`);
162
+ }
163
+ },
164
+
165
+ update(newText) {
166
+ text = newText;
167
+ }
168
+ };
169
+
170
+ return spinner;
171
+ }
172
+
173
+ export function isVijiWorkspace(dir = process.cwd()) {
174
+ // Check for key workspace files
175
+ const requiredFiles = [
176
+ join(dir, 'src'),
177
+ join(dir, 'scenes'),
178
+ join(dir, 'vite.config.ts'),
179
+ join(dir, 'package.json')
180
+ ];
181
+
182
+ // All required files must exist
183
+ if (!requiredFiles.every(file => existsSync(file))) {
184
+ return false;
185
+ }
186
+
187
+ // Check if package.json has viji workspace marker
188
+ try {
189
+ const packageJson = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
190
+ return packageJson.viji && packageJson.viji.workspaceVersion;
191
+ } catch (error) {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ export function findWorkspaceRoot(startDir = process.cwd()) {
197
+ let currentDir = startDir;
198
+
199
+ while (currentDir !== dirname(currentDir)) {
200
+ if (isVijiWorkspace(currentDir)) {
201
+ return currentDir;
202
+ }
203
+ currentDir = dirname(currentDir);
204
+ }
205
+
206
+ return null;
207
+ }
208
+