import { readdirSync, statSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
export interface ProjectInfo {
type: 'workspace' | 'project';
path: string;
name: string;
}
export interface ProjectArgs {
workspace?: string;
project?: string;
directory?: string;
}
/**
* Find an Xcode workspace or project in a directory.
* Prefers .xcworkspace over .xcodeproj.
*/
export function findProject(directory?: string): ProjectInfo | null {
const dir = directory ? resolve(directory) : process.cwd();
let entries: string[];
try {
entries = readdirSync(dir);
} catch {
return null;
}
// First look for .xcworkspace (preferred)
for (const entry of entries) {
if (entry.endsWith('.xcworkspace')) {
const fullPath = join(dir, entry);
try {
if (statSync(fullPath).isDirectory()) {
return {
type: 'workspace',
path: fullPath,
name: entry.replace('.xcworkspace', ''),
};
}
} catch {
// Skip if can't stat
}
}
}
// Then look for .xcodeproj
for (const entry of entries) {
if (entry.endsWith('.xcodeproj')) {
const fullPath = join(dir, entry);
try {
if (statSync(fullPath).isDirectory()) {
return {
type: 'project',
path: fullPath,
name: entry.replace('.xcodeproj', ''),
};
}
} catch {
// Skip if can't stat
}
}
}
return null;
}
/**
* Resolve project arguments to xcodebuild command line arguments.
* Returns an array of arguments like ['-workspace', 'path'] or ['-project', 'path'].
* Throws if both workspace and project are specified.
* Auto-detects from directory if neither is specified.
*/
export function resolveProjectArgs(args: ProjectArgs): string[] {
const { workspace, project, directory } = args;
// Explicit workspace
if (workspace) {
if (project) {
throw new Error('Cannot specify both workspace and project');
}
return ['-workspace', workspace];
}
// Explicit project
if (project) {
return ['-project', project];
}
// Auto-detect from directory
const found = findProject(directory);
if (!found) {
const searchDir = directory || process.cwd();
throw new Error(`No Xcode workspace or project found in ${searchDir}`);
}
if (found.type === 'workspace') {
return ['-workspace', found.path];
}
return ['-project', found.path];
}
/**
* Get the working directory for xcodebuild commands.
* For workspace/project paths, returns the containing directory.
*/
export function getWorkingDirectory(args: ProjectArgs): string {
if (args.directory) {
return resolve(args.directory);
}
if (args.workspace) {
// Get the directory containing the .xcworkspace
return dirname(resolve(args.workspace));
}
if (args.project) {
// Get the directory containing the .xcodeproj
return dirname(resolve(args.project));
}
return process.cwd();
}