@angular/cli 20.2.0-next.1 → 20.2.0-next.3
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/package.json +17 -18
- package/src/commands/mcp/cli.js +2 -2
- package/src/commands/mcp/instructions/best-practices.md +9 -1
- package/src/commands/mcp/mcp-server.d.ts +2 -0
- package/src/commands/mcp/mcp-server.js +21 -2
- package/src/commands/mcp/tools/best-practices.js +7 -2
- package/src/commands/mcp/tools/examples.d.ts +31 -0
- package/src/commands/mcp/tools/examples.js +217 -0
- package/src/commands/update/cli.js +36 -31
- package/src/commands/version/cli.d.ts +24 -2
- package/src/commands/version/cli.js +74 -115
- package/src/commands/version/version-info.d.ts +33 -0
- package/src/commands/version/version-info.js +122 -0
- package/src/utilities/environment-options.d.ts +13 -0
- package/src/utilities/environment-options.js +43 -14
- package/src/utilities/tty.d.ts +8 -0
- package/src/utilities/tty.js +10 -10
- package/src/utilities/version.js +1 -1
- package/src/typings.d.ts +0 -15
- package/src/utilities/load-esm.d.ts +0 -20
- package/src/utilities/load-esm.js +0 -30
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/cli",
|
|
3
|
-
"version": "20.2.0-next.
|
|
3
|
+
"version": "20.2.0-next.3",
|
|
4
4
|
"description": "CLI tool for Angular",
|
|
5
5
|
"main": "lib/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,20 +25,19 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/angular/angular-cli",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@angular-devkit/architect": "0.2002.0-next.
|
|
29
|
-
"@angular-devkit/core": "20.2.0-next.
|
|
30
|
-
"@angular-devkit/schematics": "20.2.0-next.
|
|
31
|
-
"@inquirer/prompts": "7.
|
|
28
|
+
"@angular-devkit/architect": "0.2002.0-next.3",
|
|
29
|
+
"@angular-devkit/core": "20.2.0-next.3",
|
|
30
|
+
"@angular-devkit/schematics": "20.2.0-next.3",
|
|
31
|
+
"@inquirer/prompts": "7.8.0",
|
|
32
32
|
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
|
33
|
-
"@modelcontextprotocol/sdk": "1.
|
|
34
|
-
"@schematics/angular": "20.2.0-next.
|
|
33
|
+
"@modelcontextprotocol/sdk": "1.17.1",
|
|
34
|
+
"@schematics/angular": "20.2.0-next.3",
|
|
35
35
|
"@yarnpkg/lockfile": "1.1.0",
|
|
36
|
-
"algoliasearch": "5.
|
|
36
|
+
"algoliasearch": "5.35.0",
|
|
37
37
|
"ini": "5.0.0",
|
|
38
38
|
"jsonc-parser": "3.3.1",
|
|
39
39
|
"listr2": "9.0.1",
|
|
40
|
-
"npm-package-arg": "
|
|
41
|
-
"npm-pick-manifest": "10.0.0",
|
|
40
|
+
"npm-package-arg": "13.0.0",
|
|
42
41
|
"pacote": "21.0.0",
|
|
43
42
|
"resolve": "1.22.10",
|
|
44
43
|
"semver": "7.7.2",
|
|
@@ -48,14 +47,14 @@
|
|
|
48
47
|
"ng-update": {
|
|
49
48
|
"migrations": "@schematics/angular/migrations/migration-collection.json",
|
|
50
49
|
"packageGroup": {
|
|
51
|
-
"@angular/cli": "20.2.0-next.
|
|
52
|
-
"@angular/build": "20.2.0-next.
|
|
53
|
-
"@angular/ssr": "20.2.0-next.
|
|
54
|
-
"@angular-devkit/architect": "0.2002.0-next.
|
|
55
|
-
"@angular-devkit/build-angular": "20.2.0-next.
|
|
56
|
-
"@angular-devkit/build-webpack": "0.2002.0-next.
|
|
57
|
-
"@angular-devkit/core": "20.2.0-next.
|
|
58
|
-
"@angular-devkit/schematics": "20.2.0-next.
|
|
50
|
+
"@angular/cli": "20.2.0-next.3",
|
|
51
|
+
"@angular/build": "20.2.0-next.3",
|
|
52
|
+
"@angular/ssr": "20.2.0-next.3",
|
|
53
|
+
"@angular-devkit/architect": "0.2002.0-next.3",
|
|
54
|
+
"@angular-devkit/build-angular": "20.2.0-next.3",
|
|
55
|
+
"@angular-devkit/build-webpack": "0.2002.0-next.3",
|
|
56
|
+
"@angular-devkit/core": "20.2.0-next.3",
|
|
57
|
+
"@angular-devkit/schematics": "20.2.0-next.3"
|
|
59
58
|
}
|
|
60
59
|
},
|
|
61
60
|
"packageManager": "pnpm@9.15.9",
|
package/src/commands/mcp/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ To start using the Angular CLI MCP Server, add this configuration to your host:
|
|
|
18
18
|
"mcpServers": {
|
|
19
19
|
"angular-cli": {
|
|
20
20
|
"command": "npx",
|
|
21
|
-
"args": ["@angular/cli", "mcp"]
|
|
21
|
+
"args": ["-y", "@angular/cli", "mcp"]
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -37,7 +37,7 @@ class McpCommandModule extends command_module_1.CommandModule {
|
|
|
37
37
|
this.context.logger.info(INTERACTIVE_MESSAGE);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
const server = await (0, mcp_server_1.createMcpServer)({ workspace: this.context.workspace });
|
|
40
|
+
const server = await (0, mcp_server_1.createMcpServer)({ workspace: this.context.workspace }, this.context.logger);
|
|
41
41
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
42
42
|
await server.connect(transport);
|
|
43
43
|
}
|
|
@@ -9,10 +9,12 @@ You are an expert in TypeScript, Angular, and scalable web application developme
|
|
|
9
9
|
## Angular Best Practices
|
|
10
10
|
|
|
11
11
|
- Always use standalone components over NgModules
|
|
12
|
-
-
|
|
12
|
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default.
|
|
13
13
|
- Use signals for state management
|
|
14
14
|
- Implement lazy loading for feature routes
|
|
15
|
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
|
15
16
|
- Use `NgOptimizedImage` for all static images.
|
|
17
|
+
- `NgOptimizedImage` does not work for inline base64 images.
|
|
16
18
|
|
|
17
19
|
## Components
|
|
18
20
|
|
|
@@ -30,6 +32,7 @@ You are an expert in TypeScript, Angular, and scalable web application developme
|
|
|
30
32
|
- Use signals for local component state
|
|
31
33
|
- Use `computed()` for derived state
|
|
32
34
|
- Keep state transformations pure and predictable
|
|
35
|
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
|
33
36
|
|
|
34
37
|
## Templates
|
|
35
38
|
|
|
@@ -42,3 +45,8 @@ You are an expert in TypeScript, Angular, and scalable web application developme
|
|
|
42
45
|
- Design services around a single responsibility
|
|
43
46
|
- Use the `providedIn: 'root'` option for singleton services
|
|
44
47
|
- Use the `inject()` function instead of constructor injection
|
|
48
|
+
|
|
49
|
+
## Common pitfalls
|
|
50
|
+
|
|
51
|
+
- Control flow (`@if`):
|
|
52
|
+
- You cannot use `as` expressions in `@else if (...)`. E.g. invalid code: `@else if (bla(); as x)`.
|
|
@@ -9,4 +9,6 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
9
9
|
import type { AngularWorkspace } from '../../utilities/config';
|
|
10
10
|
export declare function createMcpServer(context: {
|
|
11
11
|
workspace?: AngularWorkspace;
|
|
12
|
+
}, logger: {
|
|
13
|
+
warn(text: string): void;
|
|
12
14
|
}): Promise<McpServer>;
|
|
@@ -17,15 +17,20 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
17
17
|
const version_1 = require("../../utilities/version");
|
|
18
18
|
const best_practices_1 = require("./tools/best-practices");
|
|
19
19
|
const doc_search_1 = require("./tools/doc-search");
|
|
20
|
+
const examples_1 = require("./tools/examples");
|
|
20
21
|
const projects_1 = require("./tools/projects");
|
|
21
|
-
async function createMcpServer(context) {
|
|
22
|
+
async function createMcpServer(context, logger) {
|
|
22
23
|
const server = new mcp_js_1.McpServer({
|
|
23
24
|
name: 'angular-cli-server',
|
|
24
25
|
version: version_1.VERSION.full,
|
|
26
|
+
}, {
|
|
25
27
|
capabilities: {
|
|
26
28
|
resources: {},
|
|
27
29
|
tools: {},
|
|
30
|
+
logging: {},
|
|
28
31
|
},
|
|
32
|
+
instructions: 'For Angular development, this server provides tools to adhere to best practices, search documentation, and find code examples. ' +
|
|
33
|
+
'When writing or modifying Angular code, use the MCP server and its tools instead of direct shell commands where possible.',
|
|
29
34
|
});
|
|
30
35
|
server.registerResource('instructions', 'instructions://best-practices', {
|
|
31
36
|
title: 'Angular Best Practices and Code Generation Guide',
|
|
@@ -39,7 +44,21 @@ async function createMcpServer(context) {
|
|
|
39
44
|
return { contents: [{ uri: 'instructions://best-practices', text }] };
|
|
40
45
|
});
|
|
41
46
|
(0, best_practices_1.registerBestPracticesTool)(server);
|
|
42
|
-
(
|
|
47
|
+
// If run outside an Angular workspace (e.g., globally) skip the workspace specific tools.
|
|
48
|
+
if (context.workspace) {
|
|
49
|
+
(0, projects_1.registerListProjectsTool)(server, context);
|
|
50
|
+
}
|
|
43
51
|
await (0, doc_search_1.registerDocSearchTool)(server);
|
|
52
|
+
if (process.env['NG_MCP_CODE_EXAMPLES'] === '1') {
|
|
53
|
+
// sqlite database support requires Node.js 22.16+
|
|
54
|
+
const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(Number);
|
|
55
|
+
if (nodeMajor < 22 || (nodeMajor === 22 && nodeMinor < 16)) {
|
|
56
|
+
logger.warn(`MCP tool 'find_examples' requires Node.js 22.16 (or higher). ` +
|
|
57
|
+
' Registration of this tool has been skipped.');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
await (0, examples_1.registerFindExampleTool)(server, node_path_1.default.join(__dirname, '../../../lib/code-examples.db'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
44
63
|
return server;
|
|
45
64
|
}
|
|
@@ -14,6 +14,7 @@ exports.registerBestPracticesTool = registerBestPracticesTool;
|
|
|
14
14
|
const promises_1 = require("node:fs/promises");
|
|
15
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
16
|
function registerBestPracticesTool(server) {
|
|
17
|
+
let bestPracticesText;
|
|
17
18
|
server.registerTool('get_best_practices', {
|
|
18
19
|
title: 'Get Angular Coding Best Practices Guide',
|
|
19
20
|
description: 'You **MUST** use this tool to retrieve the Angular Best Practices Guide ' +
|
|
@@ -26,12 +27,16 @@ function registerBestPracticesTool(server) {
|
|
|
26
27
|
openWorldHint: false,
|
|
27
28
|
},
|
|
28
29
|
}, async () => {
|
|
29
|
-
|
|
30
|
+
bestPracticesText ??= await (0, promises_1.readFile)(node_path_1.default.join(__dirname, '..', 'instructions', 'best-practices.md'), 'utf-8');
|
|
30
31
|
return {
|
|
31
32
|
content: [
|
|
32
33
|
{
|
|
33
34
|
type: 'text',
|
|
34
|
-
text,
|
|
35
|
+
text: bestPracticesText,
|
|
36
|
+
annotations: {
|
|
37
|
+
audience: ['assistant'],
|
|
38
|
+
priority: 0.9,
|
|
39
|
+
},
|
|
35
40
|
},
|
|
36
41
|
],
|
|
37
42
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
/**
|
|
10
|
+
* Registers the `find_examples` tool with the MCP server.
|
|
11
|
+
*
|
|
12
|
+
* This tool allows users to search for best-practice Angular code examples
|
|
13
|
+
* from a local SQLite database.
|
|
14
|
+
*
|
|
15
|
+
* @param server The MCP server instance.
|
|
16
|
+
* @param exampleDatabasePath The path to the SQLite database file containing the examples.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerFindExampleTool(server: McpServer, exampleDatabasePath: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Escapes a search query for FTS5 by tokenizing and quoting terms.
|
|
21
|
+
*
|
|
22
|
+
* This function processes a raw search string and prepares it for an FTS5 full-text search.
|
|
23
|
+
* It correctly handles quoted phrases, logical operators (AND, OR, NOT), parentheses,
|
|
24
|
+
* and prefix searches (ending with an asterisk), ensuring that individual search
|
|
25
|
+
* terms are properly quoted to be treated as literals by the search engine.
|
|
26
|
+
* This is primarily intended to avoid unintentional usage of FTS5 query syntax by consumers.
|
|
27
|
+
*
|
|
28
|
+
* @param query The raw search query string.
|
|
29
|
+
* @returns A sanitized query string suitable for FTS5.
|
|
30
|
+
*/
|
|
31
|
+
export declare function escapeSearchQuery(query: string): string;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright Google LLC All Rights Reserved.
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.registerFindExampleTool = registerFindExampleTool;
|
|
47
|
+
exports.escapeSearchQuery = escapeSearchQuery;
|
|
48
|
+
const promises_1 = require("node:fs/promises");
|
|
49
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
50
|
+
const zod_1 = require("zod");
|
|
51
|
+
/**
|
|
52
|
+
* Registers the `find_examples` tool with the MCP server.
|
|
53
|
+
*
|
|
54
|
+
* This tool allows users to search for best-practice Angular code examples
|
|
55
|
+
* from a local SQLite database.
|
|
56
|
+
*
|
|
57
|
+
* @param server The MCP server instance.
|
|
58
|
+
* @param exampleDatabasePath The path to the SQLite database file containing the examples.
|
|
59
|
+
*/
|
|
60
|
+
async function registerFindExampleTool(server, exampleDatabasePath) {
|
|
61
|
+
let db;
|
|
62
|
+
let queryStatement;
|
|
63
|
+
// Runtime directory of examples uses an in-memory database
|
|
64
|
+
if (process.env['NG_MCP_EXAMPLES_DIR']) {
|
|
65
|
+
db = await setupRuntimeExamples(process.env['NG_MCP_EXAMPLES_DIR']);
|
|
66
|
+
}
|
|
67
|
+
suppressSqliteWarning();
|
|
68
|
+
server.registerTool('find_examples', {
|
|
69
|
+
title: 'Find Angular Code Examples',
|
|
70
|
+
description: 'Before writing or modifying any Angular code including templates, ' +
|
|
71
|
+
'**ALWAYS** use this tool to find current best-practice examples. ' +
|
|
72
|
+
'This is critical for ensuring code quality and adherence to modern Angular standards. ' +
|
|
73
|
+
'This tool searches a curated database of approved Angular code examples and returns the most relevant results for your query. ' +
|
|
74
|
+
'Example Use Cases: ' +
|
|
75
|
+
"1) Creating new components, directives, or services (e.g., query: 'standalone component' or 'signal input'). " +
|
|
76
|
+
"2) Implementing core features (e.g., query: 'lazy load route', 'httpinterceptor', or 'route guard'). " +
|
|
77
|
+
"3) Refactoring existing code to use modern patterns (e.g., query: 'ngfor trackby' or 'form validation').",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
query: zod_1.z.string().describe(`Performs a full-text search using FTS5 syntax. The query should target relevant Angular concepts.
|
|
80
|
+
|
|
81
|
+
Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):
|
|
82
|
+
- AND (default): Space-separated terms are combined with AND.
|
|
83
|
+
- Example: 'standalone component' (finds results with both "standalone" and "component")
|
|
84
|
+
- OR: Use the OR operator to find results with either term.
|
|
85
|
+
- Example: 'validation OR validator'
|
|
86
|
+
- NOT: Use the NOT operator to exclude terms.
|
|
87
|
+
- Example: 'forms NOT reactive'
|
|
88
|
+
- Grouping: Use parentheses () to group expressions.
|
|
89
|
+
- Example: '(validation OR validator) AND forms'
|
|
90
|
+
- Phrase Search: Use double quotes "" for exact phrases.
|
|
91
|
+
- Example: '"template-driven forms"'
|
|
92
|
+
- Prefix Search: Use an asterisk * for prefix matching.
|
|
93
|
+
- Example: 'rout*' (matches "route", "router", "routing")
|
|
94
|
+
|
|
95
|
+
Examples of queries:
|
|
96
|
+
- Find standalone components: 'standalone component'
|
|
97
|
+
- Find ngFor with trackBy: 'ngFor trackBy'
|
|
98
|
+
- Find signal inputs: 'signal input'
|
|
99
|
+
- Find lazy loading a route: 'lazy load route'
|
|
100
|
+
- Find forms with validation: 'form AND (validation OR validator)'`),
|
|
101
|
+
},
|
|
102
|
+
annotations: {
|
|
103
|
+
readOnlyHint: true,
|
|
104
|
+
openWorldHint: false,
|
|
105
|
+
},
|
|
106
|
+
}, async ({ query }) => {
|
|
107
|
+
if (!db) {
|
|
108
|
+
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
109
|
+
db = new DatabaseSync(exampleDatabasePath, { readOnly: true });
|
|
110
|
+
}
|
|
111
|
+
if (!queryStatement) {
|
|
112
|
+
queryStatement = db.prepare('SELECT * from examples WHERE examples MATCH ? ORDER BY rank;');
|
|
113
|
+
}
|
|
114
|
+
const sanitizedQuery = escapeSearchQuery(query);
|
|
115
|
+
// Query database and return results as text content
|
|
116
|
+
const content = [];
|
|
117
|
+
for (const exampleRecord of queryStatement.all(sanitizedQuery)) {
|
|
118
|
+
content.push({ type: 'text', text: exampleRecord['content'] });
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
content,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Escapes a search query for FTS5 by tokenizing and quoting terms.
|
|
127
|
+
*
|
|
128
|
+
* This function processes a raw search string and prepares it for an FTS5 full-text search.
|
|
129
|
+
* It correctly handles quoted phrases, logical operators (AND, OR, NOT), parentheses,
|
|
130
|
+
* and prefix searches (ending with an asterisk), ensuring that individual search
|
|
131
|
+
* terms are properly quoted to be treated as literals by the search engine.
|
|
132
|
+
* This is primarily intended to avoid unintentional usage of FTS5 query syntax by consumers.
|
|
133
|
+
*
|
|
134
|
+
* @param query The raw search query string.
|
|
135
|
+
* @returns A sanitized query string suitable for FTS5.
|
|
136
|
+
*/
|
|
137
|
+
function escapeSearchQuery(query) {
|
|
138
|
+
// This regex tokenizes the query string into parts:
|
|
139
|
+
// 1. Quoted phrases (e.g., "foo bar")
|
|
140
|
+
// 2. Parentheses ( and )
|
|
141
|
+
// 3. FTS5 operators (AND, OR, NOT, NEAR)
|
|
142
|
+
// 4. Words, which can include a trailing asterisk for prefix search (e.g., foo*)
|
|
143
|
+
const tokenizer = /"([^"]*)"|([()])|\b(AND|OR|NOT|NEAR)\b|([^\s()]+)/g;
|
|
144
|
+
let match;
|
|
145
|
+
const result = [];
|
|
146
|
+
let lastIndex = 0;
|
|
147
|
+
while ((match = tokenizer.exec(query)) !== null) {
|
|
148
|
+
// Add any whitespace or other characters between tokens
|
|
149
|
+
if (match.index > lastIndex) {
|
|
150
|
+
result.push(query.substring(lastIndex, match.index));
|
|
151
|
+
}
|
|
152
|
+
const [, quoted, parenthesis, operator, term] = match;
|
|
153
|
+
if (quoted !== undefined) {
|
|
154
|
+
// It's a quoted phrase, keep it as is.
|
|
155
|
+
result.push(`"${quoted}"`);
|
|
156
|
+
}
|
|
157
|
+
else if (parenthesis) {
|
|
158
|
+
// It's a parenthesis, keep it as is.
|
|
159
|
+
result.push(parenthesis);
|
|
160
|
+
}
|
|
161
|
+
else if (operator) {
|
|
162
|
+
// It's an operator, keep it as is.
|
|
163
|
+
result.push(operator);
|
|
164
|
+
}
|
|
165
|
+
else if (term) {
|
|
166
|
+
// It's a term that needs to be quoted.
|
|
167
|
+
if (term.endsWith('*')) {
|
|
168
|
+
result.push(`"${term.slice(0, -1)}"*`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
result.push(`"${term}"`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
lastIndex = tokenizer.lastIndex;
|
|
175
|
+
}
|
|
176
|
+
// Add any remaining part of the string
|
|
177
|
+
if (lastIndex < query.length) {
|
|
178
|
+
result.push(query.substring(lastIndex));
|
|
179
|
+
}
|
|
180
|
+
return result.join('');
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Suppresses the experimental warning emitted by Node.js for the `node:sqlite` module.
|
|
184
|
+
*
|
|
185
|
+
* This is a workaround to prevent the console from being cluttered with warnings
|
|
186
|
+
* about the experimental status of the SQLite module, which is used by this tool.
|
|
187
|
+
*/
|
|
188
|
+
function suppressSqliteWarning() {
|
|
189
|
+
const originalProcessEmit = process.emit;
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
|
+
process.emit = function (event, error) {
|
|
192
|
+
if (event === 'warning' &&
|
|
193
|
+
error instanceof Error &&
|
|
194
|
+
error.name === 'ExperimentalWarning' &&
|
|
195
|
+
error.message.includes('SQLite')) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-rest-params
|
|
199
|
+
return originalProcessEmit.apply(process, arguments);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async function setupRuntimeExamples(examplesPath) {
|
|
203
|
+
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
204
|
+
const db = new DatabaseSync(':memory:');
|
|
205
|
+
db.exec(`CREATE VIRTUAL TABLE examples USING fts5(content, tokenize = 'porter ascii');`);
|
|
206
|
+
const insertStatement = db.prepare('INSERT INTO examples(content) VALUES(?);');
|
|
207
|
+
db.exec('BEGIN TRANSACTION');
|
|
208
|
+
for await (const entry of (0, promises_1.glob)('*.md', { cwd: examplesPath, withFileTypes: true })) {
|
|
209
|
+
if (!entry.isFile()) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const example = await (0, promises_1.readFile)(node_path_1.default.join(entry.parentPath, entry.name), 'utf-8');
|
|
213
|
+
insertStatement.run(example);
|
|
214
|
+
}
|
|
215
|
+
db.exec('END TRANSACTION');
|
|
216
|
+
return db;
|
|
217
|
+
}
|
|
@@ -52,7 +52,6 @@ const node_module_1 = require("node:module");
|
|
|
52
52
|
const path = __importStar(require("node:path"));
|
|
53
53
|
const node_path_1 = require("node:path");
|
|
54
54
|
const npm_package_arg_1 = __importDefault(require("npm-package-arg"));
|
|
55
|
-
const npm_pick_manifest_1 = __importDefault(require("npm-pick-manifest"));
|
|
56
55
|
const semver = __importStar(require("semver"));
|
|
57
56
|
const workspace_schema_1 = require("../../../lib/config/workspace-schema");
|
|
58
57
|
const command_module_1 = require("../../command-builder/command-module");
|
|
@@ -189,9 +188,11 @@ class UpdateCommandModule extends command_module_1.CommandModule {
|
|
|
189
188
|
if (options.migrateOnly && packageIdentifier.rawSpec !== '*') {
|
|
190
189
|
logger.warn('Package specifier has no effect when using "migrate-only" option.');
|
|
191
190
|
}
|
|
192
|
-
//
|
|
193
|
-
if
|
|
194
|
-
|
|
191
|
+
// Wildcard uses the next tag if next option is used otherwise use latest tag.
|
|
192
|
+
// Wildcard is present if no selector is provided on the command line.
|
|
193
|
+
if (packageIdentifier.rawSpec === '*') {
|
|
194
|
+
packageIdentifier.fetchSpec = options.next ? 'next' : 'latest';
|
|
195
|
+
packageIdentifier.type = 'tag';
|
|
195
196
|
}
|
|
196
197
|
packages.push(packageIdentifier);
|
|
197
198
|
}
|
|
@@ -480,35 +481,39 @@ class UpdateCommandModule extends command_module_1.CommandModule {
|
|
|
480
481
|
// Try to find a package version based on the user requested package specifier
|
|
481
482
|
// registry specifier types are either version, range, or tag
|
|
482
483
|
let manifest;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// If not found and next was used and user did not provide a specifier, try latest.
|
|
493
|
-
// Package may not have a next tag.
|
|
494
|
-
if (requestIdentifier.type === 'tag' &&
|
|
495
|
-
requestIdentifier.fetchSpec === 'next' &&
|
|
496
|
-
!requestIdentifier.rawSpec) {
|
|
497
|
-
try {
|
|
498
|
-
manifest = (0, npm_pick_manifest_1.default)(metadata, 'latest');
|
|
499
|
-
}
|
|
500
|
-
catch (e) {
|
|
501
|
-
(0, error_1.assertIsError)(e);
|
|
502
|
-
if (e.code !== 'ETARGET' && e.code !== 'ENOVERSIONS') {
|
|
503
|
-
throw e;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
484
|
+
switch (requestIdentifier.type) {
|
|
485
|
+
case 'tag':
|
|
486
|
+
manifest = metadata.tags[requestIdentifier.fetchSpec];
|
|
487
|
+
// If not found and next option was used and user did not provide a specifier, try latest.
|
|
488
|
+
// Package may not have a next tag.
|
|
489
|
+
if (!manifest &&
|
|
490
|
+
requestIdentifier.fetchSpec === 'next' &&
|
|
491
|
+
requestIdentifier.rawSpec === '*') {
|
|
492
|
+
manifest = metadata.tags['latest'];
|
|
507
493
|
}
|
|
508
|
-
|
|
509
|
-
|
|
494
|
+
break;
|
|
495
|
+
case 'version':
|
|
496
|
+
manifest = metadata.versions[requestIdentifier.fetchSpec];
|
|
497
|
+
break;
|
|
498
|
+
case 'range':
|
|
499
|
+
for (const potentialManifest of Object.values(metadata.versions)) {
|
|
500
|
+
// Ignore deprecated package versions
|
|
501
|
+
if (potentialManifest.deprecated) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
// Only consider versions that are within the range
|
|
505
|
+
if (!semver.satisfies(potentialManifest.version, requestIdentifier.fetchSpec, {
|
|
506
|
+
loose: true,
|
|
507
|
+
})) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
// Update the used manifest if current potential is newer than existing or there is not one yet
|
|
511
|
+
if (!manifest ||
|
|
512
|
+
semver.gt(potentialManifest.version, manifest.version, { loose: true })) {
|
|
513
|
+
manifest = potentialManifest;
|
|
514
|
+
}
|
|
510
515
|
}
|
|
511
|
-
|
|
516
|
+
break;
|
|
512
517
|
}
|
|
513
518
|
if (!manifest) {
|
|
514
519
|
logger.error(`Package specified by '${requestIdentifier.raw}' does not exist within the registry.`);
|
|
@@ -5,14 +5,36 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
-
import { Argv } from 'yargs';
|
|
8
|
+
import type { Argv } from 'yargs';
|
|
9
9
|
import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module';
|
|
10
|
+
/**
|
|
11
|
+
* The command-line module for the `ng version` command.
|
|
12
|
+
*/
|
|
10
13
|
export default class VersionCommandModule extends CommandModule implements CommandModuleImplementation {
|
|
11
14
|
command: string;
|
|
12
15
|
aliases: string[] | undefined;
|
|
13
16
|
describe: string;
|
|
14
17
|
longDescriptionPath?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Builds the command-line options for the `ng version` command.
|
|
20
|
+
* @param localYargs The `yargs` instance to configure.
|
|
21
|
+
* @returns The configured `yargs` instance.
|
|
22
|
+
*/
|
|
15
23
|
builder(localYargs: Argv): Argv;
|
|
24
|
+
/**
|
|
25
|
+
* The main execution logic for the `ng version` command.
|
|
26
|
+
*/
|
|
16
27
|
run(): Promise<void>;
|
|
17
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Formats the Angular packages section of the version output.
|
|
30
|
+
* @param versionInfo An object containing the version information.
|
|
31
|
+
* @returns A string containing the formatted Angular packages information.
|
|
32
|
+
*/
|
|
33
|
+
private formatAngularPackages;
|
|
34
|
+
/**
|
|
35
|
+
* Formats the package table section of the version output.
|
|
36
|
+
* @param versions A map of package names to their versions.
|
|
37
|
+
* @returns A string containing the formatted package table.
|
|
38
|
+
*/
|
|
39
|
+
private formatPackageTable;
|
|
18
40
|
}
|
|
@@ -6,104 +6,74 @@
|
|
|
6
6
|
* Use of this source code is governed by an MIT-style license that can be
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
-
};
|
|
12
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
const node_module_1 = __importDefault(require("node:module"));
|
|
14
|
-
const node_path_1 = require("node:path");
|
|
15
10
|
const command_module_1 = require("../../command-builder/command-module");
|
|
16
11
|
const color_1 = require("../../utilities/color");
|
|
17
12
|
const command_config_1 = require("../command-config");
|
|
13
|
+
const version_info_1 = require("./version-info");
|
|
18
14
|
/**
|
|
19
|
-
*
|
|
15
|
+
* The Angular CLI logo, displayed as ASCII art.
|
|
16
|
+
*/
|
|
17
|
+
const ASCII_ART = `
|
|
18
|
+
_ _ ____ _ ___
|
|
19
|
+
/ \\ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
|
|
20
|
+
/ △ \\ | '_ \\ / _\` | | | | |/ _\` | '__| | | | | | |
|
|
21
|
+
/ ___ \\| | | | (_| | |_| | | (_| | | | |___| |___ | |
|
|
22
|
+
/_/ \\_\\_| |_|\\__, |\\__,_|_|\\__,_|_| \\____|_____|___|
|
|
23
|
+
|___/
|
|
24
|
+
`
|
|
25
|
+
.split('\n')
|
|
26
|
+
.map((x) => color_1.colors.red(x))
|
|
27
|
+
.join('\n');
|
|
28
|
+
/**
|
|
29
|
+
* The command-line module for the `ng version` command.
|
|
20
30
|
*/
|
|
21
|
-
const SUPPORTED_NODE_MAJORS = [20, 22, 24];
|
|
22
|
-
const PACKAGE_PATTERNS = [
|
|
23
|
-
/^@angular\/.*/,
|
|
24
|
-
/^@angular-devkit\/.*/,
|
|
25
|
-
/^@ngtools\/.*/,
|
|
26
|
-
/^@schematics\/.*/,
|
|
27
|
-
/^rxjs$/,
|
|
28
|
-
/^typescript$/,
|
|
29
|
-
/^ng-packagr$/,
|
|
30
|
-
/^webpack$/,
|
|
31
|
-
/^zone\.js$/,
|
|
32
|
-
];
|
|
33
31
|
class VersionCommandModule extends command_module_1.CommandModule {
|
|
34
32
|
command = 'version';
|
|
35
33
|
aliases = command_config_1.RootCommands['version'].aliases;
|
|
36
34
|
describe = 'Outputs Angular CLI version.';
|
|
37
35
|
longDescriptionPath;
|
|
36
|
+
/**
|
|
37
|
+
* Builds the command-line options for the `ng version` command.
|
|
38
|
+
* @param localYargs The `yargs` instance to configure.
|
|
39
|
+
* @returns The configured `yargs` instance.
|
|
40
|
+
*/
|
|
38
41
|
builder(localYargs) {
|
|
39
42
|
return localYargs;
|
|
40
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The main execution logic for the `ng version` command.
|
|
46
|
+
*/
|
|
41
47
|
async run() {
|
|
42
|
-
const {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
...cliPackage.devDependencies,
|
|
57
|
-
...workspacePackage?.dependencies,
|
|
58
|
-
...workspacePackage?.devDependencies,
|
|
59
|
-
}));
|
|
60
|
-
const versions = {};
|
|
61
|
-
for (const name of packageNames) {
|
|
62
|
-
if (PACKAGE_PATTERNS.some((p) => p.test(name))) {
|
|
63
|
-
versions[name] = this.getVersion(name, workspaceRequire, localRequire);
|
|
64
|
-
}
|
|
48
|
+
const { logger } = this.context;
|
|
49
|
+
const versionInfo = (0, version_info_1.gatherVersionInfo)(this.context);
|
|
50
|
+
const { ngCliVersion, nodeVersion, unsupportedNodeVersion, packageManagerName, packageManagerVersion, os, arch, versions, } = versionInfo;
|
|
51
|
+
const header = `
|
|
52
|
+
Angular CLI: ${ngCliVersion}
|
|
53
|
+
Node: ${nodeVersion}${unsupportedNodeVersion ? ' (Unsupported)' : ''}
|
|
54
|
+
Package Manager: ${packageManagerName} ${packageManagerVersion ?? '<error>'}
|
|
55
|
+
OS: ${os} ${arch}
|
|
56
|
+
`.replace(/^ {6}/gm, '');
|
|
57
|
+
const angularPackages = this.formatAngularPackages(versionInfo);
|
|
58
|
+
const packageTable = this.formatPackageTable(versions);
|
|
59
|
+
logger.info([ASCII_ART, header, angularPackages, packageTable].join('\n\n'));
|
|
60
|
+
if (unsupportedNodeVersion) {
|
|
61
|
+
logger.warn(`Warning: The current version of Node (${nodeVersion}) is not supported by Angular.`);
|
|
65
62
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
delete versions[name];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Make sure we list them in alphabetical order.
|
|
80
|
-
angularSameAsCore.sort();
|
|
81
|
-
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Formats the Angular packages section of the version output.
|
|
66
|
+
* @param versionInfo An object containing the version information.
|
|
67
|
+
* @returns A string containing the formatted Angular packages information.
|
|
68
|
+
*/
|
|
69
|
+
formatAngularPackages(versionInfo) {
|
|
70
|
+
const { angularCoreVersion, angularSameAsCore } = versionInfo;
|
|
71
|
+
if (!angularCoreVersion) {
|
|
72
|
+
return 'Angular: <error>';
|
|
82
73
|
}
|
|
83
|
-
const
|
|
84
|
-
const asciiArt = `
|
|
85
|
-
_ _ ____ _ ___
|
|
86
|
-
/ \\ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
|
|
87
|
-
/ △ \\ | '_ \\ / _\` | | | | |/ _\` | '__| | | | | | |
|
|
88
|
-
/ ___ \\| | | | (_| | |_| | | (_| | | | |___| |___ | |
|
|
89
|
-
/_/ \\_\\_| |_|\\__, |\\__,_|_|\\__,_|_| \\____|_____|___|
|
|
90
|
-
|___/
|
|
91
|
-
`
|
|
92
|
-
.split('\n')
|
|
93
|
-
.map((x) => color_1.colors.red(x))
|
|
94
|
-
.join('\n');
|
|
95
|
-
logger.info(asciiArt);
|
|
96
|
-
logger.info(`
|
|
97
|
-
Angular CLI: ${ngCliVersion}
|
|
98
|
-
Node: ${process.versions.node}${unsupportedNodeVersion ? ' (Unsupported)' : ''}
|
|
99
|
-
Package Manager: ${packageManager.name} ${packageManager.version ?? '<error>'}
|
|
100
|
-
OS: ${process.platform} ${process.arch}
|
|
101
|
-
|
|
102
|
-
Angular: ${angularCoreVersion}
|
|
103
|
-
... ${angularSameAsCore
|
|
74
|
+
const wrappedPackages = angularSameAsCore
|
|
104
75
|
.reduce((acc, name) => {
|
|
105
|
-
|
|
106
|
-
if (acc.length == 0) {
|
|
76
|
+
if (acc.length === 0) {
|
|
107
77
|
return [name];
|
|
108
78
|
}
|
|
109
79
|
const line = acc[acc.length - 1] + ', ' + name;
|
|
@@ -115,43 +85,32 @@ class VersionCommandModule extends command_module_1.CommandModule {
|
|
|
115
85
|
}
|
|
116
86
|
return acc;
|
|
117
87
|
}, [])
|
|
118
|
-
.join('\n... ')
|
|
119
|
-
|
|
120
|
-
Package${namePad.slice(7)}Version
|
|
121
|
-
-------${namePad.replace(/ /g, '-')}------------------
|
|
122
|
-
${Object.keys(versions)
|
|
123
|
-
.map((module) => `${module}${namePad.slice(module.length)}${versions[module]}`)
|
|
124
|
-
.sort()
|
|
125
|
-
.join('\n')}
|
|
126
|
-
`.replace(/^ {6}/gm, ''));
|
|
127
|
-
if (unsupportedNodeVersion) {
|
|
128
|
-
logger.warn(`Warning: The current version of Node (${process.versions.node}) is not supported by Angular.`);
|
|
129
|
-
}
|
|
88
|
+
.join('\n... ');
|
|
89
|
+
return `Angular: ${angularCoreVersion}\n... ${wrappedPackages}`;
|
|
130
90
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!packageInfo) {
|
|
141
|
-
try {
|
|
142
|
-
packageInfo = localRequire(`${moduleName}/package.json`);
|
|
143
|
-
cliOnly = true;
|
|
144
|
-
}
|
|
145
|
-
catch { }
|
|
91
|
+
/**
|
|
92
|
+
* Formats the package table section of the version output.
|
|
93
|
+
* @param versions A map of package names to their versions.
|
|
94
|
+
* @returns A string containing the formatted package table.
|
|
95
|
+
*/
|
|
96
|
+
formatPackageTable(versions) {
|
|
97
|
+
const versionKeys = Object.keys(versions);
|
|
98
|
+
if (versionKeys.length === 0) {
|
|
99
|
+
return '';
|
|
146
100
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
101
|
+
const header = 'Package';
|
|
102
|
+
const maxNameLength = Math.max(...versionKeys.map((key) => key.length));
|
|
103
|
+
const namePad = ' '.repeat(Math.max(0, maxNameLength - header.length) + 3);
|
|
104
|
+
const tableHeader = `${header}${namePad}Version`;
|
|
105
|
+
const separator = '-'.repeat(tableHeader.length);
|
|
106
|
+
const tableRows = versionKeys
|
|
107
|
+
.map((module) => {
|
|
108
|
+
const padding = ' '.repeat(maxNameLength - module.length + 3);
|
|
109
|
+
return `${module}${padding}${versions[module]}`;
|
|
110
|
+
})
|
|
111
|
+
.sort()
|
|
112
|
+
.join('\n');
|
|
113
|
+
return `${tableHeader}\n${separator}\n${tableRows}`;
|
|
155
114
|
}
|
|
156
115
|
}
|
|
157
116
|
exports.default = VersionCommandModule;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* An object containing all the version information that will be displayed by the command.
|
|
10
|
+
*/
|
|
11
|
+
export interface VersionInfo {
|
|
12
|
+
ngCliVersion: string;
|
|
13
|
+
angularCoreVersion: string;
|
|
14
|
+
angularSameAsCore: string[];
|
|
15
|
+
versions: Record<string, string>;
|
|
16
|
+
unsupportedNodeVersion: boolean;
|
|
17
|
+
nodeVersion: string;
|
|
18
|
+
packageManagerName: string;
|
|
19
|
+
packageManagerVersion: string | undefined;
|
|
20
|
+
os: string;
|
|
21
|
+
arch: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Gathers all the version information from the environment and workspace.
|
|
25
|
+
* @returns An object containing all the version information.
|
|
26
|
+
*/
|
|
27
|
+
export declare function gatherVersionInfo(context: {
|
|
28
|
+
packageManager: {
|
|
29
|
+
name: string;
|
|
30
|
+
version: string | undefined;
|
|
31
|
+
};
|
|
32
|
+
root: string;
|
|
33
|
+
}): VersionInfo;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright Google LLC All Rights Reserved.
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.gatherVersionInfo = gatherVersionInfo;
|
|
11
|
+
const node_module_1 = require("node:module");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
/**
|
|
14
|
+
* Major versions of Node.js that are officially supported by Angular.
|
|
15
|
+
* @see https://angular.dev/reference/versions#supported-node-js-versions
|
|
16
|
+
*/
|
|
17
|
+
const SUPPORTED_NODE_MAJORS = [20, 22, 24];
|
|
18
|
+
/**
|
|
19
|
+
* A list of regular expression patterns that match package names that should be included in the
|
|
20
|
+
* version output.
|
|
21
|
+
*/
|
|
22
|
+
const PACKAGE_PATTERNS = [
|
|
23
|
+
/^@angular\/.*/,
|
|
24
|
+
/^@angular-devkit\/.*/,
|
|
25
|
+
/^@ngtools\/.*/,
|
|
26
|
+
/^@schematics\/.*/,
|
|
27
|
+
/^rxjs$/,
|
|
28
|
+
/^typescript$/,
|
|
29
|
+
/^ng-packagr$/,
|
|
30
|
+
/^webpack$/,
|
|
31
|
+
/^zone\.js$/,
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Gathers all the version information from the environment and workspace.
|
|
35
|
+
* @returns An object containing all the version information.
|
|
36
|
+
*/
|
|
37
|
+
function gatherVersionInfo(context) {
|
|
38
|
+
const localRequire = (0, node_module_1.createRequire)((0, node_path_1.resolve)(__filename, '../../../'));
|
|
39
|
+
// Trailing slash is used to allow the path to be treated as a directory
|
|
40
|
+
const workspaceRequire = (0, node_module_1.createRequire)(context.root + '/');
|
|
41
|
+
const cliPackage = localRequire('./package.json');
|
|
42
|
+
let workspacePackage;
|
|
43
|
+
try {
|
|
44
|
+
workspacePackage = workspaceRequire('./package.json');
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
const [nodeMajor] = process.versions.node.split('.').map((part) => Number(part));
|
|
48
|
+
const unsupportedNodeVersion = !SUPPORTED_NODE_MAJORS.includes(nodeMajor);
|
|
49
|
+
const packageNames = new Set(Object.keys({
|
|
50
|
+
...cliPackage.dependencies,
|
|
51
|
+
...cliPackage.devDependencies,
|
|
52
|
+
...workspacePackage?.dependencies,
|
|
53
|
+
...workspacePackage?.devDependencies,
|
|
54
|
+
}));
|
|
55
|
+
const versions = {};
|
|
56
|
+
for (const name of packageNames) {
|
|
57
|
+
if (PACKAGE_PATTERNS.some((p) => p.test(name))) {
|
|
58
|
+
versions[name] = getVersion(name, workspaceRequire, localRequire);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const ngCliVersion = cliPackage.version;
|
|
62
|
+
let angularCoreVersion = '';
|
|
63
|
+
const angularSameAsCore = [];
|
|
64
|
+
if (workspacePackage) {
|
|
65
|
+
// Filter all angular versions that are the same as core.
|
|
66
|
+
angularCoreVersion = versions['@angular/core'];
|
|
67
|
+
if (angularCoreVersion) {
|
|
68
|
+
for (const [name, version] of Object.entries(versions)) {
|
|
69
|
+
if (version === angularCoreVersion && name.startsWith('@angular/')) {
|
|
70
|
+
angularSameAsCore.push(name.replace(/^@angular\//, ''));
|
|
71
|
+
delete versions[name];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Make sure we list them in alphabetical order.
|
|
75
|
+
angularSameAsCore.sort();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
ngCliVersion,
|
|
80
|
+
angularCoreVersion,
|
|
81
|
+
angularSameAsCore,
|
|
82
|
+
versions,
|
|
83
|
+
unsupportedNodeVersion,
|
|
84
|
+
nodeVersion: process.versions.node,
|
|
85
|
+
packageManagerName: context.packageManager.name,
|
|
86
|
+
packageManagerVersion: context.packageManager.version,
|
|
87
|
+
os: process.platform,
|
|
88
|
+
arch: process.arch,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gets the version of a package.
|
|
93
|
+
* @param moduleName The name of the package.
|
|
94
|
+
* @param workspaceRequire A `require` function for the workspace.
|
|
95
|
+
* @param localRequire A `require` function for the CLI.
|
|
96
|
+
* @returns The version of the package, or `<error>` if it could not be found.
|
|
97
|
+
*/
|
|
98
|
+
function getVersion(moduleName, workspaceRequire, localRequire) {
|
|
99
|
+
let packageInfo;
|
|
100
|
+
let cliOnly = false;
|
|
101
|
+
// Try to find the package in the workspace
|
|
102
|
+
try {
|
|
103
|
+
packageInfo = workspaceRequire(`${moduleName}/package.json`);
|
|
104
|
+
}
|
|
105
|
+
catch { }
|
|
106
|
+
// If not found, try to find within the CLI
|
|
107
|
+
if (!packageInfo) {
|
|
108
|
+
try {
|
|
109
|
+
packageInfo = localRequire(`${moduleName}/package.json`);
|
|
110
|
+
cliOnly = true;
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
}
|
|
114
|
+
// If found, attempt to get the version
|
|
115
|
+
if (packageInfo) {
|
|
116
|
+
try {
|
|
117
|
+
return packageInfo.version + (cliOnly ? ' (cli-only)' : '');
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
}
|
|
121
|
+
return '<error>';
|
|
122
|
+
}
|
|
@@ -5,8 +5,21 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
+
/** Disables all analytics reporting when the `NG_CLI_ANALYTICS` environment variable is set to '0' or 'false'. */
|
|
8
9
|
export declare const analyticsDisabled: boolean;
|
|
10
|
+
/** Identifies when the CLI is running in a Continuous Integration environment. */
|
|
9
11
|
export declare const isCI: boolean;
|
|
12
|
+
/** Disables the automatic version check when the `NG_DISABLE_VERSION_CHECK` environment variable is enabled. */
|
|
10
13
|
export declare const disableVersionCheck: boolean;
|
|
14
|
+
/** Enables debugging messages when the `NG_DEBUG` environment variable is enabled. */
|
|
11
15
|
export declare const ngDebug: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Forces the autocomplete script to be generated.
|
|
18
|
+
* The `NG_FORCE_AUTOCOMPLETE` environment variable can be 'true', 'false', or undefined (for default behavior).
|
|
19
|
+
*/
|
|
12
20
|
export declare const forceAutocomplete: boolean | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* When enabled, forces TTY mode.
|
|
23
|
+
* The `NG_FORCE_TTY` environment variable can be 'true', 'false', or undefined (for default behavior).
|
|
24
|
+
*/
|
|
25
|
+
export declare const forceTty: boolean | undefined;
|
|
@@ -7,24 +7,53 @@
|
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.forceAutocomplete = exports.ngDebug = exports.disableVersionCheck = exports.isCI = exports.analyticsDisabled = void 0;
|
|
10
|
+
exports.forceTty = exports.forceAutocomplete = exports.ngDebug = exports.disableVersionCheck = exports.isCI = exports.analyticsDisabled = void 0;
|
|
11
|
+
/** A set of strings that are considered "truthy" when parsing environment variables. */
|
|
12
|
+
const TRUTHY_VALUES = new Set(['1', 'true']);
|
|
13
|
+
/** A set of strings that are considered "falsy" when parsing environment variables. */
|
|
14
|
+
const FALSY_VALUES = new Set(['0', 'false']);
|
|
15
|
+
/**
|
|
16
|
+
* Checks if an environment variable is present and has a non-empty value.
|
|
17
|
+
* @param variable The environment variable to check.
|
|
18
|
+
* @returns `true` if the variable is a non-empty string.
|
|
19
|
+
*/
|
|
11
20
|
function isPresent(variable) {
|
|
12
21
|
return typeof variable === 'string' && variable !== '';
|
|
13
22
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function
|
|
23
|
+
/**
|
|
24
|
+
* Parses an environment variable into a boolean or undefined.
|
|
25
|
+
* @returns `true` if the variable is truthy ('1', 'true').
|
|
26
|
+
* @returns `false` if the variable is falsy ('0', 'false').
|
|
27
|
+
* @returns `undefined` if the variable is not present or has an unknown value.
|
|
28
|
+
*/
|
|
29
|
+
function parseTristate(variable) {
|
|
21
30
|
if (!isPresent(variable)) {
|
|
22
31
|
return undefined;
|
|
23
32
|
}
|
|
24
|
-
|
|
33
|
+
const value = variable.toLowerCase();
|
|
34
|
+
if (TRUTHY_VALUES.has(value)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (FALSY_VALUES.has(value)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
25
41
|
}
|
|
26
|
-
|
|
27
|
-
exports.
|
|
28
|
-
|
|
29
|
-
exports.
|
|
30
|
-
|
|
42
|
+
/** Disables all analytics reporting when the `NG_CLI_ANALYTICS` environment variable is set to '0' or 'false'. */
|
|
43
|
+
exports.analyticsDisabled = parseTristate(process.env['NG_CLI_ANALYTICS']) === false;
|
|
44
|
+
/** Identifies when the CLI is running in a Continuous Integration environment. */
|
|
45
|
+
exports.isCI = parseTristate(process.env['CI']) === true;
|
|
46
|
+
/** Disables the automatic version check when the `NG_DISABLE_VERSION_CHECK` environment variable is enabled. */
|
|
47
|
+
exports.disableVersionCheck = parseTristate(process.env['NG_DISABLE_VERSION_CHECK']) === true;
|
|
48
|
+
/** Enables debugging messages when the `NG_DEBUG` environment variable is enabled. */
|
|
49
|
+
exports.ngDebug = parseTristate(process.env['NG_DEBUG']) === true;
|
|
50
|
+
/**
|
|
51
|
+
* Forces the autocomplete script to be generated.
|
|
52
|
+
* The `NG_FORCE_AUTOCOMPLETE` environment variable can be 'true', 'false', or undefined (for default behavior).
|
|
53
|
+
*/
|
|
54
|
+
exports.forceAutocomplete = parseTristate(process.env['NG_FORCE_AUTOCOMPLETE']);
|
|
55
|
+
/**
|
|
56
|
+
* When enabled, forces TTY mode.
|
|
57
|
+
* The `NG_FORCE_TTY` environment variable can be 'true', 'false', or undefined (for default behavior).
|
|
58
|
+
*/
|
|
59
|
+
exports.forceTty = parseTristate(process.env['NG_FORCE_TTY']);
|
package/src/utilities/tty.d.ts
CHANGED
|
@@ -5,4 +5,12 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Determines if the `stream` is a TTY.
|
|
10
|
+
*
|
|
11
|
+
* @param stream A NodeJS stream to check. Defaults to `process.stdout`.
|
|
12
|
+
* @returns `true` if the `stream` is a TTY, `false` otherwise. This detection is overridden
|
|
13
|
+
* by the `NG_FORCE_TTY` environment variable. In a CI environment, this will also be `false`
|
|
14
|
+
* unless `NG_FORCE_TTY` is set.
|
|
15
|
+
*/
|
|
8
16
|
export declare function isTTY(stream?: NodeJS.WriteStream): boolean;
|
package/src/utilities/tty.js
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.isTTY = isTTY;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const environment_options_1 = require("./environment-options");
|
|
12
|
+
/**
|
|
13
|
+
* Determines if the `stream` is a TTY.
|
|
14
|
+
*
|
|
15
|
+
* @param stream A NodeJS stream to check. Defaults to `process.stdout`.
|
|
16
|
+
* @returns `true` if the `stream` is a TTY, `false` otherwise. This detection is overridden
|
|
17
|
+
* by the `NG_FORCE_TTY` environment variable. In a CI environment, this will also be `false`
|
|
18
|
+
* unless `NG_FORCE_TTY` is set.
|
|
19
|
+
*/
|
|
15
20
|
function isTTY(stream = process.stdout) {
|
|
16
|
-
|
|
17
|
-
const force = process.env['NG_FORCE_TTY'];
|
|
18
|
-
if (force !== undefined) {
|
|
19
|
-
return _isTruthy(force);
|
|
20
|
-
}
|
|
21
|
-
return !!stream.isTTY && !_isTruthy(process.env['CI']);
|
|
21
|
+
return environment_options_1.forceTty ?? (!!stream.isTTY && !environment_options_1.isCI);
|
|
22
22
|
}
|
package/src/utilities/version.js
CHANGED
package/src/typings.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright Google LLC All Rights Reserved.
|
|
4
|
-
*
|
|
5
|
-
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
-
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
declare module 'npm-pick-manifest' {
|
|
10
|
-
function pickManifest(
|
|
11
|
-
metadata: import('./utilities/package-metadata').PackageMetadata,
|
|
12
|
-
selector: string,
|
|
13
|
-
): import('./utilities/package-metadata').PackageManifest;
|
|
14
|
-
export = pickManifest;
|
|
15
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright Google LLC All Rights Reserved.
|
|
4
|
-
*
|
|
5
|
-
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
-
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* This uses a dynamic import to load a module which may be ESM.
|
|
10
|
-
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
|
|
11
|
-
* will currently, unconditionally downlevel dynamic import into a require call.
|
|
12
|
-
* require calls cannot load ESM code and will result in a runtime error. To workaround
|
|
13
|
-
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
|
|
14
|
-
* Once TypeScript provides support for keeping the dynamic import this workaround can
|
|
15
|
-
* be dropped.
|
|
16
|
-
*
|
|
17
|
-
* @param modulePath The path of the module to load.
|
|
18
|
-
* @returns A Promise that resolves to the dynamically imported module.
|
|
19
|
-
*/
|
|
20
|
-
export declare function loadEsmModule<T>(modulePath: string | URL): Promise<T>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @license
|
|
4
|
-
* Copyright Google LLC All Rights Reserved.
|
|
5
|
-
*
|
|
6
|
-
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
-
* found in the LICENSE file at https://angular.dev/license
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.loadEsmModule = loadEsmModule;
|
|
11
|
-
/**
|
|
12
|
-
* Lazily compiled dynamic import loader function.
|
|
13
|
-
*/
|
|
14
|
-
let load;
|
|
15
|
-
/**
|
|
16
|
-
* This uses a dynamic import to load a module which may be ESM.
|
|
17
|
-
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
|
|
18
|
-
* will currently, unconditionally downlevel dynamic import into a require call.
|
|
19
|
-
* require calls cannot load ESM code and will result in a runtime error. To workaround
|
|
20
|
-
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
|
|
21
|
-
* Once TypeScript provides support for keeping the dynamic import this workaround can
|
|
22
|
-
* be dropped.
|
|
23
|
-
*
|
|
24
|
-
* @param modulePath The path of the module to load.
|
|
25
|
-
* @returns A Promise that resolves to the dynamically imported module.
|
|
26
|
-
*/
|
|
27
|
-
function loadEsmModule(modulePath) {
|
|
28
|
-
load ??= new Function('modulePath', `return import(modulePath);`);
|
|
29
|
-
return load(modulePath);
|
|
30
|
-
}
|