Skip to content

Commit 8ba6967

Browse files
committed
refactor: improve findUp utility correctness and performance
This change modernizes the `findUp` utility across the codebase. Replaced `path.parse().root` with `path.dirname(dir) === dir` check. Introduced an asynchronous version (`findUp`) in the CLI utility and renamed the synchronous version to `findUpSync` to align with Node.js conventions. Updated `packages/angular/cli/src/utilities/config.ts` to use the asynchronous `findUp` for non-blocking configuration discovery.
1 parent d9cd609 commit 8ba6967

File tree

5 files changed

+81
-35
lines changed

5 files changed

+81
-35
lines changed

packages/angular/cli/src/utilities/config.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { existsSync, promises as fs } from 'node:fs';
1111
import * as os from 'node:os';
1212
import * as path from 'node:path';
1313
import { PackageManager } from '../../lib/config/workspace-schema';
14-
import { findUp } from './find-up';
14+
import { findUp, findUpSync } from './find-up';
1515
import { JSONFile, readAndParseJson } from './json-file';
1616

1717
function isJsonObject(value: json.JsonValue | undefined): value is json.JsonObject {
@@ -70,13 +70,13 @@ function xdgConfigHomeOld(home: string): string {
7070
return path.join(p, '.angular-config.json');
7171
}
7272

73-
function projectFilePath(projectPath?: string): string | null {
73+
async function projectFilePath(projectPath?: string): Promise<string | null> {
7474
// Find the configuration, either where specified, in the Angular CLI project
7575
// (if it's in node_modules) or from the current process.
7676
return (
77-
(projectPath && findUp(configNames, projectPath)) ||
78-
findUp(configNames, process.cwd()) ||
79-
findUp(configNames, __dirname)
77+
(projectPath && (await findUp(configNames, projectPath))) ||
78+
(await findUp(configNames, process.cwd())) ||
79+
(await findUp(configNames, __dirname))
8080
);
8181
}
8282

@@ -181,7 +181,7 @@ export async function getWorkspace(
181181
return cachedWorkspaces.get(level);
182182
}
183183

184-
const configPath = level === 'local' ? projectFilePath() : globalFilePath();
184+
const configPath = level === 'local' ? await projectFilePath() : globalFilePath();
185185
if (!configPath) {
186186
if (level === 'global') {
187187
// Unlike a local config, a global config is not mandatory.
@@ -223,7 +223,7 @@ export async function getWorkspace(
223223
export async function getWorkspaceRaw(
224224
level: 'local' | 'global' = 'local',
225225
): Promise<[JSONFile | null, string | null]> {
226-
let configPath = level === 'local' ? projectFilePath() : globalFilePath();
226+
let configPath = level === 'local' ? await projectFilePath() : globalFilePath();
227227

228228
if (!configPath) {
229229
if (level === 'global') {

packages/angular/cli/src/utilities/find-up.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,66 @@
77
*/
88

99
import { existsSync } from 'node:fs';
10-
import * as path from 'node:path';
10+
import { stat } from 'node:fs/promises';
11+
import { dirname, join, resolve } from 'node:path';
1112

12-
export function findUp(names: string | string[], from: string) {
13-
if (!Array.isArray(names)) {
14-
names = [names];
13+
/**
14+
* Find a file or directory by walking up the directory tree.
15+
* @param names The name or names of the files or directories to find.
16+
* @param from The directory to start the search from.
17+
* @returns The path to the first match found, or `null` if no match was found.
18+
*/
19+
export async function findUp(names: string | string[], from: string): Promise<string | null> {
20+
const filenames = Array.isArray(names) ? names : [names];
21+
22+
let currentDir = resolve(from);
23+
while (true) {
24+
for (const name of filenames) {
25+
const p = join(currentDir, name);
26+
try {
27+
await stat(p);
28+
29+
return p;
30+
} catch {
31+
// Ignore errors (e.g. file not found).
32+
}
33+
}
34+
35+
const parentDir = dirname(currentDir);
36+
if (parentDir === currentDir) {
37+
break;
38+
}
39+
40+
currentDir = parentDir;
1541
}
16-
const root = path.parse(from).root;
1742

18-
let currentDir = from;
19-
while (currentDir && currentDir !== root) {
20-
for (const name of names) {
21-
const p = path.join(currentDir, name);
43+
return null;
44+
}
45+
46+
/**
47+
* Synchronously find a file or directory by walking up the directory tree.
48+
* @param names The name or names of the files or directories to find.
49+
* @param from The directory to start the search from.
50+
* @returns The path to the first match found, or `null` if no match was found.
51+
*/
52+
export function findUpSync(names: string | string[], from: string): string | null {
53+
const filenames = Array.isArray(names) ? names : [names];
54+
55+
let currentDir = resolve(from);
56+
while (true) {
57+
for (const name of filenames) {
58+
const p = join(currentDir, name);
2259
if (existsSync(p)) {
2360
return p;
2461
}
2562
}
2663

27-
currentDir = path.dirname(currentDir);
64+
const parentDir = dirname(currentDir);
65+
if (parentDir === currentDir) {
66+
break;
67+
}
68+
69+
currentDir = parentDir;
2870
}
2971

3072
return null;

packages/angular/cli/src/utilities/project.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { normalize } from '@angular-devkit/core';
1010
import * as fs from 'node:fs';
1111
import * as os from 'node:os';
1212
import * as path from 'node:path';
13-
import { findUp } from './find-up';
13+
import { findUpSync } from './find-up';
1414

1515
interface PackageDependencies {
1616
dependencies?: Record<string, string>;
@@ -19,7 +19,7 @@ interface PackageDependencies {
1919

2020
export function findWorkspaceFile(currentDirectory = process.cwd()): string | null {
2121
const possibleConfigFiles = ['angular.json', '.angular.json'];
22-
const configFilePath = findUp(possibleConfigFiles, currentDirectory);
22+
const configFilePath = findUpSync(possibleConfigFiles, currentDirectory);
2323
if (configFilePath === null) {
2424
return null;
2525
}

packages/angular_devkit/architect/bin/architect.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@ import { Architect } from '../index';
1616
import { WorkspaceNodeModulesArchitectHost } from '../node/index';
1717

1818
function findUp(names: string | string[], from: string) {
19-
if (!Array.isArray(names)) {
20-
names = [names];
21-
}
22-
const root = path.parse(from).root;
19+
const filenames = Array.isArray(names) ? names : [names];
2320

24-
let currentDir = from;
25-
while (currentDir && currentDir !== root) {
26-
for (const name of names) {
21+
let currentDir = path.resolve(from);
22+
while (true) {
23+
for (const name of filenames) {
2724
const p = path.join(currentDir, name);
2825
if (existsSync(p)) {
2926
return p;
3027
}
3128
}
3229

33-
currentDir = path.dirname(currentDir);
30+
const parentDir = path.dirname(currentDir);
31+
if (parentDir === currentDir) {
32+
break;
33+
}
34+
35+
currentDir = parentDir;
3436
}
3537

3638
return null;

packages/angular_devkit/schematics_cli/bin/schematics.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,21 +177,23 @@ function _createPromptProvider(): schema.PromptProvider {
177177
}
178178

179179
function findUp(names: string | string[], from: string) {
180-
if (!Array.isArray(names)) {
181-
names = [names];
182-
}
183-
const root = path.parse(from).root;
180+
const filenames = Array.isArray(names) ? names : [names];
184181

185-
let currentDir = from;
186-
while (currentDir && currentDir !== root) {
187-
for (const name of names) {
182+
let currentDir = path.resolve(from);
183+
while (true) {
184+
for (const name of filenames) {
188185
const p = path.join(currentDir, name);
189186
if (existsSync(p)) {
190187
return p;
191188
}
192189
}
193190

194-
currentDir = path.dirname(currentDir);
191+
const parentDir = path.dirname(currentDir);
192+
if (parentDir === currentDir) {
193+
break;
194+
}
195+
196+
currentDir = parentDir;
195197
}
196198

197199
return null;

0 commit comments

Comments
 (0)