@angular/cli 21.0.0-next.5 → 21.0.0-next.7
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/config/schema.json +140 -43
- package/lib/config/workspace-schema.d.ts +1 -0
- package/lib/config/workspace-schema.js +1 -0
- package/package.json +17 -17
- package/src/command-builder/utilities/json-schema.d.ts +13 -1
- package/src/command-builder/utilities/json-schema.js +179 -100
- package/src/commands/cache/info/cli.js +35 -11
- package/src/commands/mcp/tools/doc-search.js +4 -3
- package/src/commands/mcp/tools/projects.d.ts +18 -0
- package/src/commands/mcp/tools/projects.js +123 -4
- package/src/commands/version/cli.d.ts +3 -7
- package/src/commands/version/cli.js +49 -49
- package/src/commands/version/version-info.d.ts +28 -10
- package/src/commands/version/version-info.js +33 -50
- package/src/package-managers/discovery.d.ts +23 -0
- package/src/package-managers/discovery.js +109 -0
- package/src/package-managers/error.d.ts +31 -0
- package/src/package-managers/error.js +40 -0
- package/src/package-managers/factory.d.ts +25 -0
- package/src/package-managers/factory.js +122 -0
- package/src/package-managers/host.d.ts +64 -0
- package/src/package-managers/host.js +68 -0
- package/src/package-managers/logger.d.ts +27 -0
- package/src/package-managers/logger.js +9 -0
- package/src/package-managers/package-manager-descriptor.d.ts +204 -0
- package/src/package-managers/package-manager-descriptor.js +146 -0
- package/src/package-managers/package-manager.d.ts +144 -0
- package/src/package-managers/package-manager.js +302 -0
- package/src/package-managers/package-metadata.d.ts +85 -0
- package/src/package-managers/package-metadata.js +9 -0
- package/src/package-managers/package-tree.d.ts +23 -0
- package/src/package-managers/package-tree.js +9 -0
- package/src/package-managers/parsers.d.ts +92 -0
- package/src/package-managers/parsers.js +233 -0
- package/src/package-managers/testing/mock-host.d.ts +26 -0
- package/src/package-managers/testing/mock-host.js +52 -0
- package/src/utilities/version.js +1 -1
|
@@ -14,6 +14,7 @@ exports.LIST_PROJECTS_TOOL = void 0;
|
|
|
14
14
|
const promises_1 = require("node:fs/promises");
|
|
15
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
16
|
const node_url_1 = require("node:url");
|
|
17
|
+
const semver_1 = __importDefault(require("semver"));
|
|
17
18
|
const zod_1 = __importDefault(require("zod"));
|
|
18
19
|
const config_1 = require("../../../utilities/config");
|
|
19
20
|
const error_1 = require("../../../utilities/error");
|
|
@@ -21,6 +22,10 @@ const tool_registry_1 = require("./tool-registry");
|
|
|
21
22
|
const listProjectsOutputSchema = {
|
|
22
23
|
workspaces: zod_1.default.array(zod_1.default.object({
|
|
23
24
|
path: zod_1.default.string().describe('The path to the `angular.json` file for this workspace.'),
|
|
25
|
+
frameworkVersion: zod_1.default
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('The major version of the Angular framework (`@angular/core`) in this workspace, if found.'),
|
|
24
29
|
projects: zod_1.default.array(zod_1.default.object({
|
|
25
30
|
name: zod_1.default
|
|
26
31
|
.string()
|
|
@@ -29,6 +34,10 @@ const listProjectsOutputSchema = {
|
|
|
29
34
|
.enum(['application', 'library'])
|
|
30
35
|
.optional()
|
|
31
36
|
.describe(`The type of the project, either 'application' or 'library'.`),
|
|
37
|
+
builder: zod_1.default
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('The primary builder for the project, typically from the "build" target.'),
|
|
32
41
|
root: zod_1.default
|
|
33
42
|
.string()
|
|
34
43
|
.describe('The root directory of the project, relative to the workspace root.'),
|
|
@@ -49,6 +58,15 @@ const listProjectsOutputSchema = {
|
|
|
49
58
|
}))
|
|
50
59
|
.default([])
|
|
51
60
|
.describe('A list of files that looked like workspaces but failed to parse.'),
|
|
61
|
+
versioningErrors: zod_1.default
|
|
62
|
+
.array(zod_1.default.object({
|
|
63
|
+
filePath: zod_1.default
|
|
64
|
+
.string()
|
|
65
|
+
.describe('The path to the workspace `angular.json` for which versioning failed.'),
|
|
66
|
+
message: zod_1.default.string().describe('The error message detailing why versioning failed.'),
|
|
67
|
+
}))
|
|
68
|
+
.default([])
|
|
69
|
+
.describe('A list of workspaces for which the framework version could not be determined.'),
|
|
52
70
|
};
|
|
53
71
|
exports.LIST_PROJECTS_TOOL = (0, tool_registry_1.declareTool)({
|
|
54
72
|
name: 'list_projects',
|
|
@@ -64,6 +82,8 @@ their types, and their locations.
|
|
|
64
82
|
* Identifying the \`root\` and \`sourceRoot\` of a project to read, analyze, or modify its files.
|
|
65
83
|
* Determining if a project is an \`application\` or a \`library\`.
|
|
66
84
|
* Getting the \`selectorPrefix\` for a project before generating a new component to ensure it follows conventions.
|
|
85
|
+
* Identifying the major version of the Angular framework for each workspace, which is crucial for monorepos.
|
|
86
|
+
* Determining a project's primary function by inspecting its builder (e.g., '@angular-devkit/build-angular:browser' for an application).
|
|
67
87
|
</Use Cases>
|
|
68
88
|
<Operational Notes>
|
|
69
89
|
* **Working Directory:** Shell commands for a project (like \`ng generate\`) **MUST**
|
|
@@ -122,6 +142,64 @@ async function* findAngularJsonFiles(rootDir) {
|
|
|
122
142
|
yield* foundFilesInBatch;
|
|
123
143
|
}
|
|
124
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Searches upwards from a starting directory to find the version of '@angular/core'.
|
|
147
|
+
* It caches results to avoid redundant lookups.
|
|
148
|
+
* @param startDir The directory to start the search from.
|
|
149
|
+
* @param cache A map to store cached results.
|
|
150
|
+
* @param searchRoot The directory at which to stop the search.
|
|
151
|
+
* @returns The major version of '@angular/core' as a string, otherwise undefined.
|
|
152
|
+
*/
|
|
153
|
+
async function findAngularCoreVersion(startDir, cache, searchRoot) {
|
|
154
|
+
let currentDir = startDir;
|
|
155
|
+
const dirsToCache = [];
|
|
156
|
+
while (currentDir) {
|
|
157
|
+
dirsToCache.push(currentDir);
|
|
158
|
+
if (cache.has(currentDir)) {
|
|
159
|
+
const cachedResult = cache.get(currentDir);
|
|
160
|
+
// Populate cache for all intermediate directories.
|
|
161
|
+
for (const dir of dirsToCache) {
|
|
162
|
+
cache.set(dir, cachedResult);
|
|
163
|
+
}
|
|
164
|
+
return cachedResult;
|
|
165
|
+
}
|
|
166
|
+
const pkgPath = node_path_1.default.join(currentDir, 'package.json');
|
|
167
|
+
try {
|
|
168
|
+
const pkgContent = await (0, promises_1.readFile)(pkgPath, 'utf-8');
|
|
169
|
+
const pkg = JSON.parse(pkgContent);
|
|
170
|
+
const versionSpecifier = pkg.dependencies?.['@angular/core'] ?? pkg.devDependencies?.['@angular/core'];
|
|
171
|
+
if (versionSpecifier) {
|
|
172
|
+
const minVersion = semver_1.default.minVersion(versionSpecifier);
|
|
173
|
+
const result = minVersion ? String(minVersion.major) : undefined;
|
|
174
|
+
for (const dir of dirsToCache) {
|
|
175
|
+
cache.set(dir, result);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
(0, error_1.assertIsError)(error);
|
|
182
|
+
if (error.code !== 'ENOENT') {
|
|
183
|
+
// Ignore missing package.json files, but rethrow other errors.
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Stop if we are at the search root or the filesystem root.
|
|
188
|
+
if (currentDir === searchRoot) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
const parentDir = node_path_1.default.dirname(currentDir);
|
|
192
|
+
if (parentDir === currentDir) {
|
|
193
|
+
break; // Reached the filesystem root.
|
|
194
|
+
}
|
|
195
|
+
currentDir = parentDir;
|
|
196
|
+
}
|
|
197
|
+
// Cache the failure for all traversed directories.
|
|
198
|
+
for (const dir of dirsToCache) {
|
|
199
|
+
cache.set(dir, undefined);
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
125
203
|
/**
|
|
126
204
|
* Loads, parses, and transforms a single angular.json file into the tool's output format.
|
|
127
205
|
* It checks a set of seen paths to avoid processing the same workspace multiple times.
|
|
@@ -142,6 +220,7 @@ async function loadAndParseWorkspace(configFile, seenPaths) {
|
|
|
142
220
|
projects.push({
|
|
143
221
|
name,
|
|
144
222
|
type: project.extensions['projectType'],
|
|
223
|
+
builder: project.targets.get('build')?.builder,
|
|
145
224
|
root: project.root,
|
|
146
225
|
sourceRoot: project.sourceRoot ?? node_path_1.default.posix.join(project.root, 'src'),
|
|
147
226
|
selectorPrefix: project.extensions['prefix'],
|
|
@@ -160,11 +239,44 @@ async function loadAndParseWorkspace(configFile, seenPaths) {
|
|
|
160
239
|
return { workspace: null, error: { filePath: configFile, message } };
|
|
161
240
|
}
|
|
162
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Processes a single `angular.json` file to extract workspace and framework version information.
|
|
244
|
+
* @param configFile The path to the `angular.json` file.
|
|
245
|
+
* @param searchRoot The directory at which to stop the upward search for `package.json`.
|
|
246
|
+
* @param seenPaths A Set of absolute paths that have already been processed to avoid duplicates.
|
|
247
|
+
* @param versionCache A Map to cache framework version lookups for performance.
|
|
248
|
+
* @returns A promise resolving to an object containing the processed data and any errors.
|
|
249
|
+
*/
|
|
250
|
+
async function processConfigFile(configFile, searchRoot, seenPaths, versionCache) {
|
|
251
|
+
const { workspace, error } = await loadAndParseWorkspace(configFile, seenPaths);
|
|
252
|
+
if (error) {
|
|
253
|
+
return { parsingError: error };
|
|
254
|
+
}
|
|
255
|
+
if (!workspace) {
|
|
256
|
+
return {}; // Skipped as it was already seen.
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const workspaceDir = node_path_1.default.dirname(configFile);
|
|
260
|
+
workspace.frameworkVersion = await findAngularCoreVersion(workspaceDir, versionCache, searchRoot);
|
|
261
|
+
return { workspace };
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
return {
|
|
265
|
+
workspace,
|
|
266
|
+
versioningError: {
|
|
267
|
+
filePath: workspace.path,
|
|
268
|
+
message: e instanceof Error ? e.message : 'An unknown error occurred.',
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
163
273
|
async function createListProjectsHandler({ server }) {
|
|
164
274
|
return async () => {
|
|
165
275
|
const workspaces = [];
|
|
166
276
|
const parsingErrors = [];
|
|
277
|
+
const versioningErrors = [];
|
|
167
278
|
const seenPaths = new Set();
|
|
279
|
+
const versionCache = new Map();
|
|
168
280
|
let searchRoots;
|
|
169
281
|
const clientCapabilities = server.server.getClientCapabilities();
|
|
170
282
|
if (clientCapabilities?.roots) {
|
|
@@ -177,12 +289,15 @@ async function createListProjectsHandler({ server }) {
|
|
|
177
289
|
}
|
|
178
290
|
for (const root of searchRoots) {
|
|
179
291
|
for await (const configFile of findAngularJsonFiles(root)) {
|
|
180
|
-
const { workspace,
|
|
292
|
+
const { workspace, parsingError, versioningError } = await processConfigFile(configFile, root, seenPaths, versionCache);
|
|
181
293
|
if (workspace) {
|
|
182
294
|
workspaces.push(workspace);
|
|
183
295
|
}
|
|
184
|
-
if (
|
|
185
|
-
parsingErrors.push(
|
|
296
|
+
if (parsingError) {
|
|
297
|
+
parsingErrors.push(parsingError);
|
|
298
|
+
}
|
|
299
|
+
if (versioningError) {
|
|
300
|
+
versioningErrors.push(versioningError);
|
|
186
301
|
}
|
|
187
302
|
}
|
|
188
303
|
}
|
|
@@ -204,9 +319,13 @@ async function createListProjectsHandler({ server }) {
|
|
|
204
319
|
text += `\n\nWarning: The following ${parsingErrors.length} file(s) could not be parsed and were skipped:\n`;
|
|
205
320
|
text += parsingErrors.map((e) => `- ${e.filePath}: ${e.message}`).join('\n');
|
|
206
321
|
}
|
|
322
|
+
if (versioningErrors.length > 0) {
|
|
323
|
+
text += `\n\nWarning: The framework version for the following ${versioningErrors.length} workspace(s) could not be determined:\n`;
|
|
324
|
+
text += versioningErrors.map((e) => `- ${e.filePath}: ${e.message}`).join('\n');
|
|
325
|
+
}
|
|
207
326
|
return {
|
|
208
327
|
content: [{ type: 'text', text }],
|
|
209
|
-
structuredContent: { workspaces, parsingErrors },
|
|
328
|
+
structuredContent: { workspaces, parsingErrors, versioningErrors },
|
|
210
329
|
};
|
|
211
330
|
};
|
|
212
331
|
}
|
|
@@ -24,13 +24,9 @@ export default class VersionCommandModule extends CommandModule implements Comma
|
|
|
24
24
|
/**
|
|
25
25
|
* The main execution logic for the `ng version` command.
|
|
26
26
|
*/
|
|
27
|
-
run(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @param versionInfo An object containing the version information.
|
|
31
|
-
* @returns A string containing the formatted Angular packages information.
|
|
32
|
-
*/
|
|
33
|
-
private formatAngularPackages;
|
|
27
|
+
run(options: {
|
|
28
|
+
json?: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
34
30
|
/**
|
|
35
31
|
* Formats the package table section of the version output.
|
|
36
32
|
* @param versions A map of package names to their versions.
|
|
@@ -39,55 +39,44 @@ class VersionCommandModule extends command_module_1.CommandModule {
|
|
|
39
39
|
* @returns The configured `yargs` instance.
|
|
40
40
|
*/
|
|
41
41
|
builder(localYargs) {
|
|
42
|
-
return localYargs
|
|
42
|
+
return localYargs.option('json', {
|
|
43
|
+
describe: 'Outputs version information in JSON format.',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* The main execution logic for the `ng version` command.
|
|
46
49
|
*/
|
|
47
|
-
async run() {
|
|
50
|
+
async run(options) {
|
|
48
51
|
const { logger } = this.context;
|
|
49
52
|
const versionInfo = (0, version_info_1.gatherVersionInfo)(this.context);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
if (options.json) {
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.log(JSON.stringify(versionInfo, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const { cli: { version: ngCliVersion }, framework, system: { node: { version: nodeVersion, unsupported: unsupportedNodeVersion }, os: { platform: os, architecture: arch }, packageManager: { name: packageManagerName, version: packageManagerVersion }, }, packages, } = versionInfo;
|
|
59
|
+
const headerInfo = [{ label: 'Angular CLI', value: ngCliVersion }];
|
|
60
|
+
if (framework.version) {
|
|
61
|
+
headerInfo.push({ label: 'Angular', value: framework.version });
|
|
62
|
+
}
|
|
63
|
+
headerInfo.push({
|
|
64
|
+
label: 'Node.js',
|
|
65
|
+
value: `${nodeVersion}${unsupportedNodeVersion ? color_1.colors.yellow(' (Unsupported)') : ''}`,
|
|
66
|
+
}, {
|
|
67
|
+
label: 'Package Manager',
|
|
68
|
+
value: `${packageManagerName} ${packageManagerVersion ?? '<error>'}`,
|
|
69
|
+
}, { label: 'Operating System', value: `${os} ${arch}` });
|
|
70
|
+
const maxHeaderLabelLength = Math.max(...headerInfo.map((l) => l.label.length));
|
|
71
|
+
const header = headerInfo
|
|
72
|
+
.map(({ label, value }) => color_1.colors.bold(label.padEnd(maxHeaderLabelLength + 2)) + `: ${color_1.colors.cyan(value)}`)
|
|
73
|
+
.join('\n');
|
|
74
|
+
const packageTable = this.formatPackageTable(packages);
|
|
75
|
+
logger.info([ASCII_ART, header, packageTable].join('\n\n'));
|
|
60
76
|
if (unsupportedNodeVersion) {
|
|
61
77
|
logger.warn(`Warning: The current version of Node (${nodeVersion}) is not supported by Angular.`);
|
|
62
78
|
}
|
|
63
79
|
}
|
|
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>';
|
|
73
|
-
}
|
|
74
|
-
const wrappedPackages = angularSameAsCore
|
|
75
|
-
.reduce((acc, name) => {
|
|
76
|
-
if (acc.length === 0) {
|
|
77
|
-
return [name];
|
|
78
|
-
}
|
|
79
|
-
const line = acc[acc.length - 1] + ', ' + name;
|
|
80
|
-
if (line.length > 60) {
|
|
81
|
-
acc.push(name);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
acc[acc.length - 1] = line;
|
|
85
|
-
}
|
|
86
|
-
return acc;
|
|
87
|
-
}, [])
|
|
88
|
-
.join('\n... ');
|
|
89
|
-
return `Angular: ${angularCoreVersion}\n... ${wrappedPackages}`;
|
|
90
|
-
}
|
|
91
80
|
/**
|
|
92
81
|
* Formats the package table section of the version output.
|
|
93
82
|
* @param versions A map of package names to their versions.
|
|
@@ -98,19 +87,30 @@ class VersionCommandModule extends command_module_1.CommandModule {
|
|
|
98
87
|
if (versionKeys.length === 0) {
|
|
99
88
|
return '';
|
|
100
89
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
90
|
+
const headers = {
|
|
91
|
+
name: 'Package',
|
|
92
|
+
installed: 'Installed Version',
|
|
93
|
+
requested: 'Requested Version',
|
|
94
|
+
};
|
|
95
|
+
const maxNameLength = Math.max(headers.name.length, ...versionKeys.map((key) => key.length));
|
|
96
|
+
const maxInstalledLength = Math.max(headers.installed.length, ...versionKeys.map((key) => versions[key].installed.length));
|
|
97
|
+
const maxRequestedLength = Math.max(headers.requested.length, ...versionKeys.map((key) => versions[key].requested.length));
|
|
106
98
|
const tableRows = versionKeys
|
|
107
99
|
.map((module) => {
|
|
108
|
-
const
|
|
109
|
-
|
|
100
|
+
const { requested, installed } = versions[module];
|
|
101
|
+
const name = module.padEnd(maxNameLength);
|
|
102
|
+
const coloredInstalled = installed === '<error>' ? color_1.colors.red(installed) : color_1.colors.cyan(installed);
|
|
103
|
+
const installedPadding = ' '.repeat(maxInstalledLength - installed.length);
|
|
104
|
+
return `│ ${name} │ ${coloredInstalled}${installedPadding} │ ${requested.padEnd(maxRequestedLength)} │`;
|
|
110
105
|
})
|
|
111
|
-
.sort()
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
.sort();
|
|
107
|
+
const top = `┌─${'─'.repeat(maxNameLength)}─┬─${'─'.repeat(maxInstalledLength)}─┬─${'─'.repeat(maxRequestedLength)}─┐`;
|
|
108
|
+
const header = `│ ${headers.name.padEnd(maxNameLength)} │ ` +
|
|
109
|
+
`${headers.installed.padEnd(maxInstalledLength)} │ ` +
|
|
110
|
+
`${headers.requested.padEnd(maxRequestedLength)} │`;
|
|
111
|
+
const separator = `├─${'─'.repeat(maxNameLength)}─┼─${'─'.repeat(maxInstalledLength)}─┼─${'─'.repeat(maxRequestedLength)}─┤`;
|
|
112
|
+
const bottom = `└─${'─'.repeat(maxNameLength)}─┴─${'─'.repeat(maxInstalledLength)}─┴─${'─'.repeat(maxRequestedLength)}─┘`;
|
|
113
|
+
return [top, header, separator, ...tableRows, bottom].join('\n');
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
exports.default = VersionCommandModule;
|
|
@@ -5,20 +5,38 @@
|
|
|
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
|
+
* An object containing version information for a single package.
|
|
10
|
+
*/
|
|
11
|
+
export interface PackageVersionInfo {
|
|
12
|
+
requested: string;
|
|
13
|
+
installed: string;
|
|
14
|
+
}
|
|
8
15
|
/**
|
|
9
16
|
* An object containing all the version information that will be displayed by the command.
|
|
10
17
|
*/
|
|
11
18
|
export interface VersionInfo {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
cli: {
|
|
20
|
+
version: string;
|
|
21
|
+
};
|
|
22
|
+
framework: {
|
|
23
|
+
version: string | undefined;
|
|
24
|
+
};
|
|
25
|
+
system: {
|
|
26
|
+
node: {
|
|
27
|
+
version: string;
|
|
28
|
+
unsupported: boolean;
|
|
29
|
+
};
|
|
30
|
+
os: {
|
|
31
|
+
platform: string;
|
|
32
|
+
architecture: string;
|
|
33
|
+
};
|
|
34
|
+
packageManager: {
|
|
35
|
+
name: string;
|
|
36
|
+
version: string | undefined;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
packages: Record<string, PackageVersionInfo>;
|
|
22
40
|
}
|
|
23
41
|
/**
|
|
24
42
|
* Gathers all the version information from the environment and workspace.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.gatherVersionInfo = gatherVersionInfo;
|
|
11
11
|
const node_module_1 = require("node:module");
|
|
12
|
-
const
|
|
12
|
+
const version_1 = require("../../utilities/version");
|
|
13
13
|
/**
|
|
14
14
|
* Major versions of Node.js that are officially supported by Angular.
|
|
15
15
|
* @see https://angular.dev/reference/versions#supported-node-js-versions
|
|
@@ -35,10 +35,8 @@ const PACKAGE_PATTERNS = [
|
|
|
35
35
|
* @returns An object containing all the version information.
|
|
36
36
|
*/
|
|
37
37
|
function gatherVersionInfo(context) {
|
|
38
|
-
const localRequire = (0, node_module_1.createRequire)((0, node_path_1.resolve)(__filename, '../../../'));
|
|
39
38
|
// Trailing slash is used to allow the path to be treated as a directory
|
|
40
39
|
const workspaceRequire = (0, node_module_1.createRequire)(context.root + '/');
|
|
41
|
-
const cliPackage = localRequire('./package.json');
|
|
42
40
|
let workspacePackage;
|
|
43
41
|
try {
|
|
44
42
|
workspacePackage = workspaceRequire('./package.json');
|
|
@@ -46,46 +44,43 @@ function gatherVersionInfo(context) {
|
|
|
46
44
|
catch { }
|
|
47
45
|
const [nodeMajor] = process.versions.node.split('.').map((part) => Number(part));
|
|
48
46
|
const unsupportedNodeVersion = !SUPPORTED_NODE_MAJORS.includes(nodeMajor);
|
|
49
|
-
const
|
|
50
|
-
...cliPackage.dependencies,
|
|
51
|
-
...cliPackage.devDependencies,
|
|
47
|
+
const allDependencies = {
|
|
52
48
|
...workspacePackage?.dependencies,
|
|
53
49
|
...workspacePackage?.devDependencies,
|
|
54
|
-
}
|
|
55
|
-
const
|
|
50
|
+
};
|
|
51
|
+
const packageNames = new Set(Object.keys(allDependencies));
|
|
52
|
+
const packages = {};
|
|
56
53
|
for (const name of packageNames) {
|
|
57
54
|
if (PACKAGE_PATTERNS.some((p) => p.test(name))) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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();
|
|
55
|
+
packages[name] = {
|
|
56
|
+
requested: allDependencies[name] ?? 'error',
|
|
57
|
+
installed: getVersion(name, workspaceRequire),
|
|
58
|
+
};
|
|
76
59
|
}
|
|
77
60
|
}
|
|
61
|
+
const angularCoreVersion = packages['@angular/core'];
|
|
78
62
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
cli: {
|
|
64
|
+
version: version_1.VERSION.full,
|
|
65
|
+
},
|
|
66
|
+
framework: {
|
|
67
|
+
version: angularCoreVersion?.installed,
|
|
68
|
+
},
|
|
69
|
+
system: {
|
|
70
|
+
node: {
|
|
71
|
+
version: process.versions.node,
|
|
72
|
+
unsupported: unsupportedNodeVersion,
|
|
73
|
+
},
|
|
74
|
+
os: {
|
|
75
|
+
platform: process.platform,
|
|
76
|
+
architecture: process.arch,
|
|
77
|
+
},
|
|
78
|
+
packageManager: {
|
|
79
|
+
name: context.packageManager.name,
|
|
80
|
+
version: context.packageManager.version,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
packages,
|
|
89
84
|
};
|
|
90
85
|
}
|
|
91
86
|
/**
|
|
@@ -95,28 +90,16 @@ function gatherVersionInfo(context) {
|
|
|
95
90
|
* @param localRequire A `require` function for the CLI.
|
|
96
91
|
* @returns The version of the package, or `<error>` if it could not be found.
|
|
97
92
|
*/
|
|
98
|
-
function getVersion(moduleName, workspaceRequire
|
|
93
|
+
function getVersion(moduleName, workspaceRequire) {
|
|
99
94
|
let packageInfo;
|
|
100
|
-
let cliOnly = false;
|
|
101
95
|
// Try to find the package in the workspace
|
|
102
96
|
try {
|
|
103
97
|
packageInfo = workspaceRequire(`${moduleName}/package.json`);
|
|
104
98
|
}
|
|
105
99
|
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
100
|
// If found, attempt to get the version
|
|
115
101
|
if (packageInfo) {
|
|
116
|
-
|
|
117
|
-
return packageInfo.version + (cliOnly ? ' (cli-only)' : '');
|
|
118
|
-
}
|
|
119
|
-
catch { }
|
|
102
|
+
return packageInfo.version;
|
|
120
103
|
}
|
|
121
104
|
return '<error>';
|
|
122
105
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { Host } from './host';
|
|
9
|
+
import { Logger } from './logger';
|
|
10
|
+
import { PackageManagerName } from './package-manager-descriptor';
|
|
11
|
+
/**
|
|
12
|
+
* Discovers the package manager used in a project by searching for lockfiles.
|
|
13
|
+
*
|
|
14
|
+
* This function searches for lockfiles in the given directory and its ancestors.
|
|
15
|
+
* If multiple lockfiles are found, it uses the precedence array to determine
|
|
16
|
+
* which package manager to use. The search is bounded by the git repository root.
|
|
17
|
+
*
|
|
18
|
+
* @param host A `Host` instance for interacting with the file system.
|
|
19
|
+
* @param startDir The directory to start the search from.
|
|
20
|
+
* @param logger An optional logger instance.
|
|
21
|
+
* @returns A promise that resolves to the name of the discovered package manager, or null if none is found.
|
|
22
|
+
*/
|
|
23
|
+
export declare function discover(host: Host, startDir: string, logger?: Logger): Promise<PackageManagerName | null>;
|
|
@@ -0,0 +1,109 @@
|
|
|
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.discover = discover;
|
|
11
|
+
/**
|
|
12
|
+
* @fileoverview This file contains the logic for discovering the package manager
|
|
13
|
+
* used in a project by searching for lockfiles. It is designed to be efficient
|
|
14
|
+
* and to correctly handle monorepo structures.
|
|
15
|
+
*/
|
|
16
|
+
const node_path_1 = require("node:path");
|
|
17
|
+
const package_manager_descriptor_1 = require("./package-manager-descriptor");
|
|
18
|
+
/**
|
|
19
|
+
* A map from lockfile names to their corresponding package manager.
|
|
20
|
+
* This is a performance optimization to avoid iterating over all possible
|
|
21
|
+
* lockfiles in every directory.
|
|
22
|
+
*/
|
|
23
|
+
const LOCKFILE_TO_PACKAGE_MANAGER = new Map();
|
|
24
|
+
for (const [name, descriptor] of Object.entries(package_manager_descriptor_1.SUPPORTED_PACKAGE_MANAGERS)) {
|
|
25
|
+
for (const lockfile of descriptor.lockfiles) {
|
|
26
|
+
LOCKFILE_TO_PACKAGE_MANAGER.set(lockfile, name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Searches a directory for lockfiles and returns a set of package managers that correspond to them.
|
|
31
|
+
* @param host A `Host` instance for interacting with the file system.
|
|
32
|
+
* @param directory The directory to search.
|
|
33
|
+
* @param logger An optional logger instance.
|
|
34
|
+
* @returns A promise that resolves to a set of package manager names.
|
|
35
|
+
*/
|
|
36
|
+
async function findLockfiles(host, directory, logger) {
|
|
37
|
+
logger?.debug(`Searching for lockfiles in '${directory}'...`);
|
|
38
|
+
try {
|
|
39
|
+
const files = await host.readdir(directory);
|
|
40
|
+
const foundPackageManagers = new Set();
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const packageManager = LOCKFILE_TO_PACKAGE_MANAGER.get(file);
|
|
43
|
+
if (packageManager) {
|
|
44
|
+
logger?.debug(` Found '${file}'.`);
|
|
45
|
+
foundPackageManagers.add(packageManager);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return foundPackageManagers;
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
logger?.debug(` Failed to read directory: ${e}`);
|
|
52
|
+
// Ignore directories that don't exist or can't be read.
|
|
53
|
+
return new Set();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a given path is a directory.
|
|
58
|
+
* @param host A `Host` instance for interacting with the file system.
|
|
59
|
+
* @param path The path to check.
|
|
60
|
+
* @returns A promise that resolves to true if the path is a directory, false otherwise.
|
|
61
|
+
*/
|
|
62
|
+
async function isDirectory(host, path) {
|
|
63
|
+
try {
|
|
64
|
+
return (await host.stat(path)).isDirectory();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Discovers the package manager used in a project by searching for lockfiles.
|
|
72
|
+
*
|
|
73
|
+
* This function searches for lockfiles in the given directory and its ancestors.
|
|
74
|
+
* If multiple lockfiles are found, it uses the precedence array to determine
|
|
75
|
+
* which package manager to use. The search is bounded by the git repository root.
|
|
76
|
+
*
|
|
77
|
+
* @param host A `Host` instance for interacting with the file system.
|
|
78
|
+
* @param startDir The directory to start the search from.
|
|
79
|
+
* @param logger An optional logger instance.
|
|
80
|
+
* @returns A promise that resolves to the name of the discovered package manager, or null if none is found.
|
|
81
|
+
*/
|
|
82
|
+
async function discover(host, startDir, logger) {
|
|
83
|
+
logger?.debug(`Starting package manager discovery in '${startDir}'...`);
|
|
84
|
+
let currentDir = startDir;
|
|
85
|
+
while (true) {
|
|
86
|
+
const found = await findLockfiles(host, currentDir, logger);
|
|
87
|
+
if (found.size > 0) {
|
|
88
|
+
logger?.debug(`Found lockfile(s): [${[...found].join(', ')}]. Applying precedence...`);
|
|
89
|
+
for (const packageManager of package_manager_descriptor_1.PACKAGE_MANAGER_PRECEDENCE) {
|
|
90
|
+
if (found.has(packageManager)) {
|
|
91
|
+
logger?.debug(`Selected '${packageManager}' based on precedence.`);
|
|
92
|
+
return packageManager;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Stop searching if we reach the git repository root.
|
|
97
|
+
if (await isDirectory(host, (0, node_path_1.join)(currentDir, '.git'))) {
|
|
98
|
+
logger?.debug(`Reached repository root at '${currentDir}'. Stopping search.`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const parentDir = (0, node_path_1.dirname)(currentDir);
|
|
102
|
+
if (parentDir === currentDir) {
|
|
103
|
+
// We have reached the filesystem root.
|
|
104
|
+
logger?.debug('Reached filesystem root. No lockfile found.');
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
currentDir = parentDir;
|
|
108
|
+
}
|
|
109
|
+
}
|