[build-utils] Add getNodeBinPaths() function (#10150)

The `getNodeBinPath()` function is problematic because it assumes that commands are installed in the `node_modules` directory alongside the detected lockfile. This works fine the majority of the time, but ends up not being the case when using a monorepo that uses a package manager in "linked" mode (i.e. pnpm by default).

Consider the following:

```
.
├── pnpm-lock.yaml
├── node_modules
├── blog
│   ├── node_modules
│   │   ├── hexo -> .pnpm/hexo@3.9.0/node_modules/hexo
```

In this setup, adding the root-level `node_modules/.bin` would not make the `hexo` command be visible in the `$PATH`.

To solve this issue, the new `getNodeBinPaths()` function returns an array of all directories up to the specified `root`, which can then be placed into the `$PATH`. It's also more efficient (synchronous) since it does not need to scan for a lockfile anymore (the `root` needs to be specified explicitly).

The new function is being used in `@vercel/next` and `@vercel/static-build`.

The `traverseUpDirectories()` function from CLI was moved to `build-utils` to implement this function. Consequently, that makes the implementations of `walkParentDirs()` and `walkParentDirsMulti()` simpler, since it's using this generator now.
This commit is contained in:
Nathan Rajlich
2023-06-27 18:53:34 -07:00
committed by GitHub
parent e4895d979b
commit 3468922108
11 changed files with 150 additions and 82 deletions

View File

@@ -2,7 +2,7 @@ import chalk from 'chalk';
import pluralize from 'pluralize';
import { homedir } from 'os';
import { join, normalize } from 'path';
import { normalizePath } from '@vercel/build-utils';
import { normalizePath, traverseUpDirectories } from '@vercel/build-utils';
import { lstat, readJSON, outputJSON } from 'fs-extra';
import confirm from '../input/confirm';
import toHumanPath from '../humanize-path';
@@ -229,7 +229,7 @@ export async function findRepoRoot(
const REPO_JSON_PATH = join(VERCEL_DIR, VERCEL_DIR_REPO);
const GIT_CONFIG_PATH = normalize('.git/config');
for (const current of traverseUpDirectories(start)) {
for (const current of traverseUpDirectories({ start })) {
if (current === home) {
// Sometimes the $HOME directory is set up as a Git repo
// (for dotfiles, etc.). In this case it's safe to say that
@@ -264,16 +264,6 @@ export async function findRepoRoot(
debug('Aborting search for repo root');
}
export function* traverseUpDirectories(start: string) {
let current: string | undefined = normalize(start);
while (current) {
yield current;
// Go up one directory
const next = join(current, '..');
current = next === current ? undefined : next;
}
}
function sortByDirectory(a: RepoProjectConfig, b: RepoProjectConfig): number {
const aParts = a.directory.split('/');
const bParts = b.directory.split('/');

View File

@@ -4,12 +4,9 @@ import {
findProjectsFromPath,
findRepoRoot,
RepoProjectConfig,
traverseUpDirectories,
} from '../../../../src/util/link/repo';
import { client } from '../../../mocks/client';
const isWindows = process.platform === 'win32';
// Root of `vercel/vercel` repo
const vercelRepoRoot = join(__dirname, '../../../../../..');
@@ -30,34 +27,6 @@ describe('findRepoRoot()', () => {
});
});
describe('traverseUpDirectories()', () => {
test.each(
isWindows
? [
{
input: 'C:\\foo\\bar\\baz',
expected: ['C:\\foo\\bar\\baz', 'C:\\foo\\bar', 'C:\\foo', 'C:\\'],
},
{
input: 'C:\\foo\\..\\bar\\.\\baz',
expected: ['C:\\bar\\baz', 'C:\\bar', 'C:\\'],
},
]
: [
{
input: '/foo/bar/baz',
expected: ['/foo/bar/baz', '/foo/bar', '/foo', '/'],
},
{
input: '/foo/../bar/./baz',
expected: ['/bar/baz', '/bar', '/'],
},
]
)('should traverse "$input"', ({ input, expected }) => {
expect(Array.from(traverseUpDirectories(input))).toEqual(expected);
});
});
describe('findProjectsFromPath()', () => {
const projects: RepoProjectConfig[] = [
{ id: 'root', name: 'r', directory: '.' },