1111 * Utility functions shared across MCP tools.
1212 */
1313
14- import { workspaces } from '@angular-devkit/core' ;
15- import { dirname , join } from 'node:path' ;
16- import { AngularWorkspace } from '../../utilities/config' ;
17- import { CommandError , type Host , LocalWorkspaceHost } from './host' ;
18- import { McpToolContext } from './tools/tool-registry' ;
14+ import { CommandError } from './host' ;
1915
2016/**
2117 * Returns simple structured content output from an MCP tool.
@@ -29,74 +25,6 @@ export function createStructuredContentOutput<OutputType>(structuredContent: Out
2925 } ;
3026}
3127
32- /**
33- * Searches for an angular.json file by traversing up the directory tree from a starting directory.
34- *
35- * @param startDir The directory path to start searching from
36- * @param host The workspace host instance used to check file existence. Defaults to LocalWorkspaceHost
37- * @returns The absolute path to the directory containing angular.json, or null if not found
38- *
39- * @remarks
40- * This function performs an upward directory traversal starting from `startDir`.
41- * It checks each directory for the presence of an angular.json file until either:
42- * - The file is found (returns the directory path)
43- * - The root of the filesystem is reached (returns null)
44- */
45- export function findAngularJsonDir ( startDir : string , host = LocalWorkspaceHost ) : string | null {
46- let currentDir = startDir ;
47- while ( true ) {
48- if ( host . existsSync ( join ( currentDir , 'angular.json' ) ) ) {
49- return currentDir ;
50- }
51- const parentDir = dirname ( currentDir ) ;
52- if ( parentDir === currentDir ) {
53- return null ;
54- }
55- currentDir = parentDir ;
56- }
57- }
58-
59- /**
60- * Searches for a project in the current workspace, by name.
61- */
62- export function getProject (
63- context : McpToolContext ,
64- name : string ,
65- ) : workspaces . ProjectDefinition | undefined {
66- const projects = context . workspace ?. projects ;
67- if ( ! projects ) {
68- return undefined ;
69- }
70-
71- return projects . get ( name ) ;
72- }
73-
74- /**
75- * Returns the name of the default project in the current workspace, or undefined if none exists.
76- *
77- * If no default project is defined but there's only a single project in the workspace, its name will
78- * be returned.
79- */
80- export function getDefaultProjectName ( workspace : AngularWorkspace | undefined ) : string | undefined {
81- const projects = workspace ?. projects ;
82-
83- if ( ! projects ) {
84- return undefined ;
85- }
86-
87- const defaultProjectName = workspace ?. extensions [ 'defaultProject' ] as string | undefined ;
88- if ( defaultProjectName ) {
89- return defaultProjectName ;
90- }
91-
92- // No default project defined? This might still be salvageable if only a single project exists.
93- if ( projects . size === 1 ) {
94- return Array . from ( projects . keys ( ) ) [ 0 ] ;
95- }
96-
97- return undefined ;
98- }
99-
10028/**
10129 * Get the logs of a failing command.
10230 *
@@ -111,139 +39,3 @@ export function getCommandErrorLogs(e: unknown): string[] {
11139 return [ String ( e ) ] ;
11240 }
11341}
114-
115- export function createWorkspaceNotFoundError ( ) : Error {
116- return new Error (
117- 'Could not find an Angular workspace (angular.json) in the current directory. ' +
118- "You can use 'list_projects' to find available workspaces." ,
119- ) ;
120- }
121-
122- export function createWorkspacePathDoesNotExistError ( path : string ) : Error {
123- return new Error (
124- `Workspace path does not exist: ${ path } . ` +
125- "You can use 'list_projects' to find available workspaces." ,
126- ) ;
127- }
128-
129- export function createNoAngularJsonFoundError ( path : string ) : Error {
130- return new Error (
131- `No angular.json found at ${ path } . ` +
132- "You can use 'list_projects' to find available workspaces." ,
133- ) ;
134- }
135-
136- export function createProjectNotFoundError ( projectName : string , workspacePath : string ) : Error {
137- return new Error (
138- `Project '${ projectName } ' not found in workspace path ${ workspacePath } . ` +
139- "You can use 'list_projects' to find available projects." ,
140- ) ;
141- }
142-
143- export function createNoProjectResolvedError ( workspacePath : string ) : Error {
144- return new Error (
145- `No project name provided and no default project found in workspace path ${ workspacePath } . ` +
146- 'Please provide a project name or set a default project in angular.json. ' +
147- "You can use 'list_projects' to find available projects." ,
148- ) ;
149- }
150-
151- export function createDevServerNotFoundError (
152- devservers : Map < string , { project : string ; workspacePath : string } > ,
153- ) : Error {
154- if ( devservers . size === 0 ) {
155- return new Error ( 'No development servers are currently running.' ) ;
156- }
157-
158- const runningServers = Array . from ( devservers . values ( ) )
159- . map ( ( server ) => `- Project '${ server . project } ' in workspace path '${ server . workspacePath } '` )
160- . join ( '\n' ) ;
161-
162- return new Error (
163- `Dev server not found. Currently running servers:\n${ runningServers } \n` +
164- 'Please provide the correct workspace and project arguments.' ,
165- ) ;
166- }
167-
168- /**
169- * Resolves workspace and project for tools to operate on.
170- *
171- * If `workspacePathInput` is absent, uses the MCP's configured workspace. If none is configured, use the
172- * current directory as the workspace.
173- * If `projectNameInput` is absent, uses the default project in the workspace.
174- */
175- export async function resolveWorkspaceAndProject ( {
176- host,
177- workspacePathInput,
178- projectNameInput,
179- mcpWorkspace,
180- workspaceLoader = AngularWorkspace . load ,
181- } : {
182- host : Host ;
183- workspacePathInput ?: string ;
184- projectNameInput ?: string ;
185- mcpWorkspace ?: AngularWorkspace ;
186- workspaceLoader ?: ( path : string ) => Promise < AngularWorkspace > ;
187- } ) : Promise < {
188- workspace : AngularWorkspace ;
189- workspacePath : string ;
190- projectName : string ;
191- } > {
192- let workspacePath : string ;
193- let workspace : AngularWorkspace ;
194-
195- if ( workspacePathInput ) {
196- if ( ! host . existsSync ( workspacePathInput ) ) {
197- throw createWorkspacePathDoesNotExistError ( workspacePathInput ) ;
198- }
199- if ( ! host . existsSync ( join ( workspacePathInput , 'angular.json' ) ) ) {
200- throw createNoAngularJsonFoundError ( workspacePathInput ) ;
201- }
202- workspacePath = workspacePathInput ;
203- const configPath = join ( workspacePath , 'angular.json' ) ;
204- try {
205- workspace = await workspaceLoader ( configPath ) ;
206- } catch ( e ) {
207- throw new Error (
208- `Failed to load workspace configuration at ${ configPath } : ${
209- e instanceof Error ? e . message : e
210- } `,
211- ) ;
212- }
213- } else if ( mcpWorkspace ) {
214- workspace = mcpWorkspace ;
215- workspacePath = workspace . basePath ;
216- } else {
217- const found = findAngularJsonDir ( process . cwd ( ) , host ) ;
218-
219- if ( ! found ) {
220- throw createWorkspaceNotFoundError ( ) ;
221- }
222- workspacePath = found ;
223- const configPath = join ( workspacePath , 'angular.json' ) ;
224- try {
225- workspace = await workspaceLoader ( configPath ) ;
226- } catch ( e ) {
227- throw new Error (
228- `Failed to load workspace configuration at ${ configPath } : ${
229- e instanceof Error ? e . message : e
230- } `,
231- ) ;
232- }
233- }
234-
235- let projectName = projectNameInput ;
236- if ( projectName ) {
237- if ( ! workspace . projects . has ( projectName ) ) {
238- throw createProjectNotFoundError ( projectName , workspacePath ) ;
239- }
240- } else {
241- projectName = getDefaultProjectName ( workspace ) ;
242- }
243-
244- if ( ! projectName ) {
245- throw createNoProjectResolvedError ( workspacePath ) ;
246- }
247-
248- return { workspace, workspacePath, projectName } ;
249- }
0 commit comments