@angular/cli 21.0.0-next.2 → 21.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/code-examples.db +0 -0
- package/lib/config/schema.json +75 -19
- package/package.json +19 -19
- package/src/commands/mcp/mcp-server.d.ts +0 -10
- package/src/commands/mcp/mcp-server.js +35 -8
- package/src/commands/mcp/tools/examples.d.ts +25 -0
- package/src/commands/mcp/tools/examples.js +268 -44
- package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.js +1 -0
- package/src/utilities/package-manager.d.ts +12 -0
- package/src/utilities/package-manager.js +31 -22
- package/src/utilities/version.js +1 -1
package/lib/code-examples.db
CHANGED
|
Binary file
|
package/lib/config/schema.json
CHANGED
|
@@ -4304,23 +4304,23 @@
|
|
|
4304
4304
|
"properties": {
|
|
4305
4305
|
"buildTarget": {
|
|
4306
4306
|
"type": "string",
|
|
4307
|
-
"description": "
|
|
4307
|
+
"description": "Specifies the build target to use for the unit test build in the format `project:target[:configuration]`. You can also pass a comma-separated list of configurations. Example: `project:target:production,staging`.",
|
|
4308
4308
|
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
|
|
4309
4309
|
},
|
|
4310
4310
|
"tsConfig": {
|
|
4311
4311
|
"type": "string",
|
|
4312
|
-
"description": "The
|
|
4312
|
+
"description": "The path to the TypeScript configuration file, relative to the workspace root."
|
|
4313
4313
|
},
|
|
4314
4314
|
"runner": {
|
|
4315
4315
|
"type": "string",
|
|
4316
|
-
"description": "
|
|
4316
|
+
"description": "Specifies the test runner to use for test execution.",
|
|
4317
4317
|
"enum": [
|
|
4318
4318
|
"karma",
|
|
4319
4319
|
"vitest"
|
|
4320
4320
|
]
|
|
4321
4321
|
},
|
|
4322
4322
|
"browsers": {
|
|
4323
|
-
"description": "
|
|
4323
|
+
"description": "Specifies the browsers to use for test execution. When not specified, tests are run in a Node.js environment using jsdom. For both Vitest and Karma, browser names ending with 'Headless' (e.g., 'ChromeHeadless') will enable headless mode.",
|
|
4324
4324
|
"type": "array",
|
|
4325
4325
|
"items": {
|
|
4326
4326
|
"type": "string"
|
|
@@ -4335,41 +4335,43 @@
|
|
|
4335
4335
|
"default": [
|
|
4336
4336
|
"**/*.spec.ts"
|
|
4337
4337
|
],
|
|
4338
|
-
"description": "
|
|
4338
|
+
"description": "Specifies glob patterns of files to include for testing, relative to the project root. This option also has special handling for directory paths (includes all `.spec.ts` files within) and file paths (includes the corresponding `.spec` file if one exists)."
|
|
4339
4339
|
},
|
|
4340
4340
|
"exclude": {
|
|
4341
4341
|
"type": "array",
|
|
4342
4342
|
"items": {
|
|
4343
4343
|
"type": "string"
|
|
4344
4344
|
},
|
|
4345
|
-
"
|
|
4346
|
-
|
|
4345
|
+
"description": "Specifies glob patterns of files to exclude from testing, relative to the project root."
|
|
4346
|
+
},
|
|
4347
|
+
"filter": {
|
|
4348
|
+
"type": "string",
|
|
4349
|
+
"description": "Specifies a regular expression pattern to match against test suite and test names. Only tests with a name matching the pattern will be executed. For example, `^App` will run only tests in suites beginning with 'App'."
|
|
4347
4350
|
},
|
|
4348
4351
|
"watch": {
|
|
4349
4352
|
"type": "boolean",
|
|
4350
|
-
"description": "
|
|
4353
|
+
"description": "Enables watch mode, which re-runs tests when source files change. Defaults to `true` in TTY environments and `false` otherwise."
|
|
4351
4354
|
},
|
|
4352
4355
|
"debug": {
|
|
4353
4356
|
"type": "boolean",
|
|
4354
|
-
"description": "
|
|
4357
|
+
"description": "Enables debugging mode for tests, allowing the use of the Node Inspector.",
|
|
4355
4358
|
"default": false
|
|
4356
4359
|
},
|
|
4357
4360
|
"codeCoverage": {
|
|
4358
4361
|
"type": "boolean",
|
|
4359
|
-
"description": "
|
|
4362
|
+
"description": "Enables code coverage reporting for tests.",
|
|
4360
4363
|
"default": false
|
|
4361
4364
|
},
|
|
4362
4365
|
"codeCoverageExclude": {
|
|
4363
4366
|
"type": "array",
|
|
4364
|
-
"description": "
|
|
4367
|
+
"description": "Specifies glob patterns of files to exclude from the code coverage report.",
|
|
4365
4368
|
"items": {
|
|
4366
4369
|
"type": "string"
|
|
4367
|
-
}
|
|
4368
|
-
"default": []
|
|
4370
|
+
}
|
|
4369
4371
|
},
|
|
4370
4372
|
"codeCoverageReporters": {
|
|
4371
4373
|
"type": "array",
|
|
4372
|
-
"description": "
|
|
4374
|
+
"description": "Specifies the reporters to use for code coverage results. Each reporter can be a string representing its name, or a tuple containing the name and an options object. Built-in reporters include 'html', 'lcov', 'lcovonly', 'text', 'text-summary', 'cobertura', 'json', and 'json-summary'.",
|
|
4373
4375
|
"items": {
|
|
4374
4376
|
"oneOf": [
|
|
4375
4377
|
{
|
|
@@ -4393,14 +4395,49 @@
|
|
|
4393
4395
|
},
|
|
4394
4396
|
"reporters": {
|
|
4395
4397
|
"type": "array",
|
|
4396
|
-
"description": "
|
|
4398
|
+
"description": "Specifies the reporters to use during test execution. Each reporter can be a string representing its name, or a tuple containing the name and an options object. Built-in reporters include 'default', 'verbose', 'dots', 'json', 'junit', 'tap', 'tap-flat', and 'html'. You can also provide a path to a custom reporter.",
|
|
4397
4399
|
"items": {
|
|
4398
|
-
"
|
|
4400
|
+
"oneOf": [
|
|
4401
|
+
{
|
|
4402
|
+
"anyOf": [
|
|
4403
|
+
{
|
|
4404
|
+
"$ref": "#/definitions/AngularBuildBuildersUnitTestSchema/definitions/reporters-enum"
|
|
4405
|
+
},
|
|
4406
|
+
{
|
|
4407
|
+
"type": "string"
|
|
4408
|
+
}
|
|
4409
|
+
]
|
|
4410
|
+
},
|
|
4411
|
+
{
|
|
4412
|
+
"type": "array",
|
|
4413
|
+
"minItems": 1,
|
|
4414
|
+
"maxItems": 2,
|
|
4415
|
+
"items": [
|
|
4416
|
+
{
|
|
4417
|
+
"anyOf": [
|
|
4418
|
+
{
|
|
4419
|
+
"$ref": "#/definitions/AngularBuildBuildersUnitTestSchema/definitions/reporters-enum"
|
|
4420
|
+
},
|
|
4421
|
+
{
|
|
4422
|
+
"type": "string"
|
|
4423
|
+
}
|
|
4424
|
+
]
|
|
4425
|
+
},
|
|
4426
|
+
{
|
|
4427
|
+
"type": "object"
|
|
4428
|
+
}
|
|
4429
|
+
]
|
|
4430
|
+
}
|
|
4431
|
+
]
|
|
4399
4432
|
}
|
|
4400
4433
|
},
|
|
4434
|
+
"outputFile": {
|
|
4435
|
+
"type": "string",
|
|
4436
|
+
"description": "Specifies a file path for the test report, applying only to the first reporter. To configure output files for multiple reporters, use the tuple format `['reporter-name', { outputFile: '...' }]` within the `reporters` option. When not provided, output is written to the console."
|
|
4437
|
+
},
|
|
4401
4438
|
"providersFile": {
|
|
4402
4439
|
"type": "string",
|
|
4403
|
-
"description": "TypeScript file that
|
|
4440
|
+
"description": "Specifies the path to a TypeScript file that provides an array of Angular providers for the test environment. The file must contain a default export of the provider array.",
|
|
4404
4441
|
"minLength": 1
|
|
4405
4442
|
},
|
|
4406
4443
|
"setupFiles": {
|
|
@@ -4408,11 +4445,17 @@
|
|
|
4408
4445
|
"items": {
|
|
4409
4446
|
"type": "string"
|
|
4410
4447
|
},
|
|
4411
|
-
"description": "A list of global setup
|
|
4448
|
+
"description": "A list of paths to global setup files that are executed before the test files. The application's polyfills and the Angular TestBed are always initialized before these files."
|
|
4412
4449
|
},
|
|
4413
4450
|
"progress": {
|
|
4414
4451
|
"type": "boolean",
|
|
4415
|
-
"description": "
|
|
4452
|
+
"description": "Shows build progress information in the console. Defaults to the `progress` setting of the specified `buildTarget`."
|
|
4453
|
+
},
|
|
4454
|
+
"dumpVirtualFiles": {
|
|
4455
|
+
"type": "boolean",
|
|
4456
|
+
"description": "Dumps build output files to the `.angular/cache` directory for debugging purposes.",
|
|
4457
|
+
"default": false,
|
|
4458
|
+
"visible": false
|
|
4416
4459
|
}
|
|
4417
4460
|
},
|
|
4418
4461
|
"additionalProperties": false,
|
|
@@ -4428,6 +4471,19 @@
|
|
|
4428
4471
|
"json",
|
|
4429
4472
|
"json-summary"
|
|
4430
4473
|
]
|
|
4474
|
+
},
|
|
4475
|
+
"reporters-enum": {
|
|
4476
|
+
"type": "string",
|
|
4477
|
+
"enum": [
|
|
4478
|
+
"default",
|
|
4479
|
+
"verbose",
|
|
4480
|
+
"dots",
|
|
4481
|
+
"json",
|
|
4482
|
+
"junit",
|
|
4483
|
+
"tap",
|
|
4484
|
+
"tap-flat",
|
|
4485
|
+
"html"
|
|
4486
|
+
]
|
|
4431
4487
|
}
|
|
4432
4488
|
}
|
|
4433
4489
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/cli",
|
|
3
|
-
"version": "21.0.0-next.
|
|
3
|
+
"version": "21.0.0-next.4",
|
|
4
4
|
"description": "CLI tool for Angular",
|
|
5
5
|
"main": "lib/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/angular/angular-cli",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@angular-devkit/architect": "0.2100.0-next.
|
|
29
|
-
"@angular-devkit/core": "21.0.0-next.
|
|
30
|
-
"@angular-devkit/schematics": "21.0.0-next.
|
|
31
|
-
"@inquirer/prompts": "7.8.
|
|
32
|
-
"@listr2/prompt-adapter-inquirer": "3.0.
|
|
33
|
-
"@modelcontextprotocol/sdk": "1.
|
|
34
|
-
"@schematics/angular": "21.0.0-next.
|
|
28
|
+
"@angular-devkit/architect": "0.2100.0-next.4",
|
|
29
|
+
"@angular-devkit/core": "21.0.0-next.4",
|
|
30
|
+
"@angular-devkit/schematics": "21.0.0-next.4",
|
|
31
|
+
"@inquirer/prompts": "7.8.6",
|
|
32
|
+
"@listr2/prompt-adapter-inquirer": "3.0.4",
|
|
33
|
+
"@modelcontextprotocol/sdk": "1.18.0",
|
|
34
|
+
"@schematics/angular": "21.0.0-next.4",
|
|
35
35
|
"@yarnpkg/lockfile": "1.1.0",
|
|
36
36
|
"algoliasearch": "5.37.0",
|
|
37
37
|
"ini": "5.0.0",
|
|
38
38
|
"jsonc-parser": "3.3.1",
|
|
39
|
-
"listr2": "9.0.
|
|
39
|
+
"listr2": "9.0.4",
|
|
40
40
|
"npm-package-arg": "13.0.0",
|
|
41
|
-
"pacote": "21.0.
|
|
41
|
+
"pacote": "21.0.1",
|
|
42
42
|
"resolve": "1.22.10",
|
|
43
43
|
"semver": "7.7.2",
|
|
44
44
|
"yargs": "18.0.0",
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
"ng-update": {
|
|
48
48
|
"migrations": "@schematics/angular/migrations/migration-collection.json",
|
|
49
49
|
"packageGroup": {
|
|
50
|
-
"@angular/cli": "21.0.0-next.
|
|
51
|
-
"@angular/build": "21.0.0-next.
|
|
52
|
-
"@angular/ssr": "21.0.0-next.
|
|
53
|
-
"@angular-devkit/architect": "0.2100.0-next.
|
|
54
|
-
"@angular-devkit/build-angular": "21.0.0-next.
|
|
55
|
-
"@angular-devkit/build-webpack": "0.2100.0-next.
|
|
56
|
-
"@angular-devkit/core": "21.0.0-next.
|
|
57
|
-
"@angular-devkit/schematics": "21.0.0-next.
|
|
50
|
+
"@angular/cli": "21.0.0-next.4",
|
|
51
|
+
"@angular/build": "21.0.0-next.4",
|
|
52
|
+
"@angular/ssr": "21.0.0-next.4",
|
|
53
|
+
"@angular-devkit/architect": "0.2100.0-next.4",
|
|
54
|
+
"@angular-devkit/build-angular": "21.0.0-next.4",
|
|
55
|
+
"@angular-devkit/build-webpack": "0.2100.0-next.4",
|
|
56
|
+
"@angular-devkit/core": "21.0.0-next.4",
|
|
57
|
+
"@angular-devkit/schematics": "21.0.0-next.4"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
|
-
"packageManager": "pnpm@10.
|
|
60
|
+
"packageManager": "pnpm@10.17.0",
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
63
63
|
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
|
|
@@ -13,16 +13,6 @@ import { AnyMcpToolDeclaration } from './tools/tool-registry';
|
|
|
13
13
|
* These tools are considered experimental and may have limitations.
|
|
14
14
|
*/
|
|
15
15
|
export declare const EXPERIMENTAL_TOOLS: readonly [import("./tools/tool-registry").McpToolDeclaration<{
|
|
16
|
-
query: import("zod").ZodString;
|
|
17
|
-
}, {
|
|
18
|
-
examples: import("zod").ZodArray<import("zod").ZodObject<{
|
|
19
|
-
content: import("zod").ZodString;
|
|
20
|
-
}, "strip", import("zod").ZodTypeAny, {
|
|
21
|
-
content: string;
|
|
22
|
-
}, {
|
|
23
|
-
content: string;
|
|
24
|
-
}>, "many">;
|
|
25
|
-
}>, import("./tools/tool-registry").McpToolDeclaration<{
|
|
26
16
|
transformations: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
|
|
27
17
|
}, {
|
|
28
18
|
instructions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
@@ -28,16 +28,17 @@ const tool_registry_1 = require("./tools/tool-registry");
|
|
|
28
28
|
* The set of tools that are enabled by default for the MCP server.
|
|
29
29
|
* These tools are considered stable and suitable for general use.
|
|
30
30
|
*/
|
|
31
|
-
const STABLE_TOOLS = [
|
|
31
|
+
const STABLE_TOOLS = [
|
|
32
|
+
best_practices_1.BEST_PRACTICES_TOOL,
|
|
33
|
+
doc_search_1.DOC_SEARCH_TOOL,
|
|
34
|
+
examples_1.FIND_EXAMPLE_TOOL,
|
|
35
|
+
projects_1.LIST_PROJECTS_TOOL,
|
|
36
|
+
];
|
|
32
37
|
/**
|
|
33
38
|
* The set of tools that are available but not enabled by default.
|
|
34
39
|
* These tools are considered experimental and may have limitations.
|
|
35
40
|
*/
|
|
36
|
-
exports.EXPERIMENTAL_TOOLS = [
|
|
37
|
-
examples_1.FIND_EXAMPLE_TOOL,
|
|
38
|
-
modernize_1.MODERNIZE_TOOL,
|
|
39
|
-
zoneless_migration_1.ZONELESS_MIGRATION_TOOL,
|
|
40
|
-
];
|
|
41
|
+
exports.EXPERIMENTAL_TOOLS = [modernize_1.MODERNIZE_TOOL, zoneless_migration_1.ZONELESS_MIGRATION_TOOL];
|
|
41
42
|
async function createMcpServer(options, logger) {
|
|
42
43
|
const server = new mcp_js_1.McpServer({
|
|
43
44
|
name: 'angular-cli-server',
|
|
@@ -48,8 +49,34 @@ async function createMcpServer(options, logger) {
|
|
|
48
49
|
tools: {},
|
|
49
50
|
logging: {},
|
|
50
51
|
},
|
|
51
|
-
instructions:
|
|
52
|
-
|
|
52
|
+
instructions: `
|
|
53
|
+
<General Purpose>
|
|
54
|
+
This server provides a safe, programmatic interface to the Angular CLI for an AI assistant.
|
|
55
|
+
Your primary goal is to use these tools to understand, analyze, refactor, and run Angular
|
|
56
|
+
projects. You MUST prefer the tools provided by this server over using \`run_shell_command\` for
|
|
57
|
+
equivalent actions.
|
|
58
|
+
</General Purpose>
|
|
59
|
+
|
|
60
|
+
<Core Workflows & Tool Guide>
|
|
61
|
+
* **1. Discover Project Structure (Mandatory First Step):** Always begin by calling
|
|
62
|
+
\`list_projects\` to understand the workspace. The outputs from this tool are often
|
|
63
|
+
required inputs for other tools.
|
|
64
|
+
|
|
65
|
+
* **2. Write & Modify Code:** Before writing or changing code, you MUST consult the
|
|
66
|
+
\`get_best_practices\` tool to learn the current, non-negotiable coding standards.
|
|
67
|
+
|
|
68
|
+
* **3. Answer User Questions:**
|
|
69
|
+
- For conceptual questions ("what is..."), use \`search_documentation\`.
|
|
70
|
+
- For code examples ("show me how to..."), use \`find_examples\`.
|
|
71
|
+
</Core Workflows & Tool Guide>
|
|
72
|
+
|
|
73
|
+
<Key Concepts>
|
|
74
|
+
* **Workspace vs. Project:** A 'workspace' contains an \`angular.json\` file and defines 'projects'
|
|
75
|
+
(applications or libraries). A monorepo can have multiple workspaces.
|
|
76
|
+
* **Targeting Projects:** Always use the \`workspaceConfigPath\` from \`list_projects\` when
|
|
77
|
+
available to ensure you are targeting the correct project in a monorepo.
|
|
78
|
+
</Key Concepts>
|
|
79
|
+
`,
|
|
53
80
|
});
|
|
54
81
|
(0, instructions_1.registerInstructionsResource)(server);
|
|
55
82
|
const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, exports.EXPERIMENTAL_TOOLS, {
|
|
@@ -8,13 +8,38 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
export declare const FIND_EXAMPLE_TOOL: import("./tool-registry").McpToolDeclaration<{
|
|
10
10
|
query: z.ZodString;
|
|
11
|
+
keywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
12
|
+
required_packages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
13
|
+
related_concepts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
14
|
+
includeExperimental: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
15
|
}, {
|
|
12
16
|
examples: z.ZodArray<z.ZodObject<{
|
|
17
|
+
title: z.ZodString;
|
|
18
|
+
summary: z.ZodString;
|
|
19
|
+
keywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
required_packages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
21
|
+
related_concepts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
22
|
+
related_tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
13
23
|
content: z.ZodString;
|
|
24
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
14
25
|
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
title: string;
|
|
15
27
|
content: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
keywords?: string[] | undefined;
|
|
30
|
+
required_packages?: string[] | undefined;
|
|
31
|
+
related_concepts?: string[] | undefined;
|
|
32
|
+
related_tools?: string[] | undefined;
|
|
33
|
+
snippet?: string | undefined;
|
|
16
34
|
}, {
|
|
35
|
+
title: string;
|
|
17
36
|
content: string;
|
|
37
|
+
summary: string;
|
|
38
|
+
keywords?: string[] | undefined;
|
|
39
|
+
required_packages?: string[] | undefined;
|
|
40
|
+
related_concepts?: string[] | undefined;
|
|
41
|
+
related_tools?: string[] | undefined;
|
|
42
|
+
snippet?: string | undefined;
|
|
18
43
|
}>, "many">;
|
|
19
44
|
}>;
|
|
20
45
|
/**
|
|
@@ -50,28 +50,92 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
50
50
|
const zod_1 = require("zod");
|
|
51
51
|
const tool_registry_1 = require("./tool-registry");
|
|
52
52
|
const findExampleInputSchema = zod_1.z.object({
|
|
53
|
-
query: zod_1.z
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
query: zod_1.z
|
|
54
|
+
.string()
|
|
55
|
+
.describe(`The primary, conceptual search query. This should capture the user's main goal or question ` +
|
|
56
|
+
`(e.g., 'lazy loading a route' or 'how to use signal inputs'). The query will be processed ` +
|
|
57
|
+
'by a powerful full-text search engine.\n\n' +
|
|
58
|
+
'Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):\n' +
|
|
59
|
+
' - AND (default): Space-separated terms are combined with AND.\n' +
|
|
60
|
+
' - Example: \'standalone component\' (finds results with both "standalone" and "component")\n' +
|
|
61
|
+
' - OR: Use the OR operator to find results with either term.\n' +
|
|
62
|
+
" - Example: 'validation OR validator'\n" +
|
|
63
|
+
' - NOT: Use the NOT operator to exclude terms.\n' +
|
|
64
|
+
" - Example: 'forms NOT reactive'\n" +
|
|
65
|
+
' - Grouping: Use parentheses () to group expressions.\n' +
|
|
66
|
+
" - Example: '(validation OR validator) AND forms'\n" +
|
|
67
|
+
' - Phrase Search: Use double quotes "" for exact phrases.\n' +
|
|
68
|
+
' - Example: \'"template-driven forms"\'\n' +
|
|
69
|
+
' - Prefix Search: Use an asterisk * for prefix matching.\n' +
|
|
70
|
+
' - Example: \'rout*\' (matches "route", "router", "routing")'),
|
|
71
|
+
keywords: zod_1.z
|
|
72
|
+
.array(zod_1.z.string())
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('A list of specific, exact keywords to narrow the search. Use this for precise terms like ' +
|
|
75
|
+
'API names, function names, or decorators (e.g., `ngFor`, `trackBy`, `inject`).'),
|
|
76
|
+
required_packages: zod_1.z
|
|
77
|
+
.array(zod_1.z.string())
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("A list of NPM packages that an example must use. Use this when the user's request is " +
|
|
80
|
+
'specific to a feature within a certain package (e.g., if the user asks about `ngModel`, ' +
|
|
81
|
+
'you should filter by `@angular/forms`).'),
|
|
82
|
+
related_concepts: zod_1.z
|
|
83
|
+
.array(zod_1.z.string())
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('A list of high-level concepts to filter by. Use this to find examples related to broader ' +
|
|
86
|
+
'architectural ideas or patterns (e.g., `signals`, `dependency injection`, `routing`).'),
|
|
87
|
+
includeExperimental: zod_1.z
|
|
88
|
+
.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.default(false)
|
|
91
|
+
.describe('By default, this tool returns only production-safe examples. Set this to `true` **only if** ' +
|
|
92
|
+
'the user explicitly asks for a bleeding-edge feature or if a stable solution to their ' +
|
|
93
|
+
'problem cannot be found. If you set this to `true`, you **MUST** preface your answer by ' +
|
|
94
|
+
'warning the user that the example uses experimental APIs that are not suitable for production.'),
|
|
95
|
+
});
|
|
96
|
+
const findExampleOutputSchema = zod_1.z.object({
|
|
97
|
+
examples: zod_1.z.array(zod_1.z.object({
|
|
98
|
+
title: zod_1.z
|
|
99
|
+
.string()
|
|
100
|
+
.describe('The title of the example. Use this as a heading when presenting the example to the user.'),
|
|
101
|
+
summary: zod_1.z
|
|
102
|
+
.string()
|
|
103
|
+
.describe("A one-sentence summary of the example's purpose. Use this to help the user decide " +
|
|
104
|
+
'if the example is relevant to them.'),
|
|
105
|
+
keywords: zod_1.z
|
|
106
|
+
.array(zod_1.z.string())
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('A list of keywords for the example. You can use these to explain why this example ' +
|
|
109
|
+
"was a good match for the user's query."),
|
|
110
|
+
required_packages: zod_1.z
|
|
111
|
+
.array(zod_1.z.string())
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('A list of NPM packages required for the example to work. Before presenting the code, ' +
|
|
114
|
+
'you should inform the user if any of these packages need to be installed.'),
|
|
115
|
+
related_concepts: zod_1.z
|
|
116
|
+
.array(zod_1.z.string())
|
|
117
|
+
.optional()
|
|
118
|
+
.describe('A list of related concepts. You can suggest these to the user as topics for ' +
|
|
119
|
+
'follow-up questions.'),
|
|
120
|
+
related_tools: zod_1.z
|
|
121
|
+
.array(zod_1.z.string())
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('A list of related MCP tools. You can suggest these as potential next steps for the user.'),
|
|
124
|
+
content: zod_1.z
|
|
125
|
+
.string()
|
|
126
|
+
.describe('A complete, self-contained Angular code example in Markdown format. This should be ' +
|
|
127
|
+
'presented to the user inside a markdown code block.'),
|
|
128
|
+
snippet: zod_1.z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('A contextual snippet from the content showing the matched search term. This field is ' +
|
|
132
|
+
'critical for efficiently evaluating a result`s relevance. It enables two primary ' +
|
|
133
|
+
'workflows:\n\n' +
|
|
134
|
+
'1. For direct questions: You can internally review snippets to select the single best ' +
|
|
135
|
+
'result before generating a comprehensive answer from its full `content`.\n' +
|
|
136
|
+
'2. For ambiguous or exploratory questions: You can present a summary of titles and ' +
|
|
137
|
+
'snippets to the user, allowing them to guide the next step.'),
|
|
138
|
+
})),
|
|
75
139
|
});
|
|
76
140
|
exports.FIND_EXAMPLE_TOOL = (0, tool_registry_1.declareTool)({
|
|
77
141
|
name: 'find_examples',
|
|
@@ -89,7 +153,9 @@ new or evolving features.
|
|
|
89
153
|
* **Modern Implementation:** Finding the correct modern syntax for features
|
|
90
154
|
(e.g., query: 'functional route guard' or 'http client with fetch').
|
|
91
155
|
* **Refactoring to Modern Patterns:** Upgrading older code by finding examples of new syntax
|
|
92
|
-
(e.g., query: 'built-in control flow' to replace "*ngIf
|
|
156
|
+
(e.g., query: 'built-in control flow' to replace "*ngIf").
|
|
157
|
+
* **Advanced Filtering:** Combining a full-text search with filters to narrow results.
|
|
158
|
+
(e.g., query: 'forms', required_packages: ['@angular/forms'], keywords: ['validation'])
|
|
93
159
|
</Use Cases>
|
|
94
160
|
<Operational Notes>
|
|
95
161
|
* **Tool Selection:** This database primarily contains examples for new and recently updated Angular
|
|
@@ -98,15 +164,11 @@ new or evolving features.
|
|
|
98
164
|
* The examples in this database are the single source of truth for modern Angular coding patterns.
|
|
99
165
|
* The search query uses a powerful full-text search syntax (FTS5). Refer to the 'query'
|
|
100
166
|
parameter description for detailed syntax rules and examples.
|
|
167
|
+
* You can combine the main 'query' with optional filters like 'keywords', 'required_packages',
|
|
168
|
+
and 'related_concepts' to create highly specific searches.
|
|
101
169
|
</Operational Notes>`,
|
|
102
170
|
inputSchema: findExampleInputSchema.shape,
|
|
103
|
-
outputSchema:
|
|
104
|
-
examples: zod_1.z.array(zod_1.z.object({
|
|
105
|
-
content: zod_1.z
|
|
106
|
-
.string()
|
|
107
|
-
.describe('A complete, self-contained Angular code example in Markdown format.'),
|
|
108
|
-
})),
|
|
109
|
-
},
|
|
171
|
+
outputSchema: findExampleOutputSchema.shape,
|
|
110
172
|
isReadOnly: true,
|
|
111
173
|
isLocalOnly: true,
|
|
112
174
|
shouldRegister: ({ logger }) => {
|
|
@@ -123,12 +185,11 @@ new or evolving features.
|
|
|
123
185
|
});
|
|
124
186
|
async function createFindExampleHandler({ exampleDatabasePath }) {
|
|
125
187
|
let db;
|
|
126
|
-
let queryStatement;
|
|
127
188
|
if (process.env['NG_MCP_EXAMPLES_DIR']) {
|
|
128
189
|
db = await setupRuntimeExamples(process.env['NG_MCP_EXAMPLES_DIR']);
|
|
129
190
|
}
|
|
130
191
|
suppressSqliteWarning();
|
|
131
|
-
return async (
|
|
192
|
+
return async (input) => {
|
|
132
193
|
if (!db) {
|
|
133
194
|
if (!exampleDatabasePath) {
|
|
134
195
|
// This should be prevented by the registration logic in mcp-server.ts
|
|
@@ -137,17 +198,67 @@ async function createFindExampleHandler({ exampleDatabasePath }) {
|
|
|
137
198
|
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
138
199
|
db = new DatabaseSync(exampleDatabasePath, { readOnly: true });
|
|
139
200
|
}
|
|
140
|
-
|
|
141
|
-
|
|
201
|
+
const { query, keywords, required_packages, related_concepts, includeExperimental } = input;
|
|
202
|
+
// Build the query dynamically
|
|
203
|
+
const params = [];
|
|
204
|
+
let sql = 'SELECT title, summary, keywords, required_packages, related_concepts, related_tools, content, ' +
|
|
205
|
+
// The `snippet` function generates a contextual snippet of the matched text.
|
|
206
|
+
// Column 6 is the `content` column. We highlight matches with asterisks and limit the snippet size.
|
|
207
|
+
"snippet(examples_fts, 6, '**', '**', '...', 15) AS snippet " +
|
|
208
|
+
'FROM examples_fts';
|
|
209
|
+
const whereClauses = [];
|
|
210
|
+
// FTS query
|
|
211
|
+
if (query) {
|
|
212
|
+
whereClauses.push('examples_fts MATCH ?');
|
|
213
|
+
params.push(escapeSearchQuery(query));
|
|
214
|
+
}
|
|
215
|
+
// JSON array filters
|
|
216
|
+
const addJsonFilter = (column, values) => {
|
|
217
|
+
if (values?.length) {
|
|
218
|
+
for (const value of values) {
|
|
219
|
+
whereClauses.push(`${column} LIKE ?`);
|
|
220
|
+
params.push(`%"${value}"%`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
addJsonFilter('keywords', keywords);
|
|
225
|
+
addJsonFilter('required_packages', required_packages);
|
|
226
|
+
addJsonFilter('related_concepts', related_concepts);
|
|
227
|
+
if (!includeExperimental) {
|
|
228
|
+
whereClauses.push('experimental = 0');
|
|
229
|
+
}
|
|
230
|
+
if (whereClauses.length > 0) {
|
|
231
|
+
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
142
232
|
}
|
|
143
|
-
|
|
233
|
+
// Order the results by relevance using the BM25 algorithm.
|
|
234
|
+
// The weights assigned to each column boost the ranking of documents where the
|
|
235
|
+
// search term appears in a more important field.
|
|
236
|
+
// Column order: title, summary, keywords, required_packages, related_concepts, related_tools, content
|
|
237
|
+
sql += ' ORDER BY bm25(examples_fts, 10.0, 5.0, 5.0, 1.0, 2.0, 1.0, 1.0);';
|
|
238
|
+
const queryStatement = db.prepare(sql);
|
|
144
239
|
// Query database and return results
|
|
145
240
|
const examples = [];
|
|
146
241
|
const textContent = [];
|
|
147
|
-
for (const exampleRecord of queryStatement.all(
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
242
|
+
for (const exampleRecord of queryStatement.all(...params)) {
|
|
243
|
+
const record = exampleRecord;
|
|
244
|
+
const example = {
|
|
245
|
+
title: record['title'],
|
|
246
|
+
summary: record['summary'],
|
|
247
|
+
keywords: JSON.parse(record['keywords'] || '[]'),
|
|
248
|
+
required_packages: JSON.parse(record['required_packages'] || '[]'),
|
|
249
|
+
related_concepts: JSON.parse(record['related_concepts'] || '[]'),
|
|
250
|
+
related_tools: JSON.parse(record['related_tools'] || '[]'),
|
|
251
|
+
content: record['content'],
|
|
252
|
+
snippet: record['snippet'],
|
|
253
|
+
};
|
|
254
|
+
examples.push(example);
|
|
255
|
+
// Also create a more structured text output
|
|
256
|
+
let text = `## Example: ${example.title}\n**Summary:** ${example.summary}`;
|
|
257
|
+
if (example.snippet) {
|
|
258
|
+
text += `\n**Snippet:** ${example.snippet}`;
|
|
259
|
+
}
|
|
260
|
+
text += `\n\n---\n\n${example.content}`;
|
|
261
|
+
textContent.push({ type: 'text', text });
|
|
151
262
|
}
|
|
152
263
|
return {
|
|
153
264
|
content: textContent,
|
|
@@ -232,18 +343,131 @@ function suppressSqliteWarning() {
|
|
|
232
343
|
return originalProcessEmit.apply(process, arguments);
|
|
233
344
|
};
|
|
234
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* A simple YAML front matter parser.
|
|
348
|
+
*
|
|
349
|
+
* This function extracts the YAML block enclosed by `---` at the beginning of a string
|
|
350
|
+
* and parses it into a JavaScript object. It is not a full YAML parser and only
|
|
351
|
+
* supports simple key-value pairs and string arrays.
|
|
352
|
+
*
|
|
353
|
+
* @param content The string content to parse.
|
|
354
|
+
* @returns A record containing the parsed front matter data.
|
|
355
|
+
*/
|
|
356
|
+
function parseFrontmatter(content) {
|
|
357
|
+
const match = content.match(/^---\r?\n(.*?)\r?\n---/s);
|
|
358
|
+
if (!match) {
|
|
359
|
+
return {};
|
|
360
|
+
}
|
|
361
|
+
const frontmatter = match[1];
|
|
362
|
+
const data = {};
|
|
363
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
364
|
+
let currentKey = '';
|
|
365
|
+
let isArray = false;
|
|
366
|
+
const arrayValues = [];
|
|
367
|
+
for (const line of lines) {
|
|
368
|
+
const keyValueMatch = line.match(/^([^:]+):\s*(.*)/);
|
|
369
|
+
if (keyValueMatch) {
|
|
370
|
+
if (currentKey && isArray) {
|
|
371
|
+
data[currentKey] = arrayValues.slice();
|
|
372
|
+
arrayValues.length = 0;
|
|
373
|
+
}
|
|
374
|
+
const [, key, value] = keyValueMatch;
|
|
375
|
+
currentKey = key.trim();
|
|
376
|
+
isArray = value.trim() === '';
|
|
377
|
+
if (!isArray) {
|
|
378
|
+
const trimmedValue = value.trim();
|
|
379
|
+
if (trimmedValue === 'true') {
|
|
380
|
+
data[currentKey] = true;
|
|
381
|
+
}
|
|
382
|
+
else if (trimmedValue === 'false') {
|
|
383
|
+
data[currentKey] = false;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
data[currentKey] = trimmedValue;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const arrayItemMatch = line.match(/^\s*-\s*(.*)/);
|
|
392
|
+
if (arrayItemMatch && currentKey && isArray) {
|
|
393
|
+
arrayValues.push(arrayItemMatch[1].trim());
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (currentKey && isArray) {
|
|
398
|
+
data[currentKey] = arrayValues;
|
|
399
|
+
}
|
|
400
|
+
return data;
|
|
401
|
+
}
|
|
235
402
|
async function setupRuntimeExamples(examplesPath) {
|
|
236
403
|
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
237
404
|
const db = new DatabaseSync(':memory:');
|
|
238
|
-
|
|
239
|
-
|
|
405
|
+
// Create a relational table to store the structured example data.
|
|
406
|
+
db.exec(`
|
|
407
|
+
CREATE TABLE examples (
|
|
408
|
+
id INTEGER PRIMARY KEY,
|
|
409
|
+
title TEXT NOT NULL,
|
|
410
|
+
summary TEXT NOT NULL,
|
|
411
|
+
keywords TEXT,
|
|
412
|
+
required_packages TEXT,
|
|
413
|
+
related_concepts TEXT,
|
|
414
|
+
related_tools TEXT,
|
|
415
|
+
experimental INTEGER NOT NULL DEFAULT 0,
|
|
416
|
+
content TEXT NOT NULL
|
|
417
|
+
);
|
|
418
|
+
`);
|
|
419
|
+
// Create an FTS5 virtual table to provide full-text search capabilities.
|
|
420
|
+
db.exec(`
|
|
421
|
+
CREATE VIRTUAL TABLE examples_fts USING fts5(
|
|
422
|
+
title,
|
|
423
|
+
summary,
|
|
424
|
+
keywords,
|
|
425
|
+
required_packages,
|
|
426
|
+
related_concepts,
|
|
427
|
+
related_tools,
|
|
428
|
+
content,
|
|
429
|
+
content='examples',
|
|
430
|
+
content_rowid='id',
|
|
431
|
+
tokenize = 'porter ascii'
|
|
432
|
+
);
|
|
433
|
+
`);
|
|
434
|
+
// Create triggers to keep the FTS table synchronized with the examples table.
|
|
435
|
+
db.exec(`
|
|
436
|
+
CREATE TRIGGER examples_after_insert AFTER INSERT ON examples BEGIN
|
|
437
|
+
INSERT INTO examples_fts(rowid, title, summary, keywords, required_packages, related_concepts, related_tools, content)
|
|
438
|
+
VALUES (
|
|
439
|
+
new.id, new.title, new.summary, new.keywords, new.required_packages, new.related_concepts,
|
|
440
|
+
new.related_tools, new.content
|
|
441
|
+
);
|
|
442
|
+
END;
|
|
443
|
+
`);
|
|
444
|
+
const insertStatement = db.prepare('INSERT INTO examples(' +
|
|
445
|
+
'title, summary, keywords, required_packages, related_concepts, related_tools, experimental, content' +
|
|
446
|
+
') VALUES(?, ?, ?, ?, ?, ?, ?, ?);');
|
|
447
|
+
const frontmatterSchema = zod_1.z.object({
|
|
448
|
+
title: zod_1.z.string(),
|
|
449
|
+
summary: zod_1.z.string(),
|
|
450
|
+
keywords: zod_1.z.array(zod_1.z.string()).optional(),
|
|
451
|
+
required_packages: zod_1.z.array(zod_1.z.string()).optional(),
|
|
452
|
+
related_concepts: zod_1.z.array(zod_1.z.string()).optional(),
|
|
453
|
+
related_tools: zod_1.z.array(zod_1.z.string()).optional(),
|
|
454
|
+
experimental: zod_1.z.boolean().optional(),
|
|
455
|
+
});
|
|
240
456
|
db.exec('BEGIN TRANSACTION');
|
|
241
|
-
for await (const entry of (0, promises_1.glob)('
|
|
457
|
+
for await (const entry of (0, promises_1.glob)('**/*.md', { cwd: examplesPath, withFileTypes: true })) {
|
|
242
458
|
if (!entry.isFile()) {
|
|
243
459
|
continue;
|
|
244
460
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
461
|
+
const content = await (0, promises_1.readFile)(node_path_1.default.join(entry.parentPath, entry.name), 'utf-8');
|
|
462
|
+
const frontmatter = parseFrontmatter(content);
|
|
463
|
+
const validation = frontmatterSchema.safeParse(frontmatter);
|
|
464
|
+
if (!validation.success) {
|
|
465
|
+
// eslint-disable-next-line no-console
|
|
466
|
+
console.warn(`Skipping invalid example file ${entry.name}:`, validation.error.issues);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const { title, summary, keywords, required_packages, related_concepts, related_tools, experimental, } = validation.data;
|
|
470
|
+
insertStatement.run(title, summary, JSON.stringify(keywords ?? []), JSON.stringify(required_packages ?? []), JSON.stringify(related_concepts ?? []), JSON.stringify(related_tools ?? []), experimental ? 1 : 0, content);
|
|
247
471
|
}
|
|
248
472
|
db.exec('END TRANSACTION');
|
|
249
473
|
return db;
|
|
@@ -148,6 +148,7 @@ function generateZonelessMigrationInstructionsForComponent(filePath) {
|
|
|
148
148
|
3. **DO NOT** use \`ChangeDetectionStrategy.OnPush\`. This will be the next step in the migration, but it is not part of this task.
|
|
149
149
|
4. **DO NOT** modify properties that are already signals or are used with the \`async\` pipe in the template, as they are already zoneless-compatible.
|
|
150
150
|
5. **DO NOT** make any changes to files other than the component file at \`${filePath}\` and its direct template/style files if necessary.
|
|
151
|
+
6. **DO NOT** remove or modify usages of \`NgZone.run\` or \`NgZone.runOutsideAngular\`. These are still required.
|
|
151
152
|
|
|
152
153
|
### Final Step
|
|
153
154
|
After you have applied all the required changes and followed all the rules, consult this tool again for the next steps in the migration process.`;
|
|
@@ -12,8 +12,14 @@ export interface PackageManagerUtilsContext {
|
|
|
12
12
|
workspace?: AngularWorkspace;
|
|
13
13
|
root: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Utilities for interacting with various package managers.
|
|
17
|
+
*/
|
|
15
18
|
export declare class PackageManagerUtils {
|
|
16
19
|
private readonly context;
|
|
20
|
+
/**
|
|
21
|
+
* @param context The context for the package manager utilities, including workspace and global configuration.
|
|
22
|
+
*/
|
|
17
23
|
constructor(context: PackageManagerUtilsContext);
|
|
18
24
|
/** Get the package manager name. */
|
|
19
25
|
get name(): PackageManager;
|
|
@@ -32,6 +38,12 @@ export declare class PackageManagerUtils {
|
|
|
32
38
|
private run;
|
|
33
39
|
private getVersion;
|
|
34
40
|
private getName;
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a lockfile for a specific package manager exists in the root directory.
|
|
43
|
+
* @param packageManager The package manager to check for.
|
|
44
|
+
* @param filesInRoot An array of file names in the root directory.
|
|
45
|
+
* @returns True if the lockfile exists, false otherwise.
|
|
46
|
+
*/
|
|
35
47
|
private hasLockfile;
|
|
36
48
|
private getConfiguredPackageManager;
|
|
37
49
|
}
|
|
@@ -50,6 +50,18 @@ const node_path_1 = require("node:path");
|
|
|
50
50
|
const workspace_schema_1 = require("../../lib/config/workspace-schema");
|
|
51
51
|
const config_1 = require("./config");
|
|
52
52
|
const memoize_1 = require("./memoize");
|
|
53
|
+
/**
|
|
54
|
+
* A map of package managers to their corresponding lockfile names.
|
|
55
|
+
*/
|
|
56
|
+
const LOCKFILE_NAMES = {
|
|
57
|
+
[workspace_schema_1.PackageManager.Yarn]: 'yarn.lock',
|
|
58
|
+
[workspace_schema_1.PackageManager.Pnpm]: 'pnpm-lock.yaml',
|
|
59
|
+
[workspace_schema_1.PackageManager.Bun]: ['bun.lockb', 'bun.lock'],
|
|
60
|
+
[workspace_schema_1.PackageManager.Npm]: 'package-lock.json',
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Utilities for interacting with various package managers.
|
|
64
|
+
*/
|
|
53
65
|
let PackageManagerUtils = (() => {
|
|
54
66
|
let _instanceExtraInitializers = [];
|
|
55
67
|
let _getVersion_decorators;
|
|
@@ -64,6 +76,9 @@ let PackageManagerUtils = (() => {
|
|
|
64
76
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
65
77
|
}
|
|
66
78
|
context = __runInitializers(this, _instanceExtraInitializers);
|
|
79
|
+
/**
|
|
80
|
+
* @param context The context for the package manager utilities, including workspace and global configuration.
|
|
81
|
+
*/
|
|
67
82
|
constructor(context) {
|
|
68
83
|
this.context = context;
|
|
69
84
|
}
|
|
@@ -211,10 +226,11 @@ let PackageManagerUtils = (() => {
|
|
|
211
226
|
if (packageManager) {
|
|
212
227
|
return packageManager;
|
|
213
228
|
}
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
229
|
+
const filesInRoot = (0, node_fs_1.readdirSync)(this.context.root);
|
|
230
|
+
const hasNpmLock = this.hasLockfile(workspace_schema_1.PackageManager.Npm, filesInRoot);
|
|
231
|
+
const hasYarnLock = this.hasLockfile(workspace_schema_1.PackageManager.Yarn, filesInRoot);
|
|
232
|
+
const hasPnpmLock = this.hasLockfile(workspace_schema_1.PackageManager.Pnpm, filesInRoot);
|
|
233
|
+
const hasBunLock = this.hasLockfile(workspace_schema_1.PackageManager.Bun, filesInRoot);
|
|
218
234
|
// PERF NOTE: `this.getVersion` spawns the package a the child_process which can take around ~300ms at times.
|
|
219
235
|
// Therefore, we should only call this method when needed. IE: don't call `this.getVersion(PackageManager.Pnpm)` unless truly needed.
|
|
220
236
|
// The result of this method is not stored in a variable because it's memoized.
|
|
@@ -259,24 +275,17 @@ let PackageManagerUtils = (() => {
|
|
|
259
275
|
// Potentially with a prompt to choose and optionally set as the default.
|
|
260
276
|
return workspace_schema_1.PackageManager.Npm;
|
|
261
277
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
break;
|
|
274
|
-
case workspace_schema_1.PackageManager.Npm:
|
|
275
|
-
default:
|
|
276
|
-
lockfileName = 'package-lock.json';
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
return (0, node_fs_1.existsSync)((0, node_path_1.join)(this.context.root, lockfileName));
|
|
278
|
+
/**
|
|
279
|
+
* Checks if a lockfile for a specific package manager exists in the root directory.
|
|
280
|
+
* @param packageManager The package manager to check for.
|
|
281
|
+
* @param filesInRoot An array of file names in the root directory.
|
|
282
|
+
* @returns True if the lockfile exists, false otherwise.
|
|
283
|
+
*/
|
|
284
|
+
hasLockfile(packageManager, filesInRoot) {
|
|
285
|
+
const lockfiles = LOCKFILE_NAMES[packageManager];
|
|
286
|
+
return typeof lockfiles === 'string'
|
|
287
|
+
? filesInRoot.includes(lockfiles)
|
|
288
|
+
: lockfiles.some((lockfile) => filesInRoot.includes(lockfile));
|
|
280
289
|
}
|
|
281
290
|
getConfiguredPackageManager() {
|
|
282
291
|
const getPackageManager = (source) => {
|
package/src/utilities/version.js
CHANGED