@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
|
@@ -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
|
+
|