Skip to content

Commit 341f39b

Browse files
Peter Wangmeta-codesync[bot]
authored andcommitted
fix iwsdk improve the cosmo cli path
Summary: Fix Meta Spatial CLI Path Resolution for Multiple Versions and Add Cross-Platform Support # Problem The previous implementation had several issues: 1. Incorrect Windows version selection: Used files[0] which would randomly pick the first directory entry instead of the highest version number 2. No environment variable support: No way to override the CLI path for custom installations 3. Missing Linux support: Only handled macOS and Windows platforms # Changes ## 1. Fixed Windows Version Selection - Implemented getHighestVersion() function that correctly identifies and selects the highest version number - Properly sorts version directories numerically (e.g., v20 > v9 > v2, not alphabetically) - Handles edge cases like multi-digit versions (v100) and missing version directories - Before: const files = fs.readdirSync(directoryPath); metaSpatialCliPath = directoryPath + files[0] + '\Resources\CLI.exe'; // ❌ Wrong - After: const highestVersion = getHighestVersion(directoryPath); if (highestVersion) { metaSpatialCliPath = path.join(directoryPath, highestVersion, 'Resources', 'CLI.exe'); // ✅ Uses v20 } ## 2. Added Environment Variable Support - Added META_SPATIAL_EDITOR_CLI_PATH environment variable check - Takes priority over platform-specific defaults - Allows users to specify custom CLI paths for non-standard installations ## 3. Added Linux Platform Support - Falls back to MetaSpatialEditorCLI command (assumes it's in PATH) - Covers all major platforms: macOS, Windows, and Linux # Resolution Priority 1. META_SPATIAL_EDITOR_CLI_PATH environment variable (if set) 2. Platform-specific defaults: - macOS: /Applications/Meta Spatial Editor.app/Contents/MacOS/CLI - Windows: C:\Program Files\Meta Spatial Editor\v{highest}\Resources\CLI.exe - Linux: MetaSpatialEditorCLI (from PATH) Reviewed By: sforte, zjm-meta Differential Revision: D88068427 Privacy Context Container: L1334777 fbshipit-source-id: e8373635032e235ac00615adfd2d33ade3690431
1 parent 4bef46e commit 341f39b

File tree

5 files changed

+312
-15
lines changed

5 files changed

+312
-15
lines changed

