Skip to main content
Glama

AI Code Toolkit

by AgiFlow
ProjectConfigResolver.ts8.73 kB
/** * ProjectConfigResolver * * DESIGN PATTERNS: * - Class-based service pattern for resolving project configuration * - Priority-based configuration resolution (project.json > toolkit.yaml > package.json) * - Singleton-like static methods for common operations * * CODING STANDARDS: * - Service class names use PascalCase with 'Service' suffix * - Method names use camelCase with descriptive verbs * - Return types should be explicit (never use implicit any) * - Use async/await for asynchronous operations * - Handle errors with try-catch and throw descriptive Error objects * - Document public methods with JSDoc comments * * AVOID: * - Side effects in constructors (keep them lightweight) * - Mixing concerns (keep services focused on single domain) * - Direct coupling to other services (use dependency injection) * - Exposing internal implementation details */ import path from 'node:path'; import * as fs from 'fs-extra'; import { ConfigSource, ProjectType } from '../constants/projectType'; import type { NxProjectJson, ProjectConfigResult, ToolkitConfig } from '../types'; import { log } from '../utils/logger'; import { TemplatesManagerService } from './TemplatesManagerService'; /** * ProjectConfigResolver * * Resolves project configuration from multiple sources with priority: * 1. project.json (monorepo - Nx/Lerna/Turborepo) * 2. toolkit.yaml at workspace root (monolith) */ export class ProjectConfigResolver { /** * Resolve project configuration with priority fallback * * Priority order: * 1. Check for project.json (monorepo case) * 2. Check for toolkit.yaml at workspace root (monolith case) * 3. Error with helpful message * * @param projectPath - Absolute path to the project directory * @param explicitTemplate - Optional explicit template override * @returns Project configuration result * @throws Error if no configuration found */ static async resolveProjectConfig( projectPath: string, explicitTemplate?: string, ): Promise<ProjectConfigResult> { try { const absolutePath = path.resolve(projectPath); // If explicit template provided, use it if (explicitTemplate) { return { type: ProjectType.MONOLITH, sourceTemplate: explicitTemplate, configSource: ConfigSource.TOOLKIT_YAML, // Will be created if doesn't exist }; } // 1. Check for project.json (monorepo) const projectJsonPath = path.join(absolutePath, 'project.json'); if (await fs.pathExists(projectJsonPath)) { const projectJson = await fs.readJson(projectJsonPath); // Validate sourceTemplate is a non-empty string if ( projectJson.sourceTemplate && typeof projectJson.sourceTemplate === 'string' && projectJson.sourceTemplate.trim() ) { return { type: ProjectType.MONOREPO, sourceTemplate: projectJson.sourceTemplate.trim(), configSource: ConfigSource.PROJECT_JSON, }; } } // 2. Check for toolkit.yaml at workspace root (monolith) // This is optional, so we catch and continue if not found try { const workspaceRoot = await TemplatesManagerService.getWorkspaceRoot(absolutePath); const toolkitConfig = await TemplatesManagerService.readToolkitConfig(workspaceRoot); if (toolkitConfig?.projectType === 'monolith' && toolkitConfig.sourceTemplate) { return { type: ProjectType.MONOLITH, sourceTemplate: toolkitConfig.sourceTemplate, configSource: ConfigSource.TOOLKIT_YAML, workspaceRoot, }; } } catch (_error) { // toolkit.yaml doesn't exist or couldn't be read - this is expected for some projects // Fall through to error message } // 3. No configuration found - throw helpful error throw new Error(ProjectConfigResolver.getHelpfulErrorMessage(absolutePath)); } catch (error) { // If it's already our helpful error message, rethrow it if (error instanceof Error && error.message.includes('No project configuration found')) { throw error; } // Otherwise, wrap unexpected errors with context throw new Error( `Failed to resolve project configuration at ${projectPath}: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Get helpful error message when no configuration is found */ private static getHelpfulErrorMessage(projectPath: string): string { return `No project configuration found at ${projectPath}. For monorepo projects: - Ensure project.json exists with sourceTemplate field For monolith projects: - Create toolkit.yaml at workspace root: projectType: monolith sourceTemplate: your-template-name - OR use --template flag: scaffold-mcp scaffold add <feature> --template <template-name> Run 'scaffold-mcp scaffold list --help' for more info.`; } /** * Check if project has configuration (without throwing error) * * @param projectPath - Absolute path to the project directory * @returns true if configuration exists, false otherwise * @throws Error if there's a filesystem error (not just missing config) */ static async hasConfiguration(projectPath: string): Promise<boolean> { try { await ProjectConfigResolver.resolveProjectConfig(projectPath); return true; } catch (error) { // If error is about missing configuration, return false if (error instanceof Error && error.message.includes('No project configuration found')) { return false; } // Otherwise, it's a real error (filesystem, permissions, etc) - rethrow with context throw new Error( `Error checking project configuration: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Create toolkit.yaml for monolith projects * * @param sourceTemplate - The template identifier * @param workspaceRoot - Optional workspace root path (defaults to current directory) */ static async createToolkitYaml(sourceTemplate: string, workspaceRoot?: string): Promise<void> { try { const rootPath = workspaceRoot || (await TemplatesManagerService.getWorkspaceRoot(process.cwd())); // Get existing toolkit config or create new one const existingConfig = await TemplatesManagerService.readToolkitConfig(rootPath); const toolkitConfig: ToolkitConfig = { version: existingConfig?.version || '1.0', templatesPath: existingConfig?.templatesPath || './templates', projectType: 'monolith' as const, sourceTemplate, }; await TemplatesManagerService.writeToolkitConfig(toolkitConfig, rootPath); log.info('Created toolkit.yaml with monolith configuration'); } catch (error) { throw new Error( `Failed to create toolkit.yaml: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Create or update project.json for monorepo projects * * @param projectPath - Absolute path to the project directory * @param projectName - Name of the project * @param sourceTemplate - The template identifier */ static async createProjectJson( projectPath: string, projectName: string, sourceTemplate: string, ): Promise<void> { const projectJsonPath = path.join(projectPath, 'project.json'); try { let projectJson: NxProjectJson; if (await fs.pathExists(projectJsonPath)) { // Read existing project.json projectJson = await fs.readJson(projectJsonPath); } else { // Create minimal project.json // Calculate relative path to node_modules for $schema const relativePath = path.relative(projectPath, process.cwd()); const schemaPath = relativePath ? `${relativePath}/node_modules/nx/schemas/project-schema.json` : 'node_modules/nx/schemas/project-schema.json'; projectJson = { name: projectName, $schema: schemaPath, sourceRoot: projectPath, projectType: 'application', }; } // Add/update sourceTemplate field projectJson.sourceTemplate = sourceTemplate; // Write back to file const content = JSON.stringify(projectJson, null, 2); await fs.writeFile(projectJsonPath, `${content}\n`); log.info(`Created/updated project.json with sourceTemplate: ${sourceTemplate}`); } catch (error) { throw new Error( `Failed to create project.json: ${error instanceof Error ? error.message : String(error)}`, ); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AgiFlow/aicode-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server