scaffold_macos_project
Quickly generate a structured macOS Xcode project with workspace, SPM package, and proper configuration. Specify project name, output path, bundle ID, display name, and deployment target for customization.
Instructions
Scaffold a new macOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper macOS configuration.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| bundleIdentifier | No | Bundle identifier (e.g., com.example.myapp). If not provided, will use com.example.projectname | |
| currentProjectVersion | No | Build number (e.g., 1, 42, 100). If not provided, will use 1 | |
| customizeNames | No | Whether to customize project names and identifiers. Default is true. | |
| deploymentTarget | No | macOS deployment target (e.g., 15.4, 14.0). If not provided, will use 15.4 | |
| displayName | No | App display name (shown on home screen/dock). If not provided, will use projectName | |
| marketingVersion | No | Marketing version (e.g., 1.0, 2.1.3). If not provided, will use 1.0 | |
| outputPath | Yes | Path where the project should be created | |
| projectName | Yes | Name of the new project |
Implementation Reference
- Core handler function `scaffold_macos_projectLogic` that orchestrates project scaffolding, template processing, error handling, and response generation for the `scaffold_macos_project` tool.export async function scaffold_macos_projectLogic( params: ScaffoldMacOSProjectParams, commandExecutor: CommandExecutor, fileSystemExecutor: FileSystemExecutor = getDefaultFileSystemExecutor(), ): Promise<ToolResponse> { try { const projectParams = { ...params, platform: 'macOS' as const }; const projectPath = await scaffoldProject(projectParams, commandExecutor, fileSystemExecutor); const response = { success: true, projectPath, platform: 'macOS', message: `Successfully scaffolded macOS project "${params.projectName}" in ${projectPath}`, nextSteps: [ `Important: Before working on the project make sure to read the README.md file in the workspace root directory.`, `Build for macOS: build_macos({ workspacePath: "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace", scheme: "${params.customizeNames ? params.projectName : 'MyProject'}" })`, `Build & Run on macOS: build_run_macos({ workspacePath: "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace", scheme: "${params.customizeNames ? params.projectName : 'MyProject'}" })`, ], }; return { content: [ { type: 'text', text: JSON.stringify(response, null, 2), }, ], }; } catch (error) { log( 'error', `Failed to scaffold macOS project: ${error instanceof Error ? error.message : String(error)}`, ); return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }, null, 2, ), }, ], isError: true, }; } }
- Zod schema definitions: `BaseScaffoldSchema` (shared with iOS) and `ScaffoldmacOSProjectSchema` (macOS-specific with deploymentTarget) for input validation.const BaseScaffoldSchema = z.object({ projectName: z.string().min(1).describe('Name of the new project'), outputPath: z.string().describe('Path where the project should be created'), bundleIdentifier: z .string() .optional() .describe( 'Bundle identifier (e.g., com.example.myapp). If not provided, will use com.example.projectname', ), displayName: z .string() .optional() .describe( 'App display name (shown on home screen/dock). If not provided, will use projectName', ), marketingVersion: z .string() .optional() .describe('Marketing version (e.g., 1.0, 2.1.3). If not provided, will use 1.0'), currentProjectVersion: z .string() .optional() .describe('Build number (e.g., 1, 42, 100). If not provided, will use 1'), customizeNames: z .boolean() .default(true) .describe('Whether to customize project names and identifiers. Default is true.'), }); // macOS-specific schema const ScaffoldmacOSProjectSchema = BaseScaffoldSchema.extend({ deploymentTarget: z .string() .optional() .describe('macOS deployment target (e.g., 15.4, 14.0). If not provided, will use 15.4'), });
- Default export object that registers the `scaffold_macos_project` tool, including name, description, schema reference, and handler that validates inputs and delegates to the core logic function.export default { name: 'scaffold_macos_project', description: 'Scaffold a new macOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper macOS configuration.', schema: ScaffoldmacOSProjectSchema.shape, async handler(args: Record<string, unknown>): Promise<ToolResponse> { // Validate the arguments against the schema before processing const validatedArgs = ScaffoldmacOSProjectSchema.parse(args); return scaffold_macos_projectLogic( validatedArgs, getDefaultCommandExecutor(), getDefaultFileSystemExecutor(), ); }, };
- Key helper function `scaffoldProject` that handles template retrieval, validation, directory processing, and cleanup for scaffolding the project structure.async function scaffoldProject( params: ScaffoldMacOSProjectParams & { platform: string }, commandExecutor: CommandExecutor, fileSystemExecutor: FileSystemExecutor, ): Promise<string> { const projectName = params.projectName; const outputPath = params.outputPath; const platform = params.platform; const customizeNames = params.customizeNames ?? true; log('info', `Scaffolding project: ${projectName} (${platform}) at ${outputPath}`); // Validate project name if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(projectName)) { throw new ValidationError( 'Project name must start with a letter and contain only letters, numbers, and underscores', ); } // Get template path from TemplateManager let templatePath; try { templatePath = await TemplateManager.getTemplatePath( platform as 'macOS' | 'iOS', commandExecutor, fileSystemExecutor, ); } catch (error) { throw new ValidationError( `Failed to get template for ${platform}: ${error instanceof Error ? error.message : String(error)}`, ); } // Use outputPath directly as the destination const projectPath = outputPath; // Check if the output directory already has Xcode project files const xcworkspaceExists = fileSystemExecutor.existsSync( join(projectPath, `${customizeNames ? projectName : 'MyProject'}.xcworkspace`), ); const xcodeprojExists = fileSystemExecutor.existsSync( join(projectPath, `${customizeNames ? projectName : 'MyProject'}.xcodeproj`), ); if (xcworkspaceExists || xcodeprojExists) { throw new ValidationError(`Xcode project files already exist in ${projectPath}`); } try { // Process the template directly into the output path await processDirectory(templatePath, projectPath, params, fileSystemExecutor); return projectPath; } finally { // Clean up downloaded template if needed await TemplateManager.cleanup(templatePath, fileSystemExecutor); } }
- Recursive helper `processDirectory` that traverses the template directory, customizes names, processes files with placeholders, and copies structure to output.async function processDirectory( sourceDir: string, destDir: string, params: ScaffoldMacOSProjectParams & { platform: string }, fileSystemExecutor: FileSystemExecutor, ): Promise<void> { const entries = await fileSystemExecutor.readdir(sourceDir, { withFileTypes: true }); for (const entry of entries) { const dirent = entry as { isDirectory(): boolean; isFile(): boolean; name: string }; const sourcePath = join(sourceDir, dirent.name); let destName = dirent.name; if (params.customizeNames) { // Replace MyProject in directory names destName = destName.replace(/MyProject/g, params.projectName); } const destPath = join(destDir, destName); if (dirent.isDirectory()) { // Skip certain directories if (dirent.name === '.git' || dirent.name === 'xcuserdata') { continue; } await fileSystemExecutor.mkdir(destPath, { recursive: true }); await processDirectory(sourcePath, destPath, params, fileSystemExecutor); } else if (dirent.isFile()) { // Skip certain files if (dirent.name === '.DS_Store' || dirent.name.endsWith('.xcuserstate')) { continue; } await processFile(sourcePath, destPath, params, fileSystemExecutor); } } }