packages/vite-plugin-metaspatial/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
"scripts": {
1313
"build": "rollup -c",
1414
"dev": "rollup -c --watch",
15-
"clean": "rm -rf dist"
15+
"clean": "rm -rf dist",
16+
"test": "vitest run",
17+
"test:watch": "vitest",
18+
"test:coverage": "vitest run --coverage"
1619
},
1720
"keywords": [
1821
"vite",
@@ -39,7 +42,9 @@
3942
"devDependencies": {
4043
"@types/babel__traverse": "^7.20.6",
4144
"@types/chokidar": "^2.1.7",
42-
"@types/fs-extra": "^11.0.4"
45+
"@types/fs-extra": "^11.0.4",
46+
"vitest": "^2.1.8",
47+
"@vitest/coverage-v8": "^2.1.8"
4348
},
4449
"peerDependencies": {
4550
"vite": "^7.0.0"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import * as path from 'path';
9+
import fs from 'fs-extra';
10+
11+
/**
12+
* Get the highest version directory from Meta Spatial Editor installation
13+
* @param directoryPath - Base directory path
14+
* @returns The version folder with the highest version number (e.g., 'v20')
15+
*/
16+
export function getHighestVersion(directoryPath: string): string | null {
17+
try {
18+
const files = fs.readdirSync(directoryPath);
19+
20+
// Filter for version directories (e.g., v1, v2, v20)
21+
const versionDirs = files.filter(file => {
22+
const fullPath = path.join(directoryPath, file);
23+
return fs.statSync(fullPath).isDirectory() && /^v\d+$/.test(file);
24+
});
25+
26+
if (versionDirs.length === 0) {
27+
return null;
28+
}
29+
30+
// Sort by version number (extract number from 'vN' format)
31+
versionDirs.sort((a, b) => {
32+
const numA = parseInt(a.substring(1), 10);
33+
const numB = parseInt(b.substring(1), 10);
34+
return numB - numA; // Descending order (highest first)
35+
});
36+
37+
return versionDirs[0];
38+
} catch (error) {
39+
console.warn(`Warning: Could not read directory ${directoryPath}:`, error);
40+
return null;
41+
}
42+
}
43+
44+
/**
45+
* Resolve the Meta Spatial CLI path based on environment and platform
46+
* @returns The path to the Meta Spatial CLI executable
47+
*/
48+
export function resolveMetaSpatialCliPath(): string {
49+
// First, check if META_SPATIAL_EDITOR_CLI_PATH environment variable is set
50+
if (process.env.META_SPATIAL_EDITOR_CLI_PATH) {
51+
return process.env.META_SPATIAL_EDITOR_CLI_PATH;
52+
}
53+
54+
// Fall back to platform-specific defaults
55+
const os = process.platform;
56+
57+
if (os === 'darwin') {
58+
return '/Applications/Meta Spatial Editor.app/Contents/MacOS/CLI';
59+
} else if (os === 'win32') {
60+
const directoryPath = 'C:\\Program Files\\Meta Spatial Editor\\';
61+
const highestVersion = getHighestVersion(directoryPath);
62+
63+
if (highestVersion) {
64+
return path.join(directoryPath, highestVersion, 'Resources', 'CLI.exe');
65+
} else {
66+
// Fallback to a default path if no version directories found
67+
return path.join(directoryPath, 'Resources', 'CLI.exe');
68+
}
69+
} else {
70+
// Linux - assume MetaSpatialEditorCLI is in PATH
71+
return 'MetaSpatialEditorCLI';
72+
}
73+
}

packages/vite-plugin-metaspatial/src/generate-glxf/index.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import fs from 'fs-extra';
1111
import type { Plugin, ViteDevServer } from 'vite';
1212
import { regenerateGLXF, createFileWatcher, cleanup } from './file-watcher.js';
1313
import type { GLXFGenerationOptions, ProcessedGLXFOptions } from './types.js';
14+
import { resolveMetaSpatialCliPath } from './cli-path-resolver.js';
1415

1516
// Export types
1617
export type { GLXFGenerationOptions } from './types.js';
@@ -30,24 +31,12 @@ interface GLXFGenerationStat {
3031
function processOptions(
3132
options: GLXFGenerationOptions = {},
3233
): ProcessedGLXFOptions {
33-
const os = process.platform;
34-
var metaSpatialCliPath;
35-
36-
if (os === 'darwin') {
37-
metaSpatialCliPath =
38-
'/Applications/Meta Spatial Editor.app/Contents/MacOS/CLI';
39-
} else {
40-
const directoryPath = 'C:\\Program Files\\Meta Spatial Editor\\';
41-
const files = fs.readdirSync(directoryPath);
42-
metaSpatialCliPath = directoryPath + files[0] + '\\Resources\\CLI.exe';
43-
}
44-
4534
return {
4635
metaSpatialDir: 'metaspatial',
4736
outputDir: 'generated/glxf',
4837
watchDebounceMs: 500,
4938
formats: ['glxf'] as const,
50-
metaSpatialCliPath: metaSpatialCliPath,
39+
metaSpatialCliPath: resolveMetaSpatialCliPath(),
5140
verbose: false,
5241
enableWatcher: true,
5342
ignorePattern: /components\//,
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9+
import { getHighestVersion, resolveMetaSpatialCliPath } from '../src/generate-glxf/cli-path-resolver.js';
10+
import fs from 'fs-extra';
11+
import * as path from 'path';
12+
13+
// Mock fs module
14+
vi.mock('fs-extra');
15+
16+
describe('CLI Path Resolver', () => {
17+
let originalPlatform: string;
18+
let originalEnv: NodeJS.ProcessEnv;
19+
20+
beforeEach(() => {
21+
// Save original values
22+
originalPlatform = process.platform;
23+
originalEnv = { ...process.env };
24+
25+
// Clear mocks
26+
vi.clearAllMocks();
27+
});
28+
29+
afterEach(() => {
30+
// Restore original values
31+
Object.defineProperty(process, 'platform', {
32+
value: originalPlatform,
33+
});
34+
process.env = originalEnv;
35+
});
36+
37+
describe('getHighestVersion', () => {
38+
it('should return the highest version number', () => {
39+
const mockFiles = ['v1', 'v2', 'v9', 'v11', 'v12', 'v20', 'other-file.txt'];
40+
41+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
42+
vi.mocked(fs.statSync).mockImplementation((filePath: any) => {
43+
const fileName = path.basename(filePath.toString());
44+
return {
45+
isDirectory: () => /^v\d+$/.test(fileName),
46+
} as any;
47+
});
48+
49+
const result = getHighestVersion('C:\\Program Files\\Meta Spatial Editor\\');
50+
51+
expect(result).toBe('v20');
52+
});
53+
54+
it('should handle single version directory', () => {
55+
const mockFiles = ['v5'];
56+
57+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
58+
vi.mocked(fs.statSync).mockImplementation((filePath: any) => {
59+
const fileName = path.basename(filePath.toString());
60+
return {
61+
isDirectory: () => /^v\d+$/.test(fileName),
62+
} as any;
63+
});
64+
65+
const result = getHighestVersion('C:\\Program Files\\Meta Spatial Editor\\');
66+
67+
expect(result).toBe('v5');
68+
});
69+
70+
it('should return null when no version directories exist', () => {
71+
const mockFiles = ['other-file.txt', 'config.json'];
72+
73+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
74+
vi.mocked(fs.statSync).mockImplementation(() => {
75+
return {
76+
isDirectory: () => false,
77+
} as any;
78+
});
79+
80+
const result = getHighestVersion('C:\\Program Files\\Meta Spatial Editor\\');
81+
82+
expect(result).toBeNull();
83+
});
84+
85+
it('should correctly sort multi-digit version numbers', () => {
86+
const mockFiles = ['v2', 'v100', 'v20', 'v3'];
87+
88+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
89+
vi.mocked(fs.statSync).mockImplementation((filePath: any) => {
90+
const fileName = path.basename(filePath.toString());
91+
return {
92+
isDirectory: () => /^v\d+$/.test(fileName),
93+
} as any;
94+
});
95+
96+
const result = getHighestVersion('C:\\Program Files\\Meta Spatial Editor\\');
97+
98+
expect(result).toBe('v100');
99+
});
100+
101+
it('should handle directory read errors gracefully', () => {
102+
vi.mocked(fs.readdirSync).mockImplementation(() => {
103+
throw new Error('Directory not found');
104+
});
105+
106+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
107+
108+
const result = getHighestVersion('C:\\Program Files\\Meta Spatial Editor\\');
109+
110+
expect(result).toBeNull();
111+
expect(consoleWarnSpy).toHaveBeenCalled();
112+
113+
consoleWarnSpy.mockRestore();
114+
});
115+
});
116+
117+
describe('resolveMetaSpatialCliPath', () => {
118+
it('should use META_SPATIAL_EDITOR_CLI_PATH environment variable if set', () => {
119+
process.env.META_SPATIAL_EDITOR_CLI_PATH = '/custom/path/to/CLI';
120+
121+
const result = resolveMetaSpatialCliPath();
122+
123+
expect(result).toBe('/custom/path/to/CLI');
124+
});
125+
126+
it('should return macOS path when platform is darwin and no env var is set', () => {
127+
delete process.env.META_SPATIAL_EDITOR_CLI_PATH;
128+
Object.defineProperty(process, 'platform', {
129+
value: 'darwin',
130+
});
131+
132+
const result = resolveMetaSpatialCliPath();
133+
134+
expect(result).toBe('/Applications/Meta Spatial Editor.app/Contents/MacOS/CLI');
135+
});
136+
137+
it('should return Windows path with highest version when platform is win32', () => {
138+
delete process.env.META_SPATIAL_EDITOR_CLI_PATH;
139+
Object.defineProperty(process, 'platform', {
140+
value: 'win32',
141+
});
142+
143+
const mockFiles = ['v1', 'v2', 'v20'];
144+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
145+
vi.mocked(fs.statSync).mockImplementation((filePath: any) => {
146+
const fileName = path.basename(filePath.toString());
147+
return {
148+
isDirectory: () => /^v\d+$/.test(fileName),
149+
} as any;
150+
});
151+
152+
const result = resolveMetaSpatialCliPath();
153+
154+
expect(result).toContain('v20');
155+
expect(result).toContain('Resources');
156+
expect(result).toContain('CLI.exe');
157+
});
158+
159+
it('should return Windows fallback path when no version directories found', () => {
160+
delete process.env.META_SPATIAL_EDITOR_CLI_PATH;
161+
Object.defineProperty(process, 'platform', {
162+
value: 'win32',
163+
});
164+
165+
const mockFiles = ['other-file.txt'];
166+
vi.mocked(fs.readdirSync).mockReturnValue(mockFiles as any);
167+
vi.mocked(fs.statSync).mockImplementation(() => {
168+
return {
169+
isDirectory: () => false,
170+
} as any;
171+
});
172+
173+
const result = resolveMetaSpatialCliPath();
174+
175+
expect(result).toContain('Resources');
176+
expect(result).toContain('CLI.exe');
177+
expect(result).not.toContain('v');
178+
});
179+
180+
it('should return MetaSpatialEditorCLI for Linux platform', () => {
181+
delete process.env.META_SPATIAL_EDITOR_CLI_PATH;
182+
Object.defineProperty(process, 'platform', {
183+
value: 'linux',
184+
});
185+
186+
const result = resolveMetaSpatialCliPath();
187+
188+
expect(result).toBe('MetaSpatialEditorCLI');
189+
});
190+
191+
it('should prioritize environment variable over platform detection', () => {
192+
process.env.META_SPATIAL_EDITOR_CLI_PATH = '/override/path';
193+
Object.defineProperty(process, 'platform', {
194+
value: 'darwin',
195+
});
196+
197+
const result = resolveMetaSpatialCliPath();
198+
199+
expect(result).toBe('/override/path');
200+
expect(result).not.toBe('/Applications/Meta Spatial Editor.app/Contents/MacOS/CLI');
201+
});
202+
});
203+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { defineConfig } from 'vitest/config';
9+
10+
export default defineConfig({
11+
test: {
12+
globals: true,
13+
environment: 'node',
14+
coverage: {
15+
provider: 'v8',
16+
reporter: ['text', 'json', 'html'],
17+
exclude: [
18+
'node_modules/',
19+
'dist/',
20+
'tests/',
21+
'**/*.d.ts',
22+
'**/*.config.*',
23+
'**/rollup.config.js',
24+
],
25+
},
26+
},
27+
});

0 commit comments

Comments
 (0)