-
Notifications
You must be signed in to change notification settings - Fork 11.9k
feat(@angular/cli): standardize MCP tools around workspace/project options #32294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
dgp1130
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor suggestions, I'm not sure how much of the resolveWorkspaceAndProject implementation was reused so I might be repeating previous feedback. Feel free to ignore / defer anything we've already discussed or which can be followed up on later.
| } | ||
|
|
||
| const deadline = Date.now() + input.timeout; | ||
| async function performWait(devserver: Devserver, timeout: number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Use an explicit return type. Otherwise, TS will implicitly union all the return statement types and wouldn't prevent from accidentally returning the wrong type.
https://techhub.social/@develwithoutacause/111842243141199473
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting the factory function types is actually trickier than it seems and is cross-cutting across all tools (including ones not included in this change) so I don't want to include it in this PR.
| fail('Should have thrown'); | ||
| } catch (e) { | ||
| expect((e as Error).message).toContain('Dev server not found. Currently running servers:'); | ||
| expect((e as Error).message).toContain("- Project 'app-one' in workspace path '/test'"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Should this message contain the port number too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These errors are worded around how the agent should correct its argument for the next call, the port is not needed for that. Also presumably the agent should have all the ports already reported from all the start commands.
| ); | ||
| } | ||
|
|
||
| export function createDevServerNotFoundError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Might be better to leave this in devserver-stop.ts since it specifically relates to devserver functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used for both stop and wait_for_build so I wanted it in a central place. I did move it to devserver.ts though.
| 'Please provide a project name or set a default project in angular.json. ' + | ||
| "You can use 'list_projects' to find available projects.", | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Do these helpers need to be exported? Will they ever realistically be called outside this file if resolveWorkspaceAndProject is doing most of the work?
Going a step further, if these errors are only created in one place, would it be more clear to just inline them at their individual call sites?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inlined them.
| workspacePathInput, | ||
| projectNameInput, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: I don't think we need the Input suffix here, I'd just call these workspacePath and projectName.
If the goal is to have a local variable with the same name, you can do that by aliasing in the destructure without altering the public API of the function.
function func({foo: bar}: {foo: string}): string {
const foo = bar;
return foo;
}
func({foo: 'test'});There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to separate them because the input varieties are 1-to-1 with the function input, so for example they could be missing, while the non-input versions are non-nullable results of looking the inputs up.
| * current directory as the workspace. | ||
| * If `projectNameInput` is absent, uses the default project in the workspace. | ||
| */ | ||
| export async function resolveWorkspaceAndProject({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: I'm a bit wary of this utils file getting a bit long and have a nebulous purpose. Could we split it up into separate (or at least rename to a well-defined) domain-specific file(s)? Something like projects.ts or workspaces.ts? Could also combined shared-options.ts into the same file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split into a separate workspace-utils.ts file, now only some small stuff remained in utils.ts.
| let workspace: AngularWorkspace; | ||
|
|
||
| if (workspacePathInput) { | ||
| if (!host.existsSync(workspacePathInput)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Can/should we use async APIs here for improved parallelism? Not sure how much of a concern multiple MCP requests would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's critical here, and if anything I feel like if this becomes noticeable, some sort of caching would be a better direction.
| * current directory as the workspace. | ||
| * If `projectNameInput` is absent, uses the default project in the workspace. | ||
| */ | ||
| export async function resolveWorkspaceAndProject({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Can we split this implementation into separate resolveWorkspace and resolveProject functions? I think this would make the internals easier to follow as we can leverage early returns rather than stateful local variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original implementation was split! But then I realized (1) practically all tools want them both together, and (2) it would lead to duplicate lines of code running. So batched them together for now.
| let workspace: AngularWorkspace; | ||
|
|
||
| if (workspacePathInput) { | ||
| if (!host.existsSync(workspacePathInput)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Do we really care about this as a distinct error case from ${workspacePathInput}/angular.json as checked below? I'm not sure this is worth the maintenance effort / performance cost of an additional stat call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted very precise error messages for the agent. I think it also helps clarify that the correct input is a workspace path and not an angular.json path. Ack regarding the cost but I don't think the added check is significant here.
| workspacePathInput, | ||
| projectNameInput, | ||
| mcpWorkspace, | ||
| workspaceLoader = AngularWorkspace.load, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider: Is this option just for testing purposes? Can we drop it from the public API or at least make it more clearly not for general usage?
I'm thinking you can either directly depend on AngularWorkspace.load and then spy on it in the test, or define this as a test only parameter like:
function resolveWorkspaceAndProject(
{/* regular options */}: {/* ... */},
testOnlyOptions: {
workspaceLoader = AngularWorkspace.load,
} = {},
): Promise<{ /* ... */}>;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, now spying on it.
workspaceandprojectoptions that make them better suited to monorepo scenarios.Tested manually with Gemini-CLI, both in an Angular directory and in a non-Angular parent directory.