[fs-detectors] Use LocalFileSystemDetector instead of FixtureFilesystem (#10100)

The code for these two are almost identical, so consolidate into one codebase.

Also adjusts the `pnpm test` script to allow for specifying a file name to be executed, instead of running all tests.
This commit is contained in:
Nathan Rajlich
2023-06-13 16:30:00 -07:00
committed by GitHub
parent d61a1a7988
commit 42c0b32a8d
13 changed files with 50 additions and 87 deletions

View File

@@ -0,0 +1,5 @@
---
'@vercel/fs-detectors': major
---
`LocalFileSystemDetector#readdir()` now returns paths relative to the root dir, instead of absolute paths. This is to align with the usage of the detectors that are using the `DetectorFilesystem` interface.

View File

@@ -15,8 +15,8 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"test": "jest --env node --verbose --runInBand --bail test/unit.*test.*", "test": "jest --env node --verbose --runInBand --bail",
"test-unit": "pnpm test" "test-unit": "pnpm test test/unit.*test.*"
}, },
"dependencies": { "dependencies": {
"@vercel/error-utils": "1.0.10", "@vercel/error-utils": "1.0.10",

View File

@@ -6,10 +6,12 @@ import { isErrnoException } from '@vercel/error-utils';
export class LocalFileSystemDetector extends DetectorFilesystem { export class LocalFileSystemDetector extends DetectorFilesystem {
private rootPath: string; private rootPath: string;
constructor(rootPath: string) { constructor(rootPath: string) {
super(); super();
this.rootPath = rootPath; this.rootPath = rootPath;
} }
async _hasPath(name: string): Promise<boolean> { async _hasPath(name: string): Promise<boolean> {
try { try {
await fs.stat(this.getFilePath(name)); await fs.stat(this.getFilePath(name));
@@ -21,13 +23,16 @@ export class LocalFileSystemDetector extends DetectorFilesystem {
throw err; throw err;
} }
} }
_readFile(name: string): Promise<Buffer> { _readFile(name: string): Promise<Buffer> {
return fs.readFile(this.getFilePath(name)); return fs.readFile(this.getFilePath(name));
} }
async _isFile(name: string): Promise<boolean> { async _isFile(name: string): Promise<boolean> {
const stat = await fs.stat(this.getFilePath(name)); const stat = await fs.stat(this.getFilePath(name));
return stat.isFile(); return stat.isFile();
} }
async _readdir(name: string): Promise<DetectorFilesystemStat[]> { async _readdir(name: string): Promise<DetectorFilesystemStat[]> {
const dirPath = this.getFilePath(name); const dirPath = this.getFilePath(name);
const dir = await fs.readdir(dirPath, { const dir = await fs.readdir(dirPath, {
@@ -44,14 +49,22 @@ export class LocalFileSystemDetector extends DetectorFilesystem {
}; };
return dir.map(dirent => ({ return dir.map(dirent => ({
name: dirent.name, name: dirent.name,
path: path.join(dirPath, dirent.name), path: path.join(this.getRelativeFilePath(name), dirent.name),
type: getType(dirent), type: getType(dirent),
})); }));
} }
_chdir(name: string): DetectorFilesystem { _chdir(name: string): DetectorFilesystem {
return new LocalFileSystemDetector(this.getFilePath(name)); return new LocalFileSystemDetector(this.getFilePath(name));
} }
private getRelativeFilePath(name: string) {
return name.startsWith(this.rootPath)
? path.relative(this.rootPath, name)
: name;
}
private getFilePath(name: string) { private getFilePath(name: string) {
return path.join(this.rootPath, name); return path.join(this.rootPath, this.getRelativeFilePath(name));
} }
} }

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { LocalFileSystemDetector } from '../src';
import { detectFramework } from '../src/detect-framework'; import { detectFramework } from '../src/detect-framework';
import monorepoManagers from '../src/monorepos/monorepo-managers'; import monorepoManagers from '../src/monorepos/monorepo-managers';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe('monorepo-managers', () => { describe('monorepo-managers', () => {
describe.each([ describe.each([
@@ -17,7 +17,7 @@ describe('monorepo-managers', () => {
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const result = await detectFramework({ const result = await detectFramework({
fs, fs,

View File

@@ -1,6 +1,9 @@
import path from 'path'; import path from 'path';
import { packageManagers, detectFramework } from '../src'; import {
import { FixtureFilesystem } from './utils/fixture-filesystem'; packageManagers,
detectFramework,
LocalFileSystemDetector,
} from '../src';
describe('package-managers', () => { describe('package-managers', () => {
describe.each([ describe.each([
@@ -16,7 +19,7 @@ describe('package-managers', () => {
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const result = await detectFramework({ const result = await detectFramework({
fs, fs,

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { LocalFileSystemDetector } from '../src';
import { detectFramework } from '../src/detect-framework'; import { detectFramework } from '../src/detect-framework';
import workspaceManagers from '../src/workspaces/workspace-managers'; import workspaceManagers from '../src/workspaces/workspace-managers';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe('workspace-managers', () => { describe('workspace-managers', () => {
describe.each([ describe.each([
@@ -19,7 +19,7 @@ describe('workspace-managers', () => {
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const result = await detectFramework({ const result = await detectFramework({
fs, fs,

View File

@@ -1,13 +1,12 @@
import frameworkList from '@vercel/frameworks'; import frameworkList from '@vercel/frameworks';
import { detectFramework } from '../src'; import { detectFramework, LocalFileSystemDetector } from '../src';
import { FixtureFilesystem } from './utils/fixture-filesystem';
import { getExamples } from '../../../examples/__tests__/test-utils'; import { getExamples } from '../../../examples/__tests__/test-utils';
describe('examples should be detected', () => { describe('examples should be detected', () => {
it.each(getExamples())( it.each(getExamples())(
'should detect $exampleName', 'should detect $exampleName',
async ({ exampleName, examplePath }) => { async ({ exampleName, examplePath }) => {
const fs = new FixtureFilesystem(examplePath); const fs = new LocalFileSystemDetector(examplePath);
const framework = await detectFramework({ fs, frameworkList }); const framework = await detectFramework({ fs, frameworkList });
if (!framework) { if (!framework) {
throw new Error(`Framework not detected for example "${exampleName}".`); throw new Error(`Framework not detected for example "${exampleName}".`);

View File

@@ -1,13 +1,12 @@
import os from 'os';
import path from 'path';
import { mkdtempSync } from 'fs';
import { import {
getMonorepoDefaultSettings, getMonorepoDefaultSettings,
LocalFileSystemDetector, LocalFileSystemDetector,
MissingBuildPipeline, MissingBuildPipeline,
MissingBuildTarget, MissingBuildTarget,
} from '../src'; } from '../src';
import path from 'path';
import fs from 'fs';
import os from 'os';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe('getMonorepoDefaultSettings', () => { describe('getMonorepoDefaultSettings', () => {
test('MissingBuildTarget is an error', () => { test('MissingBuildTarget is an error', () => {
@@ -69,7 +68,7 @@ describe('getMonorepoDefaultSettings', () => {
}, },
}; };
const ffs = new FixtureFilesystem( const fs = new LocalFileSystemDetector(
path.join( path.join(
__dirname, __dirname,
'fixtures', 'fixtures',
@@ -81,16 +80,16 @@ describe('getMonorepoDefaultSettings', () => {
packageName, packageName,
isRoot ? '/' : 'packages/app-1', isRoot ? '/' : 'packages/app-1',
isRoot ? '/' : '../..', isRoot ? '/' : '../..',
ffs fs
); );
expect(result).toStrictEqual(expectedResultMap[expectedResultKey]); expect(result).toStrictEqual(expectedResultMap[expectedResultKey]);
} }
); );
test('returns null when neither nx nor turbo is detected', async () => { test('returns null when neither nx nor turbo is detected', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'monorepo-test-')); const dir = mkdtempSync(path.join(os.tmpdir(), 'monorepo-test-'));
const lfs = new LocalFileSystemDetector(dir); const fs = new LocalFileSystemDetector(dir);
const result = await getMonorepoDefaultSettings('', '', '', lfs); const result = await getMonorepoDefaultSettings('', '', '', fs);
expect(result).toBe(null); expect(result).toBe(null);
}); });
}); });

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { normalizePath } from '@vercel/build-utils'; import { normalizePath } from '@vercel/build-utils';
import { getProjectPaths, ProjectPath } from '../src/get-project-paths'; import { getProjectPaths, ProjectPath } from '../src/get-project-paths';
import { FixtureFilesystem } from './utils/fixture-filesystem'; import { LocalFileSystemDetector } from '../src';
describe.each<{ describe.each<{
fixturePath: string; fixturePath: string;
@@ -52,7 +52,7 @@ describe.each<{
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const mockReaddir = jest.fn().mockImplementation(fs.readdir); const mockReaddir = jest.fn().mockImplementation(fs.readdir);
const mockHasPath = jest.fn().mockImplementation(fs.hasPath); const mockHasPath = jest.fn().mockImplementation(fs.hasPath);
fs.readdir = mockReaddir; fs.readdir = mockReaddir;

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { getWorkspaces } from '../src/workspaces/get-workspaces'; import { getWorkspaces } from '../src/workspaces/get-workspaces';
import { getWorkspacePackagePaths } from '../src/workspaces/get-workspace-package-paths'; import { getWorkspacePackagePaths } from '../src/workspaces/get-workspace-package-paths';
import { FixtureFilesystem } from './utils/fixture-filesystem'; import { LocalFileSystemDetector } from '../src';
describe.each<[string, string[]]>([ describe.each<[string, string[]]>([
['21-npm-workspaces', ['/a', '/b']], ['21-npm-workspaces', ['/a', '/b']],
@@ -32,7 +32,7 @@ describe.each<[string, string[]]>([
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const workspaces = await getWorkspaces({ fs }); const workspaces = await getWorkspaces({ fs });
const actualPackagePaths = ( const actualPackagePaths = (

View File

@@ -1,6 +1,6 @@
import path from 'path'; import path from 'path';
import { LocalFileSystemDetector } from '../src';
import { getWorkspaces, Workspace } from '../src/workspaces/get-workspaces'; import { getWorkspaces, Workspace } from '../src/workspaces/get-workspaces';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe.each<[string, Workspace[]]>([ describe.each<[string, Workspace[]]>([
['21-npm-workspaces', [{ type: 'npm', rootPath: '/' }]], ['21-npm-workspaces', [{ type: 'npm', rootPath: '/' }]],
@@ -34,7 +34,7 @@ describe.each<[string, Workspace[]]>([
it(testName, async () => { it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath); const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture); const fs = new LocalFileSystemDetector(fixture);
const actualWorkspaces = await getWorkspaces({ fs }); const actualWorkspaces = await getWorkspaces({ fs });

View File

@@ -5,7 +5,7 @@ import { LocalFileSystemDetector, DetectorFilesystem } from '../src';
const tmpdir = path.join(os.tmpdir(), 'local-file-system-test'); const tmpdir = path.join(os.tmpdir(), 'local-file-system-test');
const dirs = ['', 'a', 'a/b']; // root, single-nested, double-nested const dirs = ['', 'a', `a${path.sep}b`]; // root, single-nested, double-nested
const files = ['foo', 'bar']; const files = ['foo', 'bar'];
const filePaths = dirs.flatMap(dir => files.map(file => path.join(dir, file))); const filePaths = dirs.flatMap(dir => files.map(file => path.join(dir, file)));
@@ -63,12 +63,7 @@ describe('LocalFileSystemDetector', () => {
const readdirResults = await Promise.all( const readdirResults = await Promise.all(
dirs.map(dir => localFileSystem.readdir(dir)) dirs.map(dir => localFileSystem.readdir(dir))
); );
const expectedPaths = [ const expectedPaths = [...dirs, ...filePaths].sort().slice(1); // drop the first path since its the root
...dirs.map(dir => path.join(tmpdir, dir)),
...filePaths.map(filePath => path.join(tmpdir, filePath)),
]
.sort()
.slice(1); // drop the first path since its the root
const actualPaths = readdirResults const actualPaths = readdirResults
.flatMap(result => result.map(stat => stat.path)) .flatMap(result => result.map(stat => stat.path))
.sort(); .sort();

View File

@@ -1,51 +0,0 @@
import { promises } from 'fs';
import path from 'path';
import { DetectorFilesystem } from '../../src';
import { DetectorFilesystemStat } from '../../src/detectors/filesystem';
const { stat, readFile, readdir } = promises;
export class FixtureFilesystem extends DetectorFilesystem {
private rootPath: string;
constructor(fixturePath: string) {
super();
this.rootPath = fixturePath;
}
async _hasPath(name: string): Promise<boolean> {
try {
const filePath = path.join(this.rootPath, name);
await stat(filePath);
return true;
} catch {
return false;
}
}
async _readFile(name: string): Promise<Buffer> {
const filePath = path.join(this.rootPath, name);
return readFile(filePath);
}
async _isFile(name: string): Promise<boolean> {
const filePath = path.join(this.rootPath, name);
return (await stat(filePath)).isFile();
}
async _readdir(name: string): Promise<DetectorFilesystemStat[]> {
const dirPath = path.join(this.rootPath, name);
const files = await readdir(dirPath, { withFileTypes: true });
return files.map(file => ({
name: file.name,
type: file.isFile() ? 'file' : 'dir',
path: path.join(name, file.name),
}));
}
_chdir(name: string): DetectorFilesystem {
return new FixtureFilesystem(path.join(this.rootPath, name));
}
}