forked from angular/angular-cli
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdiscovery.ts
More file actions
131 lines (114 loc) · 4.07 KB
/
discovery.ts
File metadata and controls
131 lines (114 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
* @fileoverview This file contains the logic for discovering the package manager
* used in a project by searching for lockfiles. It is designed to be efficient
* and to correctly handle monorepo structures.
*/
import { dirname, join } from 'node:path';
import { Host } from './host';
import { Logger } from './logger';
import {
PACKAGE_MANAGER_PRECEDENCE,
PackageManagerName,
SUPPORTED_PACKAGE_MANAGERS,
} from './package-manager-descriptor';
/**
* Searches a directory for lockfiles and returns a set of package managers that correspond to them.
* @param host A `Host` instance for interacting with the file system.
* @param directory The directory to search.
* @param logger An optional logger instance.
* @returns A promise that resolves to a set of package manager names.
*/
async function findLockfiles(
host: Host,
directory: string,
logger?: Logger,
): Promise<Set<PackageManagerName>> {
logger?.debug(`Searching for lockfiles in '${directory}'...`);
const foundPackageManagers = new Set<PackageManagerName>();
const checks: Promise<void>[] = [];
for (const [name, descriptor] of Object.entries(SUPPORTED_PACKAGE_MANAGERS)) {
const manager = name as PackageManagerName;
for (const lockfile of descriptor.lockfiles) {
checks.push(
(async () => {
try {
const path = join(directory, lockfile);
const stats = await host.stat(path);
if (stats.isFile()) {
logger?.debug(` Found '${lockfile}'.`);
foundPackageManagers.add(manager);
}
} catch {
// File does not exist or cannot be accessed.
}
})(),
);
}
}
await Promise.all(checks);
return foundPackageManagers;
}
/**
* Checks if a given path is a directory.
* @param host A `Host` instance for interacting with the file system.
* @param path The path to check.
* @returns A promise that resolves to true if the path is a directory, false otherwise.
*/
async function isDirectory(host: Host, path: string): Promise<boolean> {
try {
return (await host.stat(path)).isDirectory();
} catch {
return false;
}
}
/**
* Discovers the package manager used in a project by searching for lockfiles.
*
* This function searches for lockfiles in the given directory and its ancestors.
* If multiple lockfiles are found, it uses the precedence array to determine
* which package manager to use. The search is bounded by the git repository root.
*
* @param host A `Host` instance for interacting with the file system.
* @param startDir The directory to start the search from.
* @param logger An optional logger instance.
* @returns A promise that resolves to the name of the discovered package manager, or null if none is found.
*/
export async function discover(
host: Host,
startDir: string,
logger?: Logger,
): Promise<PackageManagerName | null> {
logger?.debug(`Starting package manager discovery in '${startDir}'...`);
let currentDir = startDir;
while (true) {
const found = await findLockfiles(host, currentDir, logger);
if (found.size > 0) {
logger?.debug(`Found lockfile(s): [${[...found].join(', ')}]. Applying precedence...`);
for (const packageManager of PACKAGE_MANAGER_PRECEDENCE) {
if (found.has(packageManager)) {
logger?.debug(`Selected '${packageManager}' based on precedence.`);
return packageManager;
}
}
}
// Stop searching if we reach the git repository root.
if (await isDirectory(host, join(currentDir, '.git'))) {
logger?.debug(`Reached repository root at '${currentDir}'. Stopping search.`);
return null;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
// We have reached the filesystem root.
logger?.debug('Reached filesystem root. No lockfile found.');
return null;
}
currentDir = parentDir;
}
}